From e69cd372260d99f852ff74fda18e4b21a22dcf3a Mon Sep 17 00:00:00 2001 From: fabios Date: Mon, 18 Nov 2013 15:03:00 -0500 Subject: [PATCH 1/2] Event listener to programmatically attach entity listeners. --- .../ORM/Mapping/ClassMetadataInfo.php | 15 +- lib/Doctrine/ORM/Mapping/MappingException.php | 12 ++ .../Tools/AttachEntityListenersListener.php | 79 ++++++++ .../AttachEntityListenersListenerTest.php | 183 ++++++++++++++++++ 4 files changed, 284 insertions(+), 5 deletions(-) create mode 100644 lib/Doctrine/ORM/Tools/AttachEntityListenersListener.php create mode 100644 tests/Doctrine/Tests/ORM/Tools/AttachEntityListenersListenerTest.php diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 9b3b29492..86c7d9818 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -2510,7 +2510,11 @@ class ClassMetadataInfo implements ClassMetadata */ public function addEntityListener($eventName, $class, $method) { - $class = $this->fullyQualifiedClassName($class); + $class = $this->fullyQualifiedClassName($class); + $listener = array( + 'class' => $class, + 'method' => $method + ); if ( ! class_exists($class)) { throw MappingException::entityListenerClassNotFound($class, $this->name); @@ -2520,10 +2524,11 @@ class ClassMetadataInfo implements ClassMetadata throw MappingException::entityListenerMethodNotFound($class, $method, $this->name); } - $this->entityListeners[$eventName][] = array( - 'class' => $class, - 'method' => $method - ); + if (isset($this->entityListeners[$eventName]) && in_array($listener, $this->entityListeners[$eventName])) { + throw MappingException::duplicateEntityListener($class, $method, $this->name); + } + + $this->entityListeners[$eventName][] = $listener; } /** diff --git a/lib/Doctrine/ORM/Mapping/MappingException.php b/lib/Doctrine/ORM/Mapping/MappingException.php index 987e60917..0b2643e2c 100644 --- a/lib/Doctrine/ORM/Mapping/MappingException.php +++ b/lib/Doctrine/ORM/Mapping/MappingException.php @@ -707,6 +707,18 @@ class MappingException extends \Doctrine\ORM\ORMException return new self(sprintf('Entity Listener "%s" declared on "%s" has no method "%s".', $listenerName, $className, $methodName)); } + /** + * @param string $listenerName + * @param string $methodName + * @param string $className + * + * @return \Doctrine\ORM\Mapping\MappingException + */ + public static function duplicateEntityListener($listenerName, $methodName, $className) + { + return new self(sprintf('Entity Listener "%s#%s()" in "%s" was already declared, but it must be declared only once.', $listenerName, $methodName, $className)); + } + /** * @param string $className * @param string $annotation diff --git a/lib/Doctrine/ORM/Tools/AttachEntityListenersListener.php b/lib/Doctrine/ORM/Tools/AttachEntityListenersListener.php new file mode 100644 index 000000000..2b6c91aed --- /dev/null +++ b/lib/Doctrine/ORM/Tools/AttachEntityListenersListener.php @@ -0,0 +1,79 @@ +. + */ + +namespace Doctrine\ORM\Tools; + +use Doctrine\ORM\Event\LoadClassMetadataEventArgs; + +/** + * Mechanism to programmatically attach entity listeners. + * + * @author Fabio B. SIlva + * + * @since 2.5 + */ +class AttachEntityListenersListener +{ + /** + * @var array[] + */ + private $entityListeners = array(); + + /** + * Adds a entity listener for a specific entity. + * + * @param string $entityClass The entity to attach the listener. + * @param string $listenerClass The listener class. + * @param string $eventName The entity lifecycle event. + * @param string $listenerCallback|null The listener callback method or NULL to use $eventName. + * + * @return void + */ + public function addEntityListener($entityClass, $listenerClass, $eventName, $listenerCallback = null) + { + $this->entityListeners[ltrim($entityClass, '\\')][] = array( + 'event' => $eventName, + 'class' => $listenerClass, + 'method' => $listenerCallback ?: $eventName + ); + } + + /** + * Processes event and attach the entity listener. + * + * @param \Doctrine\ORM\Event\LoadClassMetadataEventArgs $event + * + * @return void + */ + public function loadClassMetadata(LoadClassMetadataEventArgs $event) + { + /** @var $metadata \Doctrine\ORM\Mapping\ClassMetadata */ + $metadata = $event->getClassMetadata(); + + if ( ! isset($this->entityListeners[$metadata->name])) { + return; + } + + foreach ($this->entityListeners[$metadata->name] as $listener) { + $metadata->addEntityListener($listener['event'], $listener['class'], $listener['method']); + } + + unset($this->entityListeners[$metadata->name]); + } +} diff --git a/tests/Doctrine/Tests/ORM/Tools/AttachEntityListenersListenerTest.php b/tests/Doctrine/Tests/ORM/Tools/AttachEntityListenersListenerTest.php new file mode 100644 index 000000000..8c0a7a75b --- /dev/null +++ b/tests/Doctrine/Tests/ORM/Tools/AttachEntityListenersListenerTest.php @@ -0,0 +1,183 @@ +listener = new AttachEntityListenersListener(); + $driver = $this->createAnnotationDriver(); + $this->em = $this->_getTestEntityManager(); + $evm = $this->em->getEventManager(); + $this->factory = new ClassMetadataFactory; + + $evm->addEventListener(Events::loadClassMetadata, $this->listener); + $this->em->getConfiguration()->setMetadataDriverImpl($driver); + $this->factory->setEntityManager($this->em); + } + + public function testAttachEntityListeners() + { + $this->listener->addEntityListener( + AttachEntityListenersListenerTestFooEntity::CLASSNAME, + AttachEntityListenersListenerTestListener::CLASSNAME, + Events::postLoad, + 'postLoadHandler' + ); + + $metadata = $this->factory->getMetadataFor(AttachEntityListenersListenerTestFooEntity::CLASSNAME); + + $this->assertArrayHasKey('postLoad', $metadata->entityListeners); + $this->assertCount(1, $metadata->entityListeners['postLoad']); + $this->assertEquals('postLoadHandler', $metadata->entityListeners['postLoad'][0]['method']); + $this->assertEquals(AttachEntityListenersListenerTestListener::CLASSNAME, $metadata->entityListeners['postLoad'][0]['class']); + } + + public function testAttachToExistingEntityListeners() + { + $this->listener->addEntityListener( + AttachEntityListenersListenerTestBarEntity::CLASSNAME, + AttachEntityListenersListenerTestListener2::CLASSNAME, + Events::prePersist + ); + + $this->listener->addEntityListener( + AttachEntityListenersListenerTestBarEntity::CLASSNAME, + AttachEntityListenersListenerTestListener2::CLASSNAME, + Events::postPersist, + 'postPersistHandler' + ); + + $metadata = $this->factory->getMetadataFor(AttachEntityListenersListenerTestBarEntity::CLASSNAME); + + $this->assertArrayHasKey('postPersist', $metadata->entityListeners); + $this->assertArrayHasKey('prePersist', $metadata->entityListeners); + + $this->assertCount(2, $metadata->entityListeners['prePersist']); + $this->assertCount(2, $metadata->entityListeners['postPersist']); + + $this->assertEquals('prePersist', $metadata->entityListeners['prePersist'][0]['method']); + $this->assertEquals(AttachEntityListenersListenerTestListener::CLASSNAME, $metadata->entityListeners['prePersist'][0]['class']); + + $this->assertEquals('prePersist', $metadata->entityListeners['prePersist'][1]['method']); + $this->assertEquals(AttachEntityListenersListenerTestListener2::CLASSNAME, $metadata->entityListeners['prePersist'][1]['class']); + + $this->assertEquals('postPersist', $metadata->entityListeners['postPersist'][0]['method']); + $this->assertEquals(AttachEntityListenersListenerTestListener::CLASSNAME, $metadata->entityListeners['postPersist'][0]['class']); + + $this->assertEquals('postPersistHandler', $metadata->entityListeners['postPersist'][1]['method']); + $this->assertEquals(AttachEntityListenersListenerTestListener2::CLASSNAME, $metadata->entityListeners['postPersist'][1]['class']); + } + + /** + * @expectedException \Doctrine\ORM\Mapping\MappingException + * @expectedExceptionMessage Entity Listener "Doctrine\Tests\ORM\Tools\AttachEntityListenersListenerTestListener#postPersist()" in "Doctrine\Tests\ORM\Tools\AttachEntityListenersListenerTestFooEntity" was already declared, but it must be declared only once. + */ + public function testDuplicateEntityListenerException() + { + $this->listener->addEntityListener( + AttachEntityListenersListenerTestFooEntity::CLASSNAME, + AttachEntityListenersListenerTestListener::CLASSNAME, + Events::postPersist + ); + + $this->listener->addEntityListener( + AttachEntityListenersListenerTestFooEntity::CLASSNAME, + AttachEntityListenersListenerTestListener::CLASSNAME, + Events::postPersist + ); + + $this->factory->getMetadataFor(AttachEntityListenersListenerTestFooEntity::CLASSNAME); + } +} + +/** + * @Entity + */ +class AttachEntityListenersListenerTestFooEntity +{ + const CLASSNAME = __CLASS__; + + /** + * @Id + * @Column(type="integer") + * @GeneratedValue(strategy="AUTO") + */ + public $id; +} + +/** + * @Entity + * @EntityListeners({"AttachEntityListenersListenerTestListener"}) + */ +class AttachEntityListenersListenerTestBarEntity +{ + const CLASSNAME = __CLASS__; + + /** + * @Id + * @Column(type="integer") + * @GeneratedValue(strategy="AUTO") + */ + public $id; +} + +class AttachEntityListenersListenerTestListener +{ + const CLASSNAME = __CLASS__; + + public $calls; + + public function prePersist() + { + $this->calls[__FUNCTION__][] = func_get_args(); + } + + public function postLoadHandler() + { + $this->calls[__FUNCTION__][] = func_get_args(); + } + + public function postPersist() + { + $this->calls[__FUNCTION__][] = func_get_args(); + } +} + +class AttachEntityListenersListenerTestListener2 +{ + const CLASSNAME = __CLASS__; + + public $calls; + + public function prePersist() + { + $this->calls[__FUNCTION__][] = func_get_args(); + } + + public function postPersistHandler() + { + $this->calls[__FUNCTION__][] = func_get_args(); + } +} From 9e3ad91225f0ed05b0e9fbfd7658ec10094a6e01 Mon Sep 17 00:00:00 2001 From: fabios Date: Tue, 19 Nov 2013 11:50:43 -0500 Subject: [PATCH 2/2] Version 2.5.0-DEV --- lib/Doctrine/ORM/Version.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Doctrine/ORM/Version.php b/lib/Doctrine/ORM/Version.php index 4dba7902f..afc27f5da 100644 --- a/lib/Doctrine/ORM/Version.php +++ b/lib/Doctrine/ORM/Version.php @@ -36,7 +36,7 @@ class Version /** * Current Doctrine Version */ - const VERSION = '2.4.0-DEV'; + const VERSION = '2.5.0-DEV'; /** * Compares a Doctrine version with the current one.