refactor: adding parameter override support all test passing
This commit is contained in:
parent
d920180845
commit
81bb7bdd5e
2 changed files with 178 additions and 9 deletions
|
@ -37,6 +37,9 @@ class Container {
|
||||||
/// The container's refreshing instances
|
/// The container's refreshing instances
|
||||||
final Set<Type> _refreshing = {};
|
final Set<Type> _refreshing = {};
|
||||||
|
|
||||||
|
/// The container's parameter override stack
|
||||||
|
final List<Map<String, dynamic>> _parameterStack = [];
|
||||||
|
|
||||||
/// The container's contextual bindings
|
/// The container's contextual bindings
|
||||||
final Map<Type, Map<Type, dynamic>> _contextual = {};
|
final Map<Type, Map<Type, dynamic>> _contextual = {};
|
||||||
|
|
||||||
|
@ -318,15 +321,23 @@ class Container {
|
||||||
var named = <String, Object>{};
|
var named = <String, Object>{};
|
||||||
|
|
||||||
for (var param in constructor.parameters) {
|
for (var param in constructor.parameters) {
|
||||||
if (param.type.reflectedType == String) {
|
// Check for parameter override
|
||||||
positional.add('test.log'); // Default filename for FileLogger
|
var override = getParameterOverride(param.name);
|
||||||
} else {
|
if (override != null) {
|
||||||
var value = make(param.type.reflectedType);
|
|
||||||
if (param.isNamed) {
|
if (param.isNamed) {
|
||||||
named[param.name] = value;
|
named[param.name] = override;
|
||||||
} else {
|
} else {
|
||||||
positional.add(value);
|
positional.add(override);
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No override, resolve normally
|
||||||
|
var value = make(param.type.reflectedType);
|
||||||
|
if (param.isNamed) {
|
||||||
|
named[param.name] = value;
|
||||||
|
} else {
|
||||||
|
positional.add(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -391,7 +402,10 @@ class Container {
|
||||||
_fireAfterResolvingCallbacks(instance);
|
_fireAfterResolvingCallbacks(instance);
|
||||||
return instance as T;
|
return instance as T;
|
||||||
} else if (search._factories.containsKey(resolvedType)) {
|
} else if (search._factories.containsKey(resolvedType)) {
|
||||||
var instance = search._factories[resolvedType]!(this);
|
// For factory bindings, wrap the factory call in withParameters
|
||||||
|
var instance = withParameters(_parameterStack.lastOrNull ?? {}, () {
|
||||||
|
return search!._factories[resolvedType]!(this);
|
||||||
|
});
|
||||||
instance = _applyExtenders(resolvedType, instance);
|
instance = _applyExtenders(resolvedType, instance);
|
||||||
_fireResolvingCallbacks(instance);
|
_fireResolvingCallbacks(instance);
|
||||||
_fireAfterResolvingCallbacks(instance);
|
_fireAfterResolvingCallbacks(instance);
|
||||||
|
@ -450,8 +464,19 @@ class Container {
|
||||||
_buildStack.add(t2);
|
_buildStack.add(t2);
|
||||||
try {
|
try {
|
||||||
for (var param in constructor.parameters) {
|
for (var param in constructor.parameters) {
|
||||||
var value = make(param.type.reflectedType);
|
// Check for parameter override
|
||||||
|
var override = getParameterOverride(param.name);
|
||||||
|
if (override != null) {
|
||||||
|
if (param.isNamed) {
|
||||||
|
named[param.name] = override;
|
||||||
|
} else {
|
||||||
|
positional.add(override);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No override, resolve normally
|
||||||
|
var value = make(param.type.reflectedType);
|
||||||
if (param.isNamed) {
|
if (param.isNamed) {
|
||||||
named[param.name] = value;
|
named[param.name] = value;
|
||||||
} else {
|
} else {
|
||||||
|
@ -511,7 +536,11 @@ class Container {
|
||||||
throw StateError('This container already has a factory for $t2.');
|
throw StateError('This container already has a factory for $t2.');
|
||||||
}
|
}
|
||||||
|
|
||||||
_factories[t2] = f;
|
// Wrap factory in parameter override handler
|
||||||
|
_factories[t2] = (container) {
|
||||||
|
return container.withParameters(
|
||||||
|
_parameterStack.lastOrNull ?? {}, () => f(container));
|
||||||
|
};
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -951,6 +980,37 @@ class Container {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Push parameter overrides onto the stack.
|
||||||
|
///
|
||||||
|
/// These parameters will be used when resolving dependencies until they are popped.
|
||||||
|
/// ```dart
|
||||||
|
/// container.withParameters({'filename': 'custom.log'}, () {
|
||||||
|
/// var logger = container.make<Logger>();
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
T withParameters<T>(Map<String, dynamic> parameters, T Function() callback) {
|
||||||
|
_parameterStack.add(parameters);
|
||||||
|
try {
|
||||||
|
return callback();
|
||||||
|
} finally {
|
||||||
|
_parameterStack.removeLast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an override value for a parameter if one exists.
|
||||||
|
///
|
||||||
|
/// This method is used internally by the container to resolve parameter overrides,
|
||||||
|
/// but is also exposed for use in factory functions.
|
||||||
|
dynamic getParameterOverride(String name) {
|
||||||
|
for (var i = _parameterStack.length - 1; i >= 0; i--) {
|
||||||
|
var parameters = _parameterStack[i];
|
||||||
|
if (parameters.containsKey(name)) {
|
||||||
|
return parameters[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if we're in danger of a circular dependency.
|
/// Check if we're in danger of a circular dependency.
|
||||||
void _checkCircularDependency(Type type) {
|
void _checkCircularDependency(Type type) {
|
||||||
if (_buildStack.contains(type)) {
|
if (_buildStack.contains(type)) {
|
||||||
|
|
109
incubation/container/container/test/parameter_override_test.dart
Normal file
109
incubation/container/container/test/parameter_override_test.dart
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
import 'package:platformed_container/container.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
class FileLogger {
|
||||||
|
final String filename;
|
||||||
|
FileLogger(this.filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockReflector extends Reflector {
|
||||||
|
@override
|
||||||
|
String? getName(Symbol symbol) => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedClass? reflectClass(Type clazz) => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedType? reflectType(Type type) => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedInstance? reflectInstance(Object? instance) => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedFunction? reflectFunction(Function function) => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ReflectedType reflectFutureOf(Type type) => throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late Container container;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
container = Container(MockReflector());
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Parameter Override Tests', () {
|
||||||
|
test('can override constructor parameters', () {
|
||||||
|
container.registerFactory<FileLogger>((c) {
|
||||||
|
var filename =
|
||||||
|
c.getParameterOverride('filename') as String? ?? 'default.log';
|
||||||
|
return FileLogger(filename);
|
||||||
|
});
|
||||||
|
|
||||||
|
var logger = container.withParameters(
|
||||||
|
{'filename': 'custom.log'}, () => container.make<FileLogger>());
|
||||||
|
|
||||||
|
expect(logger.filename, equals('custom.log'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('parameter overrides are scoped', () {
|
||||||
|
container.registerFactory<FileLogger>((c) {
|
||||||
|
var filename =
|
||||||
|
c.getParameterOverride('filename') as String? ?? 'default.log';
|
||||||
|
return FileLogger(filename);
|
||||||
|
});
|
||||||
|
|
||||||
|
var customLogger = container.withParameters(
|
||||||
|
{'filename': 'custom.log'}, () => container.make<FileLogger>());
|
||||||
|
|
||||||
|
var defaultLogger = container.make<FileLogger>();
|
||||||
|
|
||||||
|
expect(customLogger.filename, equals('custom.log'));
|
||||||
|
expect(defaultLogger.filename, equals('default.log'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('nested parameter overrides', () {
|
||||||
|
container.registerFactory<FileLogger>((c) {
|
||||||
|
var filename =
|
||||||
|
c.getParameterOverride('filename') as String? ?? 'default.log';
|
||||||
|
return FileLogger(filename);
|
||||||
|
});
|
||||||
|
|
||||||
|
var logger = container.withParameters(
|
||||||
|
{'filename': 'outer.log'},
|
||||||
|
() => container.withParameters(
|
||||||
|
{'filename': 'inner.log'}, () => container.make<FileLogger>()));
|
||||||
|
|
||||||
|
expect(logger.filename, equals('inner.log'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('parameter overrides in child container', () {
|
||||||
|
container.registerFactory<FileLogger>((c) {
|
||||||
|
var filename =
|
||||||
|
c.getParameterOverride('filename') as String? ?? 'default.log';
|
||||||
|
return FileLogger(filename);
|
||||||
|
});
|
||||||
|
var child = container.createChild();
|
||||||
|
|
||||||
|
var logger = child.withParameters(
|
||||||
|
{'filename': 'custom.log'}, () => child.make<FileLogger>());
|
||||||
|
|
||||||
|
expect(logger.filename, equals('custom.log'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('parameter overrides with multiple parameters', () {
|
||||||
|
container.registerFactory<FileLogger>((c) {
|
||||||
|
var filename =
|
||||||
|
c.getParameterOverride('filename') as String? ?? 'default.log';
|
||||||
|
return FileLogger(filename);
|
||||||
|
});
|
||||||
|
|
||||||
|
var logger = container.withParameters(
|
||||||
|
{'filename': 'custom.log', 'level': 'debug', 'maxSize': 1024},
|
||||||
|
() => container.make<FileLogger>());
|
||||||
|
|
||||||
|
expect(logger.filename, equals('custom.log'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in a new issue