Passer au contenu principal

Qu’est-ce que le conteneur de services

Le conteneur de services de Laravel est le mécanisme qui gère les dépendances des classes et effectue l’injection de dépendances. Injecter une dépendance, c’est fournir à une classe ce dont elle a besoin via son constructeur (ou parfois via un setter). Voyons l’exemple suivant.
<?php

namespace App\Http\Controllers;

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

class PodcastController extends Controller
{
    /**
     * Crée une nouvelle instance de contrôleur.
     */
    public function __construct(
        protected AppleMusic $apple,
    ) {}

    /**
     * Affiche les informations du podcast.
     */
    public function show(string $id): View
    {
        return view('podcasts.show', [
            'podcast' => $this->apple->findPodcast($id)
        ]);
    }
}
Ici, PodcastController a besoin d’aller chercher un podcast depuis Apple Music. On injecte donc un service capable de le faire. Cette injection permet aussi de remplacer facilement AppleMusic par un mock lors des tests.
Une bonne compréhension du conteneur de services est essentielle pour construire des applications Laravel de grande taille. C’est également utile si vous contribuez au cœur de Laravel.

Résolution sans configuration

Si une classe ne dépend que d’autres classes concrètes (pas d’interfaces), aucune configuration n’est nécessaire. Par exemple, dans routes/web.php :
<?php

class Service
{
    // ...
}

Route::get('/', function (Service $service) {
    dd($service::class);
});
Cet exemple définit une classe dans le fichier de routes uniquement à des fins de démonstration. En pratique, placez vos services dans app/Services.
En accédant à cette route, Laravel résout automatiquement Service et l’injecte dans le handler. Aucune configuration nécessaire. De nombreuses classes Laravel (contrôleurs, listeners, middlewares) reçoivent leurs dépendances automatiquement via le conteneur.

Bindings

Bindings de base

La plupart des bindings s’enregistrent dans un service provider. Dans un provider, $this->app donne accès au conteneur.

bind

bind associe une classe ou une interface à une closure.
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));
});
La closure reçoit le conteneur en argument, ce qui permet de résoudre des sous-dépendances. Hors d’un service provider, utilisez la façade App pour interagir avec le conteneur.
use App\Services\Transistor;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\App;

App::bind(Transistor::class, function (Application $app) {
    // ...
});
Les classes qui ne dépendent pas d’interfaces n’ont pas besoin d’être enregistrées. Le conteneur les résout automatiquement via réflexion.

singleton

singleton lie une classe/interface à une résolution unique. La même instance est renvoyée à chaque appel suivant.
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));
});

instance

Vous pouvez également binder une instance existante avec instance. Le conteneur renverra toujours cette instance.
use App\Services\Transistor;
use App\Services\PodcastParser;

$service = new Transistor(new PodcastParser);

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

Binding d’une interface à une implémentation

Une des grandes forces du conteneur : associer une interface à une implémentation. Par exemple, l’interface EventPusher et l’implémentation RedisEventPusher :
use App\Contracts\EventPusher;
use App\Services\RedisEventPusher;

$this->app->bind(EventPusher::class, RedisEventPusher::class);
Le conteneur injectera RedisEventPusher partout où EventPusher est demandé. Il suffit de type-hinter EventPusher dans le constructeur.
use App\Contracts\EventPusher;

/**
 * Crée une nouvelle instance.
 */
public function __construct(
    protected EventPusher $pusher,
) {}
Dépendre d’une interface permet de changer d’implémentation sans modifier le code appelant. Cela facilite les tests et l’évolution.

Résolution automatique (DI par type-hint)

Lorsqu’il résout un contrôleur, un listener ou un middleware, le conteneur inspecte les type-hints du constructeur et injecte automatiquement les dépendances.
<?php

namespace App\Http\Controllers;

use App\Repositories\UserRepository;

class UserController extends Controller
{
    /**
     * Crée une nouvelle instance de contrôleur.
     */
    public function __construct(
        protected UserRepository $users,
    ) {}
}
Si UserRepository ne dépend pas d’une interface, aucun enregistrement n’est nécessaire. Un simple accès à la route déclenche l’injection automatique.

Résolution depuis le conteneur

Méthode make

Résolvez une instance avec make.
use App\Services\Transistor;

$transistor = app()->make(Transistor::class);
Si la classe a des dépendances non résolubles, makeWith permet de passer des arguments supplémentaires.
$transistor = $this->app->makeWith(Transistor::class, ['id' => 1]);

Injection automatique

En pratique, vous appelez rarement make directement. Il suffit d’ajouter les type-hints dans le constructeur d’une classe résolue par Laravel (contrôleurs, listeners, middlewares) pour que l’injection se fasse automatiquement.

Façades et conteneur

Les façades fournissent une interface statique vers les objets du conteneur. Par exemple, Cache::get() récupère en interne le service Cache depuis le conteneur.
use Illuminate\Support\Facades\Cache;

// Via la façade
Cache::get('key');

// Équivalent via le conteneur
app('cache')->get('key');
Les façades sont un wrapper pratique du conteneur. Vous pouvez également substituer la façade par un mock pour les tests.
use Illuminate\Support\Facades\Cache;

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

Exemple d’injection dans le constructeur

Voyons un pattern typique.
1

Définir l'interface

<?php

namespace App\Contracts;

interface PaymentGateway
{
    public function charge(int $amount, string $token): bool;
}
2

Créer l'implémentation

<?php

namespace App\Services;

use App\Contracts\PaymentGateway;

class StripePaymentGateway implements PaymentGateway
{
    public function charge(int $amount, string $token): bool
    {
        // Traitement du paiement via l'API Stripe...
        return true;
    }
}
3

Enregistrer le binding dans un service provider

use App\Contracts\PaymentGateway;
use App\Services\StripePaymentGateway;

$this->app->singleton(PaymentGateway::class, StripePaymentGateway::class);
4

Recevoir l'injection dans le contrôleur

<?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
        );

        // ...
    }
}
Grâce à ce pattern, changer de fournisseur de paiement (Stripe vers un autre) se fait en modifiant un seul binding.

Prochaines étapes

Service providers

Apprenez à enregistrer des bindings via les service providers.
Dernière modification le 13 juillet 2026