Not My Idea

Carnets Web d'Alexis Métaireau

Injection de dépendances – Utilisation du SpiralDi Container

one comment

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:

< ?xml version="1.0" encoding="UTF-8"?>
<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 = new SpiralDi_SchemaBuilder_Xml();
$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:

// construction du 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é

scope="prototype"

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:

$config = new Config();
$service = new Db($config->host, $config->user, $config->password);

Pour cela, il faut utiliser le système de réferences:

<service name="db" class="Database">
    <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,

getParam()

par exemple, pour arriver à quelque chose de ce genre:

$config = new Config();
$service = new Db(
    $config->getParam("host"),
    $config->getParam("user"),
    $config->getParam("password")
);

Le code XML est alors le suivant:

<argument ref="config" value="host" factoryMethod="getParam"/>

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.

<service name="extendedService" extends="config">
    <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» :

<service name="containerAwareService" class="Service" containerAware="true"/>
<!-- 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

$service = MyServiceFactory::createService();

Le code XML correspondant est le suivant:

<service name="serviceFactory" type="factory" class="MyServiceFactory">
    <method name="createService" />
</service>

Cela peut s’avérer pratique pour récupérer vos anciennes implémentations du pattern Singleton entres autres…

<service name="singleton" type="factory" class="Mysingleton">
    <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

hg clone https://ametaireau@bitbucket.org/ametaireau/spiraldi/

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:

  1. Il est tout nouveau, tout beau, en PHP 5.3, utilise l’AOP, la persistance Domain Model … et l’injection de dépendances! A suivre de très près: Flow3

Written by Alexis Metaireau

juin 23rd, 2009 at 11:05

One Response to 'Injection de dépendances – Utilisation du SpiralDi Container'

Subscribe to comments with RSS or TrackBack to 'Injection de dépendances – Utilisation du SpiralDi Container'.

  1. [...] note that this article is also available in French. A big thanks to Frédéric Sureau for his translation work on the English [...]

Leave a Reply