# 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](#introduction)
- [Binding Services](#binding-services)
    - [Simple Bindings](#simple-bindings)
    - [Singletons](#singletons)
- [Resolving Services](#resolving-services)
- [Automatic Resolution](#automatic-resolution)
- [Using Controllers](#using-controllers)
- [Related Documentation](#related-documentation)

## 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.

```dart
// Register a service
Magic.bind('mailer', () => MailService());

// Resolve it anywhere
final mailer = Magic.make<MailService>('mailer');
```

## Binding Services

### Simple Bindings

Use `Magic.bind()` to register a service. Each resolve creates a new instance:

```dart
Magic.bind('api', () => ApiClient());

// Each call creates a new instance
final api1 = Magic.make<ApiClient>('api');
final api2 = Magic.make<ApiClient>('api');
// api1 != api2
```

### Singletons

Use `Magic.singleton()` to register a service that's instantiated only once:

```dart
Magic.singleton('mailer', () => MailService());

// Always returns the same instance
final mailer1 = Magic.make<MailService>('mailer');
final mailer2 = Magic.make<MailService>('mailer');
// mailer1 == mailer2
```

### 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.

```dart
Magic.singleton('svc', () => StarterService());
Magic.make<Service>('svc'); // StarterService

Magic.singleton('svc', () => AppService());
Magic.make<Service>('svc'); // AppService
```

> [!WARNING]
> The same rule applies to a value placed with `setInstance`, which is how
> `Auth.fake()`, `Cache.fake()`, `Http.fake()`, `Log.fake()` and
> `Vault.fake()` install 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:

```dart
class AppServiceProvider extends ServiceProvider {
  @override
  void register() {
    // Register services
    app.bind('api', () => ApiClient(Config.get('api.url')));
    app.singleton('mailer', () => MailService());
  }

  @override
  Future<void> boot() async {
    // Perform initialization after all services are registered
    final api = app.make<ApiClient>('api');
    await api.warmUp();
  }
}
```

## Resolving Services

### Using Magic.make()

```dart
// With type inference
final service = Magic.make<MyService>('myservice');

// Without type (returns dynamic)
final service = Magic.make('myservice');
```

### Checking Existence

```dart
if (Magic.bound('api')) {
  final api = Magic.make<ApiClient>('api');
}
```

## Automatic Resolution

Magic facades automatically resolve their underlying services. You don't need to manually resolve common services:

```dart
// These facades auto-resolve from the container:
Cache.get('key');           // Resolves CacheManager
Auth.check();               // Resolves AuthManager
Http.get('/users');         // Resolves the NetworkDriver
```

## Using Controllers

Magic provides a convenient way to manage controller instances:

### findOrPut Pattern

The recommended pattern for controllers is using `Magic.findOrPut()`:

```dart
class UserController extends MagicController with MagicStateMixin<List<User>> {
  // 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

```dart
// Register a controller
Magic.put(UserController());

// Find an existing controller
final controller = Magic.find<UserController>();

// Delete a controller
Magic.delete<UserController>();
```

`Magic.delete` disposes what it removes when it is a `ChangeNotifier`: every `MagicController`, and a plain `ChangeNotifier` or `ValueNotifier` registered with `Magic.put` too. Do not dispose such a notifier yourself as well; a second `dispose()` throws in a debug build. `Magic.flush()` clears the registry without disposing.

### Controller Lifecycle

```dart
class UserController extends MagicController {
  @override
  void onInit() {
    // Called once, the first time a stateful view backs the controller.
    // super.onInit() marks the controller initialised; without it a remount
    // runs onInit (and loadUsers) again.
    super.onInit();
    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.

## Related Documentation

- [Facades](https://anilcancakir.com/magic/docs/architecture/facades.md): the static stubs that resolve these bindings.
- [Service Providers](https://anilcancakir.com/magic/docs/getting-started/service-providers.md): where bindings are registered and booted.
- [Request Lifecycle](https://anilcancakir.com/magic/docs/architecture/request-lifecycle.md): when the container is built during `Magic.init()`.
- [Controllers](https://anilcancakir.com/magic/docs/basics/controllers.md): `MagicController` and its lifecycle.
