Injection de dépendance Hein?

Il est fort probable que vous ayez rencontré le terme "injection de dépendance" à un moment de votre apprentissage. Si vous en êtes encore au tout début de votre apprentissage, vous avez probablement formé une expression confuse et sauté cette partie. Néanmoins, il s'agit d'un aspect important de l'écriture de code maintenable (et testable). Dans cet article, je vais l'expliquer d'une manière aussi simple que possible..


Un exemple

Passons à un morceau de code assez générique et discutons de ses défauts.

 class Photo / ** * @var PDO La connexion à la base de données * / protected $ db; / ** * Construct. * / fonction publique __construct () $ this-> db = DB :: getInstance (); 

À première vue, ce code peut paraître assez inoffensif. Mais considérons le fait que nous avons déjà codé en dur une dépendance: la connexion à la base de données. Et si nous voulons introduire une couche de persistance différente? Ou bien, réfléchissez de cette façon: pourquoi le Photo l'objet peut-il communiquer avec des sources extérieures? Cela ne viole-t-il pas le concept de séparation des préoccupations? C'est certainement le cas. Cet objet ne devrait pas être concerné par quoi que ce soit qui n'est pas directement lié à un Photo.

L'idée de base est que vos classes ne devraient être responsables que d'une chose. Dans cet esprit, il ne devrait pas être responsable de la connexion aux bases de données et autres choses de cette nature.

Pensez aux objets de la même manière que vous pensez à votre animal de compagnie. Le chien ne décide pas quand il sort pour se promener ou jouer au parc. Tu fais! Ce n'est pas à lui de prendre ces décisions.

Reprenons le contrôle de la classe et passons plutôt dans la connexion à la base de données. Il y a deux façons d'accomplir cela: l'injection du constructeur et l'injection du setter, respectivement. Voici des exemples des deux:

Injection de constructeur

 class Photo / ** * @var PDO La connexion à la base de données * / protected $ db; / ** * Construct. * @param PDO $ db_conn La connexion à la base de données * / fonction publique __construct ($ dbConn) $ this-> db = $ dbConn;  $ photo = new Photo ($ dbConn);

Ci-dessus, nous sommes l'injection toutes les dépendances requises, lorsque la méthode de constructeur de la classe est exécutée, plutôt que de les coder en dur directement dans la classe.

Injecteur

 class Photo / ** * @var PDO La connexion à la base de données * / protected $ db; public function __construct ()  / ** * Définit la connexion à la base de données * @param PDO $ dbConn La connexion à la base de données. * / public function setDB ($ dbConn) $ this-> db = $ dbConn;  $ photo = new Photo; $ photo-> setDB ($ dbConn);

Avec ce simple changement, la classe ne dépend plus d'aucune connexion spécifique. Le système extérieur conserve le contrôle complet, comme cela devrait être le cas. Bien qu’elle ne soit peut-être pas immédiatement visible, cette technique simplifie considérablement le test de la classe, car nous pouvons maintenant nous moquer de la base de données setDB méthode.

Mieux encore, si nous décidons plus tard d’utiliser une forme de persistance différente, grâce à l’injection de dépendance, c’est un jeu d'enfant..

"L'injection de dépendance est l'endroit où les dépendances des composants sont attribuées par l'intermédiaire de leurs constructeurs, méthodes ou directement dans des champs."


Le rub

Il y a un problème à utiliser l'injection de setter de cette manière: cela rend la classe beaucoup plus difficile à travailler. L'utilisateur doit maintenant être pleinement conscient des dépendances de la classe et ne pas oublier de les définir en conséquence. Pensez, en bout de ligne, lorsque notre classe de fiction nécessite quelques dépendances supplémentaires. Eh bien, en suivant les règles du modèle d'injection de dépendance, il faudrait faire:

 $ photo = nouvelle photo; $ photo-> setDB ($ dbConn); $ photo-> setConfig ($ config); $ photo-> setResponse ($ response);

Beurk la classe est peut-être plus modulaire, mais nous avons également accumulé beaucoup de confusion et de complexité. Auparavant, l'utilisateur pouvait simplement créer une nouvelle instance de Photo, mais, maintenant, il doit se rappeler de définir toutes ces dépendances. Quelle douleur!


La solution

La solution à ce dilemme consiste à créer une classe de conteneur qui gérera le plus gros du travail pour nous. Si vous avez déjà rencontré le terme "Inversion of Control (IoC)", vous savez maintenant à quoi ils font référence..

Définition: En génie logiciel, Inversion of Control (IoC) est une pratique de programmation orientée objet dans laquelle le couplage d'objet est lié au moment de l'exécution par un objet assembleur et n'est généralement pas connu au moment de la compilation à l'aide d'une analyse statique..

Cette classe stockera un registre de toutes les dépendances de notre projet. Chaque clé aura une fonction lambda associée qui instancie la classe associée..

Il y a plusieurs façons de résoudre ce problème. Nous pourrions être explicites, et stocker des méthodes, telles que Nouvelle photo:

 // Aussi fréquemment appelée classe "Container" IoC / ** * @var PDO La connexion à la base de données * / protected $ db; / ** * Créer une nouvelle instance de Photo et définir des dépendances. * / public static newPhoto () $ photo = new Photo; $ photo-> setDB (static :: $ db); // $ photo-> setConfig (); // $ photo-> setResponse (); retourner $ photo;  $ photo = IoC :: newPhoto ();

À présent, $ photo sera égal à une nouvelle instance du Photo classe, avec toutes les dépendances requises. De cette façon, l'utilisateur ne doit pas oublier de définir ces dépendances manuellement; il appelle simplement le Nouvelle photo méthode.

La deuxième option, plutôt que de créer une nouvelle méthode pour chaque instanciation de classe, consiste à écrire un conteneur de registre générique, comme suit:

 class IoC / ** * @var PDO La connexion à la base de données * / protected static $ registry = array (); / ** * Ajouter un nouveau résolveur au tableau de registre. * @param string $ name L'id * @param objet $ resolvez la fermeture qui crée l'instance * @return void * / public static function register ($ name, Closure $ resol) static :: $ registry [$ name] = $ resol;  / ** * Créer l'instance * @param chaîne $ name L'id * @return mixte * / public fonction statique resol ($ name) if (static :: registered ($ name)) $ name = static :: $ registre [$ name]; retourne $ name ();  throw new Exception ('Rien de enregistré avec ce nom, imbécile.');  / ** * Déterminer si l'identifiant est enregistré * @param chaîne $ name L'id * @return bool Identifiant existant ou non * / public fonction statique enregistrée ($ name) return array_key_exists ($ name, static :: $ enregistrement); 

Ne laissez pas ce code vous effrayer; c'est vraiment très simple. Lorsque l'utilisateur appelle le IoC :: s'inscrire méthode, ils ajoutent un identifiant, tel que photo, et son résolveur associé, qui est juste un lambda qui crée l'instance et définit toutes les dépendances nécessaires sur la classe.

Maintenant, nous pouvons enregistrer et résoudre les dépendances via le IoC classe, comme ceci:

 // Ajouter 'photo' au tableau de registre, avec un résolveur IoC :: register ('photo', function () $ photo = nouvelle photo; $ photo-> setDB ('…'); $ photo-> setConfig ('…'); Return $ photo;); // Récupère une nouvelle instance de photo avec dépendances set $ photo = IoC :: resol ('photo');

Nous pouvons donc constater que, avec ce modèle, nous n’instancions pas manuellement la classe. Au lieu de cela, nous l'enregistrons avec le IoC conteneur, puis demander une instance spécifique. Cela réduit la complexité introduite lorsque nous avons retiré les dépendances codées en dur de la classe..

 // Avant $ photo = new Photo; // Après $ photo = IoC :: resol ('photo');

Pratiquement le même nombre de caractères, mais la classe est maintenant beaucoup plus flexible et testable. Dans le monde réel, vous voudrez probablement étendre cette classe pour permettre également la création de singletons.


Faire place aux méthodes magiques

Si nous voulons réduire la longueur de la IoC classe encore plus loin, nous pouvons tirer parti des méthodes magiques - à savoir __ensemble() et __obtenir(), qui sera déclenché si l'utilisateur appelle une méthode qui n'existe pas dans la classe.

 classe IoC protected $ registry = array (); fonction publique __set ($ name, $ resolver) $ this-> registry [$ name] = $ resolver;  fonction publique __get ($ name) return $ this-> registry [$ name] (); 

Popularisé par Fabien Potencier, il s'agit d'une implémentation super-minimale - mais ça va marcher. Que ce soit ou non __obtenir() ou ensemble() les courses dépendront si l'utilisateur définit une valeur ou non.

L'utilisation de base serait:

 $ c = new IoC; $ c-> mailer = function () $ m = new Mailer; // crée une nouvelle instance de mailer // définit les créations, etc. return $ m; ; // Récupération, garçon $ mailer = $ c-> mailer; // instance mailer

Merci d'avoir lu!