Drupal 10 : Conteneur de Services et Services

Comprendre le fonctionnement du conteneur de Service dans Drupal et la création de services.

Cet article a été initialement rédigé pour Drupal 8 mais son contenu est toujours d'actualité pour Drupal 9 et Drupal 10.

N'hésitez pas à nous contacter ou à vous inscrire à notre formation «Drupal pour les développeurs et développeuses» pour en savoir plus !

Les applications modernes s’appuyant sur la POO, beaucoup d’objets sont impliqués dans le cycle de vie d’une page. Afin de vous épargner la création, la réutilisation et la suppression de chacun de ces nombreux objets, depuis Drupal 8, Drupal s’appuie sur un objet spécial de Symfony appelé Service Container qui vise à vous simplifier la manipulation de ces dits objets.
Comme son nom l’indique, le
Conteneur de Services gère des Services.

Un Service représente une fonctionnalité réutilisable à travers votre site (accès à la base de données, envoi d’un mail, traduction une chaîne, etc).
Si l’on prend l’exemple de la base de données, imaginez que vous ayez à configurer le nom d’utilisateur, le mot de passe et le nom de la base. Que vous soyez dans un environnement de production ou de développement, vous n’aurez probablement pas les mêmes valeurs. Si vous deviez saisir les nouvelles informations de connexion il vous faudrait passer par une recherche et remplacement de ces données à travers votre base de code. Cette action étant plutôt laborieuse, les
Services permettent de simplifier cette gestion. En ayant une définition centralisée et découplée, il devient plus facile de les réutiliser, de les étendre ou de les remplacer.

Exemple de l’utilisation d’une classe pour encoder quelque chose en JSON :

# Controller.php
use Drupal\Component\Serialization\Json;

$json_serializer = new Json();
$json_serializer->encode(...);

Conversion en Service :

# core.services.yml
services:
  serialization.json:
    class: Drupal\Component\Serialization\Json

Exemple d’utilisation du Service :

# Controller.php
$service = \Drupal::service('serialization.json');
$service->encode(...);

C’est seulement lorsque l’appel au Service est fait que l’objet est créé et retourné, pas avant qu’il ne soit nécessaire. Cette instanciation tardive augmente les performances de l’application (pas de gaspillage) et surtout permet de facilement remplacer l’implémentation d’un Service, principe très important pour réaliser des tests unitaires.

Dans le quotidien de vos projets vous serez amenés à créer peu de nouveaux Services, mais il est fort à parier que vous aurez à modifier des Services existants pour surcharger des comportements, simuler la génération de données (pour les tests notamment).

Pour cela, il vous suffit d’implémenter une classe qui étend ServiceProviderBase et qui possède une méthode alter() dans laquelle sera décrite la surcharge.

# src/MyModuleServiceProvider.php
namespace Drupal\my_module;

use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase;

class MyModuleServiceProvider extends ServiceProviderBase {
  /**
   * {@inheritdoc}
   */
  public function alter(ContainerBuilder $container) {
    // Remplace la classe qui implémente le service "language_manager".
    $definition = $container->getDefinition('language_manager');
    $definition->setClass('Drupal\language_test\LanguageTestManager');
  }
}

Certains des Services que vous implémenterez auront besoin d’arguments pour fonctionner. Ces arguments peuvent être des paramètres. Un paramètre est une donnée variable qui peut être définie dans un fichier centralisé. Les paramètres utilisés par un Service sont entourés du symbole % pour indiquer qu’il s’agit de paramètres.

Exemple :

# module.services.yml
services:
twig:
 class: Drupal\Core\Template\TwigEnvironment
 arguments: ['@app.root', '@cache.default', '%twig_extension_hash%', '@twig.loader', '%twig.config%']

Les paramètres peuvent être définis directement dans le fichier de déclaration des services ou dans un autre fichier. Notez que si votre chaîne contient un @, il faut l’échapper en doublant l’arobase. Dans l’exemple ci-dessus vous voyez le paramètre %twig.config% dont la valeur par défaut est définie dans le fichier core.services.yml mais que vous pouvez surcharger dans votre fichier services.yml (dans sites/default).

Découvrez maintenant comment appeler et injecter les dépendances de vos plugins, services ou hooks.

Commentaires

Nico Mardi 29 août 2017 - 17:17
Hello, tout d'abord merci pour cet article. Une question cependant: peut-on mélanger les genres, et avoir une classe développée sous Symfony 2.8, et s'en servir en tant que service dans Drupal 8 ? Et question complémentaire, si j'ai développé un bundle qui fait tout ce dont j'ai besoin dans une fonctionnalité de site D8, est ce que je peux implémenter mon bundle SF en service dans Drupal 8 ? Ca m'intéresse fortement d'avoir des avis et retours sur tout cela ! merci d'avance.
En réponse à par Nico
DuaelFr Lundi 4 septembre 2017 - 10:57
Salut Nico, Drupal 8.3 utilise des composants de Symfony 2.8 donc pour le moment ça devrait pas poser de problème d'utiliser une classe développée pour cette version. Attention, à partir de la 8.4 on passera sur Symfony 3.2 et à partir de là il ne sera plus possible d'utiliser des classes Sf 2.8 car PHP et Composer ne sont pas conçus pour faire cohabiter plusieurs versions des mêmes librairies. En ce qui concerne l'implémentation d'un bundle Sf directement dans D8, ce n'est pas si simple. En effet, D8 n'utilise qu'une petite partie de Sf et il te faudra donc créer un module qui pourra faire le lien entre ton bundle et le core de D8. Il faudra notamment redéfinir les routes et encapsuler le retour de tes contrôleurs car D8 ne permet par de renvoyer du HTML directement contrairement à Sf. N'hésite pas si tu as d'autres questions !
Mehdi Jeudi 30 août 2018 - 14:49
Bonjour, Pour commencer, je vous félicite pour cet article très intéressant. Pour vous expliquer mon point de blocage, j'ai développé une ressource avec la méthode 'Get' pour recevoir un lien qui contient des Id. j'ai bien réussi à extraire les paramètres du lien et à lister les fiches nécessaires que je dois les envoyer en format Json. Aussi je dois prendre en compte les types des erreurs possibles en se basant sur le code status de la requête que j'arrive pas à le récupérer. J'ai essayé avec la fonction http_response_code() ,getStatusCode(), isOk() mais rien ne ce passe, soit me renvoie un tableau vide soit toujours status =200 ok même avec des paramètres invalides ce qui ne permet pas d'exécuter la requête à la base de donnée . Ma deuxième question c'est comment je peux faire la liaison entre ma ressource 'modules/baco/src/Plugin/rest/resource/BacoResource.php avec mon contrôleur pour but de séparer le code (Controller : pour contenir la requête à la base de données et traitement d'erreur , Resource: pour la mise en relation avec l'autre application, decode de l'url reçu et des données, encode le résultat en Json et l'envoie des données. J’espère bien que vous pourriez m'aider.merci d'avance
En réponse à par Mehdi
Artusamak Jeudi 30 août 2018 - 15:09

Merci pour ton message.

Avec quel type d'objet fais tu ta requête au webservice ?

Si c'est un objet qui implément l'interface ResponseInterface, tu devrais pouvoir récupérer le code de réponse par $response->getStatusCode(). Attention cependant, certaines API sont mal développées et répondent un code 200 au lieu d'un code 400 en cas d'erreur et c'est dans le contenu de la réponse que se trouve le code d'erreur.

Concernant la question sur la séparation, il suffit de déclarer ta classe qui appelle ton API en tant que service et tu pourras t'en servir dans ton contrôleur. Regarde un œil à https://happyculture.coop/blog/drupal-8-injection-dependance aussi ça t'aidera peut être.

En réponse à par Artusamak
Mehdi Jeudi 30 août 2018 - 15:31

Merci pour votre réponse rapide. Voici mon code ci-dessous. Je suis encore débutant en drupal 8, donc si vous pouvez me citer quelques type d'objets pour faire la requête au web service ?!! Ou et comment je peux traiter la réponse de la requête ?


namespace Drupal\baco\Plugin\rest\resource;

use Drupal\rest\Plugin\ResourceBase;
use Drupal\rest\ResourceResponse;
use Drupal\node\Entity\Node;
use Symfony\Component\HttpFoundation\JsonResponse;
use Drupal\headless_drupal\ResponseHelper;
use vendor\Symfony\Component\HttpFoundation;
use vendor\GuzzleHttp\psr7\src;

/**
 * Provides a Demo Resource
 *
 * @RestResource(
 *   id = "baco_resource",
 *   label = @Translation("Baco Resource"),
 *   uri_paths = {
 *     "canonical" = "/baco_rest_api/baco_resource"
 *   }
 * )
 */
 /*list field values ​​for a content type */
class BacoResource extends ResourceBase {

  /**
  * Responds to entity GET requests.
  * @return \Drupal\rest\ResourceResponse
  */

 public function get() {
$url = "http://localhost/bacco/mapage?idfiche=7,8,9,18,77";

/*** A remplacer***/
$arg  = explode('=',$url);
$liste = explode(',',$arg[1]);


foreach ($liste as $key => $value ) {
  if (isset($value) & is_numeric($value) & ($value!=NULL) )
  {

  $query = \Drupal::entityQuery('node')
  ->condition('type','reponse_type')
  ->exists('nid')
  ->condition('nid',$liste,'IN')
  ->condition('status', 1);
  $group = $query->orConditionGroup()
  ->condition('field_question_reponse','%ponse', 'LIKE')
  ->condition('field_question_reponse','Que%','Like')
  ->condition('field_question_reponse','tion','CONTAINS');
  $fiche = $query->condition($group)->execute();
  $nodes = \Drupal::entityTypeManager()->getStorage('node')->loadMultiple($fiche);
}

$json_array=[];
foreach ($nodes as $node) {
  $json_array[$node->get("nid")->value] =  $j_array = array(
    'title' => $node->get("title")->value,
    'Résumé de la fiche' => $node->get("field_resume_de_la_fiche")->value,
    'Lien fiche' => $node->get("field_lien_fiche")->value,
    'Réponse-type' => $node->get("field_reponse_type")->value,
    'Date de publication' => $node->get("field_date_de_publication")->value,
    'Type fiche' => "Réponse Type",
  );


}

return new ResourceResponse($json_array);

}
}
}

Résultat


{
    "7": {
        "title": "fiche1",
        "Résumé de la fiche": "fiche de remboursement",
        "Lien fiche": null,
        "Réponse-type": "fiche de remboursement   uashnkajsh",
        "Date de publication": null,
        "Type fiche": "Réponse Type"
    },
    "8": {
        "title": "fiche 2",
        "Résumé de la fiche": "fiche 2",
        "Lien fiche": null,
        "Réponse-type": "fiche 2 fiche 2fiche 2fiche 2fiche 2fiche 2fiche 2 fiche 2fiche 2 fiche 2fiche 2",
        "Date de publication": "2018-03-30",
        "Type fiche": "Réponse Type"
    },
En réponse à par Mehdi
DuaelFr Mercredi 12 septembre 2018 - 16:42

Salut,

Ta question est super spécifique et demanderait une discussion pour comprendre le fond de ton problème. De fait, je doute que les commentaires de notre blog soient le lieu le plus approprié pour ça. Je te conseille donc de rejoindre la communauté sur Slack où tu trouveras plein de gens qui pourront tenter de t'aider.

Pour y parvenir, il faut suivre les instructions disponibles sur le site de Drupal : https://drupal.org/slack puis rejoindre le salon #drupal-france

À bientôt !

Votre commentaire

Le contenu de ce champ sera maintenu privé et ne sera pas affiché publiquement.
Votre adresse servira à afficher un Gravatar et à vous notifier des réponses. Votre commentaire sera anonymisé si ce billet est dépublié pendant plus de 3 mois.
Pour lutter contre le spam notre système enregistre votre adresse IP et votre adresse e-mail si vous la partagez.
Nous vous invitons à consulter notre politique de confidentialité pour comprendre les traitements faits de ces données et comment les rectifier.

À propos de Julien

Co-fondateur - Scrum master & Expert technique

Utilisateur de Drupal depuis 2008, j’ai fait mes armes comme développeur chez Commerce Guys puis me suis mis à encadrer les nouveaux arrivants avant de donner des formations, participer aux avant ventes et accompagner les équipes au passage à Scrum.

Je suis impliqué dans la communauté française de Drupal depuis 2009, j’ai été tour à tour président puis vice-président de l’association Drupal France et francophonie entre 2011 et 2013.