9.5 KiB
9.5 KiB
Container Migration Guide
Overview
This guide helps you migrate from the current Container implementation to the new Laravel-compatible version. It covers:
- Breaking changes
- New features
- Migration strategies
- Code examples
- Best practices
Breaking Changes
1. Binding Registration
Old Way
// Old implementation
container.bind<Service>(instance);
container.singleton<Service>(instance);
New Way
// New implementation
container.bind<Service>((c) => instance);
container.singleton<Service>((c) => instance);
// With contextual binding
container.when(UserController)
.needs<Service>()
.give((c) => SpecialService());
2. Service Resolution
Old Way
// Old implementation
var service = container.make<Service>();
var namedService = container.makeNamed<Service>('name');
New Way
// New implementation
var service = container.make<Service>();
var contextualService = container.make<Service>(context: UserController);
var taggedServices = container.taggedAs<Service>('tag');
3. Method Injection
Old Way
// Old implementation - manual parameter resolution
class UserService {
void process(User user) {
var logger = container.make<Logger>();
var validator = container.make<Validator>();
// Process user...
}
}
New Way
// New implementation - automatic method injection
class UserService {
void process(
User user,
Logger logger, // Automatically injected
Validator validator // Automatically injected
) {
// Process user...
}
}
// Usage
container.call(userService, 'process', {'user': user});
New Features
1. Contextual Binding
// Register different implementations based on context
void setupBindings(Container container) {
// Default storage
container.bind<Storage>((c) => LocalStorage());
// User uploads use cloud storage
container.when(UserUploadController)
.needs<Storage>()
.give((c) => CloudStorage());
// System files use local storage
container.when(SystemFileController)
.needs<Storage>()
.give((c) => LocalStorage());
}
2. Tagged Bindings
// Register and tag related services
void setupReportServices(Container container) {
// Register services
container.bind<PerformanceReport>((c) => PerformanceReport());
container.bind<FinancialReport>((c) => FinancialReport());
container.bind<UserReport>((c) => UserReport());
// Tag them for easy retrieval
container.tag([
PerformanceReport,
FinancialReport,
UserReport
], 'reports');
// Additional categorization
container.tag([PerformanceReport], 'metrics');
container.tag([FinancialReport], 'financial');
}
// Usage
var reports = container.taggedAs<Report>('reports');
var metricReports = container.taggedAs<Report>('metrics');
3. Method Injection
class ReportGenerator {
void generateReport(
Report report,
Logger logger, // Automatically injected
Formatter formatter, // Automatically injected
{required DateTime date} // Manually provided
) {
logger.info('Generating report...');
var data = report.getData();
var formatted = formatter.format(data);
// Generate report...
}
}
// Usage
container.call(
generator,
'generateReport',
{'report': report, 'date': DateTime.now()}
);
Migration Strategies
1. Gradual Migration
// Step 1: Update bindings
class ServiceRegistry {
void register(Container container) {
// Old way (still works)
container.bind<OldService>(OldService());
// New way
container.bind<NewService>((c) => NewService());
// Add contextual bindings
container.when(NewController)
.needs<Service>()
.give((c) => NewService());
}
}
// Step 2: Update resolution
class ServiceConsumer {
void process() {
// Old way (still works)
var oldService = container.make<OldService>();
// New way
var newService = container.make<NewService>();
var contextual = container.make<Service>(
context: NewController
);
}
}
// Step 3: Add method injection
class ServiceProcessor {
// Old way
void processOld(Data data) {
var service = container.make<Service>();
service.process(data);
}
// New way
void processNew(
Data data,
Service service // Injected automatically
) {
service.process(data);
}
}
2. Feature-by-Feature Migration
- Update Bindings First
// Update all bindings to new style
void registerBindings(Container container) {
// Update simple bindings
container.bind<Service>((c) => ServiceImpl());
// Add contextual bindings
container.when(Controller)
.needs<Service>()
.give((c) => SpecialService());
}
- Add Tagged Services
// Group related services
void registerServices(Container container) {
// Register services
container.bind<ServiceA>((c) => ServiceA());
container.bind<ServiceB>((c) => ServiceB());
// Add tags
container.tag([ServiceA, ServiceB], 'services');
}
- Implement Method Injection
// Convert to method injection
class UserController {
// Before
void oldProcess(User user) {
var validator = container.make<Validator>();
var logger = container.make<Logger>();
// Process...
}
// After
void newProcess(
User user,
Validator validator,
Logger logger
) {
// Process...
}
}
Testing During Migration
1. Verify Bindings
void main() {
group('Container Migration Tests', () {
late Container container;
setUp(() {
container = Container();
registerBindings(container);
});
test('should support old-style bindings', () {
var oldService = container.make<OldService>();
expect(oldService, isNotNull);
});
test('should support new-style bindings', () {
var newService = container.make<NewService>();
expect(newService, isNotNull);
});
test('should resolve contextual bindings', () {
var service = container.make<Service>(
context: Controller
);
expect(service, isA<SpecialService>());
});
});
}
2. Verify Tagged Services
void main() {
group('Tagged Services Tests', () {
late Container container;
setUp(() {
container = Container();
registerServices(container);
});
test('should resolve tagged services', () {
var services = container.taggedAs<Service>('services');
expect(services, hasLength(2));
expect(services, contains(isA<ServiceA>()));
expect(services, contains(isA<ServiceB>()));
});
});
}
3. Verify Method Injection
void main() {
group('Method Injection Tests', () {
late Container container;
setUp(() {
container = Container();
registerServices(container);
});
test('should inject method dependencies', () {
var controller = UserController();
// Call with only required parameters
container.call(
controller,
'newProcess',
{'user': testUser}
);
// Verify injection worked
verify(() => mockValidator.validate(any)).called(1);
verify(() => mockLogger.log(any)).called(1);
});
});
}
Best Practices
- Update Bindings Consistently
// Good: Consistent new style
container.bind<Service>((c) => ServiceImpl());
container.singleton<Logger>((c) => FileLogger());
// Bad: Mixed styles
container.bind<Service>(ServiceImpl()); // Old style
container.singleton<Logger>((c) => FileLogger()); // New style
- Use Contextual Bindings Appropriately
// Good: Clear context and purpose
container.when(UserController)
.needs<Storage>()
.give((c) => UserStorage());
// Bad: Unclear or overly broad context
container.when(Object)
.needs<Storage>()
.give((c) => GenericStorage());
- Organize Tagged Services
// Good: Logical grouping
container.tag([
PerformanceReport,
SystemReport
], 'system-reports');
container.tag([
UserReport,
ActivityReport
], 'user-reports');
// Bad: Mixed concerns
container.tag([
PerformanceReport,
UserReport,
Logger,
Storage
], 'services');
Common Issues and Solutions
- Binding Resolution Errors
// Problem: Missing binding
var service = container.make<Service>(); // Throws error
// Solution: Register binding first
container.bind<Service>((c) => ServiceImpl());
var service = container.make<Service>(); // Works
- Contextual Binding Conflicts
// Problem: Multiple contexts
container.when(Controller)
.needs<Service>()
.give((c) => ServiceA());
container.when(Controller) // Conflict!
.needs<Service>()
.give((c) => ServiceB());
// Solution: Use specific contexts
container.when(UserController)
.needs<Service>()
.give((c) => ServiceA());
container.when(AdminController)
.needs<Service>()
.give((c) => ServiceB());
- Method Injection Failures
// Problem: Unresolvable parameter
void process(
CustomType param // Not registered with container
) { }
// Solution: Register or provide parameter
container.bind<CustomType>((c) => CustomType());
// or
container.call(instance, 'process', {
'param': customInstance
});
Next Steps
- Audit existing container usage
- Plan migration phases
- Update bindings
- Add new features
- Update tests
- Document changes
Would you like me to create detailed specifications for any of these next steps?