From ed23448660ec5b2c5817bd54767428e977679910 Mon Sep 17 00:00:00 2001 From: ixarlie Date: Wed, 4 Apr 2018 10:38:42 +0200 Subject: [PATCH] add comparison event strategy --- .../ORM/Event/OnComparisonEventArgs.php | 114 ++++++++++++++ lib/Doctrine/ORM/Events.php | 6 + lib/Doctrine/ORM/UnitOfWork.php | 23 ++- .../ORM/Event/OnComparisonEventArgsTest.php | 44 ++++++ .../ORM/Functional/ComparisonEventTest.php | 146 ++++++++++++++++++ 5 files changed, 330 insertions(+), 3 deletions(-) create mode 100644 lib/Doctrine/ORM/Event/OnComparisonEventArgs.php create mode 100644 tests/Doctrine/Tests/ORM/Event/OnComparisonEventArgsTest.php create mode 100644 tests/Doctrine/Tests/ORM/Functional/ComparisonEventTest.php diff --git a/lib/Doctrine/ORM/Event/OnComparisonEventArgs.php b/lib/Doctrine/ORM/Event/OnComparisonEventArgs.php new file mode 100644 index 00000000000..b88b083758b --- /dev/null +++ b/lib/Doctrine/ORM/Event/OnComparisonEventArgs.php @@ -0,0 +1,114 @@ +. + */ + +namespace Doctrine\ORM\Event; + +use Doctrine\ORM\EntityManagerInterface; + +/** + * Class OnComparisonEventArgs. + * + * @author Carlos Dominguez + */ +class OnComparisonEventArgs extends LifecycleEventArgs +{ + /** + * @var string + */ + private $propertyName; + + /** + * @var mixed + */ + private $originalValue; + + /** + * @var mixed + */ + private $actualValue; + + /** + * Returns a negative integer, zero, or a positive integer as the actualValue is less than, equal to, + * or greater than the specified originalValue. + * @var int + */ + private $comparisonResult; + + /** + * Constructor. + * + * @param EntityManagerInterface $em + * @param object $entity + * @param string $propertyName + * @param mixed $originalValue + * @param mixed $actualValue + */ + public function __construct( + EntityManagerInterface $em, + $entity, + string $propertyName, + $originalValue, + $actualValue + ) { + parent::__construct($entity, $em); + $this->propertyName = $propertyName; + $this->originalValue = $originalValue; + $this->actualValue = $actualValue; + } + + /** + * @return string + */ + public function getPropertyName() + { + return $this->propertyName; + } + + /** + * @return mixed + */ + public function getOriginalValue() + { + return $this->originalValue; + } + + /** + * @return mixed + */ + public function getActualValue() + { + return $this->actualValue; + } + + /** + * @return int|null + */ + public function getComparisonResult() + { + return $this->comparisonResult; + } + + /** + * @param int $comparisonResult + */ + public function setComparisonResult(int $comparisonResult) + { + $this->comparisonResult = $comparisonResult; + } +} diff --git a/lib/Doctrine/ORM/Events.php b/lib/Doctrine/ORM/Events.php index e16b47a4214..4693d70db1b 100644 --- a/lib/Doctrine/ORM/Events.php +++ b/lib/Doctrine/ORM/Events.php @@ -164,4 +164,10 @@ private function __construct() * @var string */ const onClear = 'onClear'; + + /** + * The onComparison event occurs when the UnitOfWork#computeChangeSet or UnitOfWork#recomputeSingleEntityChangeSet + * operations are invoked. + */ + const onComparison = 'onComparison'; } diff --git a/lib/Doctrine/ORM/UnitOfWork.php b/lib/Doctrine/ORM/UnitOfWork.php index 6767e7ef310..71508f63a7b 100644 --- a/lib/Doctrine/ORM/UnitOfWork.php +++ b/lib/Doctrine/ORM/UnitOfWork.php @@ -692,8 +692,15 @@ public function computeChangeSet(ClassMetadata $class, $entity) $orgValue = $originalData[$propName]; + $event = new Event\OnComparisonEventArgs($this->em, $entity, $propName, $orgValue, $actualValue); + if ($this->evm->hasListeners(Events::onComparison)) { + $this->evm->dispatchEvent(Events::onComparison, $event); + } + // skip if value haven't changed - if ($orgValue === $actualValue) { + if (0 === $event->getComparisonResult() || + (null === $event->getComparisonResult() && $orgValue === $actualValue) + ) { continue; } @@ -1021,9 +1028,19 @@ public function recomputeSingleEntityChangeSet(ClassMetadata $class, $entity) foreach ($actualData as $propName => $actualValue) { $orgValue = $originalData[$propName] ?? null; - if ($orgValue !== $actualValue) { - $changeSet[$propName] = [$orgValue, $actualValue]; + $event = new Event\OnComparisonEventArgs($this->em, $entity, $propName, $orgValue, $actualValue); + if ($this->evm->hasListeners(Events::onComparison)) { + $this->evm->dispatchEvent(Events::onComparison, $event); } + + // skip if value haven't changed + if (0 === $event->getComparisonResult() || + (null === $event->getComparisonResult() && $orgValue === $actualValue) + ) { + continue; + } + + $changeSet[$propName] = [$orgValue, $actualValue]; } if ($changeSet) { diff --git a/tests/Doctrine/Tests/ORM/Event/OnComparisonEventArgsTest.php b/tests/Doctrine/Tests/ORM/Event/OnComparisonEventArgsTest.php new file mode 100644 index 00000000000..982e4fed268 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Event/OnComparisonEventArgsTest.php @@ -0,0 +1,44 @@ +createMock(EntityManagerInterface::class); + $entity = new City('foo'); + + $args = new OnComparisonEventArgs($objectManager, $entity, 'name', $entity->name, 'bar'); + $this->assertInstanceOf(LifecycleEventArgs::class, $args); + + $this->assertSame('foo', $args->getOriginalValue()); + $this->assertSame('bar', $args->getActualValue()); + $this->assertSame('name', $args->getPropertyName()); + $this->assertSame($objectManager, $args->getObjectManager()); + $this->assertSame($entity, $args->getObject()); + + $this->assertNull($args->getComparisonResult()); + + $args->setComparisonResult(-1); + $this->assertSame(-1, $args->getComparisonResult()); + + $args->setComparisonResult(0); + $this->assertSame(0, $args->getComparisonResult()); + + $args->setComparisonResult(1); + $this->assertSame(1, $args->getComparisonResult()); + } +} diff --git a/tests/Doctrine/Tests/ORM/Functional/ComparisonEventTest.php b/tests/Doctrine/Tests/ORM/Functional/ComparisonEventTest.php new file mode 100644 index 00000000000..19d5e06e931 --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Functional/ComparisonEventTest.php @@ -0,0 +1,146 @@ + + */ +class ComparisonEventTest extends OrmFunctionalTestCase +{ + /** + * @var ComparisonListener + */ + private $listener; + + protected function setUp() + { + $this->useModelSet('cms'); + parent::setUp(); + $this->listener = new ComparisonListener(); + $evm = $this->_em->getEventManager(); + $evm->addEventListener(Events::onComparison, $this->listener); + } + + public function testListenerShouldBeNotified() + { + $this->_em->persist($this->createNewValidUser()); + $this->_em->flush(); + $this->assertFalse($this->listener->wasNotified); + $this->assertCount(0, $this->listener->receivedArgs); + } + + public function testListenerShouldBeNotifierComputeChangeSet() + { + $user = $this->createNewValidUser(); + $this->_em->persist($user); + $this->_em->flush(); + $this->_em->clear(); + + $user = $this->_em->find(CmsUser::class, $user->getId()); + $originalValue = $user->username; + $user->username = 'foo'; + + $this->_em->getUnitOfWork()->computeChangeSet( + $this->_em->getClassMetadata(CmsUser::class), + $user + ); + + $this->assertCount(1, $this->listener->receivedArgs); + /** @var OnComparisonEventArgs $args */ + $args = $this->listener->receivedArgs[0]; + $this->assertSame($this->_em, $args->getObjectManager()); + $this->assertSame($user, $args->getObject()); + $this->assertSame('username', $args->getPropertyName()); + $this->assertSame($originalValue, $args->getOriginalValue()); + $this->assertSame('foo', $args->getActualValue()); + $this->assertNotSame(0, $args->getComparisonResult()); + + $this->assertTrue($this->listener->wasNotified); + + $changeSet = $this->_em->getUnitOfWork()->getEntityChangeSet($user); + $this->assertCount(1, $changeSet); + $this->assertArrayHasKey('username', $changeSet); + $this->assertSame($originalValue, $changeSet['username'][0]); + $this->assertSame($user->username, $changeSet['username'][1]); + } + + public function testListenerShouldBeNotifierRecomputeChangeSet() + { + $user = $this->createNewValidUser(); + $this->_em->persist($user); + $this->_em->flush(); + $this->_em->clear(); + + $user = $this->_em->find(CmsUser::class, $user->getId()); + $originalValue = $user->username; + $user->username = 'foo'; + + $this->_em->getUnitOfWork()->recomputeSingleEntityChangeSet( + $this->_em->getClassMetadata(CmsUser::class), + $user + ); + + $this->assertCount(1, $this->listener->receivedArgs); + /** @var OnComparisonEventArgs $args */ + $args = $this->listener->receivedArgs[0]; + $this->assertSame($this->_em, $args->getObjectManager()); + $this->assertSame($user, $args->getObject()); + $this->assertSame('username', $args->getPropertyName()); + $this->assertSame($originalValue, $args->getOriginalValue()); + $this->assertSame('foo', $args->getActualValue()); + $this->assertNotSame(0, $args->getComparisonResult()); + + $this->assertTrue($this->listener->wasNotified); + + $changeSet = $this->_em->getUnitOfWork()->getEntityChangeSet($user); + $this->assertCount(1, $changeSet); + $this->assertArrayHasKey('username', $changeSet); + $this->assertSame($originalValue, $changeSet['username'][0]); + $this->assertSame($user->username, $changeSet['username'][1]); + } + + /** + * @return CmsUser + */ + private function createNewValidUser() + { + $user = new CmsUser(); + $user->username = 'dfreudenberger'; + $user->name = 'Daniel Freudenberger'; + return $user; + } +} + +class ComparisonListener +{ + /** + * @var bool + */ + public $wasNotified = false; + + /** + * @var OnComparisonEventArgs + */ + public $receivedArgs = []; + + /** + * @param OnComparisonEventArgs $args + */ + public function onComparison(OnComparisonEventArgs $args) + { + $this->wasNotified = true; + if ($args->getOriginalValue() === $args->getActualValue()) { + $args->setComparisonResult(0); + } else { + $args->setComparisonResult(1); + $this->receivedArgs[] = $args; + } + } +}