• 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 évènements et extensions Doctrine

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

Maintenant que vous savez manipuler vos entités, vous allez vous rendre compte que pas mal de comportements sont répétitifs. En bon développeurs, il est hors de question de dupliquer du code ou de perdre du temps : nous sommes bien trop fainéants !

Ce chapitre a pour objectif de vous présenter les évènements et les extensions Doctrine, qui vous permettront de simplifier certains cas usuels que vous rencontrerez.

Les évènements Doctrine

L'intérêt des évènements Doctrine

Dans certains cas, vous pouvez avoir besoin d'effectuer des actions juste avant ou juste après la création, la mise à jour ou la suppression d'une entité. Par exemple, si vous stockez la date d'édition d'une annonce, à chaque modification de l'entitéAdvert il faut mettre à jour cet attribut juste avant la mise à jour dans la base de données.

Ces actions, vous devez les faire à chaque fois. Cet aspect systématique a deux impacts. D'une part, cela veut dire qu'il faut être sûrs de vraiment les effectuer à chaque fois pour que votre base de données soit cohérente. D'autre part, cela veut dire qu'on est bien trop fainéants pour se répéter !

C'est ici qu'interviennent les évènements Doctrine. Plus précisément, vous les trouverez sous le nom de callbacks du cycle de vie (lifecycle en anglais) d'une entité. Un callback est une méthode de votre entité, et on va dire à Doctrine de l'exécuter à certains moments.

On parle d'évènements de « cycle de vie », car ce sont différents évènements que Doctrine déclenche à chaque moment de la vie d'une entité : son chargement depuis la base de données, sa modification, sa suppression, etc. On en reparle plus loin, je vous dresserai une liste complète des évènements et de leur utilisation.

Définir des callbacks de cycle de vie

Pour vous expliquer le principe, nous allons prendre l'exemple de notre entité Advert, qui va comprendre un attribut $updatedAt représentant la date de la dernière édition de l'annonce. Si vous ne l'avez pas déjà, ajoutez-le maintenant, et n'oubliez pas de mettre à jour la base de données à l'aide de la commande doctrine:schema:update :

<?php
/**
 * @ORM\Column(name="updated_at", type="datetime", nullable=true)
 */
private $updatedAt;
1. Définir l'entité comme contenant des callbacks

Tout d'abord, on doit dire à Doctrine que notre entité contient des callbacks de cycle de vie ; cela se définit grâce à l'annotationHasLifecycleCallbacksdans le namespace habituel des annotations Doctrine :

<?php
// src/OC/PlatformBundle/Entity/Advert.php

namespace OC\PlatformBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="OC\PlatformBundle\Entity\AdvertRepository")
 * @ORM\HasLifecycleCallbacks()
 */
class Advert
{
  // …
}

Cette annotation permet à Doctrine de vérifier les callbacks éventuels contenus dans l'entité. Elle s'applique à la classe de l'entité, et non à un attribut particulier. Ne l'oubliez pas, car sinon vos différents callbacks seront tout simplement ignorés.

2. Définir un callback et ses évènements associés

Maintenant, il faut définir des méthodes et surtout, les évènements sur lesquels elles seront exécutées.

Continuons dans notre exemple, et créons une méthodeupdateDate()dans l'entitéAdvert. Cette méthode doit définir l'attribut$updatedAt à la date actuelle, afin de mettre à jour automatiquement la date d'édition d'une annonce. Voici à quoi elle pourrait ressembler :

<?php
// src/OC/PlatformBundle/Entity/Advert.php

namespace OC\PlatformBundle\Entity;

/**
 * @ORM\Entity(repositoryClass="OC\PlatformBundle\Entity\AdvertRepository")
 * @ORM\HasLifecycleCallbacks()
 */
class Advert
{
  // …

  public function updateDate()
  {
    $this->setUpdatedAt(new \Datetime());
  }
}

Maintenant il faut dire à Doctrine d'exécuter cette méthode (ce callback) dès que l'entitéAdvert est modifiée. On parle d'écouter un évènement. Il existe plusieurs évènements de cycle de vie avec Doctrine, celui qui nous intéresse ici est l'évènementPreUpdate: c'est-à-dire que la méthode va être exécutée juste avant que l'entité ne soit modifiée en base de données. Voici à quoi cela ressemble :

<?php

/**
 * @ORM\PreUpdate
 */
public function updateDate()

C'est tout !

Vous pouvez dès à présent tester le comportement. Essayez de faire un petit code de test pour charger une annonce, la modifier, et l'enregistrer (avec unflush()), vous verrez que l'attribut$updatedAt va se mettre à jour automatiquement. Attention, l'évènement  updaten'est pas déclenché à la création d'une entité, mais seulement à sa modification : c'est parfaitement ce qu'on veut dans notre exemple.

Pour aller plus loin, il y a deux points qu'il vous faut savoir. D'une part, au même titre que l'évènement  PreUpdate, il existe l'évènement  PostUpdate et bien d'autres, on en dresse une liste dans le tableau suivant. Le second point est que depuis la version 2.4 de Doctrine, le callback peut prendre en argument l'évènement déclencheur : il est possible d'accéder à l'entity manager et à l'unit of work pour effectuer des opérations courantes sur l'évènements. Rendez-vous sur la documentation officielle de Doctrine pour en savoir un peu plus.

Liste des évènements de cycle de vie

Les différents évènements du cycle de vie sont récapitulés dans le tableau suivant.

Évènement 

  Description

PrePersist

L'évènement PrePersist se produit juste avant que l'EntityManager ne persiste effectivement l'entité. Concrètement, cela exécute le callback juste avant un $em->persist($entity). Il ne concerne que les entités nouvellement créées. Du coup, il y a deux conséquences : d'une part, les modifications que vous apportez à l'entité seront persistées en base de données, puisqu'elles sont effectives avant que l'EntityManager n'enregistre l'entité en base. D'autre part, vous n'avez pas accès à l'id de l'entité si celui-ci est autogénéré, car justement l'entité n'est pas encore enregistrée en base de données, et donc l'id pas encore généré.

PostPersist

L'évènement postPersist se produit juste après que l'EntityManager ait effectivement persisté l'entité. Attention, cela n'exécute pas le callback juste après le $em->persist($entity), mais juste après le $em->flush(). À l'inverse du prePersist, les modifications que vous apportez à l'entité ne seront pas persistées en base (mais seront tout de même appliquées à l'entité, attention) ; mais vous avez par contre accès à l'id qui a été généré lors du flush().

PreUpdate

L'évènement preUpdate se produit juste avant que l'EntityManager ne modifie une entité. Par modifiée, j'entends que l'entité existait déjà, que vous y avez apporté des modifications, puis un $em->flush(). Le callback sera exécuté juste avant le flush(). Attention, il faut que vous ayez modifié au moins un attribut pour que l'EntityManager génère une requête et donc déclenche cet évènement.
Vous avez accès à l'id autogénéré (car l'entité existe déjà), et vos modifications seront persistées en base de données.

PostUpdate

L'évènement postUpdate se produit juste après que l'EntityManager a effectivement modifié une entité. Vous avez accès à l'id et vos modifications ne sont pas persistées en base de données.

PreRemove

L'évènement PreRemove se produit juste avant que l'EntityManager ne supprime une entité, c'est-à-dire juste avant un $em->flush() qui précède un $em->remove($entite). Attention, soyez prudents dans cet évènement, si vous souhaitez supprimer des fichiers liés à l'entité par exemple, car à ce moment l'entité n'est pas encore effectivement supprimée, et la suppression peut être annulée en cas d'erreur dans une des opérations à effectuer dans le flush().

PostRemove

L'évènement PostRemove se produit juste après que l'EntityManager a effectivement supprimé une entité. Si vous n'avez plus accès à son id, c'est ici que vous pouvez effectuer une suppression de fichier associé par exemple.

PostLoad

L'évènement PostLoad se produit juste après que l'EntityManager a chargé une entité (ou après un $em->refresh()). Utile pour appliquer une action lors du chargement d'une entité.

 

Un autre exemple d'utilisation

Pour bien comprendre l'intérêt des évènements, je vous propose un deuxième exemple : un compteur de candidatures pour les annonces.

L'idée est la suivante : nous avons un site très fréquenté, et un petit serveur. Au lieu de récupérer le nombre de candidatures par annonce de façon dynamique à l'aide d'une requêteCOUNT(*), on décide de rajouter un attributnbApplications à notre entitéAdvert. L'enjeu maintenant est de tenir cet attribut parfaitement à jour, et surtout très facilement.

C'est là que les évènements interviennent. Si on réfléchit un peu, le processus est assez simple et systématique :

  • À chaque création d'une candidature, on doit effectuer un+1au compteur contenu dans l'entitéAdvert liée ;

  • À chaque suppression d'une candidature, on doit effectuer un-1au compteur contenu dans l'entité l'Advert liée.

Ce genre de comportement, relativement simple et systématique, est typiquement ce que nous pouvons automatiser grâce aux évènements Doctrine.

Les deux évènements qui nous intéressent ici sont donc la création et la suppression d'une candidature. Il s'agit des évènementsPrePersistetPreRemove de l'entitéApplication. Pourquoi ? Car les évènements*Updatesont déclenchés à la mise à jour d'une candidature, ce qui ne change pas notre compteur ici. Et les évènementsPost*sont déclenchés après la mise à jour effective de l'entité dans la base de données, du coup la mise à jour de notre compteur ne serait pas enregistrée.

Tout d'abord, créons notre attribut$nbApplications dans l'entitéAdvert, ainsi que des méthodes pour incrémenter et décrémenter ce compteur (en plus du getter et du setter que je ne vous remets pas ici) :

<?php
// src/OC/PlatformBundle/Entity/Advert.php

namespace OC\PlatformBundle\Entity;

class Advert
{
  /**
   * @ORM\Column(name="nb_applications", type="integer")
   */
  private $nbApplications = 0;

  public function increaseApplication()
  {
    $this->nbApplications++;
  }

  public function decreaseApplication()
  {
    $this->nbApplications--;
  }
  
  // ...
}

Ensuite, on doit définir deux callbacks dans l'entitéApplication pour mettre à jour le compteur de l'entitéAdvert liée. Notez bien que nos évènements concernent bien l'entitéApplication, et non l'entitéAdvert ! Voici comment on pourrait faire :

<?php
// src/OC/PlatformBundle/Entity/Application.php

namespace OC\PlatformBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Table(name="oc_application")
 * @ORM\Entity(repositoryClass="OC\PlatformBundle\Entity\ApplicationRepository")
 * @ORM\HasLifecycleCallbacks()
 */
class Application
{
  /**
   * @ORM\PrePersist
   */
  public function increase()
  {
    $this->getAdvert()->increaseApplication();
  }

  /**
   * @ORM\PreRemove
   */
  public function decrease()
  {
    $this->getAdvert()->decreaseApplication();
  }

  // ...
}

Cette solution est possible car nous avons une relation entre ces deux entitésApplication etAdvert, il est donc possible d'accéder à l'annonce depuis une candidature.

Utiliser des services pour écouter les évènements Doctrine

Les callbacks définis directement dans les entités sont pratiques car simple à mettre en place. Quelques petites annotations et le tour est joué. Cependant, leurs limites sont vite atteintes car, comme toute méthode au sein d'une entité, les callbacks n'ont accès à aucune information de l'extérieur.

En effet, imaginez qu'on veuille mettre en place un système pour envoyer un email à chaque création d'une candidature. Dans ce cas, le code qui est exécuté à chaque création d'entité a besoin du service mailer afin d'envoyer des emails, or ce n'est pas possible depuis une entité.

Heureusement, il est possible de dire à Doctrine d'exécuter des services Symfony pour chaque évènement du cycle de vie des entités. L'idée est vraiment la même, mais au lieu d'une méthode callback dans notre entité, on a un service défini hors de notre entité. La seule différence est la syntaxe bien sûr.

Il y a tout de même un point qui diffère des callbacks, c'est que nos services seront exécutés pour un évènement (PostPersist par exemple) concernant toutes nos entités, et non attaché à une seule entité. Si vous voulez effectuer votre action seulement pour les entitésAdvert, il faut alors vérifier le type d'entité qui sera en argument de votre service. L'avantage est que du coup vous pouvez facilement effectuer une action commune à toutes vos entités.

Avant de vous montrer la syntaxe, prenons le temps pour voir comment organiser nos services ici. La fonctionnalité que je vous propose est l'envoi d'un e-mail à chaque fois qu'une candidature est reçue. Dans cette phrase se cachent deux parties : d'une part l'envoi d'un e-mail, et d'autre part l'aspect systématique à chaque candidature reçue. Pour bien organiser notre code, nous allons donc faire deux services : l'un pour envoyer l'e-mail, et l'autre qui sera appelé par Doctrine, ce sera lui le callback à proprement parler. Pourquoi séparer ? Car l'envoi d'un e-mail de notification est quelque chose que nous voudrons peut-être appeler à un autre moment qu'un évènement Doctrine. Imaginons par exemple une fonctionnalité permettant de renvoyer cet e-mail après quelques jours sans réponse, les possibilités sont nombreuses. Dans tous les cas, nous avons bien deux missions distinctes, et donc deux services distincts.

Voici donc d'abord ma proposition pour le service qui envoie les e-mails. Ce service dépend du service mailer, et la méthode en elle-même a besoin d'une instance deApplication  en argument.

<?php
// src/OC/PlatformBundle/Email/ApplicationMailer.php

namespace OC\PlatformBundle\Email;

use OC\PlatformBundle\Entity\Application;

class ApplicationMailer
{
  /**
   * @var \Swift_Mailer
   */
  private $mailer;

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

  public function sendNewNotification(Application $application)
  {
    $message = new \Swift_Message(
      'Nouvelle candidature',
      'Vous avez reçu une nouvelle candidature.'
    );

    $message
      ->addTo($application->getAdvert()->getAuthor()) // Ici bien sûr il faudrait un attribut "email", j'utilise "author" à la place
      ->addFrom('admin@votresite.com')
    ;

    $this->mailer->send($message);
  }
}

Ainsi que sa configuration :

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

services:
  oc_platform.email.application_mailer:
      class: OC\PlatformBundle\Email\ApplicationMailer
      arguments:
          - "@mailer"

Le service contient une méthode, qui se contente d'envoyer un petit email à l'adresse contenue dans l'annonce liée à la candidature passée en argument. On pourrait également rajouter une méthode pour envoyer un e-mail de confirmation au candidat qui a créé la candidature, etc. Tout cela n'a en fait rien à voir avec notre sujet, les évènements Doctrine !

Passons donc au vif du sujet, voici enfin le service callback, celui qui sera appelé par Doctrine lorsque les évènements configurés seront déclenchés.

<?php
// src/OC/PlatformBundle/DoctrineListener/ApplicationCreationListener.php

namespace OC\PlatformBundle\DoctrineListener;

use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use OC\PlatformBundle\Email\ApplicationMailer;
use OC\PlatformBundle\Entity\Application;

class ApplicationCreationListener
{
  /**
   * @var ApplicationMailer
   */
  private $applicationMailer;

  public function __construct(ApplicationMailer $applicationMailer)
  {
    $this->applicationMailer = $applicationMailer;
  }

  public function postPersist(LifecycleEventArgs $args)
  {
    $entity = $args->getObject();

    // On ne veut envoyer un email que pour les entités Application
    if (!$entity instanceof Application) {
      return;
    }

    $this->applicationMailer->sendNewNotification($entity);
  }
}

Notez que j'ai nommé la méthode du service du nom de l'évènement que nous allons écouter. Nous ferons effectivement le lien avec l'évènement via la configuration du service, mais la méthode doit respecter le même nom que l'évènement.

Ensuite, il y a deux points à retenir sur la syntaxe :

  • Le seul argument qui est donné à votre méthode est un objet LifecycleEventArgs. Il offre deux méthodes : getObject et getObjectManager. La première, getObject, retourne l'entité sur laquelle l'évènement est en train de se produire. La seconde, getObjectManager, retourne l'EntityManager nécessaire pour persister ou supprimer de nouvelles entités que vous pourriez gérer, nous ne nous en servons pas ici ;

  • Comme je l'avais mentionné, la méthode sera exécutée pour l'évènement PostPersist de toutes vos entités. Dans notre cas, comme souvent, nous ne voulons envoyer l'email que lorsqu'une entité en particulier est ajoutée, iciApplication. D'où le if pour vérifier le type d'entité auquel on a affaire. Si le callback est appelé sur une entité qui ne vous intéresse pas ici, sortez simplement de la méthode sans rien faire, c'est le rôle dureturn ligne 28.

Maintenant que notre objet est prêt, il faut en faire un service et dire à Doctrine qu'il doit être exécuté pour tous les évènements PostPersist. Voici la syntaxe à respecter :

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

services:
  oc_platform.doctrine_listener.application_creation:
    class: OC\PlatformBundle\DoctrineListener\ApplicationCreationListener
    arguments:
      - "@oc_platform.email.application_mailer"
    tags:
      - { name: doctrine.event_listener, event: postPersist }

La définition du service n'a rien de nouveau par rapport à ce que nous avons vu sur le chapitre dédié aux services.

La nouveauté est par contre la section tag. Sachez simplement que c'est ce tag qui permet au conteneur de services de dire à Doctrine que ce service doit être exécuté pour les évènements PostPersist. Pas de panique, je vous explique en détail le fonctionnement des tags de services dans un chapitre de la prochaine partie, mais pour l'instant connaître cette syntaxe vous permet de les utiliser dans ce cadre.

Bien entendu, vous pouvez écouter n'importe quel évènement avec cette syntaxe, il vous suffit de modifier l'attributevent: PostPersist du tag.

Essayons nos évènements

Si vous avez le même contrôleur que moi, l'actionaddAction enregistre une annonce ainsi que deux candidature en base de données. Vérifions donc que nos deux comportements fonctionnent bien : l'incrémentation d'un compteur de candidatures d'une part, et l'envoi d'e-mail d'autre part. Je vous invite donc à vous rendre sur la page /platform/add.

Vous aurez sans doute cette erreur Address in mailbox given [Alexandre] does not comply with RFC 2822, 3.6.2, c'est parce que nous avons utilisé le nom au lieu de l'adresse e-mail pour envoyer l'e-mail justement. Si vous voulez la régler, c'est un bon exercice pour vous que de rajouter un attribut email et de l'utiliser dans notre service. Je ne le ferai pas ici.

Voyons plutôt ce que cette erreur nous donne comme information, je vous reproduis une partie de la stack trace sur la figure suivante.

Stack trace de notre erreur
Stack trace de notre erreur

Cette stack trace est intéressante car elle nous montre le cheminement complet pour arriver jusqu'à notre service d'envoi d'e-mail. On peut y constater que c'est à partir de notre flush depuis le contrôleur que Doctrine déclenche l'évènement postPersist, qui lui va appeler notre service comme convenu. Je vous montre cela car c'est un bon réflexe de bien lire la stack trace en cas d'erreur : en comprenant bien le cheminement des opérations, cela nous aide à comprendre et donc résoudre les erreurs.

Nous venons donc de valider que notre envoi d'e-mail à la création d'une candidature est OK. Maintenant, vérifions le compteur de candidature. Pour cela, ouvrez le profiler sur l'onglet des requêtes SQL, je vous en reproduis un extrait sur la figure suivante.

Les requêtes SQL générées pour insérer l'annonce et les deux candidatures
Les requêtes SQL générées pour insérer l'annonce et les deux candidatures

Je vous ai encadré en vert la valeur de notre attributnbApplicationssur l'annonce enregistrée en base de données. Sa valeur est de 2, ce qui est bien le nombre de candidatures que nous enregistrons en même temps : parfait !

Les extensions Doctrine

L'intérêt des extensions Doctrine

Dans la gestion des entités d'un projet, il y a des comportements assez communs que vous souhaiterez implémenter.

Par exemple, il est très classique de vouloir générer des slugs pour nos annonces, pour des sujets d'un forum, etc. Un slug est une version simplifiée, compatible avec les URL, d'un autre attribut, souvent un titre. Par exemple le slug du titre "Recherche développeur !" serait "recherche-developpeur", notez que l'espace a été remplacé par un tiret, le point d'exclamation supprimé.

Plutôt que de réinventer tout le comportement nous-mêmes, nous allons utiliser les extensions Doctrine ! Doctrine est en effet très flexible, et la communauté a déjà créé une série d'extensions très pratiques afin de vous aider avec les tâches usuelles liées aux entités. À l'image des évènements, utiliser ces extensions évite de se répéter au sein de votre application Symfony : c'est la philosophie DRY.

Installer le StofDoctrineExtensionBundle

Un bundle en particulier permet d'intégrer différentes extensions Doctrine dans un projet Symfony, il s'agit de StofDoctrineExtensionsBundle. Commençons par l'installer avec Composer, rajoutez cette dépendance dans votrecomposer.json  et exécutez uncomposer update :

// composer.json

"require": {
  "stof/doctrine-extensions-bundle": "^1.2.2"
}

Ce bundle intègre la bibliothèque DoctrineExtensions sous-jacente, qui est celle qui inclut réellement les extensions Doctrine.

N'oubliez pas d'enregistrer le bundle dans le noyau :

<?php
// app/AppKernel.php

public function registerBundles()
{
  return array(
    // …
    new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),
    // …
  );
}

Voilà le bundle est installé, il faut maintenant activer telle ou telle extension.

Utiliser une extension : l'exemple de Sluggable

L'utilisation des différentes extensions est très simple grâce à la flexibilité de Doctrine et au bundle pour Symfony. Voici par exemple l'utilisation de l'extension Sluggable, qui permet de définir très facilement un attributslugdans une entité : le slug sera automatiquement généré !

Tout d'abord, il faut activer l'extension Sluggable, il faut pour cela configurer le bundle via le fichier de configurationconfig.yml. Rajoutez donc cette section :

# app/config/config.yml

# Stof\DoctrineExtensionsBundle configuration
stof_doctrine_extensions:
    orm:
        default:
            sluggable: true

Cela va activer l'extension Sluggable. De la même manière, vous pourrez activer les autres extensions en les rajoutant à la suite.

Concrètement, l'utilisation des extensions se fait grâce à de judicieuses annotations. Vous l'aurez deviné, pour l'extension Sluggable, l'annotation est tout simplementSlug. En l'occurrence, il faut ajouter un nouvel attributslug(le nom est arbitraire) dans votre entité, sur lequel nous mettrons l'annotation. Voici un exemple dans notre entitéAdvert :

<?php
// src/OC/PlatformBundle/Entity/Advert.php

namespace OC\PlatformBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
// N'oubliez pas ce use :
use Gedmo\Mapping\Annotation as Gedmo;

/**
 * @ORM\Entity
 */
class Advert
{
  // …

  /**
   * @Gedmo\Slug(fields={"title"})
   * @ORM\Column(name="slug", type="string", length=255, unique=true)
   */
  private $slug;

  // …
}

Dans un premier temps, vous avez l'habitude, on utilise le namespace de l'annotation, iciGedmo\Mapping\Annotation.

Ensuite, l'annotation Slug s'applique très simplement sur un attribut qui va contenir le slug. L'optionfieldspermet de définir le ou les attributs à partir desquels le slug sera généré : ici le titre uniquement. Mais vous pouvez en indiquer plusieurs en les séparant par des virgules.

C'est tout ! Vous pouvez dès à présent tester le nouveau comportement de votre entité. Créez une entité avec un titre de test, et enregistrez-la : son attributslugsera automatiquement rempli ! Par exemple :

<?php
// Dans un contrôleur

public function testAction()
{
  $advert = new Advert();
  $advert->setTitle("Recherche développeur !");

  $em = $this->getDoctrine()->getManager();
  $em->persist($advert);
  $em->flush(); // C'est à ce moment qu'est généré le slug

  return new Response('Slug généré : '.$advert->getSlug());
  // Affiche « Slug généré : recherche-developpeur »
}

L'attributslugest rempli automatiquement par le bundle. Ce dernier utilise en réalité tout simplement les évènements DoctrinePrePersistetPreUpdate, qui permettent d'intervenir juste avant l'enregistrement et la modification de l'entité comme on l'a vu plus haut.

Vous savez maintenant utiliser l'extension Doctrine Sluggable ! Voyons les autres extensions disponibles.

Liste des extensions Doctrine

Voici la liste des principales extensions actuellement disponibles, ainsi que leur description et des liens vers la documentation pour vous permettre de les implémenter dans votre projet.

Extension

Description

Tree

L'extension Tree automatise la gestion des arbres et ajoute des méthodes spécifiques au repository. Les arbres sont une représentation d'entités avec des liens type parents-enfants, utiles pour les catégories d'un forum par exemple.

Translatable

L'extension Translatable offre une solution aisée pour traduire des attributs spécifiques de vos entités dans différents langages. De plus, elle charge automatiquement les traductions pour la locale courante.

Sluggable

L'extension Sluggable permet de générer automatiquement un slug à partir d'attributs spécifiés.

Timestampable

L'extension Timestampable automatise la mise à jour d'attributs de typedatedans vos entités. Vous pouvez définir la mise à jour d'un attribut à la création et/ou à la modification, ou même à la modification d'un attribut particulier. Vous l'aurez compris, cette extension fait exactement la même chose que ce qu'on a fait dans le paragraphe précédent sur les évènements Doctrine (mise à jour de la date à chaque modification), et en mieux !

Blameable

L'extension Blameable permet d'assigner l'utilisateur courant (l'entité elle-même, ou alors juste le nom d'utilisateur) dans un attribut d'une autre entité. Utile pour notre entitéAdvert par exemple, laquelle pourrait être reliée à un utilisateur.

Loggable

L'extension Loggable permet de conserver les différentes versions de vos entités, et offre des outils de gestion des versions.

Sortable

L'extension Sortable permet de gérer des entités ordonnées, c'est-à-dire avec un ordre précis.

Softdeleteable

L'extension SoftDeleteable permet de « soft-supprimer » des entités, c'est-à-dire de ne pas les supprimer réellement, juste mettre un de leurs attributs àtruepour les différencier. L'extension permet également de les filtrer lors des SELECT, pour ne pas utiliser des entités « soft-supprimées ».

Uploadable

L'extension Uploadable offre des outils pour gérer l'enregistrement de fichiers associés avec des entités. Elle inclut la gestion automatique des déplacements et des suppressions des fichiers.

IpTraceable

L'extension IpTraceable permet d'assigner l'ip de l'utilisateur courant à un attribut.

Si vous n'avez pas besoin aujourd'hui de tous ces comportements, ayez-les en tête pour le jour où vous en trouverez l'utilité. Autant ne pas réinventer la roue si elle existe déjà ! ;)

Pour conclure

Ce chapitre touche à sa fin et marque la fin de la partie théorique sur Doctrine. Vous avez maintenant tous les outils pour gérer vos entités, et donc votre base de données. Surtout, n'hésitez pas à bien pratiquer, car c'est une partie qui implique de nombreuses notions : sans entraînement, pas de succès !

Le prochain chapitre est un TP permettant de mettre en pratique la plupart des notions abordées dans cette partie.

En résumé

  • Les évènements permettent de centraliser du code répétitif, afin de systématiser leur exécution et de réduire la duplication de code.

  • Plusieurs évènements jalonnent la vie d'une entité, afin de pouvoir exécuter une fonction aux endroits désirés.

  • Les extensions permettent de reproduire des comportements communs dans une application, afin d'éviter de réinventer la roue.

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

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