CMS en Symfony : le routing
Tags: symfony, webdev, cms, opensource
Published: 2022-02-28 23:22:59

Maintenant qu'on a regardé un peu la partie fonctionnelle, on va faire un peu de technique.

L'une des fonctionnalités à laquelle chaque utilisateur de CMS s'attend à trouver, est la gestion du "routing" : "je crée une page "Actualités" et je m'attends à pouvoir l'afficher si je tape "www.monsupersite.com/actualites".

2 choses à gérer :
- pouvoir créer des pages
- créer un "slug" pour chaque page
- faire en sorte que chaque page soit automatiquement enregistrée dans les routes du CMS pour les rendre disponibles.

On va donc commencer par ... la fin (je laisse la structure des pages pour le prochain épisode !).

Evidemment, notre framework préféré nous donne la possibilité de faire ça en ... 5 minutes chrono !

# Routing as a Service.

Quand on utilise Symfony, on a l'habitude d'utiliser:
- soit le fichier yaml de routing (basique, mais ça sert parfois, valable aussi en XML ou en PHP)
- soit les annotations dans les controller (ou les attributs maintenant, hein les dinosaures ! )

Evidemment, que ce soit l'un ou l'autre, ça ne répond pas à notre besoin : on a besoin de quelque chose de dynamique qui va cherche la liste de nos pages en base de données et qui les "charge".

Hé bien c'est exactement ce que Symfony nous propose !

On appelle ça un "route loader". Pour les curieux (et parce que je vous invite à lire la doc !) c'est [ici](https://symfony.com/doc/current/routing/custom_route_loader.html).

Nous, on a une contrainte supplémentaire : certes, on crée un CMS, mais on veut aussi pouvoir faire du Symfony "natif". En bref, si je veux créer une route "hors CMS", je dois pouvoir le faire.

Qu'à cela ne tienne, on a aussi cette possibilité.

## Le fichier de définition des routes

Dans Symfony, le fichier de base pour définir les routes est le fichier "config/routes.yaml".

Par défaut, dans une application Symfony (version 6) il doit ressembler à quelque chose du genre :

```yaml
controllers:
    resource: ../src/Controller/
    type: annotation

kernel:
    resource: ../src/Kernel.php
    type: annotation
```

On va ajouter quelques lignes en haut du fichier tel que:

```yaml
cms_routes:
    resource: .
    type: cms

controllers:
    resource: ../src/Controller/
    type: annotation

kernel:
    resource: ../src/Kernel.php
    type: annotation
```

On ajoute un nouveau "groupe de routes" tel que:
- cms_routes : nom arbitraire. Mettez "toto24" si ça vous fait plaisir ;)
- resource: . : en fait on ignore ce paramètre, d'où le point
- type: cms : heu c'est pas natif ce type ? Non, c'est pas natif ! C'est là où on intervient : on va aller créer notre loader, responsable de ce type de route.

Let's go.

## A votre service :-)

Pour cela dans le répertoire "src" à la racine de votre projet, créez un répertoire "Routing", puis créez dans ce dernier une classe "CmsRouteLoader".

Voici le code:

```php

namespace Routing;

use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection

class CmsRouteLoader extends Loader
{
    private $isLoaded = false;

    /**
     * @var EntityManagerInterface
     */
    private EntityManagerInterface $entityManager;

    public function __construct(EntityManagerInterface $entityManager)
    {

        $this->entityManager = $entityManager;
    }

    public function load($resource, string $type = null): RouteCollection
    {
        if (true === $this->isLoaded) {
            throw new \RuntimeException('Do not add the "CMS" loader twice');
        }

        $routes = new RouteCollection();

        $nodes = $this->entityManager->getRepository(Page::class)->findAll();
        foreach ($nodes as $node) {
            $defaults = [
                '_controller' => 'App\Controller\CmsController::slugs'
            ];
            $route = new Route($node->getPath(), $defaults);
            $routes->add('cms_slugs_' . $node->getId(), $route);
        }

        $this->isLoaded = true;
        return $routes;
    }

    public function supports($resource, string $type = null)
    {
        return 'cms' === $type;
    }

}
```

Quelques explications (notez que notre classe ressemble beaucoup à la documentation):
- notre classe étend Loader, ce qui va nous permettre de définir une méthode "supports". Vous remarquerez qu'on retourne true si le type du groupe est "cms"....ça vous rappelle le fichier de config ?
- ici la classe est un peu simplifiée, mais en bref, on charge les pages de la base, et pour chaque page on crée dynamiquement une route qu'on ajoute à son tour dans une collection de routes qu'on retourne.

Enfin, on enregistre notre service dans les services de l'application en modifiant le fichier config/services.yaml comme suit:

```yaml
# config/services.yaml
services:
    # ...

    App\Routing\CmsRouteLoader:
        tags: [routing.loader]
```

On fera quelques modifications dans le prochain chapitre pour mettre en place nos structures pour enregistrer nos pages et autres dans la base de données.

Il manque aussi le controller 'App\Controller\CmsController' mais pour l'instant on insiste pas, on s'y intéressera plus tard. 

Bientôt la suite !