Skip to content

Commit

Permalink
Service auto wiring implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
devtronic committed Apr 15, 2018
1 parent 22dc879 commit 809d88e
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 30 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.0.2

- Auto wiring implemented (See "Service auto wiring" in the README)

## 0.0.1

- Initial release
44 changes: 34 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -17,7 +17,7 @@ dependencies:
Then run `pub get`

## Usage
## 💡 Usage

### Importing
```dart
Expand Down Expand Up @@ -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 |
|:-----------|:--------------------------------|:---------------|
Expand All @@ -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 |
|:-----------|:----------------------------------|:---------------|
Expand All @@ -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
## 🤝 Contribute
Feel free to fork and add pull-requests 🤓
60 changes: 41 additions & 19 deletions lib/container.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ class Container implements ContainerInterface {
/// Container parameters
Map<String, dynamic> _parameters = new Map<String, dynamic>();

/// Determines if auto wiring is enabled
bool autoWire = true;

@override
get(String id) {
if (!this.has(id)) {
Expand All @@ -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';
Expand All @@ -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<Match> 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);
});
}
Expand All @@ -97,7 +119,7 @@ class Container implements ContainerInterface {
loadedService = (mirror as ClosureMirror).apply(arguments).reflectee;
}

this._loadedServices[id] = loadedService;
_loadedServices[id] = loadedService;
return loadedService;
}

Expand All @@ -116,69 +138,69 @@ class Container implements ContainerInterface {
/// [arguments] and the new object instance will be returned within the [get]
/// call.
register(String id, dynamic service, [List<dynamic> arguments]) {
if (this.has(id)) {
if (has(id)) {
throw new Exception('A service with the id "$id" already exist');
}

if (id.trim() == '') {
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
///
/// 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
///
/// 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
///
/// 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
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
homepage: https://github.com/Devtronic/catalyst
Expand Down
24 changes: 24 additions & 0 deletions test/container_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 809d88e

Please sign in to comment.