Accueil /  Blog / Symfony / Comment re-générer le sitemap au changement d'une entité

Comment re-générer le sitemap au changement d'une entité

Publié le mardi 9 novembre 2021

Comment re-générer votre sitemap en asynchrone lorsque les modifications apportées en back-office l'impactent.

Pré-requis

Ce snippet s'appuie sur le package PrestaSitemapBundle.
Vous pouvez retrouver les détails concernant cette librairie sur le billet associé.

Et notamment sur une intégration possible entre ce bundle et le composant Messenger de Symfony.

Cas d'usage

Dans un sitemap, on retrouve 2 catégories d'URLs :

Les URLs statiques:
Elles pointent vers des routes simples : /contact, /about, ...
Elles ne sont enregistrées qu'une seule fois.

Les URLs dynamiques:
Elles pointent vers des routes variabilisées : /blog/{date}/{slug}, /jobs/{slug}, ...
Elles sont enregistrées autant de fois qu'il y a de jeux de variables.

Les URLs dynamiques sont, par définition, sont amenées à changer.
Si vous générez votre sitemap, il doit être rafraîchi régulièrement afin d'être à jour.

Il y a plusieurs solutions pour cela, mais la plus efficace reste la ré-génération à la demande.

Un listener pour les gouverner tous

Dans le cas où vos jeux de variables proviennent d'entités ORM, il vous faut un moyen pour détecter les modifications apportées aux entités qui composent votre sitemap.
Comme toujours, une multitude de solution est possible.
Mais la plus simple reste la mise en place d'un listener, car :

  1. il sera appelé quel que soit le contexte
  2. il pourra traiter toutes les entités d'un seul bloc
  3. comme c'est un service et peut avoir des dépendances

Le rôle de ce listener est d'analyser une transaction doctrine pour y trouver d'éventuels changements qui nécessiteraient de déclencher le dump du sitemap.

<?php

namespace App\Sitemap;

use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\Event\PostFlushEventArgs;
use Doctrine\ORM\Events;
use Presta\SitemapBundle\Messenger\DumpSitemapMessage;
use Symfony\Component\Messenger\MessageBusInterface;

/**
 * Ce listener doctrine analyse les entités créées/modifiées/supprimées
 * et décide s'il faut régénérer le sitemap.
 */
final class TriggerSitemapDumpListener implements EventSubscriber
{
    private bool $dispatch = false;

    public function __construct(
        private MessageBusInterface $bus,
    ) {
    }

    public function getSubscribedEvents(): array
    {
        return [Events::onFlush, Events::postFlush];
    }

    public function onFlush(OnFlushEventArgs $event): void
    {
        if ($this->dispatch) {
            return; // d'une certaine manière, le dump est déjà demandé
        }

        $unitOfWork = $event->getEntityManager()->getUnitOfWork();

        $analyze = \array_merge(
            $unitOfWork->getScheduledEntityInsertions(),
            $unitOfWork->getScheduledEntityUpdates(),
            $unitOfWork->getScheduledEntityDeletions(),
        );
        foreach ($analyze as $entity) {
            if ($entity /*todo $entity est à mettre dans le sitemap*/) {
                $this->dispatch = true;
                break; // trouver une seule entité est suffisant
            }
        }
    }

    public function postFlush(PostFlushEventArgs $event): void
    {
        if ($this->dispatch) {
            // le dump a été demandée, on le déclenche après que les modifications aient été persitées
            $this->bus->dispatch(new DumpSitemapMessage());
        }
    }
}

Vous avez dit asynchrone ?

Jusqu'ici, nous-nous sommes contentés d'envoyer un message dans le bus, et Symfony messenger l'a probablement traité immédiatement : c'est son comportement par défaut.
Si l'on souhaite que ce traitement se fasse de manière asynchrone, il nous faut configurer le composant pour qu'il "route" notre message par un transport supportant une forme d'asynchronicité.

# config/packages/messenger.yaml
framework:
    messenger:
        transports:
            async: "%env(MESSENGER_TRANSPORT_DSN)%"
        routing:
            'Presta\SitemapBundle\Messenger\DumpSitemapMessage':  async
Suivez notre actualité en avant première. Pas plus d’une newsletter par mois.