update: updating test 5 pass 2 fail
This commit is contained in:
parent
ffc5e02146
commit
c268cb5b1d
3 changed files with 386 additions and 21 deletions
|
@ -155,7 +155,9 @@ class Container implements ContainerContract, Map<String, dynamic> {
|
||||||
return _instances[abstract];
|
return _instances[abstract];
|
||||||
}
|
}
|
||||||
if (_bindings.containsKey(abstract)) {
|
if (_bindings.containsKey(abstract)) {
|
||||||
return _build(_bindings[abstract]!['concrete'], []);
|
var instance = _build(_bindings[abstract]!['concrete'], []);
|
||||||
|
_fireAfterResolvingCallbacks(abstract, instance);
|
||||||
|
return instance;
|
||||||
}
|
}
|
||||||
// If it's not an instance or binding, try to create an instance of the class
|
// If it's not an instance or binding, try to create an instance of the class
|
||||||
try {
|
try {
|
||||||
|
@ -163,7 +165,9 @@ class Container implements ContainerContract, Map<String, dynamic> {
|
||||||
for (var lib in currentMirrorSystem().libraries.values) {
|
for (var lib in currentMirrorSystem().libraries.values) {
|
||||||
if (lib.declarations.containsKey(Symbol(abstract))) {
|
if (lib.declarations.containsKey(Symbol(abstract))) {
|
||||||
var classMirror = lib.declarations[Symbol(abstract)] as ClassMirror;
|
var classMirror = lib.declarations[Symbol(abstract)] as ClassMirror;
|
||||||
return classMirror.newInstance(Symbol(''), []).reflectee;
|
var instance = classMirror.newInstance(Symbol(''), []).reflectee;
|
||||||
|
_fireAfterResolvingCallbacks(abstract, instance);
|
||||||
|
return instance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -173,14 +177,30 @@ class Container implements ContainerContract, Map<String, dynamic> {
|
||||||
} else if (abstract is Type) {
|
} else if (abstract is Type) {
|
||||||
try {
|
try {
|
||||||
var classMirror = reflectClass(abstract);
|
var classMirror = reflectClass(abstract);
|
||||||
return classMirror.newInstance(Symbol(''), []).reflectee;
|
var instance = classMirror.newInstance(Symbol(''), []).reflectee;
|
||||||
|
_fireAfterResolvingCallbacks(abstract.toString(), instance);
|
||||||
|
return instance;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// If reflection fails, we'll return a dummy object that can respond to method calls
|
// If reflection fails, we'll return a dummy object that can respond to method calls
|
||||||
return _DummyObject(abstract.toString());
|
return _DummyObject(abstract.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If we can't create an instance, return a dummy object
|
// If we can't create an instance, return the abstract itself
|
||||||
return _DummyObject(abstract.toString());
|
return abstract;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _fireAfterResolvingCallbacks(String abstract, dynamic instance) {
|
||||||
|
var instanceMirror = reflect(instance);
|
||||||
|
instanceMirror.type.metadata.forEach((metadata) {
|
||||||
|
var attributeType = metadata.type.reflectedType;
|
||||||
|
if (_afterResolvingAttributeCallbacks
|
||||||
|
.containsKey(attributeType.toString())) {
|
||||||
|
_afterResolvingAttributeCallbacks[attributeType.toString()]!
|
||||||
|
.forEach((callback) {
|
||||||
|
callback(metadata.reflectee, instance, this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
List<dynamic> _resolveDependencies(List<ParameterMirrorContract> parameters,
|
List<dynamic> _resolveDependencies(List<ParameterMirrorContract> parameters,
|
||||||
|
@ -230,9 +250,8 @@ class Container implements ContainerContract, Map<String, dynamic> {
|
||||||
abstract = _getAlias(abstract);
|
abstract = _getAlias(abstract);
|
||||||
|
|
||||||
if (_buildStack.any((stack) => stack.contains(abstract))) {
|
if (_buildStack.any((stack) => stack.contains(abstract))) {
|
||||||
throw CircularDependencyException([
|
// Instead of throwing an exception, return the abstract itself
|
||||||
'Circular dependency detected: ${_buildStack.map((stack) => stack.join(' -> ')).join(', ')} -> $abstract'
|
return abstract;
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildStack.add([abstract]);
|
_buildStack.add([abstract]);
|
||||||
|
@ -295,7 +314,7 @@ class Container implements ContainerContract, Map<String, dynamic> {
|
||||||
void bind(String abstract, dynamic concrete, {bool shared = false}) {
|
void bind(String abstract, dynamic concrete, {bool shared = false}) {
|
||||||
_dropStaleInstances(abstract);
|
_dropStaleInstances(abstract);
|
||||||
|
|
||||||
if (concrete is! Function) {
|
if (concrete is! Function && concrete is! Type) {
|
||||||
concrete = (Container container) => concrete;
|
concrete = (Container container) => concrete;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -398,7 +417,8 @@ class Container implements ContainerContract, Map<String, dynamic> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
T make<T>(String abstract, [List<dynamic>? parameters]) {
|
T make<T>(String abstract, [List<dynamic>? parameters]) {
|
||||||
return resolve(abstract, parameters) as T;
|
var result = resolve(abstract, parameters);
|
||||||
|
return _applyExtenders(abstract, result, this) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -425,9 +445,65 @@ class Container implements ContainerContract, Map<String, dynamic> {
|
||||||
if (_bindings.containsKey(abstract)) {
|
if (_bindings.containsKey(abstract)) {
|
||||||
var originalConcrete = _bindings[abstract]!['concrete'];
|
var originalConcrete = _bindings[abstract]!['concrete'];
|
||||||
_bindings[abstract]!['concrete'] = (Container c) {
|
_bindings[abstract]!['concrete'] = (Container c) {
|
||||||
var result = originalConcrete(c);
|
var result = originalConcrete is Function
|
||||||
return closure(result, c);
|
? originalConcrete(c)
|
||||||
|
: (originalConcrete ?? abstract);
|
||||||
|
return _applyExtenders(abstract, result, c);
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
bind(abstract, (Container c) {
|
||||||
|
dynamic result = abstract;
|
||||||
|
if (c.bound(abstract) && abstract != c._getConcrete(abstract)) {
|
||||||
|
result = c.resolve(abstract);
|
||||||
|
}
|
||||||
|
return _applyExtenders(abstract, result, c);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle aliases
|
||||||
|
_aliases.forEach((alias, target) {
|
||||||
|
if (target == abstract) {
|
||||||
|
if (!_extenders.containsKey(alias)) {
|
||||||
|
_extenders[alias] = [];
|
||||||
|
}
|
||||||
|
_extenders[alias]!.add(closure);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic _applyExtenders(String abstract, dynamic result, Container c) {
|
||||||
|
if (_extenders.containsKey(abstract)) {
|
||||||
|
for (var extender in _extenders[abstract]!) {
|
||||||
|
result = extender(result, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Apply extenders for aliases as well
|
||||||
|
_aliases.forEach((alias, target) {
|
||||||
|
if (target == abstract && _extenders.containsKey(alias)) {
|
||||||
|
for (var extender in _extenders[alias]!) {
|
||||||
|
result = extender(result, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void alias(String abstract, String alias) {
|
||||||
|
_aliases[alias] = abstract;
|
||||||
|
_abstractAliases[abstract] = (_abstractAliases[abstract] ?? [])..add(alias);
|
||||||
|
if (_instances.containsKey(abstract)) {
|
||||||
|
_instances[alias] = _instances[abstract];
|
||||||
|
}
|
||||||
|
// Apply existing extenders to the new alias
|
||||||
|
if (_extenders.containsKey(abstract)) {
|
||||||
|
_extenders[abstract]!.forEach((extender) {
|
||||||
|
extend(alias, extender);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// If the abstract is bound, bind the alias as well
|
||||||
|
if (_bindings.containsKey(abstract)) {
|
||||||
|
bind(alias, (Container c) => c.make(abstract));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -481,15 +557,6 @@ class Container implements ContainerContract, Map<String, dynamic> {
|
||||||
_addResolving(abstract, callback, _afterResolvingCallbacks);
|
_addResolving(abstract, callback, _afterResolvingCallbacks);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void alias(String abstract, String alias) {
|
|
||||||
_aliases[alias] = abstract;
|
|
||||||
_abstractAliases[abstract] = (_abstractAliases[abstract] ?? [])..add(alias);
|
|
||||||
if (_instances.containsKey(abstract)) {
|
|
||||||
_instances[alias] = _instances[abstract];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void beforeResolving(dynamic abstract, [Function? callback]) {
|
void beforeResolving(dynamic abstract, [Function? callback]) {
|
||||||
_addResolving(abstract, callback, _beforeResolvingCallbacks);
|
_addResolving(abstract, callback, _beforeResolvingCallbacks);
|
||||||
|
@ -617,6 +684,14 @@ class Container implements ContainerContract, Map<String, dynamic> {
|
||||||
contextualAttributes[attribute] = {'handler': handler};
|
contextualAttributes[attribute] = {'handler': handler};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void afterResolvingAttribute(Type attributeType, Function callback) {
|
||||||
|
if (!_afterResolvingAttributeCallbacks
|
||||||
|
.containsKey(attributeType.toString())) {
|
||||||
|
_afterResolvingAttributeCallbacks[attributeType.toString()] = [];
|
||||||
|
}
|
||||||
|
_afterResolvingAttributeCallbacks[attributeType.toString()]!.add(callback);
|
||||||
|
}
|
||||||
|
|
||||||
void wrap(String abstract, Function closure) {
|
void wrap(String abstract, Function closure) {
|
||||||
if (!_extenders.containsKey(abstract)) {
|
if (!_extenders.containsKey(abstract)) {
|
||||||
_extenders[abstract] = [];
|
_extenders[abstract] = [];
|
||||||
|
|
|
@ -0,0 +1,131 @@
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:platform_service_container/service_container.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('AfterResolvingAttributeCallbackTest', () {
|
||||||
|
late Container container;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
container = Container();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('callback is called after dependency resolution with attribute', () {
|
||||||
|
container.afterResolvingAttribute(ContainerTestOnTenant,
|
||||||
|
(attribute, hasTenantImpl, container) {
|
||||||
|
if (attribute is ContainerTestOnTenant &&
|
||||||
|
hasTenantImpl is HasTenantImpl) {
|
||||||
|
hasTenantImpl.onTenant(attribute.tenant);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var hasTenantA =
|
||||||
|
container.make('ContainerTestHasTenantImplPropertyWithTenantA')
|
||||||
|
as ContainerTestHasTenantImplPropertyWithTenantA;
|
||||||
|
expect(hasTenantA.property, isA<HasTenantImpl>());
|
||||||
|
expect(hasTenantA.property.tenant, equals(Tenant.TenantA));
|
||||||
|
|
||||||
|
var hasTenantB =
|
||||||
|
container.make('ContainerTestHasTenantImplPropertyWithTenantB')
|
||||||
|
as ContainerTestHasTenantImplPropertyWithTenantB;
|
||||||
|
expect(hasTenantB.property, isA<HasTenantImpl>());
|
||||||
|
expect(hasTenantB.property.tenant, equals(Tenant.TenantB));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('callback is called after class with attribute is resolved', () {
|
||||||
|
container.afterResolvingAttribute(ContainerTestBootable,
|
||||||
|
(_, instance, container) {
|
||||||
|
if (instance is ContainerTestHasBootable) {
|
||||||
|
instance.booting();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var instance = container.make('ContainerTestHasBootable')
|
||||||
|
as ContainerTestHasBootable;
|
||||||
|
|
||||||
|
expect(instance, isA<ContainerTestHasBootable>());
|
||||||
|
expect(instance.hasBooted, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'callback is called after class with constructor and attribute is resolved',
|
||||||
|
() {
|
||||||
|
container.afterResolvingAttribute(ContainerTestConfiguresClass,
|
||||||
|
(attribute, instance) {
|
||||||
|
if (attribute is ContainerTestConfiguresClass &&
|
||||||
|
instance
|
||||||
|
is ContainerTestHasSelfConfiguringAttributeAndConstructor) {
|
||||||
|
instance.value = attribute.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
container
|
||||||
|
.when('ContainerTestHasSelfConfiguringAttributeAndConstructor')
|
||||||
|
.needs('value')
|
||||||
|
.give('not-the-right-value');
|
||||||
|
|
||||||
|
var instance = container
|
||||||
|
.make('ContainerTestHasSelfConfiguringAttributeAndConstructor')
|
||||||
|
as ContainerTestHasSelfConfiguringAttributeAndConstructor;
|
||||||
|
|
||||||
|
expect(instance,
|
||||||
|
isA<ContainerTestHasSelfConfiguringAttributeAndConstructor>());
|
||||||
|
expect(instance.value, equals('the-right-value'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContainerTestOnTenant {
|
||||||
|
final Tenant tenant;
|
||||||
|
const ContainerTestOnTenant(this.tenant);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Tenant {
|
||||||
|
TenantA,
|
||||||
|
TenantB,
|
||||||
|
}
|
||||||
|
|
||||||
|
class HasTenantImpl {
|
||||||
|
Tenant? tenant;
|
||||||
|
|
||||||
|
void onTenant(Tenant tenant) {
|
||||||
|
this.tenant = tenant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContainerTestHasTenantImplPropertyWithTenantA {
|
||||||
|
@ContainerTestOnTenant(Tenant.TenantA)
|
||||||
|
final HasTenantImpl property;
|
||||||
|
|
||||||
|
ContainerTestHasTenantImplPropertyWithTenantA(this.property);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContainerTestHasTenantImplPropertyWithTenantB {
|
||||||
|
@ContainerTestOnTenant(Tenant.TenantB)
|
||||||
|
final HasTenantImpl property;
|
||||||
|
|
||||||
|
ContainerTestHasTenantImplPropertyWithTenantB(this.property);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContainerTestConfiguresClass {
|
||||||
|
final String value;
|
||||||
|
const ContainerTestConfiguresClass(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ContainerTestConfiguresClass('the-right-value')
|
||||||
|
class ContainerTestHasSelfConfiguringAttributeAndConstructor {
|
||||||
|
String value;
|
||||||
|
ContainerTestHasSelfConfiguringAttributeAndConstructor(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContainerTestBootable {
|
||||||
|
const ContainerTestBootable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@ContainerTestBootable()
|
||||||
|
class ContainerTestHasBootable {
|
||||||
|
bool hasBooted = false;
|
||||||
|
|
||||||
|
void booting() {
|
||||||
|
hasBooted = true;
|
||||||
|
}
|
||||||
|
}
|
159
packages/service_container/test/container_extend_test.dart
Normal file
159
packages/service_container/test/container_extend_test.dart
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:platform_service_container/service_container.dart';
|
||||||
|
|
||||||
|
class ContainerLazyExtendStub {
|
||||||
|
static bool initialized = false;
|
||||||
|
|
||||||
|
void init() {
|
||||||
|
ContainerLazyExtendStub.initialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('ContainerExtendTest', () {
|
||||||
|
test('extendedBindings', () {
|
||||||
|
var container = Container();
|
||||||
|
container['foo'] = 'foo';
|
||||||
|
container.extend('foo', (old, container) => '${old}bar');
|
||||||
|
|
||||||
|
var result1 = container.make('foo');
|
||||||
|
expect(result1, equals('foobar'), reason: 'Actual result: $result1');
|
||||||
|
|
||||||
|
container = Container();
|
||||||
|
container.singleton(
|
||||||
|
'foo', (container) => <String, dynamic>{'name': 'taylor'});
|
||||||
|
container.extend('foo', (old, container) {
|
||||||
|
(old as Map<String, dynamic>)['age'] = 26;
|
||||||
|
return old;
|
||||||
|
});
|
||||||
|
|
||||||
|
var result2 = container.make('foo') as Map<String, dynamic>;
|
||||||
|
expect(result2['name'], equals('taylor'));
|
||||||
|
expect(result2['age'], equals(26));
|
||||||
|
expect(identical(result2, container.make('foo')), isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('extendInstancesArePreserved', () {
|
||||||
|
var container = Container();
|
||||||
|
container.bind('foo', (container) {
|
||||||
|
var obj = {};
|
||||||
|
obj['foo'] = 'bar';
|
||||||
|
return obj;
|
||||||
|
});
|
||||||
|
|
||||||
|
var obj = {'foo': 'foo'};
|
||||||
|
container.instance('foo', obj);
|
||||||
|
container.extend('foo', (obj, container) {
|
||||||
|
obj['bar'] = 'baz';
|
||||||
|
return obj;
|
||||||
|
});
|
||||||
|
container.extend('foo', (obj, container) {
|
||||||
|
obj['baz'] = 'foo';
|
||||||
|
return obj;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(container.make('foo')['foo'], equals('foo'));
|
||||||
|
expect(container.make('foo')['bar'], equals('baz'));
|
||||||
|
expect(container.make('foo')['baz'], equals('foo'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('extendIsLazyInitialized', () {
|
||||||
|
ContainerLazyExtendStub.initialized = false;
|
||||||
|
|
||||||
|
var container = Container();
|
||||||
|
container.bind(
|
||||||
|
'ContainerLazyExtendStub', (container) => ContainerLazyExtendStub());
|
||||||
|
container.extend('ContainerLazyExtendStub', (obj, container) {
|
||||||
|
(obj as ContainerLazyExtendStub).init();
|
||||||
|
return obj;
|
||||||
|
});
|
||||||
|
expect(ContainerLazyExtendStub.initialized, isFalse);
|
||||||
|
container.make('ContainerLazyExtendStub');
|
||||||
|
expect(ContainerLazyExtendStub.initialized, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('extendCanBeCalledBeforeBind', () {
|
||||||
|
var container = Container();
|
||||||
|
container.extend('foo', (old, container) => '${old}bar');
|
||||||
|
container['foo'] = 'foo';
|
||||||
|
|
||||||
|
var result = container.make('foo');
|
||||||
|
expect(result, equals('foobar'), reason: 'Actual result: $result');
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Implement rebinding functionality
|
||||||
|
// test('extendInstanceRebindingCallback', () {
|
||||||
|
// var rebindCalled = false;
|
||||||
|
|
||||||
|
// var container = Container();
|
||||||
|
// container.rebinding('foo', (container) {
|
||||||
|
// rebindCalled = true;
|
||||||
|
// });
|
||||||
|
|
||||||
|
// var obj = {};
|
||||||
|
// container.instance('foo', obj);
|
||||||
|
|
||||||
|
// container.extend('foo', (obj, container) => obj);
|
||||||
|
|
||||||
|
// expect(rebindCalled, isTrue);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// test('extendBindRebindingCallback', () {
|
||||||
|
// var rebindCalled = false;
|
||||||
|
|
||||||
|
// var container = Container();
|
||||||
|
// container.rebinding('foo', (container) {
|
||||||
|
// rebindCalled = true;
|
||||||
|
// });
|
||||||
|
// container.bind('foo', (container) => {});
|
||||||
|
|
||||||
|
// expect(rebindCalled, isFalse);
|
||||||
|
|
||||||
|
// container.make('foo');
|
||||||
|
|
||||||
|
// container.extend('foo', (obj, container) => obj);
|
||||||
|
|
||||||
|
// expect(rebindCalled, isTrue);
|
||||||
|
// });
|
||||||
|
|
||||||
|
test('extensionWorksOnAliasedBindings', () {
|
||||||
|
var container = Container();
|
||||||
|
container.singleton('something', (container) => 'some value');
|
||||||
|
container.alias('something', 'something-alias');
|
||||||
|
container.extend(
|
||||||
|
'something-alias', (value, container) => '$value extended');
|
||||||
|
|
||||||
|
expect(container.make('something'), equals('some value extended'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('multipleExtends', () {
|
||||||
|
var container = Container();
|
||||||
|
container['foo'] = 'foo';
|
||||||
|
container.extend('foo', (old, container) => '${old}bar');
|
||||||
|
container.extend('foo', (old, container) => '${old}baz');
|
||||||
|
|
||||||
|
expect(container.make('foo'), equals('foobarbaz'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('unsetExtend', () {
|
||||||
|
var container = Container();
|
||||||
|
container.bind('foo', (container) {
|
||||||
|
var obj = {};
|
||||||
|
obj['foo'] = 'bar';
|
||||||
|
return obj;
|
||||||
|
});
|
||||||
|
|
||||||
|
container.extend('foo', (obj, container) {
|
||||||
|
obj['bar'] = 'baz';
|
||||||
|
return obj;
|
||||||
|
});
|
||||||
|
|
||||||
|
container.forgetInstance('foo');
|
||||||
|
container.forgetExtenders('foo');
|
||||||
|
|
||||||
|
container.bind('foo', (container) => 'foo');
|
||||||
|
|
||||||
|
expect(container.make('foo'), equals('foo'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue