Injection de Dépendances en PHP : en pratique
\n\nL'Injection de Dépendances (DI) est l'un des principes clés de SOLID, qui permet d'écrire un code flexible, testable et maintenable. Dans cet article, nous allons voir comment implémenter l'injection de dépendances en PHP avec des exemples concrets, sans théories abstraites. Vous apprendrez à vous débarrasser des couplages forts entre les classes et à rendre votre code prêt pour les changements.
\n\nQu'est-ce que l'Injection de Dépendances et pourquoi en a-t-on besoin ?
\n\nImaginez que vous ayez une classe UserController qui travaille avec une base de données. Sans DI, vous créez probablement l'objet de connexion directement à l'intérieur du contrôleur :
class UserController {\n private $db;\n public function __construct() {\n $this->db = new MySQLConnection('localhost', 'user', 'pass');\n }\n}\n\nCette approche crée un couplage fort (tight coupling). Si vous souhaitez passer à PostgreSQL ou utiliser un objet mock pour les tests, vous devrez modifier le code du contrôleur. L'Injection de Dépendances résout ce problème : les dépendances sont transmises de l'extérieur, et non créées à l'intérieur de la classe.
\n\nPrincipaux avantages de la DI :
\n- \n
- Réduction du couplage du code \n
- Simplification des tests (remplacement facile des dépendances) \n
- Réutilisation des classes \n
- Architecture propre et respect du principe d'inversion des dépendances (DIP) \n
Méthodes d'injection de dépendances en PHP
\n\nIl existe trois méthodes principales d'injection de dépendances. Examinons chacune avec des exemples.
\n\n1. Injection via le constructeur
\n\nLa méthode la plus courante et recommandée. Les dépendances sont transmises via les paramètres du constructeur et stockées dans les propriétés de la classe.
\n\n<?php\ninterface DatabaseInterface {\n public function query(string $sql): array;\n}\n\nclass MySQLDatabase implements DatabaseInterface {\n public function query(string $sql): array {\n // Logique réelle de travail avec MySQL\n return ['id' => 1, 'name' => 'John'];\n }\n}\n\nclass UserRepository {\n private DatabaseInterface $db;\n \n public function __construct(DatabaseInterface $db) {\n $this->db = $db;\n }\n \n public function getUser(int $id): array {\n return $this->db->query("SELECT * FROM users WHERE id = $id");\n }\n}\n\n// Utilisation :\n$db = new MySQLDatabase();\n$repo = new UserRepository($db);\n$user = $repo->getUser(1);\n?>\n\nRemarquez : nous utilisons l'interface DatabaseInterface. Cela permet de remplacer facilement l'implémentation — par exemple, par un mock de test.
2. Injection via les setters
\n\nUtilisée lorsque la dépendance n'est pas obligatoire lors de la création de l'objet ou peut changer en cours d'exécution.
\n\n<?php\nclass MailService {\n private ?MailerInterface $mailer = null;\n \n public function setMailer(MailerInterface $mailer): void {\n $this->mailer = $mailer;\n }\n \n public function send(string $to, string $subject, string $body): void {\n if ($this->mailer === null) {\n throw new \\RuntimeException('Mailer not configured');\n }\n $this->mailer->send($to, $subject, $body);\n }\n}\n\n// Utilisation :\n$mailService = new MailService();\n$mailService->setMailer(new SmtpMailer());\n$mailService->send('user@example.com', 'Hello', 'Body');\n?>\n\nL'inconvénient de cette approche est que vous ne garantissez pas que la dépendance sera définie avant l'appel de la méthode send(). Par conséquent, pour les dépendances obligatoires, le constructeur est préférable.
3. Injection via une méthode
\n\nLa dépendance est transmise directement à la méthode où elle est nécessaire. Utile pour les dépendances rares ou contextuelles.
\n\n<?php\nclass ReportGenerator {\n public function generate(DataFetcherInterface $fetcher, string $reportType): array {\n $data = $fetcher->