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

# Service container

> Uitleg over hoe dependency injection werkt met de service container van Laravel en de basis van bindings.

## Wat is de service container

De service container van Laravel is een mechanisme voor het beheren van klasse-afhankelijkheden en het uitvoeren van dependency injection. Dependency injection betekent dat de afhankelijkheden die een klasse nodig heeft via de constructor — of soms via settermethoden — in de klasse worden "geïnjecteerd".

Bekijk het volgende voorbeeld.

```php theme={null}
<?php

namespace App\Http\Controllers;

use App\Services\AppleMusic;
use Illuminate\View\View;

class PodcastController extends Controller
{
    /**
     * Maak een nieuwe controllerinstantie
     */
    public function __construct(
        protected AppleMusic $apple,
    ) {}

    /**
     * Toon de informatie van de opgegeven podcast
     */
    public function show(string $id): View
    {
        return view('podcasts.show', [
            'podcast' => $this->apple->findPodcast($id)
        ]);
    }
}
```

In dit voorbeeld moet de `PodcastController` podcasts ophalen uit een databron zoals Apple Music. Daarom **injecteren** we een service die podcasts kan ophalen. Door de service te injecteren kun je bij het testen eenvoudig een mock (dummy-implementatie) van de `AppleMusic`-service inwisselen.

<Info>
  Een goed begrip van de service container is onmisbaar voor het bouwen van grootschalige Laravel-applicaties. Het helpt ook bij het bijdragen aan de Laravel-core zelf.
</Info>

```mermaid theme={null}
flowchart TD
    A["Service provider<br>register()"] --> B["Service container<br>bindings registreren"]
    B --> C{"Resolutieverzoek<br>make() / automatische injectie"}
    C -- "Concrete klasse" --> D["Automatisch oplossen<br>via reflectie"]
    C -- "Interface" --> E["Geregistreerde implementatieklasse<br>oplossen"]
    D --> F["Instantie maken<br>injecteren in de constructor"]
    E --> F
```

## Zero-configuration resolution

Als een klasse alleen afhankelijk is van andere concrete klassen (dus geen interfaces), hoef je de container niet te vertellen hoe die moet worden opgelost. Stel bijvoorbeeld dat je de volgende code in `routes/web.php` schrijft.

```php theme={null}
<?php

class Service
{
    // ...
}

Route::get('/', function (Service $service) {
    dd($service::class);
});
```

<Info>
  In dit voorbeeld wordt de klasse in het routebestand gedefinieerd, maar dat is alleen ter demonstratie. In een echte applicatie definieer je serviceklassen in de map `app/Services`.
</Info>

Bezoek je deze route, dan lost Laravel de `Service`-klasse automatisch op en injecteert deze in de routehandler. Je profiteert van dependency injection zonder een configuratiebestand aan te maken.

Veel van de klassen die je in een Laravel-applicatie schrijft — controllers, event listeners, middleware — krijgen hun afhankelijkheden automatisch via de container geïnjecteerd.

## Bindings

### Basisbindings

De meeste bindings registreer je in een [service provider](/nl/service-providers). Binnen een service provider heb je via de property `$this->app` toegang tot de container.

#### bind

Met de `bind`-methode registreer je een binding door een klasse- of interfacenaam en een closure door te geven.

```php theme={null}
use App\Services\Transistor;
use App\Services\PodcastParser;
use Illuminate\Contracts\Foundation\Application;

$this->app->bind(Transistor::class, function (Application $app) {
    return new Transistor($app->make(PodcastParser::class));
});
```

De closure ontvangt de container zelf als argument. Hiermee kun je subafhankelijkheden oplossen.

Wil je buiten een service provider met de container werken, gebruik dan de `App`-facade.

```php theme={null}
use App\Services\Transistor;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\App;

App::bind(Transistor::class, function (Application $app) {
    // ...
});
```

<Info>
  Klassen die niet van interfaces afhangen, hoef je niet aan de container te binden. De container kan deze objecten automatisch oplossen via reflectie.
</Info>

#### singleton

De `singleton`-methode bindt een klasse of interface zodat deze slechts één keer wordt opgelost. Eenmaal opgelost geeft de container bij volgende aanroepen dezelfde instantie terug.

```php theme={null}
use App\Services\Transistor;
use App\Services\PodcastParser;
use Illuminate\Contracts\Foundation\Application;

$this->app->singleton(Transistor::class, function (Application $app) {
    return new Transistor($app->make(PodcastParser::class));
});
```

Met de methode `singletonIf` registreer je alleen een singleton-binding als er voor het opgegeven type nog geen binding is geregistreerd.

```php theme={null}
$this->app->singletonIf(Transistor::class, function (Application $app) {
    return new Transistor($app->make(PodcastParser::class));
});
```

#### Het Singleton-attribuut

Je kunt de container ook via het attribuut `#[Singleton]` op een klasse of interface instrueren om deze slechts één keer op te lossen.

```php theme={null}
<?php

namespace App\Services;

use Illuminate\Container\Attributes\Singleton;

#[Singleton]
class Transistor
{
    // ...
}
```

#### Scoped singletons binden

De `scoped`-methode bindt een klasse of interface zodat deze slechts één keer wordt opgelost binnen de levenscyclus van een Laravel-request of -job. Deze lijkt op de `singleton`-methode, maar instanties die met `scoped` zijn geregistreerd, worden weggegooid telkens wanneer de Laravel-applicatie een nieuwe "levenscyclus" start — bijvoorbeeld wanneer een [Laravel Octane](/nl/octane)-worker een nieuw request verwerkt of een [queue worker](/nl/queues) een nieuwe job verwerkt.

```php theme={null}
use App\Services\Transistor;
use App\Services\PodcastParser;
use Illuminate\Contracts\Foundation\Application;

$this->app->scoped(Transistor::class, function (Application $app) {
    return new Transistor($app->make(PodcastParser::class));
});
```

Met de methode `scopedIf` registreer je alleen een scoped binding als er voor het opgegeven type nog geen binding is geregistreerd.

```php theme={null}
$this->app->scopedIf(Transistor::class, function (Application $app) {
    return new Transistor($app->make(PodcastParser::class));
});
```

#### Het Scoped-attribuut

Je kunt de container ook via het attribuut `#[Scoped]` op een klasse of interface instrueren om deze slechts één keer per request-/job-levenscyclus op te lossen.

```php theme={null}
<?php

namespace App\Services;

use Illuminate\Container\Attributes\Scoped;

#[Scoped]
class Transistor
{
    // ...
}
```

#### instance

Je kunt ook een bestaande objectinstantie aan de container binden met de `instance`-methode. Bij volgende aanroepen geeft de container altijd die instantie terug.

```php theme={null}
use App\Services\Transistor;
use App\Services\PodcastParser;

$service = new Transistor(new PodcastParser);

$this->app->instance(Transistor::class, $service);
```

### Interfaces aan implementaties binden

Een van de krachtigste functies van de service container is het binden van een interface aan een specifieke implementatie. Stel dat je een `EventPusher`-interface en een `RedisEventPusher`-implementatie hebt.

```php theme={null}
use App\Contracts\EventPusher;
use App\Services\RedisEventPusher;

$this->app->bind(EventPusher::class, RedisEventPusher::class);
```

Hierdoor injecteert de container `RedisEventPusher` in klassen die een implementatie van `EventPusher` nodig hebben. Je hoeft alleen nog de `EventPusher`-interface te typehinten in de constructor.

```php theme={null}
use App\Contracts\EventPusher;

/**
 * Maak een nieuwe klasse-instantie
 */
public function __construct(
    protected EventPusher $pusher,
) {}
```

<Tip>
  Door van interfaces afhankelijk te zijn, hoef je je code niet te wijzigen als je de implementatie vervangt. Dat maakt testen en toekomstige wijzigingen eenvoudiger.
</Tip>

#### Het Bind-attribuut

Laravel biedt daarnaast het handige `Bind`-attribuut. Door dit attribuut op een interface te plaatsen, vertel je Laravel welke implementatie automatisch moet worden geïnjecteerd wanneer die interface wordt gevraagd. Bij gebruik van het `Bind`-attribuut is er geen extra registratie in een service provider nodig.

Bovendien kun je meerdere `Bind`-attributen op een interface plaatsen om per omgeving een andere implementatie te injecteren.

```php theme={null}
<?php

namespace App\Contracts;

use App\Services\FakeEventPusher;
use App\Services\RedisEventPusher;
use Illuminate\Container\Attributes\Bind;

#[Bind(RedisEventPusher::class)]
#[Bind(FakeEventPusher::class, environments: ['local', 'testing'])]
interface EventPusher
{
    // ...
}
```

Voor bindings die afhangen van een willekeurige voorwaarde kun je het attribuut `BindWhen` gebruiken. Aan de closure kan de container worden doorgegeven; de closure geeft `true` terug wanneer de binding moet worden toegepast. `Bind`- en `BindWhen`-attributen worden geëvalueerd in de volgorde waarin ze zijn gedeclareerd.

```php theme={null}
use App\Services\BetaEventPusher;
use Illuminate\Container\Attributes\BindWhen;
use Laravel\Pennant\Feature;

#[BindWhen(BetaEventPusher::class, static fn () => Feature::active('beta-events'))]
interface EventPusher
{
    // ...
}
```

<Info>
  Voor het `BindWhen`-attribuut is PHP 8.5 of hoger vereist.
</Info>

Door daarnaast het [Singleton](#het-singleton-attribuut)- of [Scoped](#het-scoped-attribuut)-attribuut te combineren, geef je aan of die containerbinding één keer wordt opgelost of één keer per request/job.

```php theme={null}
use App\Services\RedisEventPusher;
use Illuminate\Container\Attributes\Bind;
use Illuminate\Container\Attributes\Singleton;

#[Bind(RedisEventPusher::class)]
#[Singleton]
interface EventPusher
{
    // ...
}
```

## Automatisch oplossen (DI via typehints)

Bij het oplossen van klassen zoals controllers, event listeners en middleware kijkt de service container naar de typehints in de constructor en injecteert de afhankelijkheden automatisch.

```php theme={null}
<?php

namespace App\Http\Controllers;

use App\Repositories\UserRepository;

class UserController extends Controller
{
    /**
     * Maak een nieuwe controllerinstantie
     */
    public function __construct(
        protected UserRepository $users,
    ) {}
}
```

Als `UserRepository` niet van een interface afhangt, is registratie in de container niet nodig. Je hoeft alleen de route te bezoeken en de container lost de afhankelijkheid automatisch op en injecteert deze in de controller.

## Oplossen vanuit de container

### De make-methode

Met de `make`-methode los je een klasse-instantie op vanuit de container.

```php theme={null}
use App\Services\Transistor;

$transistor = app()->make(Transistor::class);
```

Kunnen niet alle afhankelijkheden van de klasse via de container worden opgelost, dan kun je met de methode `makeWith` extra argumenten doorgeven.

```php theme={null}
$transistor = $this->app->makeWith(Transistor::class, ['id' => 1]);
```

### Automatische injectie

In de praktijk roep je de `make`-methode zelden rechtstreeks aan. Voeg simpelweg typehints toe aan de constructor van klassen die de container oplost (controllers, event listeners, middleware enz.) en de container injecteert ze automatisch.

## De relatie tussen facades en de container

Laravel-facades bieden een statische interface naar objecten in de container. Zo haalt `Cache::get()` intern de `Cache`-service uit de container en roept die aan.

```php theme={null}
use Illuminate\Support\Facades\Cache;

// Aanroep via de facade
Cache::get('key');

// Gelijkwaardige aanroep rechtstreeks via de container
app('cache')->get('key');
```

Facades zijn een handige wrapper om de container. Bij het testen kun je facades ook vervangen door mocks.

```php theme={null}
use Illuminate\Support\Facades\Cache;

Cache::shouldReceive('get')
    ->once()
    ->with('key')
    ->andReturn('value');
```

## Praktijkvoorbeeld van constructor injection

Laten we een typisch patroon uit een echte applicatie bekijken.

<Steps>
  <Step title="De interface definiëren">
    ```php theme={null}
    <?php

    namespace App\Contracts;

    interface PaymentGateway
    {
        public function charge(int $amount, string $token): bool;
    }
    ```
  </Step>

  <Step title="De implementatieklasse maken">
    ```php theme={null}
    <?php

    namespace App\Services;

    use App\Contracts\PaymentGateway;

    class StripePaymentGateway implements PaymentGateway
    {
        public function charge(int $amount, string $token): bool
        {
            // Betaling verwerken via de Stripe API...
            return true;
        }
    }
    ```
  </Step>

  <Step title="Binden in een service provider">
    ```php theme={null}
    use App\Contracts\PaymentGateway;
    use App\Services\StripePaymentGateway;

    $this->app->singleton(PaymentGateway::class, StripePaymentGateway::class);
    ```
  </Step>

  <Step title="De injectie ontvangen in een controller">
    ```php theme={null}
    <?php

    namespace App\Http\Controllers;

    use App\Contracts\PaymentGateway;
    use Illuminate\Http\Request;

    class OrderController extends Controller
    {
        public function __construct(
            protected PaymentGateway $payment,
        ) {}

        public function store(Request $request)
        {
            $this->payment->charge(
                $request->amount,
                $request->payment_token
            );

            // ...
        }
    }
    ```
  </Step>
</Steps>

Dankzij dit patroon hoef je, als je de betaaldienst van `Stripe` naar een andere provider omzet, alleen de binding op één plek aan te passen.

## Volgende stap

<Card title="Service providers" icon="plug" href="/nl/service-providers">
  Leer hoe je bindings registreert met service providers.
</Card>


## Related topics

- [Service providers](/nl/service-providers.md)
- [Request lifecycle](/nl/lifecycle.md)
- [Applicatiestructuur van Laravel 11 en later](/nl/advanced/app-structure.md)
- [Een custom authenticatieguard implementeren](/nl/advanced/custom-auth-guard.md)
- [Laravel-packages ontwikkelen](/nl/advanced/package-development.md)
