> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aikeedo.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Dependency injection

> How Aikeedo's container autowires services, resolves configuration by ID, and lets modules and plugins bind their own implementations.

Aikeedo uses a [small autowiring container](https://github.com/iziphp/container). Most classes never register anything: they declare what they need in the constructor and the container builds it.

## Autowiring

```php theme={null}
class NoteService
{
    public function __construct(
        private NoteRepositoryInterface $repo,
        private Dispatcher $dispatcher,
        private LoggerInterface $logger,
    ) {}
}
```

Concrete classes are constructed recursively. Interfaces resolve when something has bound them, which module bootstrappers do during boot:

```php theme={null}
$app->set(CategoryRepositoryInterface::class, CategoryRepository::class);
```

## Injecting values by ID

Scalars and configuration come from the container by ID, using an attribute:

```php theme={null}
use Easy\Container\Attributes\Inject;

public function __construct(
    #[Inject('option.billing.currency')]
    private ?string $currency = 'USD',

    #[Inject('config.dirs.uploads')]
    private ?string $uploadsDir = null,

    #[Inject('version')]
    private string $version = 'dev',
) {}
```

<Warning>
  Always pair `#[Inject('option.…')]` with a nullable type and a default. Options that were never saved don't resolve, and a non-nullable parameter then fails to construct the class.
</Warning>

## Resolvers

Two resolvers extend the container with dynamic IDs.

### Configuration

Anything starting with `config.` comes from the configuration object built at bootstrap:

| ID | Value |
| - | - |
| `config.dirs.root` | Installation root |
| `config.dirs.webroot` | Web root, honoring `PUBLIC_DIR` |
| `config.dirs.cache`, `.log`, `.uploads`, `.locale` | Runtime directories |
| `config.dirs.src`, `.views` | Source and template directories |
| `config.dirs.extensions`, `.artifacts` | Plugin directories |
| `config.enable_debugging` | Effective debug flag |
| `config.enable_caching` | Effective cache flag |
| `config.locale` | Default locale configuration |

### Options

Anything starting with `option.` comes from the options table. Values are stored as JSON, flattened into a dot map, and cast on the way out:

```php theme={null}
#[Inject('option.features.chat.is_enabled')]
private ?bool $chatEnabled = false,
```

| Stored | Resolved as |
| - | - |
| `"1"`, `"0"` | `int` |
| `"1.5"` | `float` |
| `"true"`, `"false"` | `bool` |
| A JSON object or array | `array` |
| Anything else | `string` |

Options are read once per request. If the database isn't configured, resolution quietly returns nothing, which keeps the installer working.

## Resolving imperatively

Where constructor injection isn't possible, such as inside a plugin's `boot()`:

```php theme={null}
use Application;

$isEnabled = Application::make('option.features.notes.is_enabled', false);
$dispatcher = Application::make(Dispatcher::class);
```

`Application::make($id, $default)` returns the default when the ID can't be resolved, instead of throwing.

<Tip>
  Prefer constructor injection. Static resolution hides dependencies and makes classes harder to test; use it only for bootstrap-time decisions.
</Tip>

## Binding your own services

Bind an interface when callers should depend on the contract:

```php theme={null}
$app->set(NotifierInterface::class, EmailNotifier::class);
```

Bind an instance when it needs setting up before anything resolves it, such as passing configuration or registering entries:

```php theme={null}
$collection = new ToolCollection($container);
$collection->add(WeatherTool::LOOKUP_KEY, WeatherTool::class);

$app->set(ToolCollection::class, $collection);
```

## Keyed registries

For extension points with many implementations, Aikeedo uses registries rather than container bindings, so implementations can be listed as well as resolved:

| Registry | Holds |
| - | - |
| `Shared\Infrastructure\Collections\ServiceCollectionInterface` | Any keyed implementation of a given interface |
| `Billing\Infrastructure\Payments\PaymentGatewayFactoryInterface` | Payment gateways |
| `Shared\Infrastructure\FileSystem\CdnAdapterCollectionInterface` | Storage adapters |
| `Billing\Infrastructure\Currency\RateProviderCollectionInterface` | Exchange rate providers |
| `Ai\Infrastructure\Services\AiServiceFactory` | AI services |
| `Ai\Infrastructure\Services\Tools\ToolCollection` | Chat tools |

Entries are stored as class names and resolved from the container on first use, so registering is cheap.

## Lifetimes

Services resolved from the container are shared for the request. There's no request-scoped or transient lifetime to configure: a fresh process handles each request, and everything is discarded at the end.

## Related

* [Bootstrap and lifecycle](/development/core/bootstrap-and-lifecycle)
* [Configuration](/development/core/configuration)
* [Plugin dependency injection](/development/plugins/dependency-injection-and-services)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.