Les services dans Drupal 8
Qu’est-ce qu’un service?
Un service est une classe qui propose des fonctionnalités spécifiques et globales à toute l’application. L’exemple le plus couramment utilisé est un service permettant l’envoi de mail depuis n’importe quel endroit de l’application.
Une des principales caractéristiques du service est sa méthode d’instantiation par un Service Container. Celui-ci s’occupe de la création et l’initialisation de l’objet. Drupal a adopté le concept de Service Container depuis la version 8, basée sur le framework Symfony1.
Pour un développeur, il est conseillé d’utiliser le Service Container de Drupal pour respecter le découplage du système et la réutilisation des différentes briques logicielles.
Un service peut avoir besoin de paramètres ou de l’injection d’autres services2 qui sont alors définis dans son fichier YAML de déclaration. L’utilisation d’un service sous Drupal 8 est pratiquement la même que sous Symfony 2.
Le core de Drupal 8 expose lui-même de nombreux services3.
Ce qu’on faisait avant
Dans Drupal 7, nous aurions pu utiliser la fonction module_invoke
pour pouvoir appeler une fonction d’un autre module par exemple :
module_invoke('MYMODULE', 'function_name', arg1, arg2,…)
L’utilisation de cette méthode crée un fort couplage entre deux modules.
Ce qu’on fait en Drupal 8
Avec l’injection de dépendance, il suffit de changer la classe utilisée dans le service pour changer le comportement de celui-ci qui sera alors transparent pour l’utilisateur du service.
Il est aussi possible de surcharger les services déja existants du core Drupal en utilisant un alias de service dans le sites/default/service.yml
pointant vers notre service de remplacement.
Par exemple4 :
services:
user.data:
alias: monmodule.user.data
Comment déclarer un service dans votre module Drupal 8
Je vais prendre pour exemple un service de calcul de prix. La déclaration d’un service se fait à l’aide d’un fichier YAML. Le nom du fichier de service doit contenir le nom de mon module en premier.
Je vais alors créer un fichier price.services.yml
(ici price
est le nom du module). Ce service est encore simple et il ne prend pas d’arguments.
# price.services.yaml
services:
price.calculator:
class: DrupalpriceServicePriceCalculator
arguments: []
Je vais maintenant créer la classe PriceCalculator
dans le dossier MonModule/src/Service
. Ici, rien de bien compliqué, juste une classe simple avec un constructeur vide et une fonction de calcul qui prend en entrée une liste de produits et qui renvoie le prix hors taxe.
// PriceCalculator.php
namespace DrupalpriceService;
class PriceCalculator
{
public function __construct(){}
public function getFinalPriceHT($products)
{
$finalPrice = 0;
foreach($products as $product){
$finalPrice += $product->getPrice();
}
return $finalPrice;
}
}
Utiliser un service dans votre module
Pour réutiliser le service il suffit de l’appeler en utilisant le Service Container de Drupal.
$priceCalculator = Drupal::service(‘price.calculator');
Le Service Container va alors instantier l’objet, l’initialiser et le renvoyer. Il ne reste alors plus qu’à l’utiliser pour calculer le prix hors taxe.
$finalPrice = $priceCalculator->getFinalPriceHT($products);
De la même façon, on peut utiliser un service qui fait partie du core de Drupal (ici le service d’envoi de mails ) :
Drupal::service(‘plugin.manager.mail’)->mail(…)
Utiliser l’injection de dépendance dans un controleur
Pour éviter la redondance de l’appel à Drupal::service
, il est possible d’utiliser le container de Drupal. Pour cela, il suffit d’étendre la classe ControllerBase
et de récuperer les services dans la méthode create
du contrôleur avec son container. Voici un exemple d’injection de deux services :
// CalculController.php
class CalculController extends ControllerBase {
protected $entity_query;
protected $priceCalculator;
public function __construct(QueryFactory $entity_query, PriceCalculator $calculator) {
$this->entity_query = $entity_query;
$this->priceCalculator = $calculator;
}
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.query'),
$container->get('price.calculator')
);
}
}
Cette méthode se rapproche de l’utilisation des services dans les contrôleurs du core de Drupal.
Les variables de configuration d’un module dans Drupal 8
Drupal 8 permet de rajouter des variables de configuration qui s’initialisent à l’activation du module. Une fois le module activé, nous pourrons faire des appels pour récupérer les variables de configuration du module.
Comment les déclarer ?
Pour déclarer les paramètres de configuration, il faut créer un fichier YAML dans le dossier MonModule/config/install
de mon module. Le nom du fichier de configuration doit contenir le nom du module en premier. Je vais créer pour ce module de prix le fichier price.settings.yml
. Dans celui-ci je vais mettre juste une variable qui, pour mon test, va représenter la TVA.
# price.calculator.yml
tva: 19.6
Comment les rendre administrables ?
Comme dit plus tôt, ce sont des variables. Il est donc possible de les rendre configurables par l’utilisateur. Pour cela, il suffit d’utiliser le setter de la méthode statique config()
de Drupal 8.
Drupal::config("price.settings")->set("tva","7,7");
Il faut maintenant utiliser la méthode get
pour récupérer la valeur de ma configuration :
Drupal::config("price.settings")->get("tva");
Il est aussi possible d’utiliser les formulaires de configuration de Drupal 85. Pour cela il faut créer une classe de formulaire qui étend la classe ConfigFormBase
de Drupal 8.
Allier les configurations et les services
Maintenant qu’il y a la TVA dans la configuration du module, nous allons pouvoir l’utiliser dans le service. Je peux maintenant déclarer un paramètre dans mon fichier YAML et l’envoyer à mon service pour qu’il puisse l’utiliser. Pour cette tâche la déclaration de service devient dans mon yaml :
# price.service.yml
parameters:
settings.name : price.settings
services:
price.calculator:
class: DrupalpriceServicePriceCalculator
arguments: [%settings.name%]
Dans ma classe, je vais pouvoir initialiser la variable tva
avec la variable de configuration de mon module. Je peux utiliser tva
pour créer une fonction getFinalPriceTtc()
qui va permettre de calculer le prix total avec la TVA. Une méthode setTva(…)
a été rajoutée pour mettre à jour la TVA. Ma classe devient alors :
// PriceCalculator.php
namespace DrupalpriceService;
class PriceCalculator
{
private tva;
public function __construct($configName)
{
$config = Drupal::config($configName);
$this->tva = $config->get("tva");
}
public function getFinalPriceHT($products)
{
$finalPrice = 0;
foreach($products as $product){
$finalPrice += $product->getPrice();
}
return $finalPrice;
}
public function getFinalPriceTTC($products)
{
$finalPrice = 0;
foreach($products as $product){
$finalPrice += $product->getPrice();
}
$finalPrice = $finalPrice + (($finalPrice*$this->tva)/100);
return $finalPrice;
}
public function setTva($tva)
{
$this->tva = tva;
}
}
- voir la documentation du Service Container de Symfony 2. ↩
- voir « Services and dependency injection in Drupal 8« , dans la documentation communautaire Drupal. ↩
- voir la documentation des services de Drupal 8. ↩
- exemple repris de « Overriding Drupal 8 Services », par Tim Millwood ↩
- la création d’un formulaire de configuration est bien expliquée dans « How to Create an Administration Form in Drupal 8 », de Ian Zugec ↩