From bc6714c2c8d015cb2e3ae198fcb1b98b3bbe35e0 Mon Sep 17 00:00:00 2001 From: beberlei Date: Sun, 14 Mar 2010 22:16:15 +0000 Subject: [PATCH] [2.0] DDC-414 - Changed semantics of preUpdate Event to allow only changes to the entity changeset, not the internal state of the entity anymore. --- UPGRADE_TO_2_0 | 7 ++ lib/Doctrine/ORM/Event/PreUpdateEventArgs.php | 98 +++++++++++++++++++ lib/Doctrine/ORM/UnitOfWork.php | 14 ++- .../ORM/Functional/LifecycleCallbackTest.php | 35 +++++++ 4 files changed, 146 insertions(+), 8 deletions(-) create mode 100644 lib/Doctrine/ORM/Event/PreUpdateEventArgs.php diff --git a/UPGRADE_TO_2_0 b/UPGRADE_TO_2_0 index eb07afa37..a963c7d74 100644 --- a/UPGRADE_TO_2_0 +++ b/UPGRADE_TO_2_0 @@ -19,6 +19,13 @@ mapped fields inside PHP easily. Upon persist() invocation these values are save The 'inheritance-type' attribute changed to take last bit of ClassMetadata constant names, i.e. NONE, SINGLE_TABLE, INHERITANCE_TYPE_JOINED +## Change of PreUpdate Event Listener + +Event Listeners listening to the 'preUpdate' event can only affect the primitive values of entity changesets +by using the API on the `PreUpdateEventArgs` instance passed to the preUpdate listener method. Any changes +to the state of the entitys properties won't affect the database UPDATE statement anymore. This gives drastic +performance benefits for the preUpdate event. + # Upgrade from 2.0-ALPHA3 to 2.0-ALPHA4 ## CLI Controller changes diff --git a/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php b/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php new file mode 100644 index 000000000..c61e26d4c --- /dev/null +++ b/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php @@ -0,0 +1,98 @@ + + * @author Benjamin Eberlei + * @since 2.0 + */ +class PreUpdateEventArgs extends LifecycleEventArgs +{ + /** + * @var array + */ + private $_entityChangeSet; + + /** + * + * @param object $entity + * @param EntityManager $em + * @param array $changeSet + */ + public function __construct($entity, $em, array &$changeSet) + { + parent::__construct($entity, $em); + $this->_entityChangeSet = &$changeSet; + } + + public function getEntityChangeSet() + { + return $this->_entityChangeSet; + } + + /** + * Field has a changeset? + * + * @return bool + */ + public function hasChangedField($field) + { + return isset($this->_entityChangeSet[$field]); + } + + /** + * Get the old value of the changeset of the changed field. + * + * @param string $field + * @return mixed + */ + public function getOldValue($field) + { + $this->_assertValidField($field); + + return $this->_entityChangeSet[$field][0]; + } + + /** + * Get the new value of the changeset of the changed field. + * + * @param string $field + * @return mixed + */ + public function getNewValue($field) + { + $this->_assertValidField($field); + + return $this->_entityChangeSet[$field][1]; + } + + /** + * Set the new value of this field. + * + * @param string $field + * @param mixed $value + */ + public function setNewValue($field, $value) + { + $this->_assertValidField($field); + + $this->_entityChangeSet[$field][1] = $value; + } + + private function _assertValidField($field) + { + if (!isset($this->_entityChangeSet[$field])) { + throw new \InvalidArgumentException( + "Field '".$field."' is not a valid field of the entity ". + "'".get_class($this->getEntity())."' in PreInsertUpdateEventArgs." + ); + } + } +} + diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 93ce6f56b..eacfd15aa 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -752,16 +752,14 @@ class UnitOfWork implements PropertyChangedListener if ($hasPreUpdateLifecycleCallbacks) { $class->invokeLifecycleCallbacks(Events::preUpdate, $entity); - if ( ! $hasPreUpdateListeners) { - // Need to recompute entity changeset to detect changes made in the callback. - $this->recomputeSingleEntityChangeSet($class, $entity); - } - } - if ($hasPreUpdateListeners) { - $this->_evm->dispatchEvent(Events::preUpdate, new LifecycleEventArgs($entity, $this->_em)); - // Need to recompute entity changeset to detect changes made in the listener. $this->recomputeSingleEntityChangeSet($class, $entity); } + + if ($hasPreUpdateListeners) { + $this->_evm->dispatchEvent(Events::preUpdate, new Event\PreUpdateEventArgs( + $entity, $this->_em, $this->_entityChangeSets[$oid]) + ); + } $persister->update($entity); unset($this->_entityUpdates[$oid]); diff --git a/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php b/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php index 8d323487b..74d36bcfd 100644 --- a/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php +++ b/tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php @@ -1,6 +1,7 @@ _em->getClassMetadata(__NAMESPACE__ . '\LifecycleCallbackChildEntity'); $this->assertEquals(array('prePersist' => array(0 => 'doStuff')), $childMeta->lifecycleCallbacks); } + + public function testLifecycleListener_ChangeUpdateChangeSet() + { + $listener = new LifecycleListenerPreUpdate; + $this->_em->getEventManager()->addEventListener(array('preUpdate'), $listener); + + $user = new LifecycleCallbackTestUser; + $user->setName('Bob'); + $user->setValue('value'); + $this->_em->persist($user); + $this->_em->flush(); + $this->_em->clear(); + + $dql = "SELECT u FROM Doctrine\Tests\ORM\Functional\LifecycleCallbackTestUser u WHERE u.name = 'Bob'"; + $bob = $this->_em->createQuery($dql)->getSingleResult(); + $bob->setName('Alice'); + + $this->_em->flush(); // preUpdate reverts Alice to Bob + $this->_em->clear(); + + $this->_em->getEventManager()->removeEventListener(array('preUpdate'), $listener); + + $bob = $this->_em->createQuery($dql)->getSingleResult(); + + $this->assertEquals('Bob', $bob->getName()); + } } /** @Entity @HasLifecycleCallbacks */ @@ -219,3 +246,11 @@ class LifecycleCallbackChildEntity extends LifecycleCallbackParentEntity { /** @Id @Column(type="integer") @GeneratedValue */ private $id; } + +class LifecycleListenerPreUpdate +{ + public function preUpdate(PreUpdateEventArgs $eventArgs) + { + $eventArgs->setNewValue('name', 'Bob'); + } +} \ No newline at end of file