Retour

Faut-il injecter le service container dans un service ?

1 déc. 2013
3mn

La documentation de Symfony2 explique très bien ce que c'est qu'un service et le conteneur de dépendances. Mais dans une application on a souvent besoin d'injecter plusieurs services ou paramètres. Vous vous êtes déjà dit que c'était plus simple d'injecter le container de service directement plutôt que d'injecter toutes les dépendances les unes après les autres ? Voici les "pour" et les "contre".

Pourquoi ne pas le faire ?

Tout d'abord, quand un service ressemble à ça,

class MyService { private $container; public function __construct(ContainerInterface $container) { $this->container = $container; } // .... }

... en lisant le constructeur on ne sait pas quelles sont les dépendances du service. On serait obligé d'aller lire toute la classe pour savoir de quoi elle a réellement besoin.

Deuxièmement, il faudra utiliser le conteneur de service dans les tests, ou avoir un mock du conteneur si on l'injecte. Par défaut, il ne contiendra pas de services, donc il faudra quand même mocker ceux utilisés par notre service. Il faudra donc lire toute la classe pour ne pas oublier des dépendances. Ce qui impose d'écrire du code (inutile) en plus, tout en rendant les tests plus compliqués et longs à écrire... et à maintenir.

Aussi, si on demande un service particulier au conteneur à plusieurs endroits, on n'est plus aussi découplé du reste du code que ce qu'on devrait. On est couplé avec le DIC au lieu de la classe directement, mais cela va toujours à l’encontre du principe d'injection de dépendances.

Dans quel cas injecter le conteneur de services ?

Il existe quand même des cas où injecter le conteneur de services directement peut être utile : quand vous avez un service dans un scope (champ d'application) "container" qui a besoin d'un service du scope "request". Exemple, une extension Twig qui aurait besoin de Request pour récupérer une URL.

Les extensions Twig doivent être dans le scope "container" puisqu'elle dépendent de Twig qui est dans ce scope. Mais injecter Request directement demande que le scope soit réduit à "request". Pour résoudre ce problème, on va injecter le service container dans l'extension et vérifier si une request est disponible :

class MyExtension extends \Twig_Extension { private $container; private $request; public function __construct(ContainerInterface $container) { $this->container = $container; if ($this->container->isScopeActive('request')) { $this->request = $this->container->get('request'); } } // ... }

Note : les extensions Twig ne bénéficient pas du lazy loading, et sont donc toujours chargées, même si vous lancez une commande. Puisqu’il n'y a pas de Request dans une commande, nous avons besoin de vérifier son existence avant d’essayer d'y accéder.

EDIT : Depuis symfony 2.4, un nouveau scope, "request_stack", a été introduit (cf l'article de Fabien Potencier). Le problème décrit ci-dessus ne se pose donc plus.


Articles sur le même thème

Comment créer de la dette technique dès le début d’un nouveau projet ?

Quand on arrive sur un projet existant, on doit souvent subir une dette technique qui nous fait perdre du temps et qui nous rend fou au point de vérifier qui a fait le code. Vous aussi vous voulez entrer dans la postérité lors d’un git blame et mal concevoir votre produit ?

9 août 20238mnMarianne Joseph-Géhannin

Découvrez Eleven Labs

Notre site pour mieux nous connaître

J'y vais

Contact

Eleven Labs - Paris

102, rue du Faubourg Saint Honoré

75008 Paris

Eleven Labs - Nantes

42, rue la Tour d'Auvergne

44200 Nantes

Eleven Labs - Montréal

1155, Metcalfe St Suite 1500

Montréal, QC H3B 2V6, Canada

business@eleven-labs.com

01.82.83.11.75