• 30 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 13/05/2019

Les services, utilisation poussée

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Ce chapitre fait suite au précédent chapitre sur les services, qui portait sur la théorie et l'utilisation simple.

Ici, nous allons aborder des fonctionnalités intéressantes des services, qui permettent une utilisation vraiment poussée. Maintenant que les bases vous sont acquises, nous allons pouvoir découvrir des fonctionnalités très puissantes de Symfony.

Les tags sur les services

Les tags ?

Les tags sont une fonctionnalité très importante des services. Cela permet d'ajouter des fonctionnalités à un composant sans en modifier le code. On les a déjà rapidement évoqués lorsqu'on traitait les évènements Doctrine, voici maintenant l'occasion de les théoriser un peu.

Concrètement, un tag est une information que l'on appose à un ou plusieurs services afin que le conteneur de services les identifie comme tels. Ainsi, il devient possible de récupérer tous les services qui possèdent un certain tag.

Pour bien comprendre en quoi cela permet d'ajouter des fonctionnalités à un composant existant, prenons l'exemple de Twig.

Comprendre les tags à travers Twig

Le moteur de templates Twig dispose nativement de plusieurs fonctions pratiques pour vos vues. Seulement, il serait intéressant de pouvoir ajouter nos propres fonctions qu'on pourra utiliser dans nos vues, et ce sans modifier le code meme de Twig. Vous l'aurez compris, c'est possible grâce au mécanisme des tags.

L'idée est que Twig définit un tag, dans notre cas twig.extension, et une interface, dans notre cas\Twig_ExtensionInterface. Ensuite, Twig récupère tous les services qui ont ce tag, et sait les utiliser car ils implémentent tous la même interface (ils ont donc des méthodes connues).

Construisons notre propre fonction Twig pour se rendre compte du mécanisme.

Appliquer un tag à un service

Pour que Twig puisse récupérer tous les services qui vont définir des fonctions supplémentaires utilisables dans nos vues, il faut appliquer un tag à ces services. Pour l'exemple, nous allons rendre disponible la méthode pour détecter le spam que nous avions créée dans le serviceoc_platform.antispam. L'objectif est donc d'avoir dans nos vues une fonction{{ checkIfSpam("mon message") }}  (le nom étant arbitraire !).

Nous n'allons pas taguer le serviceoc_platform.antispam en lui-même, car ce service a déjà une mission : vérifier si un message est du spam ou pas. Ce n'est pas son rôle de se rendre disponible depuis une vue Twig. Créons donc d'abord ce mini-service tout simple :

<?php
// src/OC/PlatformBundle/Twig/AntispamExtension.php

namespace OC\PlatformBundle\Twig;

use OC\PlatformBundle\Antispam\OCAntispam;

class AntispamExtension
{
  /**
   * @var OCAntispam
   */
  private $ocAntispam;

  public function __construct(OCAntispam $ocAntispam)
  {
    $this->ocAntispam = $ocAntispam;
  }

  public function checkIfArgumentIsSpam($text)
  {
    return $this->ocAntispam->isSpam($text);
  }
}

 

Ainsi que sa configuration de base :

# src/OC/PlatformBundle/Resources/config/services.yml

services:
  oc_platform.twig.antispam_extension:
    class: OC\PlatformBundle\Twig\AntispamExtension
    arguments:
      - "@oc_platform.antispam"

Voilà, nous pouvons maintenant travailler sur ce service.

Commençons donc par ajouter le tag dans sa configuration.

# src/OC/PlatformBundle/Resources/config/services.yml

services:
  oc_platform.twig.antispam_extension:
    class: OC\PlatformBundle\Twig\AntispamExtension
    arguments:
      - "@oc_platform.antispam"
    tags:
      -  { name: twig.extension }

Vous voyez, on a simplement rajouté un attributtagsà la configuration de notre service. Cet attribut contient un tableau de tags, d'où le retour à la ligne et le tiret « - ». En effet, il est tout à fait possible d'associer plusieurs tags à un même service.

Dans ce tableau de tags, on a ajouté une ligne avec un attributname, qui est le nom du tag. C'est grâce à ce nom que Twig va pouvoir récupérer tous les services avec ce tag. Icitwig.extensionest donc le tag qu'on utilise. Lorsque vous utilisez le système de tags pour votre propre fonctionnalité, vous pouvez choisir le nom de tag que vous souhaitez il n'y a pas de contrainte.

Bien entendu, maintenant que Twig peut récupérer tous les services tagués, il faut qu'il sache un peu comment votre service fonctionne, pour que tout le monde puisse se comprendre : cela passe par l'interface.

Une classe qui implémente une interface

Celui qui va récupérer les services d'un certain tag attend un certain comportement de la part des services qui ont ce tag. Il faut donc les faire implémenter une interface ou étendre une classe de base.

En particulier, Twig attend que votre service implémente l'interfaceTwig_ExtensionInterface. Encore plus simple, Twig propose une classe abstraite à hériter par notre service, il s'agit de Twig_Extension, qui implémente elle-même l'interface. Je vous invite donc à modifier notre classeAntispamExtension pour qu'elle hérite deTwig_Extension:

<?php
// src/OC/PlatformBundle/Twig/AntispamExtension.php

namespace OC\PlatformBundle\Twig;

use OC\PlatformBundle\Antispam\OCAntispam;

class AntispamExtension extends \Twig_Extension
{
  // …
}

Notre service est prêt à fonctionner avec Twig, il ne reste plus qu'à écrire au moins une des méthodes de la classe abstraiteTwig_Extension.

Écrire le code qui sera exécuté

Cette section est propre à chaque tag, où celui qui récupère les services d'un certain tag va exécuter telle ou telle méthode des services tagués. En l'occurrence, Twig va exécuter les méthodes suivantes :

  • getFilters(), qui retourne un tableau contenant les filtres que le service ajoute à Twig ;

  • getTests(), qui retourne les tests ;

  • getFunctions(), qui retourne les fonctions ;

  • getOperators(), qui retourne les opérateurs.

Pour notre exemple, nous allons juste ajouter une fonction accessible dans nos vues via{{ checkIfSpam('le message') }}. Elle vérifie si son argument est un spam. Pour cela, écrivons la méthodegetFunctions()suivante dans notre service :

<?php
// src/OC/PlatformBundle/Twig/AntispamExtension.php

namespace OC\PlatformBundle\Twig;

use OC\PlatformBundle\Antispam\OCAntispam;

class AntispamExtension extends \Twig_Extension
{
  // ...

  // Twig va exécuter cette méthode pour savoir quelle(s) fonction(s) ajoute notre service
  public function getFunctions()
  {
    return array(
      new \Twig_SimpleFunction('checkIfSpam', array($this, 'checkIfArgumentIsSpam')),
    );
  }

  // La méthode getName() identifie votre extension Twig, elle est obligatoire
  public function getName()
  {
    return 'OCAntispam';
  }
}

Dans cette méthodegetFunctions() , il faut retourner un tableau d'objet\Twig_SimpleFunction, dont :

  • Le premier argument, icicheckIfSpam, est le nom de la fonction qui sera disponible dans nos vues Twig ;

  • Le deuxième argument, iciarray($this, 'checkIfArgumentIsSpam') est un callable PHP. Dans notre cas, on appelle la méthode de l'extension Twig même, mais en réalité on pourrait également définir le callable àarray($this->ocAntispam, 'isSpam') pour qu'il appelle directement la méthode de notre serviceOCAntispam. Les deux méthodes sont possibles ici.

Au final, {{ checkIfSpam(var) }} côté Twig exécute$this->isSpam($var) côtéOCAntispam.

On a également ajouté la méthodegetName()qui identifie votre service de manière unique parmi les extensions Twig, elle est obligatoire, ne l'oubliez pas.

Et voilà ! Vous pouvez dès à présent utiliser la fonction{{ checkIfSpam() }}dans vos vues. Comment Twig est-il au courant de notre nouvelle fonction ? Grâce au tag justement !

Méthodologie

Ce qu'on vient de faire pour transformer notre simple service en extension Twig est la méthodologie à appliquer systématiquement lorsque vous taguez un service. Sachez que tous les tags ne nécessitent pas forcément que votre service implémente une certaine interface, mais c'est assez fréquent.

Pour connaître tous les services implémentant un certain tag, vous pouvez exécuter la commande suivante :

C:\wamp\www\Symfony> php bin/console debug:container --tag=twig.extension

Symfony Container Public Services Tagged with "twig.extension" Tag
==================================================================

 ------------------------------------- ------------------------------------------
 Service ID Class name
 ------------------------------------- ------------------------------------------
 oc_platform.twig.antispam_extension   OC\PlatformBundle\Twig\AntispamExtension
 ------------------------------------- ------------------------------------------
‌

Récupérer tous ces services tagués, c'est exactement ce que fait Twig lorsqu'il s'initialise. De cette façon, il peut ajouter nos fonctions à son comportement.

Les principaux tags

Il existe pas mal de tags prédéfinis dans Symfony, qui permettent d'ajouter des fonctionnalités à droite et à gauche. Je ne vais vous présenter ici que deux des principaux tags. Mais sachez que l'ensemble des tags est expliqué dans la documentation.

Les évènements du cœur

Les services peuvent être utilisés avec le gestionnaire d'évènements, via le tagkernel.event_listener. Je ne le détaille pas plus ici car le gestionnaire d'évènements est un composant très intéressant, et fait l'objet d'un prochain chapitre dédié.

Les types de champ de formulaire

Le tagform.typepermet de définir un nouveau type de champ de formulaire. Par exemple, si vous souhaitez utiliser l'éditeur WYSIWYG (What you see is what you get) ckeditor pour certains de vos champs texte, il est facile de créer un champckeditorau lieu detextarea. Pour cela, disons que vous avez ajouté le JavaScript nécessaire pour activer cet éditeur sur les<textarea>qui possèdent la classeckeditor. Il ne reste plus qu'à automatiser l'apparition de cette classe.

Commençons par créer la classe du type de champ :

<?php
// src/OC/PlatformBundle/Form/CkeditorType.php

namespace OC\PlatformBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\OptionsResolver\OptionsResolver;

class CkeditorType extends AbstractType
{
  public function configureOptions(OptionsResolver $resolver)
  {
    $resolver->setDefaults(array(
      'attr' => array('class' => 'ckeditor') // On ajoute la classe CSS
    ));
  }

  public function getParent() // On utilise l'héritage de formulaire
  {
    return TextareaType::class;
  }
}

Ce type de champ hérite de toutes les fonctionnalités d'untextarea(grâce à la méthodegetParent()) tout en disposant de la classe CSSckeditor(définie dans la méthodesetDefaultOptions()) vous permettant, en ajoutantckeditorà votre site, de transformer vos<textarea>en éditeur WYSIWYG.

Puis, déclarons cette classe en tant que service, en lui ajoutant le tagform.type:

# src/OC/PlatformBundle/Resources/config/services.yml

services:
  oc_platform.form.ckeditor:
    class: OC\PlatformBundle\Form\CkeditorType
    tags:
      - { name: form.type, alias: ckeditor }

On a ajouté l'attributaliasdans le tag, qui représente le nom sous lequel on pourra utiliser ce nouveau type. Pour l'utiliser, c'est très simple, modifiez vos formulaires pour utiliserckeditorà la place detextarea. Par exemple, dans notreAdvertType:

<?php
// src/OC/PlatformBundle/Form/ArticleType.php

namespace OC\PlatformBundle\Form;

class AdvertType extends AbstractType
{
  public function buildForm(FormBuilderInterface $builder, array $options)
  {
    $builder
      // …
      ->add('content', CkeditorType::class)
    ;
  }

  // …
}

Et voilà, votre champ a maintenant automatiquement la classe CSSckeditor, ce qui permet d'activer l'éditeur (si vous l'avez ajouté à votre site bien sûr).

C'était un exemple pour vous montrer comment utiliser les tags dans ce contexte.

Dépendances optionnelles : les appels de méthodes (ou calls en anglais)

Les dépendances optionnelles

L'injection de dépendances dans le constructeur, comme on l'a fait dans le précédent chapitre sur les services, est un très bon moyen de s'assurer que la dépendance sera bien disponible. Mais parfois vous pouvez avoir des dépendances optionnelles. Ce sont des dépendances qui peuvent être rajoutées au milieu de l'exécution de la page, grâce à des setters. Reprenons par exemple notre service d'antispam, et choisissons de définir l'argument$locale comme optionnel. L'idée est de supprimer ce dernier des arguments du constructeur, et d'ajouter le setter correspondant :

<?php
// src/OC/PlatformBundle/Antispam/OCAntispam.php

namespace OC\PlatformBundle\Antispam;

class OCAntispam
{
  private $mailer;
  private $locale;
  private $minLength;

  public function __construct(\Swift_Mailer $mailer, $minLength)
  {
    $this->mailer    = $mailer;
    $this->minLength = (int) $minLength;
  }

  public function setLocale($locale)
  {
    $this->locale = $locale;
  }

  // …
}

N'oubliez pas de supprimer l'argument%locale% de la définition du service. Rappelez-vous : si vous modifiez le constructeur d'un service, vous devez adapter sa configuration, et inversement.

Avec cette modification, notre service est créé sans renseigné l'attribut locale du service. Celui-ci vaut donc null, tant qu'il n'a pas été renseigné. Pour venir la renseigner, les calls interviennent.

Les calls

Les calls (que l'on traduirait en français par « les appels de méthodes ») sont un moyen d'exécuter des méthodes de votre service juste après sa création. Ainsi, on peut exécuter la méthodesetLocale() avec notre paramètre%locale%, qui sera une valeur par défaut pour ce service. Elle pourra tout à fait être écrasée par une autre valeur au cours de l'exécution de la page (à chaque appel de la méthodesetLocale).

Je vous invite donc à rajouter l'attribut calls à la définition du service, comme ceci :

# src/OC/PlatformBundle/Resources/config/services.yml

services:
  oc_platform.antispam:
    class: OC\PlatformBundle\Antispam\OCAntispam
    arguments:
      - "@mailer"
      - 50
    calls:
      - [ setLocale, [%locale%] ]

Comme avec les tags, vous pouvez définir plusieurs calls, en rajoutant des lignes qui commencent par un tiret. Chaque ligne de call est un tableau qui se décompose comme suit :

  • Le premier index, icisetLocale, est le nom de la méthode à exécuter ;

  • Le deuxième index, ici[ %locale% ], est le tableau des arguments à transmettre à la méthode exécutée. Ici nous avons un seul argument, mais il est tout à fait possible d'en définir plusieurs.

Concrètement, dans notre cas le code équivalent du conteneur serait celui-ci :

<?php

$antispam = new \OC\PlatformBundle\Antispam\OCAntispam($mailer, 50);
$antispam->setLocale($locale);

Ce code n'existe pas, c'est un code fictif pour vous représenter l'équivalent de ce que fait le conteneur de services dans son coin lorsque vous demandez le serviceoc_platform.antispam.

L'utilité des calls

En plus du principe de dépendance optionnelle, l'utilité des calls est également remarquable pour l'intégration des bibliothèques externes (Zend Framework, GeSHI, etc.), qui ont besoin d'exécuter quelques méthodes en plus du constructeur. Vous pourrez donc le faire grâce aux calls.

Les services courants de Symfony

Maintenant que vous savez bien utiliser les services, il vous faut les connaître tous afin d'utiliser la puissance de chacun. Il est important de bien savoir quels sont les services existants afin de bien pouvoir injecter ceux qu'il faut dans les services.

Je vous propose donc une liste des services par défaut de Symfony les plus utilisés. Gardez-la en tête !

Identifiant

Description

doctrine.orm.entity_manager

Ce service est l'instance de l'EntityManagerde Doctrine ORM. On l'a rapidement évoqué dans la partie sur Doctrine, l'EntityManagerest bien enregistré en tant que service dans Symfony. Ainsi, lorsque dans un contrôleur vous faites$this->getDoctrine()->getManager(), vous récupérez en réalité le servicedoctrine.orm.entity_manager. Ayez bien ce nom en tête, car vous aurez très souvent besoin de l'injecter dans vos propres services : il vous offre l'accès à la base de données, ce n'est pas rien !

event_dispatcher

Ce service donne accès au gestionnaire d'évènements. Le décrire en quelques lignes serait trop réducteur, je vous propose donc d'être patients, car le prochain chapitre lui est entièrement dédié. ;)

kernel

Ce service vous donne accès au noyau de Symfony. Grâce à lui, vous pouvez localiser des bundles, récupérer le chemin de base du site, etc. Voyez le fichierKernel.phppour connaître toutes les possibilités. Nous nous en servirons très peu en réalité.

logger

Ce service gère les logs de votre application. Grâce à lui, vous pouvez utiliser des fichiers de logs très simplement. Symfony utilise la classeMonologpar défaut pour gérer ses logs. La documentation à ce sujet vous expliquera comment vous en servir si vous avez besoin d'enregistrer des logs pour votre propre application ; c'est intéressant, n'hésitez pas à vous renseigner.

mailer

Ce service vous renvoie par défaut une instance deSwift_Mailer, une classe permettant d'envoyer des e-mails facilement. Encore une fois, la documentation deSwiftMailer et la documentation de son intégration dans Symfony vous seront d'une grande aide si vous souhaitez envoyer des e-mails.

request_stack

Ce service est très important : il vous donne un objet qui vous permet de récupérer la requêteRequest courante via sa méthode getCurrentRequest. Je vous réfère au chapitre sur les contrôleurs, sectionRequest pour plus d'informations sur comment récupérer la session, l'IP du visiteur, la méthode de la requête, etc.

Ce service utiliser les calls pour définir la requête justement, car la requête peut changer au cours de son exécution (lors d'une sous-requête).

router

Ce service vous donne accès au routeur (Symfony\Component\Routing\Router). On l'a déjà abordé dans le chapitre sur les routes, c'est le service qui permet de générer des routes.

security.token_storage

Ce service permet de gérer l'authentification sur votre site internet. On l'utilise notamment pour récupérer l'utilisateur courant. Le raccourci du contrôleur$this->getUser()exécute en réalité$this->container->get('security.token_storage')->getToken()->getUser()!

service_container

Ce service vous renvoie le conteneur de services lui-même. On ne l'utilise que très rarement, car, comme je vous l'ai déjà mentionné, il est bien plus propre de n'injecter que les services dont on a besoin, et non pas tout le conteneur. Mais dans certains cas il est nécessaire de s'en servir, sachez donc qu'il existe.

twig

Ce service représente une instance deTwig_Environment. Il permet d'afficher ou de retourner une vue. Vous pouvez en savoir plus en lisant la documentation de Twig. Ce service peut être utile pour modifier l'environnement de Twig depuis l’extérieur (lui ajouter des extensions, etc.).

templating

Ce service représente le moteur de templates de Symfony. Par défaut il s'agit de Twig, mais cela peut également être PHP ou tout autre moteur intégré dans un bundle tiers. Ce service montre l'intérêt de l'injection de dépendances : en injectanttemplatinget nontwigdans votre service, vous faites un code valide pour plusieurs moteurs de templates ! Et si l'utilisateur de votre bundle utilise un moteur de templates à lui, votre bundle continuera de fonctionner. Sachez également que le raccourci du contrôleur$this->render()exécute en réalité$this->container->get('templating')->renderResponse().

En résumé

  • Les tags permettent de récupérer tous les services qui remplissent une même fonction : cela ouvre les possibilités pour les extensions Twig, les évènements, etc.

  • Les calls permettent les dépendances optionnelles, et facilitent l'intégration de bibliothèques tierces.

  • Les principaux noms de services sont à connaître par cœur afin d'injecter ceux nécessaires dans les services que vous créerez !

  • Le code du cours tel qu'il doit être à ce stade est disponible sur la branche iteration-17 du dépot Github.

Exemple de certificat de réussite
Exemple de certificat de réussite