search ESC

Searching…

No results for "".

Type at least 2 characters to search.

Docs

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 how Log.setDriver, Auth.setDriver, Cache.setDriver, Http.setDriver, Vault.setDriver and Echo.setManager install a driver or a fake. A later bind or singleton on that key evicts it. Install fakes AFTER Magic.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

  1. First call: Creates a new controller instance and stores it
  2. Subsequent calls: Returns the existing instance
  3. 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 findOrPut pattern for controllers to ensure a single instance is used throughout your application while maintaining proper lifecycle management.