Service Container
The Magic service container is a string-keyed IoC container for registering factory closures and resolving singletons across your application without relying on reflection.
Introduction
The Magic service container is a powerful tool for managing class dependencies and performing dependency injection. Like Laravel's container, it allows you to register bindings and resolve them throughout your application.
// Register a service
Magic.bind('mailer', () => MailService());
// Resolve it anywhere
final mailer = Magic.make('mailer');
Binding Services
Simple Bindings
Use Magic.bind() to register a service. Each resolve creates a new instance:
Magic.bind('api', () => ApiClient());
// Each call creates a new instance
final api1 = Magic.make('api');
final api2 = Magic.make('api');
// api1 != api2
Singletons
Use Magic.singleton() to register a service that's instantiated only once:
Magic.singleton('cache', () => CacheManager());
// Always returns the same instance
final cache1 = Magic.make('cache');
final cache2 = Magic.make('cache');
// cache1 == cache2
Rebinding, and where fakes go
Binding a key that has already been resolved discards the instance the old
factory produced, so the new binding takes effect immediately. That is what
makes an override work: a starter package binds a key in its provider, your app
binds the same key to its own implementation, and the next make() gets yours
even if something resolved the key in between.
Magic.singleton('svc', () => StarterService());
Magic.make('svc'); // StarterService
Magic.singleton('svc', () => AppService());
Magic.make('svc'); // AppService
[!WARNING] The same rule applies to a value placed with
setInstance, which is howLog.setDriver,Auth.setDriver,Cache.setDriver,Http.setDriver,Vault.setDriverandEcho.setManagerinstall a driver or a fake. A laterbindorsingletonon that key evicts it. Install fakes AFTERMagic.init()and after your providers have registered, never before.
Binding in Service Providers
The recommended way to register services is in a service provider:
class AppServiceProvider extends ServiceProvider {
@override
void register() {
// Register services
app.bind('api', () => ApiClient(Config.get('api.url')));
app.singleton('cache', () => CacheManager());
}
@override
Future boot() async {
// Perform initialization after all services are registered
final cache = app.make('cache');
await cache.initialize();
}
}
Resolving Services
Using Magic.make()
// With type inference
final service = Magic.make('myservice');
// Without type (returns dynamic)
final service = Magic.make('myservice');
Checking Existence
if (Magic.bound('api')) {
final api = Magic.make('api');
}
Automatic Resolution
Magic facades automatically resolve their underlying services. You don't need to manually resolve common services:
// These facades auto-resolve from the container:
Cache.get('key'); // Resolves CacheManager
Auth.check(); // Resolves AuthManager
Lang.setLocale(locale); // Resolves LocalizationService
Using Controllers
Magic provides a convenient way to manage controller instances:
findOrPut Pattern
The recommended pattern for controllers is using Magic.findOrPut():
class UserController extends MagicController with MagicStateMixin> {
// Singleton accessor
static UserController get instance => Magic.findOrPut(UserController.new);
// Controller logic...
}
// Usage anywhere in your app
final controller = UserController.instance;
How It Works
- First call: Creates a new controller instance and stores it
- Subsequent calls: Returns the existing instance
- Automatic cleanup: Controller's
onClose()is called when disposed
Manual Controller Management
// Register a controller
Magic.put(UserController());
// Find an existing controller
final controller = Magic.find();
// Delete a controller
Magic.delete();
Controller Lifecycle
class UserController extends MagicController {
@override
void onInit() {
// Called when controller is first created
loadUsers();
}
@override
void onClose() {
// Called when controller is disposed
// Clean up subscriptions, streams, etc.
}
}
[!TIP] Use the
findOrPutpattern for controllers to ensure a single instance is used throughout your application while maintaining proper lifecycle management.