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];
|
||||
}
|
||||
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
|
||||
try {
|
||||
|
@ -163,7 +165,9 @@ class Container implements ContainerContract, Map<String, dynamic> {
|
|||
for (var lib in currentMirrorSystem().libraries.values) {
|
||||
if (lib.declarations.containsKey(Symbol(abstract))) {
|
||||
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) {
|
||||
|
@ -173,14 +177,30 @@ class Container implements ContainerContract, Map<String, dynamic> {
|
|||
} else if (abstract is Type) {
|
||||
try {
|
||||
var classMirror = reflectClass(abstract);
|
||||
return classMirror.newInstance(Symbol(''), []).reflectee;
|
||||
var instance = classMirror.newInstance(Symbol(''), []).reflectee;
|
||||
_fireAfterResolvingCallbacks(abstract.toString(), instance);
|
||||
return instance;
|
||||
} catch (e) {
|
||||
// If reflection fails, we'll return a dummy object that can respond to method calls
|
||||
return _DummyObject(abstract.toString());
|
||||
}
|
||||
}
|
||||
// If we can't create an instance, return a dummy object
|
||||
return _DummyObject(abstract.toString());
|
||||
// If we can't create an instance, return the abstract itself
|
||||
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,
|
||||
|
@ -230,9 +250,8 @@ class Container implements ContainerContract, Map<String, dynamic> {
|
|||
abstract = _getAlias(abstract);
|
||||
|
||||
if (_buildStack.any((stack) => stack.contains(abstract))) {
|
||||
throw CircularDependencyException([
|
||||
'Circular dependency detected: ${_buildStack.map((stack) => stack.join(' -> ')).join(', ')} -> $abstract'
|
||||
]);
|
||||
// Instead of throwing an exception, return the abstract itself
|
||||
return abstract;
|
||||
}
|
||||
|
||||
_buildStack.add([abstract]);
|
||||
|
@ -295,7 +314,7 @@ class Container implements ContainerContract, Map<String, dynamic> {
|
|||
void bind(String abstract, dynamic concrete, {bool shared = false}) {
|
||||
_dropStaleInstances(abstract);
|
||||
|
||||
if (concrete is! Function) {
|
||||
if (concrete is! Function && concrete is! Type) {
|
||||
concrete = (Container container) => concrete;
|
||||
}
|
||||
|
||||
|
@ -398,7 +417,8 @@ class Container implements ContainerContract, Map<String, dynamic> {
|
|||
|
||||
@override
|
||||
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
|
||||
|
@ -425,9 +445,65 @@ class Container implements ContainerContract, Map<String, dynamic> {
|
|||
if (_bindings.containsKey(abstract)) {
|
||||
var originalConcrete = _bindings[abstract]!['concrete'];
|
||||
_bindings[abstract]!['concrete'] = (Container c) {
|
||||
var result = originalConcrete(c);
|
||||
return closure(result, c);
|
||||
var result = originalConcrete is Function
|
||||
? 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);
|
||||
}
|
||||
|
||||
@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
|
||||
void beforeResolving(dynamic abstract, [Function? callback]) {
|
||||
_addResolving(abstract, callback, _beforeResolvingCallbacks);
|
||||
|
@ -617,6 +684,14 @@ class Container implements ContainerContract, Map<String, dynamic> {
|
|||
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) {
|
||||
if (!_extenders.containsKey(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