Joshua Joshua - 1 month ago 10
PHP Question

Track field changes on Doctrine entity

I want to track changes to a field of a Doctrine Entity. I use Symfony 2.5.0 and Doctrine 2.2.3.

So far i have an

EventSubscriber
that subscribes to
preUpdate
. Here I want to create a new Entity which stores the new and old value and holds a reference to the Entity that is updated.

The problem is, that I can't find a way to persist this new Entity. If I
persist()
in
preUpdate
and
flush()
in
postUpdate
, it works if I change only one Entity. If multiple Entities are changed, I get an error that the changeset is empty.

I tried fiddling with different events with different results. Blank pages, tracking entites do not get persisted etc.

I think that this should be a common use case - but I can't find examples.

Answer

Don't use preUpdate or postUpdate, you will have problems. Take a look at onFlush instead.

You have access to the complete changeset at this point, so you can find out what fields have changed, what's been added etc. You can also safely persist new entities. Note as the docs say, you will have to recompute change sets when you persist or change entities.

Simple example I knocked together, not tested but something similar to this will get you what you want.

public function onFlush(OnFlushEventArgs $args) {

    $entityManager = $args->getEntityManager();
    $unitOfWork = $entityManager->getUnitOfWork();
    $updatedEntities = $unitOfWork->getScheduledEntityUpdates();

    foreach ($updatedEntities as $updatedEntity) {

        if ($updatedEntity instanceof YourEntity) {

            $changeset = $unitOfWork->getEntityChangeSet($updatedEntity);

            if (!is_array($changeset)) {

                return null;
            }

            if (array_key_exists('someFieldInYourEntity', $changeset)) {

                $changes = $changeset['someFieldInYourEntity'];

                $previousValueForField = array_key_exists(0, $changes) ? $changes[0] : null;
                $newValueForField = array_key_exists(1, $changes) ? $changes[1] : null;

                if ($previousValueForField != $newValueForField) {

                    $yourChangeTrackingEntity = new YourChangeTrackingEntity();
                    $yourChangeTrackingEntity->setSomeFieldChanged($previousValueForField);
                    $yourChangeTrackingEntity->setSomeFieldChangedTo($newValueForField);

                    $entityManager->persist($yourChangeTrackingEntity);
                    $metaData = $entityManager->getClassMetadata('YourNameSpace\YourBundle\Entity\YourChangeTrackingEntity');
                    $unitOfWork->computeChangeSet($metaData, $yourChangeTrackingEntity);
                }
            }
        }
    }
}
Comments