Série – Créez un blog avec Symfony

#4 Doctrine et les LifecycleCallbacks

Automatisez facilement vos actions en base de données

9 oct. 2020 à 19:40 – 6min


Table des matières

Bienvenue dans ce nouvel article de notre série consacrée à la construction d'un blog ! Aujourd'hui, nous allons découvrir l'une des forces de Doctrine, les LifecycleCallbacks. Pour illustrer ceci, nous allons faire en sorte d'attribuer un slug à nos articles, ainsi qu'une date de mise à jour qui sera automatiquement définie. Vous allez le voir, ces nouvelles fonctions risquent de grandement vous simplifier la vie !

Ajoutons des slugs à nos articles

Nous voudrions que nos articles soient accessibles via des URLs compréhensibles par les humains (et appréciées par les moteurs de recherche). Pour cela, nous allons attribuer à chaque article un slug, c'est-à-dire une chaîne de caractères sans caractères spéciaux, et donc utilisable dans une URL. De plus, ce slug nous servira d'identifiant unique pour nos articles. Par exemple, un article intitulé "j'adore Symfony !" aura pour slug "j-adore-symfony", et on pourra donc y accéder via /articles/j-adore-symfony. 

Commençons par ajouter ce nouveau champ slug à notre entité Article : 

symfony console make:entity Article

Ajoutez un champ "slug" de type string, et profitez-en également pour ajouter un champ updatedAt et createdAt de type datetime que nous utiliserons par la suite, puis effectuez la migration : 

symfony console make:migration && symfony console doctrine:migration:migrate -n

Nous allons maintenant utiliser le composant Symfony string afin de générer automatiquement nos slugs. Sachez par ailleurs qu'il est assez facile de le faire "à la main" avec un peu de regex, mais nous allons ici choisir la solution de facilité. 

symfony console require string

La prochaine étape est d'indiquer à Doctrine que notre nouveau champ slug doit être unique, afin de pouvoir être utilisé comme un identifiant pour nos articles. Pour cela, rendez-vous dans la classe Article, puis ajouter l'annotation @UniqueEntity("slug") à votre entité, ainsi que le paramètre unique=true à votre champ slug :

Enfin, effectuez une nouvelle migration.

Nous allons maintenant faire en sorte de pouvoir accéder à nos articles via le slug. Pour cela, si ce n'est pas déjà fait, créez un nouveau controller qui permettra de gérer la logique de nos articles de blog. Vous pouvez le faire via la commande suivante : 

symfony console make:controller ArticleController

Ensuite, nous allons définir une nouvelle fonction capable de lister nos articles, à laquelle on attribuera la route /articles : 

Puis définir nos templates Twig associés : 

Vous remarquerez que nous avons ajouté un constructeur dans notre controller, afin d'injecter notre repository Article. Ainsi, comme toutes les fonctions de ce controller risquent d'avoir besoin de ce repository, nous n'aurons pas besoin de l'instancier à chaque fois. Cela permet de rendre votre code plus lisible. Par ailleurs, vous pouvez également remarquer que nous passons un paramètre Article à la fonction show, et un non une chaine de caractère slug. En réalité, Symfony va se charger de résoudre automatiquement notre entité en fonction du slug passé dans l'URL, grâce au ParamConverter. Cela vous évite d'avoir à chercher manuellement l'article correspondant au slug donné dans le repository, Symfony le fait pour vous !

Les LifecycleCallbacks de Doctrine

Ce que nous voulons maintenant, c'est faire en sorte que les champs slug, updatedAt et createdAt soient définis automatiquement dès que nous créons un nouvel article ou que nous en mettons un existant à jour, plutôt que d'avoir à les définir manuellement. Pour ce faire, nous allons utiliser les LifecycleCallbacks de Doctrine. 

Doctrine nous permet de spécifier certaines actions à effectuer lors du cycle de vie d'une entité, via des annotations pour les cas les plus simples comme dans l'exemple suivant : 

Dans cet exemple, nous faisons en sorte que la date de mise à jour de notre article soit définie à chaque fois que nous allons sauvegarder de nouveaux changements. Cette fonction sera automatiquement appellée par Doctrine avant d'enregistrer l'entité en base de données (pre-update, avant la mise à jour). Sachez qu'il existe d'autres callbacks de cycle de vie, comme PrePersist (avant d'enregistrer pour la première fois l'entité), PostUpdate (après une mise à jour), etc. Vous pouvez tous les retrouver dans la documentation officielle de Doctrine.

Créons maintenant une nouvelle fonction qui va générer notre slug :

Vous noterez peut-être l'utilisation des LifecycleCallbacks comme nous l'avons vu précédemment ne conviendra pas à la génération de notre slug. En effet, cette fonction nécessite l'injection de l'interface Slugger, ce qui n'est pas possible via les annotations. Si vous essayez par vous-même d'ajouter une annotation @PrePersist() à cette fonction vous verrez que Symfony vous renverra une erreur. C'est tout à fait normal. Pour contourner ce problème, nous allons créer un listener Doctrine. 

Ce listener se comporte de la même manière que les évènements Doctrine vus précédemment. Mais ici, nous avons encore plus de contrôle sur le cycle de vie de notre entité, puisque nous pouvons y injecter notre classe Slugger : 

Comme vous pouvez le constater, nous appelons ici les méthodes preUpdate et prePersit, ce qui implique que notre slug sera redéfini à chaque mise à jour de l'article. En réalité, je vous encourage grandement à définir votre slug (comme pour tout identifiant unique), seulement lors de l'insertion en base de données et pas après chaque mise à jour. Dans notre exemple de blog, la mise à jour du slug (et donc de l'URL), après la mise en ligne d'un article aura des conséquences importantes, puisque les utilisateurs (mais aussi les moteurs de recherche), ne seront pas assurés de disposer d'un lien valide.  

Il nous reste encore une chose à faire pour que tout fonctionne. Pour l'instant, nous n'avons jamais indiqué à Doctrine que nous venons de créer un nouvel EntityListener. C'est donc dans le fichier service.yaml qu'il vous faudra le faire : 

Voilà, si vous créez un nouvel article depuis votre interface d'administration EasyAdmin, vous verrez que tout fonctionne ! Le slug, la date de création ainsi que la date de mise à jour sont automatiquement définies !