L’injection de dépendances
Le modèle de conception IoC : Inversion Of Control
Le modèle de conception (ou Design Pattern) Inversion Of Control est un modèle d’architecture partant du principe que le flot d’exécution des différentes instructions n’est pas maîtrisé par l’application elle-même mais par un élément logiciel fourni par un framework. Dans le cadre de Symfony, c’est le Service Container qui apporte cette fonctionnalité.
1. Apports dans une architecture applicative
Dans une application traditionnelle, c’est-à-dire sans framework, les différents composants applicatifs sont liés entre eux par le programmeur ; c’est lui qui doit prendre à sa charge dans son code la communication entre les éléments du modèle, de la vue et du contrôleur. Cela implique de fournir une quantité assez conséquente de « code technique » en plus du code fonctionnel, nuisant à la productivité des développeurs.
Les frameworks de développement actuels apportent tous plus ou moins un conteneur logiciel qui va se charger de l’inversion de contrôle. Il faut voir l’inversion de contrôle comme un mécanisme où le flot d’exécution est déjà tout tracé (bien que configurable) et où les composants...
L’injection de dépendances
1. Principes de base
L’idée qui sous-tend l’injection de dépendances est simple : au lieu de laisser une classe aller chercher elle-même les dépendances dont elle a besoin, ce processus est externalisé.
La classe X, allégée de cette tâche, pourra se concentrer sur sa fonction première.
Découvrons ci-après les différents types d’injection.
2. Les différentes techniques d’injection de dépendances
a. L’injection de dépendances par le constructeur
Cette injection consiste à passer la dépendance lors de l’instanciation de la classe :
<?php
class X
{
private $db;
public function __construct(\PDO $db)
{
$this->db = $db;
}
public function foo()
{
$stmt = $this->db->query('SELECT...');
// ...
}
// ...
}
Ce type d’injection convient parfaitement aux dépendances...
Le Service Container
Nous avons évoqué cette entité lors du chapitre sur l’architecture du framework. On sait que le Service Container contient des « services ».
Son rôle est d’absorber la complexité que nous venons d’identifier : définir la manière d’instancier des classes passives, ne contenant que des règles et possibilités d’injection. Cette définition est ce qu’on appelle un service.
Concrètement, le Service Container est une classe dont chacune des méthodes retourne un service.
1. Les services
Un service est un objet PHP prêt à être utilisé et ayant une tâche générique et unique. Il peut servir à « persister » des données en base, envoyer des e-mails ou écrire des logs.
Un service est accessible au travers du Service Container et le framework Symfony possède par défaut un certain nombre de services.
2. Explications au travers d’un service X
Penchons-nous maintenant sur la création d’un service X donné, basé sur notre classe X (cf. au début de ce chapitre pour la définition de la classe).
Tous les services étant définis dans le Service Container et accessibles uniquement au travers de celui-ci, la création de ce service sera faite dans la classe représentant le...
Créer un service et configurer ses injections
1. Créer un service
Le moyen le plus simple de créer un service est de le configuer via le fichier config/services.yaml. Les services doivent être définis sous la clé services :
# config/services.yaml
services:
my_pdo:
class: PDO
arguments: ['mysql:host=localhost', 'bilal', 'pass']
Cette configuration crée un service my_pdo, qui est une instance de PDO (clé class). Les arguments passés au constructeur sont définis sous la clé arguments. Concrètement, avec cette configuration, le service my_pdo est créé de cette manière :
<?php
$service = new PDO('mysql:host=localhost', 'bilal', 'pass');
2. Les différents types d’injections dans un service Symfony
a. Injection par constructeur
Une fois le service my_pdo configuré, il peut être injecté dans un autre service, via le constructeur, par exemple (cf. sous-section L’injection de dépendances par le constructeur, précédemment dans ce chapitre) :
# config/services.yaml
services:
my_pdo:
class: PDO
arguments: ['mysql:host=localhost', 'bilal', 'pass']
x:
class: X
arguments: @my_pdo
Le service X est alors créé comme ceci :
<?php
$myPDO = $serviceContainer->get('my_pdo');
$service = new X($myPDO);
Ici, le Service Container récupère le service my_pdo, puis le passe en argument lors de l’instanciation...
Le chargement automatique de services
Le chargement automatique de services est une fonctionnalité apparue dans Symfony avec la version 3.4, son usage est désormais généralisé dans la mise en œuvre d’applications Symfony.
1. La configuration
Le principe est assez simple : toutes les classes que vous créez (à quelques exceptions près) sont enregistrées en tant que service, et donc injectables les unes dans les autres. Vous pouvez aussi injecter des services Symfony dans celles-ci. Ce comportement est rendu possible par la configuration par défaut fournie dans le fichier config/services.yaml, dont voici le contenu avec ladite configuration mise en gras :
services:
# default configuration for services in *this* file
defaults:
autowire: true # Automatically injects dependencies in
# your services.
autoconfigure: true # Automatically registers your services as
# commands, event subscribers...
Créer des services réutilisables et distribuables
1. Le concept de bundle
Dans les versions précédentes du framework (avant Symfony 4 pour être exact), tout le code d’une application devait être organisé dans des bundles. L’objectif de cette approche consistait à faciliter la réutilisation et la distribution de fonctionnalités. Bien que ce système d’organisation ne soit plus obligatoire, il n’en reste pas moins toujours utilisable si vous souhaitez développer des fonctionnalités et les redistribuer.
a. Créer un bundle
Pour créer des services redistribuables, il est tout d’abord nécessaire de créer un bundle et la structure de dossiers et fichiers allant avec. La notation des bundles suit des conventions assez précises ; ainsi, si vous souhaitez développer un ensemble de fonctionnalités d’administration, vous nommez le bundle MonEntrepriseAdminBundle, conformément à ces conventions.
Voici la structure de base du bundle, constituée d’une classe dans l’arborescence de dossiers indiquée en commentaire en tête du code :
// src/MonEntreprise/AdminBundle/MonEntrepriseAdminBundle.php
namespace App\MonEntreprise\AdminBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class MonEntrepriseAdminBundle extends Bundle
{
}
Pour activer ce bundle, il est ensuite nécessaire de le déclarer dans le fichier config/bundles.php avec la syntaxe suivante :
return [
// ...
App\MonEntreprise\AdminBundle\MonEntrepriseAdminBundle::class
=> ['all' => true],
];
b. Arborescence du bundle
Une fois le bundle créé, il est nécessaire d’ajouter des répertoires pour en organiser le code. Voici la structure type recommandée (bien que certains répertoires puissent s’avérer inutiles en fonction des fonctionnalités que vous développez).
-
Controller/ : contient les contrôleurs du bundle.
-
DependencyInjection/ : contient des classes d’injection de dépendances ; nous y reviendrons justement dans la suite de cette section.
-
Resources/config/ : contient...