Archives mensuelles : novembre 2019

Symfony 4 – Doctrine Event Subscriber

« Symfony, peux-tu mettre automatiquement une date d’ajout à chaque création d’une entité ? », voici un des exemples pour lesquels vous pourriez avoir besoin des subscribers.


(Il peut y avoir d’autres méthodes qui pourront faire la même chose, comme les listeners mais pour ce cours nous resterons sur les subscribers, par contre il faut savoir que les listeners sont chargés uniquement quand l’event est déclenché contrairement aux subscribers qui eux sont chargés chaque fois que l’application s’exécute).

J’avais prévu pas mal de blabla mais finalement je me dis que de travailler sur un exemple va être beaucoup mieux, et pour ça nous allons développer… un distributeur de friandises !

Le MCD sera hyper simple :

Un produit aura un nom et la quantité restante dans le distributeur. Une activité sera soit un achat (on réalimente le distributeur) soit une vente (on vend une friandise)

A chaque nouvelle activité il faut remettre à jour la quantité restante du produit, vous pouvez le faire à la main avec quelque chose comme :

$activite->getProduit()->setQteRestante($activite->getProduit->getQteRestante() - $activite->getQuantite());

Et ça c’est dans le cas d’une vente, dans le cas d’achat il faudra faire une addition. A la rigueur si vous ne devez le faire qu’à un seul endroit ça peut encore passer, mais si vous avez plusieurs endroits où c’est mis à jour la maintenance du code peut devenir compliqué.
Vous pourriez par exemple à avoir à créer des activités (donc achat ou vente) via l’import d’un fichier excel, via une tâche cron qui tournerai toutes les nuits, etc…

C’est là ou les subscribers vont nous être utiles : la mise à jour de la quantité restante (ainsi que la dateActivite) ne sera écrit qu’à un seul endroit et sera exécuté automatiquement.

Je ne vous mettrai ici que le code intéressant, mais vous pourrez retrouver l’intégralité du code sur mon github :

https://github.com/gponty/distributeur

Et une démo du résultat final ici :

https://distributeur.dev-web.io/

On va commencer par dire à Symfony que notre subscriber existe (ou en tout cas va exister!), dans le fichier config/services.yaml :

App\EventListener\ActiviteSubscriber:
    tags:
        - { name: doctrine.event_subscriber, connection: default }

Là vraiment rien de compliqué mais indispensable.

Avant de passer à l’écriture du subscriber on va regarder à quel moment il peut se déclencher, je ne vais pas tous les énumérer, vous pourrez les retrouver en intégralité sur le site de doctrine. En ce qui concerne notre projet nous en utiliserons 2 :

- postPersist : Le subscriber va se déclencher après la persistance d'une entité, ce qui va nous intéresser pour la mise à jour des quantités restantes

- prePersist :Le subscriber va se declencher avant la persistance d'une entité ce qui sera intéressant pour la mise à jour de la date d'activité.

La magie de Symfony c’est qu’on va pouvoir regrouper ces 2 events dans le même code :

namespace App\EventListener;

use App\Entity\Activite;
use Doctrine\Common\EventSubscriber;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use Doctrine\ORM\Events;

class ActiviteSubscriber implements EventSubscriber
{
    public function getSubscribedEvents()
    {
        return [
            Events::postPersist,
            Events::prePersist,
        ];
    }

    public function postPersist(LifecycleEventArgs $args)
    {
        $this->updateQuantite($args);
    }

    public function prePersist(LifecycleEventArgs $args)
    {
        $this->updateDateActivite($args);
    }

    public function updateQuantite(LifecycleEventArgs $args)
    {
        $entity = $args->getObject();
        $entityManager = $args->getObjectManager();

        if ($entity instanceof Activite) {
            if($entity->getTypeActivite()==='A') {
                $entity->getProduit()->addQteRestante($entity->getQuantite());
            }

            if($entity->getTypeActivite()==='V'){
                $entity->getProduit()->subQteRestante($entity->getQuantite());
            }

            $entityManager->flush();

        }
    }

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

        if ($entity instanceof Activite) {

            $entity->setDateAtivite(new \DateTime());

        }
    }
}

On pourrait très bien n’avoir qu’une seule fonction qui regroupe addQteRestante/subQteRestante mais je trouvais que pour l’exemple c’était mieux comme ça.

Vous pouvez maintenant tester et ajouter des « activite », vous devrez voir que la quantité restante et la date sur activite se met bien à jour. Et vous pouvez ajouter des activités de de n’importe quelle façon sans ajouter de code (mais attention, seulement à partir du moment où ça passe par doctrine, un INSERT pur dans la base ne mettra pas à jour la donnée)

Je pense qu’il n’y a pas beaucoup plus à ajouter, le code est suffisamment parlant, mais si vous avez des questions n’hésitez pas à commenter cet article.