web-dev-qa-db-fra.com

Comprendre les conteneurs IoC et l'injection de dépendances

Avance rapide:

J'écris ceci dans le but d'avoir une meilleure compréhension de l'injection de dépendances et des conteneurs IoC, mais aussi afin que je puisse ensuite corriger les erreurs et l'utiliser pour aider à en apprendre quelques-uns à mes amis.

À partir de maintenant, j'ai essayé de lire la documentation de divers cadres (laravel, fuel, codeigniter, symfony) et j'ai trouvé qu'il y avait trop d'aspects différents des cadres dont j'avais besoin pour me sentir à l'aise de l'utiliser que j'ai décidé d'essayer de apprenez individuellement chacune des pièces majeures individuellement avant d'essayer de les utiliser dans les cadres eux-mêmes.

J'ai passé des heures à googler différentes significations, à regarder les réponses de stackoverflow et à lire divers articles essayant de comprendre ce qu'est un IoC et comment l'utiliser pour gérer correctement les dépendances, et je crois que je comprends de quoi il s'agit, mais je suis toujours gris sur la façon de le mettre en œuvre correctement. Je pense que la meilleure façon pour quiconque lit ceci de m'aider est de donner ma compréhension actuelle des conteneurs IoC et de l'injection de dépendance, puis de laisser les gens qui ont une meilleure compréhension que moi indiquer où ma compréhension est insuffisante.

Ma compréhension:

  • Une dépendance est lorsqu'une instance de ClassA nécessite une instance de ClassB pour instancier une nouvelle instance de ClassA.
  • Une injection de dépendance se produit lorsque ClassA reçoit une instance de ClassB, soit via un paramètre du constructeur de ClassA, soit via une fonction set ~ DependencyNameHere ~ (~ DependencyNameHere ~ $ param). (C'est l'un des domaines dont je ne suis pas complètement certain).
  • Un conteneur IoC est une classe singleton (ne peut avoir qu'une seule instance instanciée à un moment donné) où la manière spécifique d'instancier des objets de cette classe pour ce projet peut être enregistrée. Voici un lien vers un exemple de ce que j'essaie de décrire avec la définition de classe pour le conteneur IoC que j'utilise

Donc, à ce stade, je commence à essayer d'utiliser le conteneur IoC pour des scénarios plus compliqués. À partir de maintenant, il semble que pour utiliser le conteneur IoC, je suis limité à une relation has-a pour à peu près n'importe quelle classe que je veux créer qui a des dépendances qu'il veut définir dans le conteneur IoC. Que faire si je veux créer une classe qui hérite d'une classe, mais uniquement si la classe parente a été créée d'une manière spécifique, elle a été enregistrée dans le conteneur IoC.

Ainsi, par exemple: je veux créer une classe enfant de mysqli, mais je veux enregistrer cette classe dans le conteneur IoC pour instancier uniquement avec la classe parent construite d'une manière que j'ai précédemment enregistrée dans le conteneur IoC. Je ne peux pas penser à un moyen de le faire sans dupliquer le code (et comme il s'agit d'un projet d'apprentissage, j'essaie de le garder aussi pur que possible). Voici quelques autres exemples de ce que j'essaie de décrire.

Voici donc certaines de mes questions:

  • Est-ce que ce que j'essaie de faire ci-dessus est possible sans casser un principe de la POO? Je sais qu'en c ++, je pourrais utiliser de la mémoire dynamique et un constructeur de copie pour y arriver, mais je n'ai pas pu trouver ce genre de fonctionnalité dans php. (J'admettrai que j'ai très peu d'expérience en utilisant l'une des autres méthodes magiques en plus de __construct, mais à partir de la lecture et de __clone si j'ai bien compris, je ne pouvais pas l'utiliser dans le constructeur pour faire de la classe enfant instanciée un clone d'un instance de la classe parent).
  • Où toutes mes définitions de classe de dépendance devraient-elles aller par rapport à l'IoC? (Mon IoC.php devrait-il simplement avoir un tas de require_once ('dependencyClassDefinition.php') en haut? Ma réaction instinctive est qu'il existe une meilleure façon, mais je n'en ai pas encore trouvé une)
  • Dans quel fichier dois-je enregistrer mes objets? Effectue actuellement tous les appels à IoC :: register () dans le fichier IoC.php après la définition de la classe.
  • Dois-je enregistrer une dépendance dans l'IoC avant d'enregistrer une classe qui a besoin de cette dépendance? Comme je n'invoque pas la fonction anonyme jusqu'à ce que j'instancie réellement un objet enregistré dans l'IoC, je suppose que non, mais c'est toujours une préoccupation.
  • Y a-t-il autre chose que j'oublie que je devrais faire ou utiliser? J'essaie de le faire une étape à la fois, mais je ne veux pas non plus savoir que mon code sera réutilisable et, surtout, que quelqu'un qui ne sait rien de mon projet peut le lire et le comprendre.

Je sais que c'est extrêmement long, et je voulais juste remercier d'avance tous ceux qui ont pris le temps de le lire, et plus encore ceux qui partagent leurs connaissances.

56
echochamber

En termes simples (car ce n'est pas un problème limité à OOP monde uniquement), une dépendance est une situation où le composant A a besoin (dépend) du composant B pour faire ce qu'il est censé faire. Le mot est également utilisé pour décrire le composant dépendant dans ce scénario. Pour mettre cela en termes OOP/PHP, considérez l'exemple suivant avec l'analogie obligatoire de la voiture :

class Car {

    public function start() {
        $engine = new Engine();
        $engine->vroom();
    }

}

Car dépend de Engine. Engine est la dépendance de Car. Ce morceau de code est plutôt mauvais, car:

  • la dépendance est implicite; vous ne savez pas qu'il est là jusqu'à ce que vous inspectiez le code de Car
  • les classes sont étroitement couplées; vous ne pouvez pas remplacer Engine par MockEngine à des fins de test ou TurboEngine qui étend l'original sans modifier le Car.
  • Il semble assez idiot pour une voiture de pouvoir se construire un moteur, n'est-ce pas?

L'injection de dépendances est un moyen de résoudre tous ces problèmes en rendant explicite et explicite le fait que Car a besoin de Engine avec un:

class Car {

    protected $engine;

    public function __construct(Engine $engine) {
        $this->engine = $engine;
    }

    public function start() {
        $this->engine->vroom();
    }

}

$engine = new SuperDuperTurboEnginePlus(); // a subclass of Engine
$car = new Car($engine);

Ce qui précède est un exemple d'injection de constructeur , dans lequel la dépendance (l'objet dépendant) est fournie au dépendant (consommateur) via le constructeur de classe . Une autre façon serait d'exposer une méthode setEngine dans la classe Car et de l'utiliser pour injecter une instance de Engine. Ceci est connu comme injection de setter et est surtout utile pour les dépendances qui sont censées être échangées au moment de l'exécution.

Tout projet non trivial se compose d'un tas de composants interdépendants et il devient facile de perdre la trace de ce qui est injecté où assez rapidement. Un conteneur d'injection de dépendances est un objet qui sait comment instancier et configurer d'autres objets, sait quelle est leur relation avec les autres objets du projet et fait la dépendance injection pour vous. Cela vous permet de centraliser la gestion de toutes les (inter) dépendances de votre projet et, plus important encore, permet de modifier/simuler un ou plusieurs d'entre eux sans avoir à modifier un tas d'endroits dans votre code.

Abandonnons l'analogie avec la voiture et examinons ce que OP essaie de réaliser à titre d'exemple. Disons que nous avons un objet Database en fonction de l'objet mysqli. Disons que nous voulons utiliser une classe de conteneur d'indection de dépendance vraiment primitive DIC qui expose deux méthodes: register($name, $callback) pour enregistrer une manière de créer un objet sous le nom donné et resolve($name) pour obtenir l'objet de ce nom. Notre configuration de conteneur ressemblerait à ceci:

$dic = new DIC();
$dic->register('mysqli', function() {
    return new mysqli('somehost','username','password');
});
$dic->register('database', function() use($dic) {
    return new Database($dic->resolve('mysqli'));
});

Notez que nous demandons à notre conteneur de récupérer une instance de mysqli à partir de lui-même pour assembler une instance de Database. Ensuite, pour obtenir une instance de Database avec sa dépendance automatiquement injectée, nous devrions simplement:

$database = $dic->resolve('database');

C'est l'essentiel. Un conteneur un peu plus sophistiqué mais toujours relativement simple et facile à saisir PHP DI/IoC est Pimple . Consultez sa documentation pour plus d'exemples.


Concernant le code OP et les questions:

  • N'utilisez pas de classe statique ou un singleton pour votre conteneur (ou pour toute autre chose d'ailleurs); ils sont tous les deux mauvais . Découvrez plutôt Pimple.
  • Décidez si vous voulez que votre classe mysqliWrapper étende mysql ou en dépend .
  • En appelant IoC depuis mysqliWrapper, vous échangez une dépendance pour une autre. Vos objets ne doivent pas être conscients ni utiliser le conteneur; sinon ce n'est plus DIC, c'est le modèle (Anti) Service Locator.
  • Vous n'avez pas besoin de require un fichier de classe avant de l'enregistrer dans le conteneur car vous ne savez pas du tout si vous allez utiliser un objet de cette classe. Faites toute votre installation de conteneur en un seul endroit. Si vous n'utilisez pas de chargeur automatique, vous pouvez require à l'intérieur de la fonction anonyme que vous enregistrez avec le conteneur.

Ressources supplémentaires:

120
lafor