diff --git a/CHANGELOG.md b/CHANGELOG.md index 8454a65..be5b35d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.2 + +- Auto wiring implemented (See "Service auto wiring" in the README) + ## 0.0.1 - Initial release \ No newline at end of file diff --git a/README.md b/README.md index 666294a..90f2532 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Catalyst is a dependency injection container for the dart language. It's fast, reliable and easy to understand. -## Installation +## 📦 Installation Add the following to your `pubspec.yaml`: ```yaml dependencies: @@ -17,7 +17,7 @@ dependencies: Then run `pub get` -## Usage +## 💡 Usage ### Importing ```dart @@ -106,7 +106,7 @@ Once a service is loaded, it remains in memory at runtime. When the same service is loaded again, the first instance is returned. ``` -Container::get(String id) +Container.get(String id) ``` | Parameter | Description | Example | |:-----------|:--------------------------------|:---------------| @@ -126,7 +126,7 @@ container.get('namer'); // returns "Catalyst" The service container also supports static parameters. You can add a parameter using the `addParameter`-method ``` -Container::addParameter(String name, dynamic value) +Container.addParameter(String name, dynamic value) ``` | Parameter | Description | Example | |:-----------|:----------------------------------|:---------------| @@ -145,14 +145,38 @@ return 'Connecting to $hostname'; print(container.get('db.context')); // Outputs "Connecting to localhost" ``` -## Testing +## 🔌 Service auto wiring +Catalyst supports auto wiring of services. +That means, that you only need to register the service without passing depending service names as arguments. +(Strong typing is required). + +For example: +```dart +main() { + Container container = new Container(); + container.register('greeter', SimpleGreeter); + container.register('greeting_printer', (SimpleGreeter greeter) { + print(greeter.greet('Catalyst')); + }); + + container.get('greeting_printer'); // Outputs "Hello from Catalyst!" +} + +class SimpleGreeter { + + String greet(String name) { + return "Hello from $name!"; + } +} +``` + +You can disable this behaviour with setting `Container.autoWire = false;` + +## 🔬 Testing ```bash $ pub run test ``` -## Contribute -Feel free to fork and add pull-requests 🤓 - -## Todo -Implement auto wiring of services \ No newline at end of file +## 🤝 Contribute +Feel free to fork and add pull-requests 🤓 \ No newline at end of file diff --git a/lib/container.dart b/lib/container.dart index 45d3e8e..349d12b 100644 --- a/lib/container.dart +++ b/lib/container.dart @@ -19,6 +19,9 @@ class Container implements ContainerInterface { /// Container parameters Map _parameters = new Map(); + /// Determines if auto wiring is enabled + bool autoWire = true; + @override get(String id) { if (!this.has(id)) { @@ -41,22 +44,41 @@ class Container implements ContainerInterface { mirror = reflectClass(target); var members = mirror.declarations.values; if (members.length > 0) { - reflection = members.firstWhere( - (m) => m is MethodMirror && m.isConstructor, - orElse: null); + reflection = + members.firstWhere((m) => m is MethodMirror && m.isConstructor); } } else if (isClosure) { mirror = reflect(target) as ClosureMirror; reflection = mirror.function; } - int numGiven = injections.length; - int maxArguments = reflection.parameters.length; + int parameterIndex = -1; int minArguments = 0; + bool doAutoWire = autoWire; for (var parameter in reflection.parameters) { + parameterIndex++; minArguments += !parameter.isOptional ? 1 : 0; + + if (doAutoWire && (injections.length < parameterIndex + 1)) { + var locatedSvc = this._services.values.firstWhere((Service svc) { + if (!(svc.target is Type)) { + return false; + } + var svcType = reflectType(svc.target).reflectedType; + return parameter.type.reflectedType == svcType; + }, orElse: () => null); + + if (locatedSvc == null) { + doAutoWire = false; // Auto wiring failed + continue; + } + + injections.add(get(locatedSvc.id)); + } } + int numGiven = injections.length; + int maxArguments = reflection.parameters.length; if (numGiven < minArguments || numGiven > maxArguments) { var message = 'The Service "$id" expects exact $minArguments arguments, $numGiven given'; @@ -75,11 +97,11 @@ class Container implements ContainerInterface { var matcher = new RegExp(r"%([^%]*?)%"); if (parameter.substring(0, 1) == '@') { - parameter = this.get(parameter.substring(1)); + parameter = get(parameter.substring(1)); } else if (matcher.hasMatch(parameter)) { Iterable matches = matcher.allMatches(parameter); matches.forEach((m) { - var param = this.getParameter(m.group(1)); + var param = getParameter(m.group(1)); parameter = parameter.replaceAll(m.group(0), param); }); } @@ -97,7 +119,7 @@ class Container implements ContainerInterface { loadedService = (mirror as ClosureMirror).apply(arguments).reflectee; } - this._loadedServices[id] = loadedService; + _loadedServices[id] = loadedService; return loadedService; } @@ -116,7 +138,7 @@ class Container implements ContainerInterface { /// [arguments] and the new object instance will be returned within the [get] /// call. register(String id, dynamic service, [List arguments]) { - if (this.has(id)) { + if (has(id)) { throw new Exception('A service with the id "$id" already exist'); } @@ -124,7 +146,7 @@ class Container implements ContainerInterface { throw new Exception('The id must have at least one character'); } - this._services[id] = new Service(id, service, arguments); + _services[id] = new Service(id, service, arguments); } /// Unregister a registered service @@ -132,14 +154,14 @@ class Container implements ContainerInterface { /// Pass the unique [id] of the Service. /// If a service is already loaded it can't be unregistered. unregister(String id) { - if (!this.has(id)) { + if (!has(id)) { throw new ServiceNotFoundException('The service "$id" does not exist'); } else if (_loadedServices.containsKey(id)) { var msg = 'The service "$id" can not be unregistered because it\'s loaded'; throw new Exception(msg); } - this._services.remove(id); + _services.remove(id); } /// Adds a new parameter to the container @@ -147,7 +169,7 @@ class Container implements ContainerInterface { /// Use [name] as the parameter name and [value] as the parameter value. /// If a parameter is already added an exception will be thrown. addParameter(String name, dynamic value) { - this.setParameter(name, value, false); + setParameter(name, value, false); } /// Sets a parameter @@ -155,30 +177,30 @@ class Container implements ContainerInterface { /// Sets the parameter with the [name]. If [override] is true and the [name] /// is already in use, the parameter will be overridden. setParameter(String name, dynamic value, [bool override = true]) { - if (this._parameters.containsKey(name) && !override) { + if (_parameters.containsKey(name) && !override) { throw new Exception('The parameter "$name" is already defined'); } - this._parameters[name] = value; + _parameters[name] = value; } /// Removes a parameter /// /// Pass in [name] the name of the parameter. unsetParameter(String name) { - if (!this._parameters.containsKey(name)) { + if (!_parameters.containsKey(name)) { var msg = 'A parameter with the name "$name" is not defined'; throw new ParameterNotDefinedException(msg); } - this._parameters.remove(name); + _parameters.remove(name); } /// Retrieve a specific registered parameter by [name] getParameter(String name) { - if (!this._parameters.containsKey(name)) { + if (!_parameters.containsKey(name)) { var msg = 'A parameter with the name "$name" is not defined'; throw new ParameterNotDefinedException(msg); } - return this._parameters[name]; + return _parameters[name]; } /// Returns all registered services diff --git a/pubspec.yaml b/pubspec.yaml index f46b74b..2cb6114 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: 'catalyst' -version: 0.0.1 +version: 0.0.2 description: The only dependency injection container for Dart you'll ever need author: Julian Finkler homepage: https://github.com/Devtronic/catalyst diff --git a/test/container_test.dart b/test/container_test.dart index 77b8171..adeeaaf 100644 --- a/test/container_test.dart +++ b/test/container_test.dart @@ -257,6 +257,30 @@ void main() { }, ['%database.host%:%database.port%']); expect(container.get('db.ctx'), 'Connecting to my.server.tld:1337'); }); + + test('Autowiring service fails', () { + var container = new Container(); + container.autoWire = true; + container.register('simple_date', SimpleDate, [1955]); + container.register('year_printer', (SimpleDate sd, Container nonExistent) { + return 'Year: ${sd.year}'; + }); + + var msg = 'The Service "year_printer" expects exact 2 arguments, 1 given'; + expect(() => container.get('year_printer'), + throwsA(predicate((e) => e is Exception && e.message == msg))); + }); + + test('Autowiring service', () { + var container = new Container(); + container.autoWire = true; + container.register('simple_date', SimpleDate, [1955]); + container.register('year_printer', (SimpleDate sd) { + return 'Year: ${sd.year}'; + }); + + expect(container.get('year_printer'), 'Year: 1955'); + }); } class SimpleDate {