I need to check if a persisted entity has changed and needs to be updated on the database. What I made (and did not work) was the following:
$product = $enti
I didn't need/want to create Listeners for my case so I ended up with
$product->setName('A different name');
$uow = $entityManager->getUnitOfWork();
$uow->computeChangeSets();
if ($uow->isEntityScheduled($product)) {
// My entity has changed
}
I agree with @Andrew Atkinson when he said:
You may also want to look at the PreUpdate event, if you need access to entity fields with their old and new values.
But I disagree with the example he proposed, from my experience, there is a better way to check if something changed or not.
<?php
class Spock
{
public function preUpdate(PreUpdateEventArgs $eventArgs)
{
if (!empty($eventArgs->getEntityChangeSet())) {
// fill this how you see fit
}
}
}
This way the if will only be triggered if there is really some field that changed or not.
As to how to do it if this or that field was changed, then yeah, I recommend his solution.
Based on my needs, answers here and the docs, I came up with the following solution for a modifiedAt
timestamp in an Entity.
/**
* @Doctrine\ORM\Mapping\PreUpdate()
*
* @param \Doctrine\ORM\Event\PreUpdateEventArgs $args
* @return $this
*/
public function preUpdateModifiedAt(\Doctrine\ORM\Event\PreUpdateEventArgs $args)
{
$this->setModifiedAt(new \DateTime('now'));
return $this;
}
This is based on what the docs say about this Event
as opposed to the other available ones, such as PostPersist
and PreFlush
:
PreUpdate is the most restrictive to use event, since it is called right before an update statement is called for an entity inside the EntityManager#flush() method. Note that this event is not triggered when the computed changeset is empty.
Using PreUpdate
as opposed to the others lets you leave all the computations and calculation intensive functions to the process already defined by Doctrine. Manually triggering computation of changesets, such as in these answers above are server CPU intensive. The onFlush Event, such as used in the accepted answer is an option (in the way demonstrated), but not if you rely on detecting a change to the Entity, as you can with the function above (preUpdateModifiedAt(PreUpdateEventArgs $args)
).
Doctrine2 Docs. 17. Change Tracking Policies
If you use third form (17.3. Notify) as i do, you can test if your entity is changed doing:
$uow = $entityManager->getUnitOfWork();
$uow->computeChangeSets();
$aChangeSet = $uow->getEntityChangeSet($oEntity);
If nothing changed it will return blank array.
The issue is quite old but there may be still some group of people that might face this problem from a different point of view.
The UnitOfWork
works great but it only returns the array of changes. It can be a pain in butt when someone doesn't actually knows which fields may have changed and just wants to get the whole entity as an object to compare $oldEntity
and $newEntity
. Even though the event's name is preUpdate if someone will try to fetch the data from the database as follows:
$er->find($id);
the returned entity will contain all changes. The workaround is quite simple but it has some hooks:
public function preUpdate(Entity $entity, PreUpdateEventArgs $args)
{
$entity = clone $entity; //as Doctrine under the hood
//uses reference to manage entities you might want
//to work on the entity copy. Otherwise,
//the below refresh($entity) will affect both
//old and new entity.
$em = $args->getEntityManager();
$currentEntity = $em->getRepository('AppBundle:Entity')->find($entity->getId());
$em->refresh($currentEntity);
}
For those who are using another event, like preFlush, I've quickly checked it and the workaround didn't work well because probably the refresh()
method discards any flush changes so what needs to be done is to call the flush once again in listener and create some static $alreadyFlushed
toggle to avoid circular reference.
If you only need to compare old and new state of object then probably this would be simpler:
$originalEntityData = $entityManager->getUnitOfWork()->getOriginalEntityData($entityObject);