Pour terminer notre introduction aux DIC, nous allons maintenant étudier une « vraie » bibliothèque fournissant beaucoup plus de possibilités que Pimple. Il s'agit du
Symfony Dependency Injection Component. C'est une bibliothèque écrite par Fabien Potencier du temps de symfony1 et donc compatible avec PHP 5.2.
Avant toute chose, pensez à enregistrer l'
autoloader du composant afin d'éviter de devoir inclure les fichiers avant chaque utilisation :
Code : PHP | <?php
require_once '/chemin/vers/sfServiceContainerAutoloader.php';
sfServiceContainerAutoloader::register();
|
Manipulation des paramètres et services
La première classe importante du composant est le
sfServiceContainer qui est similaire à Pimple (quoique plus complète). Cette classe implémente l'interface
sfServiceContainerInterface que je vais reproduire ici.
Code : PHP 1
2
3
4
5
6
7
8
9
10
11
12
13 | <?php
interface sfServiceContainerInterface
{
public function setParameters(array $parameters);
public function addParameters(array $parameters);
public function getParameters();
public function getParameter($name);
public function setParameter($name, $value);
public function hasParameter($name);
public function setService($id, $service);
public function getService($id);
public function hasService($name);
}
|
Ici le conteneur ne se manipule plus comme un tableau mais avec des méthodes plus classiques. On retrouve la manipulation des paramètres et des services. Je ne détaillerai pas plus car je trouve les noms de méthodes suffisamment explicites. Si vous souhaitez en savoir plus, je vous renvoie à
la page de documentation en parlant.
Définition des services
J'en viens maintenant à ce qui constitue le point fort du composant : la définition des services. En effet, en plus de fournir une implémentation d'un conteneur dans la classe
sfServiceContainer, il fournit une classe
sfServiceContainerBuilder très pratique. Il est utile de noter que sfServiceContainerBuilder implémente sfServiceContainerInterface et que par conséquent ce n'est qu'une extension d'un conteneur simple.
Contrairement à Pimple où la démarche de construction et de configuration de l'objet était faite entièrement par le développeur à l'intérieur de la fonction anonyme, cette nouvelle classe permet de construire nos services de façon plus descriptive. Cela revient à indiquer une recette de cuisine à la classe qui se chargera ensuite de la transformer en instructions PHP au moment de créer l'objet.
Code : PHP 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | <?php
$container = new sfServiceContainerBuilder();
//Ajout des paramètres de façon classique
$container->addParameters(array(
'user.class' => 'User',
'user.storage.class' => 'SessionStorage',
'user.storage.cookie_name' => 'SESSION_ID',
));
//Définition du service « user »
$container
->register('user', '%user.class%')
->addArgument(new sfServiceReference('user.storage'))
;
//Définition du service « user.storage »
$container
->register('user.storage', '%user.storage.class%')
->addArgument('%user.storage.cookie_name%')
->setShared(false)
;
|
La première méthode à appeler pour enregistrer un service est toujours
sfServiceContainerBuilder::register() avec en premier paramètre le nom du service et en second paramètre le nom de la classe. Diverses méthodes sont ensuite appelables sur l'objet qui est renvoyé (il est possible de les chainer entre elles) :
- addArgument : ajoute un argument lors de l'appel du constructeur (l'ordre des arguments est l'ordre de définition via addArgument()) ;
- setArguments : définit tous les arguments du constructeur en une seule fois ;
- setConstructor : définit une méthode statique servant de constructeur à la place de l'utilisation de l'opérateur new (utile dans le cas de l'emploi du patron factory) ;
- setShared : définit si le service est partagé (oui par défaut) ou non ;
- setFile : indique un fichier à inclure avant d'instancier le classe (utile si elle n'utilise pas l'autoload) ;
- addMethodCall : définit une méthode de l'objet à appeler juste après son instanciation afin d'achever sa configuration. Le premier argument est le nom de la méthode, le second un tableau d'arguments à transmettre. Les méthodes sont appelés dans le même ordre que leur définition ;
- setMethodCalls : définit plusieurs appels de méthodes en une seule fois ;
- setConfigurator : définit une fonction ou méthode statique à appeler après l'instanciation de l'objet et l'appel de toutes les méthodes définies.
Pour toutes ces méthodes il est possible de donner des arguments provenant de types de base de PHP (entier, chaine de caractères, ...). En plus, vous pouvez indiquer une référence à un paramètre en entourant son nom avec deux symboles pourcentage %. Les références à des services se font elles en fournissant une instance de
sfServiceReference (l'argument du constructeur étant le nom du service référencé).
Définition des services en YAML
La définition des services précédentes ressemble au final fortement à celle proposée par Pimple, si ce n'est que l'abstraction est plus grande. Pourquoi s'embêter à apprendre cette syntaxe alors me direz-vous ? Parce que le meilleur arrive ! En effet, vous pouvez décrire vos services de façon bien moins verbeuse qu'en PHP à l'aide
du format YAML. Peut-être ne connaissez-vous pas ce format, peu utilisé en PHP, auquel cas je vous invite avant à prendre connaissance avec sa syntaxe sur son site officiel ou bien
dans la documentation de cet autre composant. YAML est comme JSON destiné avant tout à la manipulation de tableaux.
Notez que pour utiliser ce format, vous aurez besoin du composant
sfYaml pour
parser ces fichiers. Je vous invite à le télécharger et à rendre disponible la classe sfYaml qu'il contient en l'incluant.
Vous pouvez ensuite remplacer toute la configuration précédente par le code YAML suivant, strictement équivalent.
Code : yaml1
2
3
4
5
6
7
8
9
10
11
12
13
14
| parameters:
user.class: User
user.storage.class: SessionStorage
user.storage.cookie_name : SESSION_ID
services:
user:
class: %user.class%
arguments: [@user.storage]
user.storage:
class: %user.storage.class%
arguments: [%user.storage.cookie_name%]
shared: false |
Tout ce qui est configurable en PHP l'est également en YAML, je vous renvoie
à la documentation officielle pour en savoir plus. Grâce à la concision du YAML, nous gagnons en lisibilité et en maintenabilité. Le chargement d'une telle configuration dans notre DIC se fait ensuite très facilement :
Code : PHP | <?php
$loader = new sfServiceContainerLoaderFileYaml($container);
$loader->load('/chemin/vers/la/configuration.yml');
|
C'est à mon sens ce qui fait la force d'un tel composant par rapport à Pimple sur un gros projet. La configuration des services est aisée grâce à la centralisation des paramètres. Changer le nom du cookie de session ou le type de persistance de session est alors redoutablement simple : nous avons maintenant répondu à nos problématiques initiales !
Pour aller plus loin
Le composant décrit sommairement ici fournit encore plus de fonctionnalités ! Vous pouvez par exemple utiliser le format XML pour décrire vos services à la place du YAML. J'ai présenté le YAML car j'aime beaucoup ce format pour sa concision et son élégance. Cependant le XML offre d'autres avantages :
- il est plus utilisé et donc mieux intégré dans les IDE ;
- il autorise une validation de son contenu via un schéma inclus dans le composant présenté ;
- il est plus rapide car intégré de base dans PHP alors que YAML repose sur un composant externe.
À vous de choisir ! Gardez juste bien en tête que peu importe que vous choisissiez du YAML, XML ou PHP, tout ce qui est faisable dans l'un est faisable dans l'autre.
Les conteneurs devenant rapidement complexes, vous pouvez aussi en obtenir une représentation au format Graphviz. Ce format permet ensuite de générer des graphiques comme celui qui suit (repris de la documentation du composant), très utiles pour s'y retrouver dans un gros projet.
Outre Graphviz, vous pouvez obtenir une représentation de votre conteneur dans d'autres formats : YAML, XML ou PHP. Cela permet de convertir facilement un format de configuration dans un autre. Mais la représentation PHP offre un autre avantage : vous pourrez mettre en cache votre conteneur afin d'éviter une analyse systématique des fichiers de configuration.
Toutes ces fonctionnalités sont décrites de façon exhaustive dans
la documentation du composant que je vous invite très fortement à lire.