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.
<?phpnamespace 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.
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 :
<?phpclass 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.
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 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));});
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);
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.
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.
<?phpnamespace 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.
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.
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çadeCache::get('key');// Équivalent via le conteneurapp('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');