Symfony

SYMFONY 4 SLUGGABLE

D

Dimanche 31 mars 2019

Mis à jour le dimanche 31 mars 2019

Symfony 4 sluggable

La documentation concernant l'extension sluggable de Doctrine est éclatée à plusieurs endroits différents.
Voici comment installer et utiliser l'extension sluggable de Doctrine.

Installer l'extension Sluggable

Pour installer l'extension Sluggable, il faut installer le bundle stof/doctrine-extensions-bundle qui permet d'implémenter les extensions Doctrine.
Exécuter cette commande pour installer le bundle stof/doctrine-extensions-bundle :
composer require stof/doctrine-extensions-bundle
Entrez y et appuyez sur Entrée quand la console vous demandera si vous souhaitez exécuter les recipes :
Symfony operations: 1 recipe (ff1cacb136314e5c284c704cc082a47e)
  -  WARNING  stof/doctrine-extensions-bundle (>=1.2): From github.com/symfony/recipes-contrib:master
    The recipe for this package comes from the "contrib" repository, which is open to community contributions.
    Review the recipe at https://github.com/symfony/recipes-contrib/tree/master/stof/doctrine-extensions-bundle/1.2

    Do you want to execute this recipe?
    [y] Yes
    [n] No
    [a] Yes for all packages, only for the current installation session
    [p] Yes permanently, never ask again for this project
    (defaults to n): y
Voilà, le bundle stof/doctrine-extensions-bundle est maintenant installé. L'extension Sluggable est donc maintenant utilisable dans votre projet. Voyons comment l'implémenter.

Activer l'extension Sluggable

Lorsque vous avez répondu y, Symfony a crée pour vous le fichier de configuration du bundle stof/doctrine-extensions-bundle dans config/packages/stof_doctrine_extensions.yaml.
Vous devriez avoir ceci :
# Read the documentation: https://symfony.com/doc/current/bundles/StofDoctrineExtensionsBundle/index.html
# See the official DoctrineExtensions documentation for more details: https://github.com/Atlantic18/DoctrineExtensions/tree/master/doc/
stof_doctrine_extensions:
    default_locale: en_US
Rajoutez-y les éléments suivants pour obtenir ceci :
# Read the documentation: https://symfony.com/doc/current/bundles/StofDoctrineExtensionsBundle/index.html
# See the official DoctrineExtensions documentation for more details: https://github.com/Atlantic18/DoctrineExtensions/tree/master/doc/
stof_doctrine_extensions:
    default_locale: en_US
    orm:
        default: # Ici, laissez "default" sauf si vous avez plusieurs connexions différentes à vos bases de données et dans ce cas, mettez le nom de votre connexion
            sluggable: true
Voilà, l'extension Sluggable est maintenant activée et utilisable dans votre projet. Voyons comment l'utiliser.

Utiliser l'extension Sluggable

Il ne vous reste plus qu'à utiliser cette extension Sluggable dans vos entités.
Prenons l'exemple d'une entité appelée Post qui contient les postes d'un blog avec leurs titres, leurs descriptions et leurs date de publication :
<?php

namespace App\Entity;

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

/**
 * @ORM\Entity(repositoryClass="App\Repository\PostRepository")
 */
class Post
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    private $titre;

    /**
     * @ORM\Column(type="string", length=8191, nullable=true)
     */
    private $description;

    /**
     * @ORM\Column(type="datetime", nullable=true)
     */
    private $dateDePublication;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getTitre(): ?string
    {
        return $this->titre;
    }

    public function setTitre(?string $titre): self
    {
        $this->titre = $titre;

        return $this;
    }

    public function getDescription(): ?string
    {
        return $this->description;
    }

    public function setDescription(?string $description): self
    {
        $this->description = $description;

        return $this;
    }

    public function getDateDePublication(): ?\DateTimeInterface
    {
        return $this->dateDePublication;
    }

    public function setDateDePublication(?\DateTimeInterface $dateDePublication): self
    {
        $this->dateDePublication = $dateDePublication;

        return $this;
    }
}

Pour ajouter à cette entité un champ slug qui contiendra le champ titre sluggé, il faut rajouter un champ unique qui contiendra le slug (ici, je l'ai appeler tout bêtement slug mais vous pouvez le nommer comme bon vous semble) :
// ...
class Post
{
    // ...

    /**
     * @ORM\Column(type="string", length=255, unique=true)
     */
    private $slug;

    public function getSlug(): ?string
    {
        return $this->slug;
    }

    public function setSlug(string $slug): self
    {
        $this->slug = $slug;

        return $this;
    }
}
Ensuite, il faut rajouter l'utilisation du namespace Gedmo comme ceci :
use Gedmo\Mapping\Annotation as Gedmo;
Et rajouter l'annotation @Gedmo\Slug(fields={"titre"}) sur le champ qui contient le slug :
@Gedmo\Slug(fields={"titre"})
Nous nous retrouvons avec notre entité Post comme ceci :
<?php

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;

/**
 * @ORM\Entity(repositoryClass="App\Repository\PostRepository")
 */
class Post
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    private $titre;

    /**
     * @ORM\Column(type="string", length=8191, nullable=true)
     */
    private $description;

    /**
     * @ORM\Column(type="datetime", nullable=true)
     */
    private $dateDePublication;

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

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getTitre(): ?string
    {
        return $this->titre;
    }

    public function setTitre(?string $titre): self
    {
        $this->titre = $titre;

        return $this;
    }

    public function getDescription(): ?string
    {
        return $this->description;
    }

    public function setDescription(?string $description): self
    {
        $this->description = $description;

        return $this;
    }

    public function getDateDePublication(): ?\DateTimeInterface
    {
        return $this->dateDePublication;
    }

    public function setDateDePublication(?\DateTimeInterface $dateDePublication): self
    {
        $this->dateDePublication = $dateDePublication;

        return $this;
    }

    public function getSlug(): ?string
    {
        return $this->slug;
    }

    public function setSlug(string $slug): self
    {
        $this->slug = $slug;

        return $this;
    }
}
Et voilà, l'entité Post contient un champ nommé slug qui contient le titre sluggé.

PS : A cette étape, n'oubliez pas de mettre à jour le schéma de votre base de données en exécutant les commandes php bin/console make:migration et php bin/console doctrine:migrations:migrate.

Utiliser les slug au lieu des id dans les liens

Maintenant, avouons qu'il serait plus user-friendly d'avoir des liens de la forme /blog/post/comment-cultiver-des-tomates plutôt que de la forme /blog/post/18. Pour cela, dans nos contrôleurs, au lieu d'utiliser l'attribut {id} de notre entité pour l'identifier, nous allons utiliser son nouvel attribut {slug}.
Nous avions ceci avant :
<?php

// ...

class BlogController extends AbstractController
{
    
    // ...

    /**
     * @Route("/{id}", name="blog_post_show", methods={"GET"})
     */
    public function show(Post $post): Response
    {
        return $this->render('blog/post/show.html.twig', [
            'post' => $post,
        ]);
    }
}
Nous avons maintenant ceci :
<?php

// ...

class BlogController extends AbstractController
{
    
    // ...

    /**
     * @Route("/{slug}", name="blog_post_show", methods={"GET"})
     */
    public function show(Post $post): Response
    {
        return $this->render('blog/post/show.html.twig', [
            'post' => $post,
        ]);
    }
}

Avant, dans nos vues Twig, nous générions les liens comme ceci :
<a href="{{ path('blog_post_show', {'id': post.id}) }}">Voir le post</a>
Maintenant, nous les générons comme ceci :
<a href="{{ path('blog_post_show', {'slug': post.slug}) }}">Voir le post</a>
Même chose dans nos controlleurs, avant, nous générions des liens/redirections comme ceci :
$this->redirectToRoute('blog_post_show', ['id' => $post->getId()])
Maintenant, nous les générons comme ceci :
$this->redirectToRoute('blog_post_show', ['slug' => $post->getSlug()])

Et voilà, vous savez maintenant utiliser l'extension Sluggable de Doctrine !


Une erreur ? une question ? une critique ? une faute ? un conseil ? ou tout simplement un merci ?

Lâche ton commentaire


Attila Le dimanche 28 juillet 2019 à 23:01:28
Un grand merci pour ces explications claires qui m'ont fait gagner bien du temps

shengovou Le jeudi 15 août 2019 à 16:09:49
le bundle stof génère une vingtaine de warning de dépréciation, sur symfony 4.3.3, est-ce qu'il est toujours maintenu ?

David Le samedi 17 août 2019 à 12:11:38
@shengovou, quels sont les dépréciations qui sont émises ?

Lemzo Le mardi 28 janvier 2020 à 11:26:09
Merci pour cet article, très riche.

semi Le samedi 19 mars 2022 à 17:47:16
j'ai cette erreur "An exception has been thrown during the rendering of a template ("Parameter "slug" for route "appartement_show" must match "[^/]++" ("" given) to generate a corresponding URL.")."

David Le lundi 21 mars 2022 à 10:58:00
@semi, pour comprendre un peu mieux l'erreur, pouvez-vous dire quel lien avez-vous appelé pour obtenir cette erreur et pouvez-vous me donner l'annotatiion "@Route(...)" que vous avez utilisé ?

Olivier Le vendredi 25 mars 2022 à 18:50:31
Bonjour, je suis sous Symfony 5.3 et visiblement une fois la dépendance installée et configurée telle que précisé, le 'use Gedmo\Mapping\Annotation ne fonctionne pas.

Davi Le lundi 28 mars 2022 à 07:42:51
@Olivier, alors il se peut qu'il y ai des petites différences pour Symfony 5 car le tuto est fait pour Symfony 4. Il faudrait regarder directement sur la documentation du bundle pour voir s'il n'y a pas des adaptations à faire pour Symfony 5