In my symfony2/doctrine2 application, I have a weird case where when persisting a modified entity, the change is not flushed to the database, and I can't understand why.
Below is my code :
$date = $subscription->getPaymentValidUntil()->format('d/m/Y');
$period = $payment->getDetail('period');
$validDate = $subscription->getPaymentValidUntil()
->add(new\DateInterval($period == Subscription::MONTH ? 'P1M' : 'P1Y'))
;
$subscription->setPaymentValidUntil($validDate);
$this->em->persist($subscription);
exit(var_dump(array(
$date,
$this->em->getUnitOfWork()->getScheduledEntityUpdates(),
$subscription->getPaymentValidUntil(),
)));
$this->em->flush();
The output of the vardump is the following:
array (size=3)
0 => string '12/05/2015' (length=10)
1 =>
array (size=0)
empty
2 =>
object(DateTime)[2295]
public 'date' => string '2015-06-12 18:52:37' (length=19)
public 'timezone_type' => int 3
public 'timezone' => string 'Europe/Paris' (length=12)
If I flush before the vardump or remove the vardump, indeed, the date value does not change in my database. Why?
As you can see, I add one month to this date value, it's reflected in the entity, but it's not scheduled for update. How can I solve this ?
EDIT : I've digged into the entity manager persist function and I figured out that when dumping the entity in Doctrine\ORM\EntityManager::persist the date was good but when dumping it in Doctrine\ORM\UnitOfWork::persist, the date had somehow been reseted to its original one :
Doctrine\ORM\EntityManager::persist
: dumps the updated entity
public function persist($entity)
{
if ( ! is_object($entity)) {
throw ORMInvalidArgumentException::invalidObject('EntityManager#persist()' , $entity);
}
$this->errorIfClosed();
if ($entity instanceof Subscription) exit(var_dump($entity));
$this->unitOfWork->persist($entity);
}
Doctrine\ORM\UnitOfWork::persist : dumps the non-modified entity : WHY ?
private function doPersist($entity, array &$visited)
{
if ($entity instanceof Subscription) exit(var_dump($entity));
$oid = spl_object_hash($entity);
if (isset($visited[$oid])) {
return; // Prevent infinite recursion
}
}
Doctrine's public interface is through the EntityManager . This class provides access points to the complete lifecycle management for your entities, and transforms entities from and back to persistence. You have to configure and create it to use your entities with Doctrine ORM.
Persist and Flush flush() . em. persist(entity) is used to mark new entities for future persisting. It will make the entity managed by given EntityManager and once flush will be called, it will be written to the database.
Doctrine uses the Identity Map pattern to track objects. Whenever you fetch an object from the database, Doctrine will keep a reference to this object inside its UnitOfWork. The array holding all the entity references is two-levels deep and has the keys root entity name and id.
It means the place where our data can be accessed from, a repository of data. This is to distinguish it from a database as a repository does not care how its data is stored.
DateTime
are object after all, and are always passed by reference. So, I strongly suggest you to edit your code like this:
$period = $payment->getDetail('period');
$validDate = clone $subscription->getPaymentValidUntil();
$validDate->add(new \DateInterval($period == Subscription::MONTH ? 'P1M' : 'P1Y'));
$subscription->setPaymentValidUntil($validDate);
$this->em->persist($subscription);
$this->em->flush();
This way, you ensure that you're passing a different object to the entity, and avoid any subtle bug in the EntityManager.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With