Evénements Symfony2


Table des matières

Référence
Le système d'événements
La convention de nommage
Evénements Lifecycle
Rappels Lifecycle
L'écoute des événements de cycle de vie
La mise en œuvre des écouteurs d'événements
PrePersist
pré-suppression
onFlush
PreUpdate
PostUpdate, de post-suppression, postPersist
postLoad
Charger l'événement ClassMetadata

Référence

http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/events.html

Doctrine2 dispose d'un système de gestion d'événements qui fait partie du paquet commun.

Le système d'événements

Le système d'événements est contrôlé par l'EventManager. Elle est le point central du système de Doctrine d'écouteur d'événements. Les "listeners" sont inscrits sur le manager et les événements sont distribués par le gestionnaire.

<?php
$evm = new EventManager();

Maintenant, nous pouvons ajouter quelques écouteurs d'événement à l'$evm. Créons une classe EventTest pour tester avec celle-ci.

<?php
class EventTest
{
    const preFoo = 'preFoo';
    const postFoo = 'postFoo';

    private $_evm;

    public $preFooInvoked = false;
    public $postFooInvoked = false;

    public function __construct($evm)
    {
        $evm->addEventListener(array(self::preFoo, self::postFoo), $this);
    }

    public function preFoo(EventArgs $e)
    {
        $this->preFooInvoked = true;
    }

    public function postFoo(EventArgs $e)
    {
        $this->postFooInvoked = true;
    }
}

// Create a new instance
$test = new EventTest($evm);

Les événements peuvent être envoyés en utilisant la méthode dispatchEvent().

<?php
$evm->dispatchEvent(EventTest::preFoo);
$evm->dispatchEvent(EventTest::postFoo);

Vous pouvez facilement supprimer un écouteur avec la méthode removeEventListener().

<?php
$evm->removeEventListener(array(self::preFoo, self::postFoo), $this);

Le système d'événements Doctrine2 a également un concept simple d'abonnés d'événements. Nous pouvons définir une classe TestEventSubscriber simples qui implémente l'interface \Doctrine\Common\EventSubscriber et met en œuvre une méthode getSubscribedEvents() qui retourne un tableau des événements, il doit être soumis.

<?php
class TestEventSubscriber implements \Doctrine\Common\EventSubscriber
{
    public $preFooInvoked = false;

    public function preFoo()
    {
        $this->preFooInvoked = true;
    }

    public function getSubscribedEvents()
    {
        return array(TestEvent::preFoo);
    }
}

$eventSubscriber = new TestEventSubscriber();
$evm->addEventSubscriber($eventSubscriber);

Maintenant, quand vous envoyez un événement tous les abonnés à cette événement seront notifiés.

<?php
$evm->dispatchEvent(TestEvent::preFoo);

Vous pouvez tester l'instance $eventSubscriber pour voir si la méthode preFoo() a été invoquée.

<?php
if ($eventSubscriber->preFooInvoked) {
    echo 'pre foo invoked!';
}

La convention de nommage

Les événements qui sont utilisés avec Doctrine2 EventManager sont meilleurs quand ils sont nommés avec camelcase et la valeur de la constante correspondante qui doit être le nom de la constante elle-même, avec la même l'orthographe. Pour plusieurs raisons:

Un exemple d'une notation correcte se trouve dans l'exemple ci-dessus EventTest.

Evénements Lifecycle

L'EntityManager et UnitOfWork déclenchent beaucoup d'événements pendant la durée de vie de leurs entités enregistrées.

Notez que l'événement survient après un test de charge pour une entité avant que toutes les associations aient été initialisés. Par conséquent, il n'est pas sûr d'accéder à des associations dans un rappel postLoad ou de gestionnaire d'événements.

Vous pouvez accéder aux constantes de la classe d'événement Evénements dans le paquet ORM.

<?php
use Doctrine\ORM\Events;
echo Events::preUpdate;

Ceux-ci peuvent être accrochés par deux dans les différents types d'écouteurs d'événement:

Tous les événements qui se produisent au cours du cycle de vie du flush() d'un EntityManager ont des contraintes très spécifiques sur les opérations autorisées qui peuvent être exécutées. S'il vous plaît lire la mise en oeuvre de l'événement Listeners très attentivement pour comprendre quelles opérations sont autorisées dans ce cas du cycle de vie.

Rappels Lifecycle

Un événement de cycle de vie est un événement régulier avec la fonction supplémentaire de fournir un mécanisme pour enregistrer les rappels directe à l'intérieur des classes d'entité correspondant qui sont exécutées lorsque l'événement survient cycle de vie.

<?php

/** @Entity @HasLifecycleCallbacks */
class User
{
    // ...

    /**
     * @Column(type="string", length=255)
     */
    public $value;

    /** @Column(name="created_at", type="string", length=255) */
    private $createdAt;

    /** @PrePersist */
    public function doStuffOnPrePersist()
    {
        $this->createdAt = date('Y-m-d H:m:s');
    }

    /** @PrePersist */
    public function doOtherStuffOnPrePersist()
    {
        $this->value = 'changed from prePersist callback!';
    }

    /** @PostPersist */
    public function doStuffOnPostPersist()
    {
        $this->value = 'changed from postPersist callback!';
    }

    /** @PostLoad */
    public function doStuffOnPostLoad()
    {
        $this->value = 'changed from postLoad callback!';
    }

    /** @PreUpdate */
    public function doStuffOnPreUpdate()
    {
        $this->value = 'changed from preUpdate callback!';
    }
}

Notez que lorsque vous utilisez les annotations que vous avez appliqué l'annotation @HasLifecycleCallbacks marque sur la classe d'entité.

Si vous voulez enregistrer les rappels de cycle de vie avec YAML ou XML, vous pouvez le faire de la façon suivante.

User:
  type: entity
  fields:
# ...
    name:
      type: string(50)
  lifecycleCallbacks:
    prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ]
    postPersist: [ doStuffOnPostPersist ]

XML devrait ressembler à ceci:

<?xml version="1.0" encoding="UTF-8"?>

<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
                          /Users/robo/dev/php/Doctrine/doctrine-mapping.xsd">

    <entity name="User">

        <lifecycle-callbacks>
            <lifecycle-callback type="prePersist" method="doStuffOnPrePersist"/>
            <lifecycle-callback type="postPersist" method="doStuffOnPostPersist"/>
        </lifecycle-callbacks>

    </entity>

</doctrine-mapping>

Vous avez juste besoin de vous assurer qu'un public doStuffOnPrePersist() et doStuffOnPostPersist() soit définie sur dans votre modèle User.

<?php
// ...

class User
{
    // ...

    public function doStuffOnPrePersist()
    {
        // ...
    }

    public function doStuffOnPostPersist()
    {
        // ...
    }
}

La clef de lifecycleCallbacks est le nom de la méthode et la valeur est le type d'événement. Les types d'événements autorisées sont celles figurant dans les événements Lifecycle vue à la section précédente.

L'écoute des événements de cycle de vie

Les événements listerners Lifecycle sont beaucoup plus puissants que les simple callbacks lifecycle qui sont définies sur les classes d'entité. Ils permettent de mettre en œuvre des comportements réutilisables entre les classes d'entité différente, mais nécessitent une connaissance beaucoup plus détaillée sur le fonctionnement interne de l'EntityManager et UnitOfWork. Lire les écouteurs d'événement application de l'article attentivement si vous essayez d'écrire votre auditeur propres.

Pour enregistrer un écouteur d'événement, vous devez le crocheter dans la EventManager qui est passé à l'usine EntityManager:

<?php
$eventManager = new EventManager();
$eventManager->addEventListener(array(Events::preUpdate), MyEventListener());
$eventManager->addEventSubscriber(new MyEventSubscriber());

$entityManager = EntityManager::create($dbOpts, $config, $eventManager);

Vous pouvez également récupérer l'instance responsable de l'événement après la EntityManager a été créé:

<?php
$entityManager->getEventManager()->addEventListener(array(Events::preUpdate), MyEventListener());
$entityManager->getEventManager()->addEventSubscriber(new MyEventSubscriber());

La mise en œuvre des écouteurs d'événements

Cette section explique ce qu'est et ce n'est pas autorisé lors des événements du cycle de vie spécifique de l'UnitOfWork. Bien que vous obtenez passé l'EntityManager dans tous ces événements, vous devez suivre ces restrictions très soigneusement car les opérations en cas mauvais peut produire beaucoup d'erreurs différentes, telles que les données incohérentes et mises à jour updates/persists/removes.

Pour les événements décrits qui sont aussi des événements de rappel de cycle de vie des restrictions s'appliquent aussi bien, avec la restriction supplémentaire que vous n'avez pas accès à l'API EntityManager ou UnitOfWork l'intérieur de ces événements.

PrePersist

Il y a deux façons pour l'événement PrePersist être déclenchée. On est évidemment lorsque vous appelez EntityManager#persist(). L'événement est également appelé à toutes les associations en cascade.

Il y a une autre façon pour PrePersist d'être appelé, à l'intérieur du méthode flush () lorsque des modifications aux associations sont calculées et cette association est marquée comme en cascade persistent. Toute nouvelle entité trouvés au cours de cette opération est également persisté et PrePersist appelé sur elle. C'est ce qu'on appelle «la persistance par accessibilité».

Dans les deux cas vous obtenez passé une instance LifecycleEventArgs qui a accès à l'entité et le gestionnaire d'entités.

Les restrictions suivantes s'appliquent aux PrePersist:

pré-suppression

L'événement de preRemove est appelée à chaque entité lors de son passage à la méthode EntityManager#remove(). C'est en cascade de toutes les associations qui sont marqués comme la suppression en cascade.

Il n'existe aucune restriction à ce que les méthodes puissent être appelées dans l'événement preRemovede, sauf lorsque la méthode remove elle-même est appelé lors d'une opération de vidage.

onFlush

OnFlush est un événement très puissant. Il est appelé à l'intérieur de EntityManager#flush() après les changements à toutes les entités gérées et leurs associations ont été calculés. Cela signifie, l'événement onFlush a accès aux séries de:

Pour faire usage de l'événement onFlush vous devez être familier avec l'API interne UnitOfWork, qui vous donne accès à des jeux mentionnés précédemment. Voir cet exemple:

<?php
class FlushExampleListener
{
    public function onFlush(OnFlushEventArgs $eventArgs)
    {
        $em = $eventArgs->getEntityManager();
        $uow = $em->getUnitOfWork();

        foreach ($uow->getScheduledEntityInsertions() AS $entity) {

        }

        foreach ($uow->getScheduledEntityUpdates() AS $entity) {

        }

        foreach ($uow->getScheduledEntityDeletions() AS $entity) {

        }

        foreach ($uow->getScheduledCollectionDeletions() AS $col) {

        }

        foreach ($uow->getScheduledCollectionUpdates() AS $col) {

        }
    }
}

Les restrictions suivantes s'appliquent à l'événement onFlush:

PreUpdate

PreUpdate est la plus restrictive d'utiliser l'événement, car il est appelé juste avant une déclaration mise à jour est appelé pour une entité à l'intérieur de la méthode EntityManager#flush()

Modifications aux associations de l'entité à jour ne sont jamais autorisées à cet événement, puisque la doctrine ne peut garantir à gérer correctement l'intégrité référentielle à ce stade de l'opération de rinçage. Cet événement a une fonctionnalité puissante cependant, il est exécuté avec une instance PreUpdateEventArgs, qui contient une référence change-set de cette entité.

Cela signifie que vous avez accès à tous les champs qui ont changé pour cette entité avec leur valeur anciennes et nouvelles. Les méthodes suivantes sont disponibles sur le PreUpdateEventArgs:

Un exemple simple pour cet événement ressemble à:

<?php
class NeverAliceOnlyBobListener
{
    public function preUpdate(PreUpdateEventArgs $eventArgs)
    {
        if ($eventArgs->getEntity() instanceof User) {
            if ($eventArgs->hasChangedField('name') && $eventArgs->getNewValue('name') == 'Alice') {
                $eventArgs->setNewValue('name', 'Bob');
            }
        }
    }
}

Vous pouvez également utiliser cet écouteur à mettre en œuvre la validation de tous les champs qui ont changé. C'est plus efficace que d'utiliser un rappel du cycle de vie quand il ya des validations coûteux d'appeler:

<?php
class ValidCreditCardListener
{
    public function preUpdate(PreUpdateEventArgs $eventArgs)
    {
        if ($eventArgs->getEntity() instanceof Account) {
            if ($eventArgs->hasChangedField('creditCard')) {
                $this->validateCreditCard($eventArgs->getNewValue('creditCard'));
            }
        }
    }

    private function validateCreditCard($no)
    {
        // throw an exception to interrupt flush event. Transaction will be rolled back.
    }
}

Restrictions pour cet événement:

PostUpdate, de post-suppression, postPersist

Les trois événements postérieurs sont appelés à l'intérieur EntityManager#flush(). Les changements dans ici ne sont pas pertinents pour la persistance dans la base de données, mais vous pouvez utiliser ces événements pour modifier non persistable articles, comme la non-cartographiée champs, l'exploitation forestière ou même de classes associées qui sont directement mappés par la doctrine.

postLoad

Cet événement est appelé après qu'une entité ait été construite par l'EntityManager.

Charger l'événement ClassMetadata

Lorsque les informations de mappage d'une entité sont lues, ils sont peuplés d'une instance ClassMetadataInfo. Vous pouvez crocheté dans ce processus pour manipuler l'instance.

<?php
$test = new EventTest();
$metadataFactory = $em->getMetadataFactory();
$evm = $em->getEventManager();
$evm->addEventListener(Events::loadClassMetadata, $test);

class EventTest
{
    public function loadClassMetadata(\Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs)
    {
        $classMetadata = $eventArgs->getClassMetadata();
        $fieldMapping = array(
            'fieldName' => 'about',
            'type' => 'string',
            'length' => 255
        );
        $classMetadata->mapField($fieldMapping);
    }
}