Injection de dépendances – Utilisation du SpiralDi Container
par Alexis Metaireau
Cet article est également disponible en version anglaise. Un grand merci à Frédéric Sureau pour son travail de traduction.
Suite à mon précédent article, j’ai continué à travailler sur l’injecteur de dépendances de Spiral, que je viens de publier dans une version standalone.
Voici un tour d’horizon des fonctionnalités apportées par SpiralDi, ainsi que quelques exemples d’utilisation.
Table des matières
Tour d’horizon
SpiralDi arrive avec son lot de nouveautés, pour certaines inspirées de l’excellent framework Java Spring. Il est désormais possible, en vrac, de:
- Utiliser des factories pour renseigner les valeurs des parametres (pratique pour la configuration)
- Utiliser un système d’héritage, pour alléger les fichiers de configuration
- Utiliser des Factories pour résoudre les services
- Injecter directement le conteneur à certains services, dits ContainerAware
Mais peut être que ça ne vous parles pas trop, nous y reviendrons tout à l’heure.
Principes
Si vous n’êtes pas à l’aise avec le principe d’inversion de contrôle, je vous conseille d’aller lire mon introduction sur l’injection de dépendances.
L’utilisation d’un conteneur léger se fait toujours en deux étapes:
- La description et le renseignement du schéma de services qui pourront être utilisés par la suite
- la demande de résolution de ces derniers, via le conteneur
SpiralDi fournit plusieurs manières de décrire le Schema. La plus appropriée est surement l’XML, mais il est également possible de passer via du PHP si cela vous semble plus pratique. Nous prendrons ici l’exemple du XML. Voici un fichier de description assez simple, qui décrit deux services:
<container>
<service name="db" class="Database">
<constructor>
<argument value="localhost"/>
<argument value="root"/>
<argument value="password"/>
</constructor>
</service>
<service name="user" class="User">
<method name="setDb">
<argument type="service" value="db"/>
</method>
</service>
</container>
Une fois le fichier écrit, il faut le transformer dans un format compréhensible par SpiralDi: le Schema.
$builder->setFileName("schema.xml");
$schema = $builder->buildSchema();
Nous pouvons désormais exploiter notre Schema. La première utilité d’un Schéma est d’être utilisé pour résoudre nos objets. Nous verrons un peu plus tard qu’il peut avoir d’autres utilités. Passons donc le Schema au Conteneur:
$container = new SpiralDi_Container_Default($schema);
// récupération du service souhaité
$myUser = $container->user;
Plutôt sympa non? Le conteneur s’est occupé de tout: résolution des dépendances de l’objet, et injection de ces dernières quand nécessaire.
Portée des services (Scopes)
Les services construits via SpiralDi ont une durée de vie contrôlée.
Par défaut, plusieurs demandes au conteneur pour un même service retournerons la même instance de l’objet, il s’agit de la portée de type singleton.
Le conteneur est configuré par défaut pour que les services qu’ils crée aient une portée de type singleton.
Il est possible d’utiliser la portée prototype pour qu’un nouvel objet soit créé à chaque appel du service, en utilisant la propriété
dans le fichier XML.
Utilisation « avancée»
Vous savez désormais comment décrire « basiquement» vos services et leurs dépendances. Voyons maintenant quelques concepts un peu plus poussés.
Utilisation des Références et des MethodFactory
Il peut arriver que l’on ai besoin d’injecter des paramètres de manière dynamique. Imaginez que vous ne connaissiez pas la valeur des paramètres à injecter, ceux-ci étant contenus dans un objet de configuration par exemple.
Nous souhaitons donc que le conteneur construise nos objets en utilisant des informations contenues dans la configuration. Le conteneur est donc capable de s’occuper de générer le code suivant à notre place:
$service = new Db($config->host, $config->user, $config->password);
Pour cela, il faut utiliser le système de réferences:
<constructor>
<argument ref="config" value="host"/>
<argument ref="config" value="user"/>
<argument ref="config" value="password"/>
</constructor>
</service>
Il est également possible de spécifier la méthode à utiliser pour résoudre les arguments,
par exemple, pour arriver à quelque chose de ce genre:
$service = new Db(
$config->getParam("host"),
$config->getParam("user"),
$config->getParam("password")
);
Le code XML est alors le suivant:
Héritage et surcharge de services
Assez souvent, certains services à injecter se ressemblent. Il est possible d’utiliser un système d’héritage pour rendre la description des services moins rébarbative. Il ne s’agit pas d’un héritage « concret» dans nos classes, mais du principe de l’héritage. Ainsi, une description peut en étendre une autre, facilitant à la fois son écriture et sa compréhension.
<method name="method2">
<argument value="value"/>
</method>
</service>
Container Aware
Dans certains cas, il peut être utile qu’un service soit conscient de l’existence du conteneur, et qu’il puisse y accéder. Il est facile d’injecter le conteneur dans un service, en utilisant le type « container» , ou la facilité « containerAware» :
<!-- est exactement équivalent à-->
<service name="containerAwareService" class="Service">
<method name="setDiContainer">
<argument type="container" />
</method>
</service>
Du coté de votre classe, si celle-ci doit récupérer le conteneur, elle à simplement à implémenter l’interface SpiralDi_ContainerAware. Lors de sa construction, la méthode setDiContainer sera directement appelée.
A noter que dans ce cas, il n’est pas nécessaire de définir votre service comme étant « ContainerAware» , le conteneur étant capable de le découvrir tout seul.
Factories
Il est également possible d’utiliser des Factory depuis le DI, pour générer un comportement comme celui ci
Le code XML correspondant est le suivant:
<method name="createService" />
</service>
Cela peut s’avérer pratique pour récupérer vos anciennes implémentations du pattern Singleton entres autres…
<method name="getInstance" />
</service>
Téléchargez le !
Convaincu ? Pour récupérer la dernière version de SpiralDi, vous avez le choix entre plusieurs solutions:
Via le dépôt mercurial
Via les archives
Le code est disponible en zip, gz ou bz2
Vous pouvez aussi aller jeter directement un oeil au dépot.
Et voila! Vous avez de quoi vous amuser ! Pour toute demande de fonctionnalité, rapport de bug etc, n’hésitez surtout pas!
Et ailleurs ?
SpiralDi n’est bien évidemment pas le seul Conteneur Léger écrit en PHP. Depuis peu, ces derniers commencent d’ailleurs à fleurir sur la toile. Les frameworks Symfony et Flow31 leur font d’ailleurs la part belle, et c’est tant mieux! Quelques alternatives possibles au conteneur de Spiral donc:
Lectures intéressantes
Si vous souhaitez aller plus loin, voici quelques lectures intéressantes sur le sujet de l’injection de dépendances:
- L’article de Martin Fowler sur l’injection de dépendances
- La série d’articles de Fabien potencier (1,2,3,4,5)
- Celle de Padraic Brady (1,2)
- Les conteneurs légers du futur, par Sami Jabber
Comments
[...] note that this article is also available in French. A big thanks to Frédéric Sureau for his translation work on the English [...]