Track field changes on Doctrine entity

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 (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);
                }
            }
        }
    }
}

You might be interested in EntityAudit bundle.

It allows to configure which entities should be tracked. Then it introduces a concept of revisions of the database. Each revision has timestamp, username and list of entities affected.

Then, you can find all revisions that affect particular entity:

$revisions = $auditReader->findRevisions('AppBundle\Entity\Article', 1);

or instantiate it in a particular revision:

$oldArticle = $auditReader->find(
  'AppBundle\Entity\Article',
  $id = 1,
  $rev = 2
);

so you can easily compare the current and the old state of the entity.

The bundle also ships with example views demonstrating how to display revision list, compare objects in different versions and more.

selecting comparison

comparison view