Injection de dépendances en PHP : pour débutants
Imaginez que vous assemblez un ordinateur. Vous ne soudez pas le processeur directement sur la carte mère pour toujours — vous l'insérez dans un connecteur spécial, afin de pouvoir le remplacer par un plus puissant si nécessaire. L'injection de dépendances (DI) en PHP fonctionne sur le même principe : au lieu de créer des dépendances à l'intérieur d'une classe, vous les « injectez » de l'extérieur.
Dans cet article, nous allons voir ce qu'est l'injection de dépendances, pourquoi elle est nécessaire et comment l'utiliser en PHP. Vous apprendrez à rendre votre code flexible, testable et facile à maintenir.
Qu'est-ce que l'injection de dépendances en termes simples
L'injection de dépendances est un patron de conception dans lequel un objet reçoit ses dépendances (d'autres objets, services) de l'extérieur, plutôt que de les créer lui-même. Au lieu d'écrire new Database() à l'intérieur d'une classe, vous transmettez un objet de base de données déjà prêt via le constructeur ou un setter.
Examinons le problème que résout le DI. Considérons la classe UserService, qui crée directement une connexion à la base de données :
<?phpclass UserService { private $db; public function __construct() { // Couplage fort avec une implémentation spécifique $this->db = new MySQLConnection('localhost', 'user', 'pass'); } public function getUser($id) { return $this->db->query("SELECT * FROM users WHERE id = $id"); }}?>Les problèmes de cette approche sont évidents :
- Couplage fort (tight coupling) — la classe
UserServiceest liée àMySQLConnection. Pour passer à PostgreSQL, il faudrait modifier le code de la classe. - Difficulté de test — vous ne pourrez pas remplacer la base de données par un objet mock dans les tests unitaires.
- Violation du principe de responsabilité unique — la classe ne se contente pas de gérer les utilisateurs, elle crée aussi la connexion à la base de données.
Réécrivons maintenant cette classe en utilisant l'injection de dépendances :
<?phpinterface DatabaseInterface { public function query($sql);}
class MySQLConnection implements DatabaseInterface { public function query($sql) { // Implémentation pour MySQL }}
class PostgreSQLConnection implements DatabaseInterface { public function query($sql) { // Implémentation pour PostgreSQL }}
class UserService { private $db; // La dépendance est injectée via le constructeur public function __construct(DatabaseInterface $db) { $this->db = $db; } public function getUser($id) { return $this->db->query("SELECT * FROM users WHERE id = $id"); }}
// Utilisation$db = new MySQLConnection('localhost', 'user', 'pass');$userService = new UserService($db);?>Maintenant, UserService ne sait pas avec quelle base de données il travaille. Il utilise simplement l'interface DatabaseInterface. C'est l'essence même de l'injection de dépendances.
Les trois principaux types d'injection de dépendances
En PHP, il existe trois façons principales d'injecter des dépendances. Examinons chacune d'elles.
1. Injection via le constructeur (Constructor Injection)
C'est la méthode la plus courante et recommandée. Les dépendances sont transmises via les paramètres du constructeur et stockées dans des propriétés privées de la classe.
<?phpclass Logger { private $writer; public function __construct(LogWriterInterface $writer) { $this->writer = $writer; } public function log($message) { $this->writer->write($message); }}?>Avantages : les dépendances sont clairement visibles, l'objet est toujours dans un état valide (toutes les dépendances sont obligatoires).
Inconvénients : e