refactor: adding parameter override support all test passing

This commit is contained in:
Patrick Stewart 2024-12-26 23:08:58 -07:00
parent d920180845
commit 81bb7bdd5e
2 changed files with 178 additions and 9 deletions

View file

@ -37,6 +37,9 @@ class Container {
/// The container's refreshing instances
final Set<Type> _refreshing = {};
/// The container's parameter override stack
final List<Map<String, dynamic>> _parameterStack = [];
/// The container's contextual bindings
final Map<Type, Map<Type, dynamic>> _contextual = {};
@ -318,9 +321,18 @@ class Container {
var named = <String, Object>{};
for (var param in constructor.parameters) {
if (param.type.reflectedType == String) {
positional.add('test.log'); // Default filename for FileLogger
// 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) {
named[param.name] = value;
@ -328,7 +340,6 @@ class Container {
positional.add(value);
}
}
}
instance = reflectedType.newInstance(
isDefault(constructor.name) ? '' : constructor.name,
@ -391,7 +402,10 @@ class Container {
_fireAfterResolvingCallbacks(instance);
return instance as T;
} 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);
_fireResolvingCallbacks(instance);
_fireAfterResolvingCallbacks(instance);
@ -450,8 +464,19 @@ class Container {
_buildStack.add(t2);
try {
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) {
named[param.name] = value;
} else {
@ -511,7 +536,11 @@ class Container {
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;
}
@ -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.
void _checkCircularDependency(Type type) {
if (_buildStack.contains(type)) {

View 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'));
});
});
}