Permalink
Browse files

Merge pull request #423 from FabioBatSilva/DDC-1955

DDC-1955 - @EntityListeners
  • Loading branch information...
2 parents dea37ed + c5aecd4 commit 71a68a5c6fcd49538c3ef2f86d64bcde1958251c @beberlei beberlei committed Feb 2, 2013
Showing with 2,162 additions and 164 deletions.
  1. +227 −3 docs/en/reference/events.rst
  2. +16 −0 doctrine-mapping.xsd
  3. +28 −0 lib/Doctrine/ORM/Configuration.php
  4. +120 −0 lib/Doctrine/ORM/Event/ListenersInvoker.php
  5. +72 −0 lib/Doctrine/ORM/Mapping/Builder/EntityListenerBuilder.php
  6. +5 −1 lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php
  7. +57 −23 lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
  8. +75 −0 lib/Doctrine/ORM/Mapping/DefaultEntityListenerResolver.php
  9. +86 −45 lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php
  10. +1 −0 lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php
  11. +25 −4 lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php
  12. +19 −0 lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php
  13. +55 −0 lib/Doctrine/ORM/Mapping/EntityListenerResolver.php
  14. +41 −0 lib/Doctrine/ORM/Mapping/EntityListeners.php
  15. +23 −0 lib/Doctrine/ORM/Mapping/MappingException.php
  16. +56 −78 lib/Doctrine/ORM/UnitOfWork.php
  17. +35 −1 tests/Doctrine/Tests/Models/CMS/CmsAddress.php
  18. +58 −0 tests/Doctrine/Tests/Models/CMS/CmsAddressListener.php
  19. +41 −0 tests/Doctrine/Tests/Models/Company/CompanyContract.php
  20. +84 −0 tests/Doctrine/Tests/Models/Company/CompanyContractListener.php
  21. +9 −0 tests/Doctrine/Tests/Models/Company/CompanyFixContract.php
  22. +15 −0 tests/Doctrine/Tests/Models/Company/CompanyFlexContract.php
  23. +24 −0 tests/Doctrine/Tests/Models/Company/CompanyFlexUltraContract.php
  24. +26 −0 tests/Doctrine/Tests/Models/Company/CompanyFlexUltraContractListener.php
  25. +12 −0 tests/Doctrine/Tests/ORM/ConfigurationTest.php
  26. +247 −0 tests/Doctrine/Tests/ORM/Functional/EntityListenersTest.php
  27. +148 −0 tests/Doctrine/Tests/ORM/Functional/LifecycleCallbackTest.php
  28. +5 −3 tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1707Test.php
  29. +127 −4 tests/Doctrine/Tests/ORM/Mapping/AbstractMappingDriverTest.php
  30. +28 −0 tests/Doctrine/Tests/ORM/Mapping/ClassMetadataTest.php
  31. +97 −0 tests/Doctrine/Tests/ORM/Mapping/EntityListenerResolverTest.php
  32. +34 −1 tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.CMS.CmsAddress.php
  33. +40 −0 tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.Company.CompanyContract.php
  34. +7 −0 tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.Company.CompanyFixContract.php
  35. +13 −0 tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.Company.CompanyFlexContract.php
  36. +21 −0 tests/Doctrine/Tests/ORM/Mapping/php/Doctrine.Tests.Models.Company.CompanyFlexUltraContract.php
  37. +4 −0 tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.CMS.CmsAddress.dcm.xml
  38. +41 −0 tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.Company.CompanyContract.dcm.xml
  39. +16 −0 tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.Company.CompanyFixContract.dcm.xml
  40. +16 −0 tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.Company.CompanyFlexContract.dcm.xml
  41. +36 −0 tests/Doctrine/Tests/ORM/Mapping/xml/Doctrine.Tests.Models.Company.CompanyFlexUltraContract.dcm.xml
  42. +3 −1 tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.CMS.CmsAddress.dcm.yml
  43. +32 −0 tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.Company.CompanyContract.dcm.yml
  44. +5 −0 tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.Company.CompanyFixContract.dcm.yml
  45. +7 −0 tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.Company.CompanyFlexContract.dcm.yml
  46. +25 −0 tests/Doctrine/Tests/ORM/Mapping/yaml/Doctrine.Tests.Models.Company.CompanyFlexUltraContract.dcm.yml
View
230 docs/en/reference/events.rst
@@ -196,9 +196,7 @@ listeners:
- Lifecycle Callbacks are methods on the entity classes that are
- called when the event is triggered. They receive absolutely no
- arguments and are specifically designed to allow changes inside the
- entity classes state.
+ called when the event is triggered. They receives some kind of ``EventArgs``.
- Lifecycle Event Listeners are classes with specific callback
methods that receives some kind of ``EventArgs`` instance which
give access to the entity, EntityManager or other relevant data.
@@ -336,6 +334,31 @@ The ``key`` of the lifecycleCallbacks is the name of the method and
the value is the event type. The allowed event types are the ones
listed in the previous Lifecycle Events section.
+.. versionadded:: 2.4
+
+Lifecycle Callbacks Event Argument
+-----------------------------------
+
+Since 2.4 the triggered event is given to the lifecycle-callback.
+
+With the additional argument you have access to the
+``EntityManager`` and ``UnitOfWork`` APIs inside these callback methods.
+
+.. code-block:: php
+
+ <?php
+ // ...
+
+ class User
+ {
+ public function preUpdate(PreUpdateEventArgs $event)
+ {
+ if ($event->hasChangedField('username')) {
+ // Do something when the username is changed.
+ }
+ }
+ }
+
Listening to Lifecycle Events
-----------------------------
@@ -626,6 +649,207 @@ postLoad
This event is called after an entity is constructed by the
EntityManager.
+Entity listeners
+----------------
+
+An entity listeners is a lifecycle listener classes used for an entity.
+
+- The entity listeners mapping may be applied to an entity class or mapped superclass.
+- An entity listener is defined by mapping the entity class with the corresponding mapping.
+
+.. configuration-block::
+
+ .. code-block:: php
+
+ <?php
+ namespace MyProject\Entity;
+
+ /** @Entity @EntityListeners({"UserListener"}) */
+ class User
+ {
+ // ....
+ }
+ .. code-block:: xml
+
+ <doctrine-mapping>
+ <entity name="MyProject\Entity\User">
+ <entity-listeners>
+ <entity-listener class="UserListener"/>
+ </entity-listeners>
+ <!-- .... -->
+ </entity>
+ </doctrine-mapping>
+ .. code-block:: yaml
+
+ MyProject\Entity\User:
+ type: entity
+ entityListeners:
+ UserListener:
+ # ....
+
+.. _reference-entity-listeners:
+
+Entity listeners class
+~~~~~~~~~~~~~~~~~~~~~~
+
+An ``Entity Listener`` could be any class, by default it should be a class with a no-arg constructor.
+
+- Different from :ref:`reference-events-implementing-listeners` an ``Entity Listener`` is invoked just to the specified entity
+- An entity listener method receives two arguments, the entity instance and the lifecycle event.
+- A callback method could be defined by naming convention or specifying a method mapping.
+- When the listener mapping is not given the parser will lookup for methods that match with the naming convention.
+- When the listener mapping is given the parser won't lookup for any naming convention.
+
+.. code-block:: php
+
+ <?php
+ class UserListener
+ {
+ public function preUpdate(User $user, PreUpdateEventArgs $event)
+ {
+ // Do something on pre update.
+ }
+ }
+
+To define a specific event listener method
+you should map the listener method using the event type mapping.
+
+.. configuration-block::
+
+ .. code-block:: php
+
+ <?php
+ class UserListener
+ {
+ /** @PrePersist */
+ public function prePersistHandler(User $user, LifecycleEventArgs $event) { // ... }
+
+ /** @PostPersist */
+ public function postPersistHandler(User $user, LifecycleEventArgs $event) { // ... }
+
+ /** @PreUpdate */
+ public function preUpdateHandler(User $user, PreUpdateEventArgs $event) { // ... }
+
+ /** @PostUpdate */
+ public function postUpdateHandler(User $user, LifecycleEventArgs $event) { // ... }
+
+ /** @PostRemove */
+ public function postRemoveHandler(User $user, LifecycleEventArgs $event) { // ... }
+
+ /** @PreRemove */
+ public function preRemoveHandler(User $user, LifecycleEventArgs $event) { // ... }
+
+ /** @PreFlush */
+ public function preFlushHandler(User $user, PreFlushEventArgs $event) { // ... }
+
+ /** @PostLoad */
+ public function postLoadHandler(User $user, LifecycleEventArgs $event) { // ... }
+ }
+ .. code-block:: xml
+
+ <doctrine-mapping>
+ <entity name="MyProject\Entity\User">
+ <entity-listeners>
+ <entity-listener class="UserListener">
+ <lifecycle-callback type="preFlush" method="preFlushHandler"/>
+ <lifecycle-callback type="postLoad" method="postLoadHandler"/>
+
+ <lifecycle-callback type="postPersist" method="postPersistHandler"/>
+ <lifecycle-callback type="prePersist" method="prePersistHandler"/>
+
+ <lifecycle-callback type="postUpdate" method="postUpdateHandler"/>
+ <lifecycle-callback type="preUpdate" method="preUpdateHandler"/>
+
+ <lifecycle-callback type="postRemove" method="postRemoveHandler"/>
+ <lifecycle-callback type="preRemove" method="preRemoveHandler"/>
+ </entity-listener>
+ </entity-listeners>
+ <!-- .... -->
+ </entity>
+ </doctrine-mapping>
+ .. code-block:: yaml
+
+ MyProject\Entity\User:
+ type: entity
+ entityListeners:
+ UserListener:
+ preFlush: [preFlushHandler]
+ postLoad: [postLoadHandler]
+
+ postPersist: [postPersistHandler]
+ prePersist: [prePersistHandler]
+
+ postUpdate: [postUpdateHandler]
+ preUpdate: [preUpdateHandler]
+
+ postRemove: [postRemoveHandler]
+ preRemove: [preRemoveHandler]
+ # ....
+
+
+
+Entity listeners resolver
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+Doctrine invoke the listener resolver to get the listener instance.
+
+- An resolver allows you register a specific ``Entity Listener`` instance.
+- You can also implement your own resolver by extending ``Doctrine\ORM\Mapping\DefaultEntityListenerResolver`` or implementing ``Doctrine\ORM\Mapping\EntityListenerResolver``
+
+Specifying an entity listener instance :
+
+.. code-block:: php
+
+ <?php
+ // User.php
+
+ /** @Entity @EntityListeners({"UserListener"}) */
+ class User
+ {
+ // ....
+ }
+
+ // UserListener.php
+ class UserListener
+ {
+ public function __construct(MyService $service)
+ {
+ $this->service = $service;
+ }
+
+ public function preUpdate(User $user, PreUpdateEventArgs $event)
+ {
+ $this->service->doSomething($user);
+ }
+ }
+
+ // register a entity listener.
+ $listener = $container->get('user_listener');
+ $em->getConfiguration()->getEntityListenerResolver()->register($listener);
+
+Implementing your own resolver :
+
+.. code-block:: php
+
+ <?php
+ class MyEntityListenerResolver extends \Doctrine\ORM\Mapping\DefaultEntityListenerResolver
+ {
+ public function __construct($container)
+ {
+ $this->container = $container;
+ }
+
+ public function resolve($className)
+ {
+ // resolve the service id by the given class name;
+ $id = 'user_listener';
+
+ return $this->container->get($id);
+ }
+ }
+
+ // configure the listener resolver.
+ $em->getConfiguration()->setEntityListenerResolver($container->get('my_resolver'));
+
Load ClassMetadata Event
------------------------
View
16 doctrine-mapping.xsd
@@ -51,6 +51,7 @@
<xs:enumeration value="preRemove"/>
<xs:enumeration value="postRemove"/>
<xs:enumeration value="postLoad"/>
+ <xs:enumeration value="preFlush"/>
</xs:restriction>
</xs:simpleType>
@@ -98,6 +99,20 @@
</xs:sequence>
</xs:complexType>
+ <xs:complexType name="entity-listener">
+ <xs:sequence>
+ <xs:element name="lifecycle-callback" type="orm:lifecycle-callback" minOccurs="0" maxOccurs="unbounded"/>
+ <xs:any minOccurs="1" maxOccurs="unbounded" namespace="##other"/>
+ </xs:sequence>
+ <xs:attribute name="class" type="xs:string"/>
+ </xs:complexType>
+
+ <xs:complexType name="entity-listeners">
+ <xs:sequence>
+ <xs:element name="entity-listener" type="orm:entity-listener" minOccurs="1" maxOccurs="unbounded" />
+ </xs:sequence>
+ </xs:complexType>
+
<xs:complexType name="column-result">
<xs:attribute name="name" type="xs:string" use="required" />
</xs:complexType>
@@ -137,6 +152,7 @@
<xs:element name="discriminator-column" type="orm:discriminator-column" minOccurs="0"/>
<xs:element name="discriminator-map" type="orm:discriminator-map" minOccurs="0"/>
<xs:element name="lifecycle-callbacks" type="orm:lifecycle-callbacks" minOccurs="0" maxOccurs="1" />
+ <xs:element name="entity-listeners" type="orm:entity-listeners" minOccurs="0" maxOccurs="1" />
<xs:element name="named-queries" type="orm:named-queries" minOccurs="0" maxOccurs="1" />
<xs:element name="named-native-queries" type="orm:named-native-queries" minOccurs="0" maxOccurs="1" />
<xs:element name="id" type="orm:id" minOccurs="0" maxOccurs="unbounded" />
View
28 lib/Doctrine/ORM/Configuration.php
@@ -29,6 +29,8 @@
use Doctrine\ORM\Mapping\DefaultQuoteStrategy;
use Doctrine\ORM\Mapping\NamingStrategy;
use Doctrine\ORM\Mapping\DefaultNamingStrategy;
+use Doctrine\ORM\Mapping\EntityListenerResolver;
+use Doctrine\ORM\Mapping\DefaultEntityListenerResolver;
use Doctrine\Common\Annotations\SimpleAnnotationReader;
use Doctrine\Common\Annotations\CachedReader;
@@ -763,4 +765,30 @@ public function getQuoteStrategy()
return $this->_attributes['quoteStrategy'];
}
+
+ /**
+ * Set the entity listener resolver.
+ *
+ * @since 2.4
+ * @param \Doctrine\ORM\Mapping\EntityListenerResolver $resolver
+ */
+ public function setEntityListenerResolver(EntityListenerResolver $resolver)
+ {
+ $this->_attributes['entityListenerResolver'] = $resolver;
+ }
+
+ /**
+ * Get the entity listener resolver.
+ *
+ * @since 2.4
+ * @return \Doctrine\ORM\Mapping\EntityListenerResolver
+ */
+ public function getEntityListenerResolver()
+ {
+ if ( ! isset($this->_attributes['entityListenerResolver'])) {
+ $this->_attributes['entityListenerResolver'] = new DefaultEntityListenerResolver();
+ }
+
+ return $this->_attributes['entityListenerResolver'];
+ }
}
View
120 lib/Doctrine/ORM/Event/ListenersInvoker.php
@@ -0,0 +1,120 @@
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Event;
+
+use Doctrine\ORM\Mapping\ClassMetadata;
+use Doctrine\ORM\EntityManager;
+use Doctrine\Common\EventArgs;
+
+/**
+ * A method invoker based on entity lifecycle.
+ *
+ * @author Fabio B. Silva <fabio.bat.silva@gmail.com>
+ * @since 2.4
+ */
+class ListenersInvoker
+{
+ const INVOKE_NONE = 0;
+ const INVOKE_LISTENERS = 1;
+ const INVOKE_CALLBACKS = 2;
+ const INVOKE_MANAGER = 4;
+
+ /**
+ * @var \Doctrine\ORM\Mapping\EntityListenerResolver The Entity listener resolver.
+ */
+ private $resolver;
+
+ /**
+ * The EventManager used for dispatching events.
+ *
+ * @var \Doctrine\Common\EventManager
+ */
+ private $eventManager;
+
+ /**
+ * Initializes a new ListenersInvoker instance.
+ *
+ * @param \Doctrine\ORM\EntityManager $em
+ */
+ public function __construct(EntityManager $em)
+ {
+ $this->eventManager = $em->getEventManager();
+ $this->resolver = $em->getConfiguration()->getEntityListenerResolver();
+ }
+
+ /**
+ * Get the subscribed event systems
+ *
+ * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
+ * @param string $eventName The entity lifecycle event.
+ *
+ * @return integer Bitmask of subscribed event systems.
+ */
+ public function getSubscribedSystems(ClassMetadata $metadata, $eventName)
+ {
+ $invoke = self::INVOKE_NONE;
+
+ if (isset($metadata->lifecycleCallbacks[$eventName])) {
+ $invoke |= self::INVOKE_CALLBACKS;
+ }
+
+ if (isset($metadata->entityListeners[$eventName])) {
+ $invoke |= self::INVOKE_LISTENERS;
+ }
+
+ if ($this->eventManager->hasListeners($eventName)) {
+ $invoke |= self::INVOKE_MANAGER;
+ }
+
+ return $invoke;
+ }
+
+ /**
+ * Dispatches the lifecycle event of the given entity.
+ *
+ * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
+ * @param string $eventName The entity lifecycle event.
+ * @param object $entity The Entity on which the event occured.
+ * @param \Doctrine\Common\EventArgs $event The Event args.
+ * @param integer $invoke Bitmask to invoke listeners.
+ */
+ public function invoke(ClassMetadata $metadata, $eventName, $entity, EventArgs $event, $invoke)
+ {
+ if($invoke & self::INVOKE_CALLBACKS) {
+ foreach ($metadata->lifecycleCallbacks[$eventName] as $callback) {
+ $entity->$callback($event);
+ }
+ }
+
+ if($invoke & self::INVOKE_LISTENERS) {
+ foreach ($metadata->entityListeners[$eventName] as $listener) {
+ $class = $listener['class'];
+ $method = $listener['method'];
+ $instance = $this->resolver->resolve($class);
+
+ $instance->$method($entity, $event);
+ }
+ }
+
+ if($invoke & self::INVOKE_MANAGER) {
+ $this->eventManager->dispatchEvent($eventName, $event);
+ }
+ }
+}
View
72 lib/Doctrine/ORM/Mapping/Builder/EntityListenerBuilder.php
@@ -0,0 +1,72 @@
+<?php
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Mapping\Builder;
+
+use Doctrine\ORM\Mapping\MappingException;
+use Doctrine\ORM\Mapping\ClassMetadata;
+use Doctrine\ORM\Events;
+
+/**
+ * Builder for entity listeners.
+ *
+ * @since 2.4
+ * @author Fabio B. Silva <fabio.bat.silva@gmail.com>
+ */
+class EntityListenerBuilder
+{
+ /**
+ * @var array Hash-map to handle event names.
+ */
+ static private $events = array(
+ Events::preRemove => true,
+ Events::postRemove => true,
+ Events::prePersist => true,
+ Events::postPersist => true,
+ Events::preUpdate => true,
+ Events::postUpdate => true,
+ Events::postLoad => true,
+ Events::preFlush => true
+ );
+
+ /**
+ * Lookup the entity class to find methods that match to event lifecycle names
+ *
+ * @param \Doctrine\ORM\Mapping\ClassMetadata $metadata The entity metadata.
+ * @param string $className The listener class name.
+ *
+ * @throws \Doctrine\ORM\Mapping\MappingException When the listener class not found.
+ */
+ static public function bindEntityListener(ClassMetadata $metadata, $className)
+ {
+ $class = $metadata->fullyQualifiedClassName($className);
+
+ if ( ! class_exists($class)) {
+ throw MappingException::entityListenerClassNotFound($class, $className);
+ }
+
+ foreach (get_class_methods($class) as $method) {
+ if ( ! isset(self::$events[$method])) {
+ continue;
+ }
+
+ $metadata->addEntityListener($method, $class, $method);
+ }
+ }
+}
View
6 lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php
@@ -156,6 +156,10 @@ protected function doLoadMetadata($class, $parent, $rootEntityFound, array $nonS
$this->addInheritedSqlResultSetMappings($class, $parent);
}
+ if ($parent && !empty($parent->entityListeners) && empty($class->entityListeners)) {
+ $class->entityListeners = $parent->entityListeners;
+ }
+
$class->setParentClasses($nonSuperclassParents);
if ( $class->isRootEntity() && ! $class->isInheritanceTypeNone() && ! $class->discriminatorMap) {
@@ -458,7 +462,7 @@ private function completeIdGeneratorMapping(ClassMetadataInfo $class)
$sequenceName = $this->em->getConfiguration()->getQuoteStrategy()->getSequenceName($definition, $class, $this->targetPlatform);
}
- $generator = ($fieldName && $class->fieldMappings[$fieldName]['type'] === "bigint")
+ $generator = ($fieldName && $class->fieldMappings[$fieldName]['type'] === 'bigint')
? new BigIntegerIdentityGenerator($sequenceName)
: new IdentityGenerator($sequenceName);
View
80 lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
@@ -26,6 +26,7 @@
use ReflectionClass;
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
use Doctrine\Common\ClassLoader;
+use Doctrine\Common\EventArgs;
/**
* A <tt>ClassMetadata</tt> instance holds all the object-relational mapping metadata
@@ -437,6 +438,13 @@ class ClassMetadataInfo implements ClassMetadata
public $lifecycleCallbacks = array();
/**
+ * READ-ONLY: The registered entity listeners.
+ *
+ * @var array
+ */
+ public $entityListeners = array();
+
+ /**
* READ-ONLY: The association mappings of this class.
*
* The mapping definition array supports the following keys:
@@ -1313,10 +1321,7 @@ protected function _validateAndCompleteAssociationMapping(array $mapping)
$mapping['sourceEntity'] = $this->name;
if (isset($mapping['targetEntity'])) {
- if (strlen($this->namespace) > 0 && strpos($mapping['targetEntity'], '\\') === false) {
- $mapping['targetEntity'] = $this->namespace . '\\' . $mapping['targetEntity'];
- }
-
+ $mapping['targetEntity'] = $this->fullyQualifiedClassName($mapping['targetEntity']);
$mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\');
}
@@ -1904,11 +1909,7 @@ public function getTemporaryIdTableName()
public function setSubclasses(array $subclasses)
{
foreach ($subclasses as $subclass) {
- if (strpos($subclass, '\\') === false && strlen($this->namespace)) {
- $this->subClasses[] = $this->namespace . '\\' . $subclass;
- } else {
- $this->subClasses[] = $subclass;
- }
+ $this->subClasses[] = $this->fullyQualifiedClassName($subclass);
}
}
@@ -2261,11 +2262,9 @@ public function addNamedNativeQuery(array $queryMapping)
$queryMapping['isSelfClass'] = true;
$queryMapping['resultClass'] = $this->name;
-
- } else if (strlen($this->namespace) > 0 && strpos($queryMapping['resultClass'], '\\') === false) {
- $queryMapping['resultClass'] = $this->namespace . '\\' . $queryMapping['resultClass'];
}
+ $queryMapping['resultClass'] = $this->fullyQualifiedClassName($queryMapping['resultClass']);
$queryMapping['resultClass'] = ltrim($queryMapping['resultClass'], '\\');
}
@@ -2304,10 +2303,10 @@ public function addSqlResultSetMapping(array $resultMapping)
$entityResult['isSelfClass'] = true;
$entityResult['entityClass'] = $this->name;
- } else if (strlen($this->namespace) > 0 && strpos($entityResult['entityClass'], '\\') === false) {
- $entityResult['entityClass'] = $this->namespace . '\\' . $entityResult['entityClass'];
}
+ $entityResult['entityClass'] = $this->fullyQualifiedClassName($entityResult['entityClass']);
+
$resultMapping['entities'][$key]['entityClass'] = ltrim($entityResult['entityClass'], '\\');
$resultMapping['entities'][$key]['isSelfClass'] = $entityResult['isSelfClass'];
@@ -2419,17 +2418,15 @@ protected function _storeAssociationMapping(array $assocMapping)
*/
public function setCustomRepositoryClass($repositoryClassName)
{
- if ($repositoryClassName !== null && strpos($repositoryClassName, '\\') === false
- && strlen($this->namespace) > 0) {
- $repositoryClassName = $this->namespace . '\\' . $repositoryClassName;
- }
- $this->customRepositoryClassName = $repositoryClassName;
+ $this->customRepositoryClassName = $this->fullyQualifiedClassName($repositoryClassName);
}
/**
* Dispatches the lifecycle event of the given entity to the registered
* lifecycle callbacks and lifecycle listeners.
*
+ * @deprecated Deprecated since version 2.4 in favor of \Doctrine\ORM\Event\ListenersInvoker
+ *
* @param string $lifecycleEvent The lifecycle event.
* @param object $entity The Entity on which the event occured.
*
@@ -2493,6 +2490,33 @@ public function setLifecycleCallbacks(array $callbacks)
}
/**
+ * Adds a entity listener for entities of this class.
+ *
+ * @param string $eventName The entity lifecycle event.
+ * @param string $class The listener class.
+ * @param string $method The listener callback method.
+ *
+ * @throws \Doctrine\ORM\Mapping\MappingException
+ */
+ public function addEntityListener($eventName, $class, $method)
+ {
+ $class = $this->fullyQualifiedClassName($class);
+
+ if ( ! class_exists($class)) {
+ throw MappingException::entityListenerClassNotFound($class, $this->name);
+ }
+
+ if ( ! method_exists($class, $method)) {
+ throw MappingException::entityListenerMethodNotFound($class, $method, $this->name);
+ }
+
+ $this->entityListeners[$eventName][] = array(
+ 'class' => $class,
+ 'method' => $method
+ );
+ }
+
+ /**
* Sets the discriminator column definition.
*
* @param array $columnDef
@@ -2557,10 +2581,7 @@ public function setDiscriminatorMap(array $map)
*/
public function addDiscriminatorMapClass($name, $className)
{
- if (strlen($this->namespace) > 0 && strpos($className, '\\') === false) {
- $className = $this->namespace . '\\' . $className;
- }
-
+ $className = $this->fullyQualifiedClassName($className);
$className = ltrim($className, '\\');
$this->discriminatorMap[$name] = $className;
@@ -2982,4 +3003,17 @@ public function getAssociationsByTargetClass($targetClass)
}
return $relations;
}
+
+ /**
+ * @param string $className
+ * @return string
+ */
+ public function fullyQualifiedClassName($className)
+ {
+ if ($className !== null && strpos($className, '\\') === false && strlen($this->namespace) > 0) {
+ return $this->namespace . '\\' . $className;
+ }
+
+ return $className;
+ }
}
View
75 lib/Doctrine/ORM/Mapping/DefaultEntityListenerResolver.php
@@ -0,0 +1,75 @@
+<?php
+
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Mapping;
+
+/**
+ * The default DefaultEntityListene
+ *
+ * @since 2.4
+ * @author Fabio B. Silva <fabio.bat.silva@gmail.com>
+ */
+class DefaultEntityListenerResolver implements EntityListenerResolver
+{
+ /**
+ * @var array Map to store entity listener instances.
+ */
+ private $instances = array();
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear($className = null)
+ {
+ if ($className === null) {
+ $this->instances = array();
+
+ return;
+ }
+
+ if (isset($this->instances[$className = trim($className, '\\')])) {
+ unset($this->instances[$className]);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function register($object)
+ {
+ if ( ! is_object($object)) {
+ throw new \InvalidArgumentException(sprintf('An object was expected, but got "%s".', gettype($object)));
+ }
+
+ $this->instances[get_class($object)] = $object;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function resolve($className)
+ {
+ if (isset($this->instances[$className = trim($className, '\\')])) {
+ return $this->instances[$className];
+ }
+
+ return $this->instances[$className] = new $className();
+ }
+}
View
131 lib/Doctrine/ORM/Mapping/Driver/AnnotationDriver.php
@@ -23,8 +23,10 @@
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\ORM\Mapping\JoinColumn;
use Doctrine\ORM\Mapping\Column;
+use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
use Doctrine\Common\Persistence\Mapping\Driver\AnnotationDriver as AbstractAnnotationDriver;
+use Doctrine\ORM\Events;
/**
* The AnnotationDriver reads the mapping metadata from docblock annotations.
@@ -384,9 +386,8 @@ public function loadMetadataForClass($className, ClassMetadata $metadata)
// Check for JoinTable annotations
if ($associationOverride->joinTable) {
- $joinTable = null;
$joinTableAnnot = $associationOverride->joinTable;
- $joinTable = array(
+ $joinTable = array(
'name' => $joinTableAnnot->name,
'schema' => $joinTableAnnot->schema
);
@@ -415,54 +416,47 @@ public function loadMetadataForClass($className, ClassMetadata $metadata)
}
}
- // Evaluate @HasLifecycleCallbacks annotation
- if (isset($classAnnotations['Doctrine\ORM\Mapping\HasLifecycleCallbacks'])) {
- /* @var $method \ReflectionMethod */
- foreach ($class->getMethods() as $method) {
- // filter for the declaring class only, callbacks from parents will already be registered.
- if ($method->isPublic() && $method->getDeclaringClass()->getName() == $class->name) {
- $annotations = $this->reader->getMethodAnnotations($method);
-
- if ($annotations) {
- foreach ($annotations as $key => $annot) {
- if ( ! is_numeric($key)) {
- continue;
- }
- $annotations[get_class($annot)] = $annot;
- }
- }
+ // Evaluate EntityListeners annotation
+ if (isset($classAnnotations['Doctrine\ORM\Mapping\EntityListeners'])) {
+ $entityListenersAnnot = $classAnnotations['Doctrine\ORM\Mapping\EntityListeners'];
- if (isset($annotations['Doctrine\ORM\Mapping\PrePersist'])) {
- $metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::prePersist);
- }
+ foreach ($entityListenersAnnot->value as $item) {
+ $listenerClassName = $metadata->fullyQualifiedClassName($item);
- if (isset($annotations['Doctrine\ORM\Mapping\PostPersist'])) {
- $metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postPersist);
- }
-
- if (isset($annotations['Doctrine\ORM\Mapping\PreUpdate'])) {
- $metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::preUpdate);
- }
-
- if (isset($annotations['Doctrine\ORM\Mapping\PostUpdate'])) {
- $metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postUpdate);
- }
+ if ( ! class_exists($listenerClassName)) {
+ throw MappingException::entityListenerClassNotFound($listenerClassName, $className);
+ }
- if (isset($annotations['Doctrine\ORM\Mapping\PreRemove'])) {
- $metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::preRemove);
- }
+ $hasMapping = false;
+ $listenerClass = new \ReflectionClass($listenerClassName);
+ /* @var $method \ReflectionMethod */
+ foreach ($listenerClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
+ // find method callbacks.
+ $callbacks = $this->getMethodCallbacks($method);
+ $hasMapping = $hasMapping ?: ( ! empty($callbacks));
- if (isset($annotations['Doctrine\ORM\Mapping\PostRemove'])) {
- $metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postRemove);
+ foreach ($callbacks as $value) {
+ $metadata->addEntityListener($value[1], $listenerClassName, $value[0]);
}
+ }
+ // Evaluate the listener using naming convention.
+ if ( ! $hasMapping ) {
+ EntityListenerBuilder::bindEntityListener($metadata, $listenerClassName);
+ }
+ }
+ }
- if (isset($annotations['Doctrine\ORM\Mapping\PostLoad'])) {
- $metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postLoad);
- }
+ // Evaluate @HasLifecycleCallbacks annotation
+ if (isset($classAnnotations['Doctrine\ORM\Mapping\HasLifecycleCallbacks'])) {
+ /* @var $method \ReflectionMethod */
+ foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
+ // filter for the declaring class only, callbacks from parents will already be registered.
+ if ($method->getDeclaringClass()->name !== $class->name) {
+ continue;
+ }
- if (isset($annotations['Doctrine\ORM\Mapping\PreFlush'])) {
- $metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::preFlush);
- }
+ foreach ($this->getMethodCallbacks($method) as $value) {
+ $metadata->addLifecycleCallback($value[0], $value[1]);
}
}
}
@@ -488,9 +482,56 @@ private function getFetchMode($className, $fetchMode)
}
/**
- * Parses the given JoinColumn as array.
+ * Parses the given method.
*
- * @param JoinColumn $joinColumn
+ * @param \ReflectionMethod $method
+ *
+ * @return array
+ */
+ private function getMethodCallbacks(\ReflectionMethod $method)
+ {
+ $callbacks = array();
+ $annotations = $this->reader->getMethodAnnotations($method);
+
+ foreach ($annotations as $annot) {
+ if ($annot instanceof \Doctrine\ORM\Mapping\PrePersist) {
+ $callbacks[] = array($method->name, Events::prePersist);
+ }
+
+ if ($annot instanceof \Doctrine\ORM\Mapping\PostPersist) {
+ $callbacks[] = array($method->name, Events::postPersist);
+ }
+
+ if ($annot instanceof \Doctrine\ORM\Mapping\PreUpdate) {
+ $callbacks[] = array($method->name, Events::preUpdate);
+ }
+
+ if ($annot instanceof \Doctrine\ORM\Mapping\PostUpdate) {
+ $callbacks[] = array($method->name, Events::postUpdate);
+ }
+
+ if ($annot instanceof \Doctrine\ORM\Mapping\PreRemove) {
+ $callbacks[] = array($method->name, Events::preRemove);
+ }
+
+ if ($annot instanceof \Doctrine\ORM\Mapping\PostRemove) {
+ $callbacks[] = array($method->name, Events::postRemove);
+ }
+
+ if ($annot instanceof \Doctrine\ORM\Mapping\PostLoad) {
+ $callbacks[] = array($method->name, Events::postLoad);
+ }
+
+ if ($annot instanceof \Doctrine\ORM\Mapping\PreFlush) {
+ $callbacks[] = array($method->name, Events::preFlush);
+ }
+ }
+
+ return $callbacks;
+ }
+
+ /**
+ * Parse the given JoinColumn as array
*
* @return array
*/
View
1 lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php
@@ -64,3 +64,4 @@
require_once __DIR__.'/../AssociationOverrides.php';
require_once __DIR__.'/../AttributeOverride.php';
require_once __DIR__.'/../AttributeOverrides.php';
+require_once __DIR__.'/../EntityListeners.php';
View
29 lib/Doctrine/ORM/Mapping/Driver/XmlDriver.php
@@ -19,10 +19,11 @@
namespace Doctrine\ORM\Mapping\Driver;
-use SimpleXMLElement,
- Doctrine\Common\Persistence\Mapping\Driver\FileDriver,
- Doctrine\Common\Persistence\Mapping\ClassMetadata,
- Doctrine\ORM\Mapping\MappingException;
+use SimpleXMLElement;
+use Doctrine\Common\Persistence\Mapping\Driver\FileDriver;
+use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
+use Doctrine\Common\Persistence\Mapping\ClassMetadata;
+use Doctrine\ORM\Mapping\MappingException;
/**
* XmlDriver is a metadata driver that enables mapping through XML files.
@@ -556,6 +557,26 @@ public function loadMetadataForClass($className, ClassMetadata $metadata)
$metadata->addLifecycleCallback((string)$lifecycleCallback['method'], constant('Doctrine\ORM\Events::' . (string)$lifecycleCallback['type']));
}
}
+
+ // Evaluate entity listener
+ if (isset($xmlRoot->{'entity-listeners'})) {
+ foreach ($xmlRoot->{'entity-listeners'}->{'entity-listener'} as $listenerElement) {
+ $className = (string) $listenerElement['class'];
+ // Evaluate the listener using naming convention.
+ if($listenerElement->count() === 0) {
+ EntityListenerBuilder::bindEntityListener($metadata, $className);
+
+ continue;
+ }
+
+ foreach ($listenerElement as $callbackElement) {
+ $eventName = (string) $callbackElement['type'];
+ $methodName = (string) $callbackElement['method'];
+
+ $metadata->addEntityListener($eventName, $className, $methodName);
+ }
+ }
+ }
}
/**
View
19 lib/Doctrine/ORM/Mapping/Driver/YamlDriver.php
@@ -20,6 +20,7 @@
namespace Doctrine\ORM\Mapping\Driver;
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
+use Doctrine\ORM\Mapping\Builder\EntityListenerBuilder;
use Doctrine\Common\Persistence\Mapping\Driver\FileDriver;
use Doctrine\ORM\Mapping\MappingException;
use Symfony\Component\Yaml\Yaml;
@@ -572,6 +573,24 @@ public function loadMetadataForClass($className, ClassMetadata $metadata)
}
}
}
+
+ // Evaluate entityListeners
+ if (isset($element['entityListeners'])) {
+ foreach ($element['entityListeners'] as $className => $entityListener) {
+ // Evaluate the listener using naming convention.
+ if (empty($entityListener)) {
+ EntityListenerBuilder::bindEntityListener($metadata, $className);
+
+ continue;
+ }
+
+ foreach ($entityListener as $eventName => $callbackElement){
+ foreach ($callbackElement as $methodName){
+ $metadata->addEntityListener($eventName, $className, $methodName);
+ }
+ }
+ }
+ }
}
/**
View
55 lib/Doctrine/ORM/Mapping/EntityListenerResolver.php
@@ -0,0 +1,55 @@
+<?php
+
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Mapping;
+
+/**
+ * A resolver is used to instantiate an entity listener.
+ *
+ * @since 2.4
+ * @author Fabio B. Silva <fabio.bat.silva@gmail.com>
+ */
+interface EntityListenerResolver
+{
+ /**
+ * Clear all instances from the set, or a specific class when given.
+ *
+ * @param string $className The fully-qualified class name
+ *
+ * @return void
+ */
+ function clear($className = null);
+
+ /**
+ * Returns a entity listener instance for the given class name.
+ *
+ * @param string $className The fully-qualified class name
+ *
+ * @return object An entity listener
+ */
+ function resolve($className);
+
+ /**
+ * Register a entity listener instance.
+ *
+ * @return object An entity listener
+ */
+ function register($object);
+}
View
41 lib/Doctrine/ORM/Mapping/EntityListeners.php
@@ -0,0 +1,41 @@
+<?php
+
+/*
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the MIT license. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Doctrine\ORM\Mapping;
+
+/**
+ * The EntityListeners annotation specifies the callback listener classes to be used for an entity or mapped superclass.
+ * The EntityListeners annotation may be applied to an entity class or mapped superclass.
+ *
+ * @author Fabio B. Silva <fabio.bat.silva@gmail.com>
+ * @since 2.4
+ *
+ * @Annotation
+ * @Target("CLASS")
+ */
+final class EntityListeners implements Annotation
+{
+ /**
+ * Specifies the names of the entity listeners.
+ *
+ * @var array<string>
+ */
+ public $value = array();
+}
View
23 lib/Doctrine/ORM/Mapping/MappingException.php
@@ -686,6 +686,29 @@ public static function lifecycleCallbackMethodNotFound($className, $methodName)
/**
* @param string $className
+ * @param string $methodName
+ *
+ * @return \Doctrine\ORM\Mapping\MappingException
+ */
+ public static function entityListenerClassNotFound($listenerName, $className)
+ {
+ return new self(sprintf('Entity Listener "%s" declared on "%s" not found.', $listenerName, $className));
+ }
+
+ /**
+ * @param string $listenerName
+ * @param string $methodName
+ * @param string $className
+ *
+ * @return \Doctrine\ORM\Mapping\MappingException
+ */
+ public static function entityListenerMethodNotFound($listenerName, $methodName, $className)
+ {
+ return new self(sprintf('Entity Listener "%s" declared on "%s" has no method "%s".', $listenerName, $className, $methodName));
+ }
+
+ /**
+ * @param string $className
* @param string $annotation
*
* @return MappingException
View
134 lib/Doctrine/ORM/UnitOfWork.php
@@ -22,15 +22,22 @@
use Exception;
use InvalidArgumentException;
use UnexpectedValueException;
+
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\NotifyPropertyChanged;
use Doctrine\Common\PropertyChangedListener;
use Doctrine\Common\Persistence\ObjectManagerAware;
-use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Proxy\Proxy;
+use Doctrine\ORM\Event\LifecycleEventArgs;
+use Doctrine\ORM\Event\PreUpdateEventArgs;
+use Doctrine\ORM\Event\PreFlushEventArgs;
+use Doctrine\ORM\Event\OnFlushEventArgs;
+use Doctrine\ORM\Event\PostFlushEventArgs;
+use Doctrine\ORM\Event\ListenersInvoker;
+
/**
* The UnitOfWork is responsible for tracking changes to objects during an
* "object-level" transaction and for writing out changes to the database
@@ -213,6 +220,13 @@ class UnitOfWork implements PropertyChangedListener
private $evm;
/**
+ * The ListenersInvoker used for dispatching events.
+ *
+ * @var \Doctrine\ORM\Event\ListenersInvoker
+ */
+ private $listenersInvoker;
+
+ /**
* Orphaned entities that are scheduled for removal.
*
* @var array
@@ -240,8 +254,9 @@ class UnitOfWork implements PropertyChangedListener
*/
public function __construct(EntityManager $em)
{
- $this->em = $em;
- $this->evm = $em->getEventManager();
+ $this->em = $em;
+ $this->evm = $em->getEventManager();
+ $this->listenersInvoker = new ListenersInvoker($em);
}
/**
@@ -267,7 +282,7 @@ public function commit($entity = null)
{
// Raise preFlush
if ($this->evm->hasListeners(Events::preFlush)) {
- $this->evm->dispatchEvent(Events::preFlush, new Event\PreFlushEventArgs($this->em));
+ $this->evm->dispatchEvent(Events::preFlush, new PreFlushEventArgs($this->em));
}
// Compute changes done since last commit.
@@ -506,9 +521,10 @@ public function computeChangeSet(ClassMetadata $class, $entity)
$class = $this->em->getClassMetadata(get_class($entity));
}
- // Fire PreFlush lifecycle callbacks
- if (isset($class->lifecycleCallbacks[Events::preFlush])) {
- $class->invokeLifecycleCallbacks(Events::preFlush, $entity);
+ $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preFlush);
+
+ if ($invoke !== ListenersInvoker::INVOKE_NONE) {
+ $this->listenersInvoker->invoke($class, Events::preFlush, $entity, new PreFlushEventArgs($entity, $this->em), $invoke);
}
$actualData = array();
@@ -798,21 +814,18 @@ private function computeAssociationChanges($assoc, $value)
}
/**
- * @param ClassMetadata $class
+ * @param \Doctrine\ORM\Mapping\ClassMetadata $class
* @param object $entity
*
* @return void
*/
private function persistNew($class, $entity)
{
- $oid = spl_object_hash($entity);
+ $oid = spl_object_hash($entity);
+ $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::prePersist);
- if (isset($class->lifecycleCallbacks[Events::prePersist])) {
- $class->invokeLifecycleCallbacks(Events::prePersist, $entity);
- }
-
- if ($this->evm->hasListeners(Events::prePersist)) {
- $this->evm->dispatchEvent(Events::prePersist, new LifecycleEventArgs($entity, $this->em));
+ if ($invoke !== ListenersInvoker::INVOKE_NONE) {
+ $this->listenersInvoker->invoke($class, Events::prePersist, $entity, new LifecycleEventArgs($entity, $this->em), $invoke);
}
$idGen = $class->idGenerator;
@@ -908,12 +921,10 @@ public function recomputeSingleEntityChangeSet(ClassMetadata $class, $entity)
*/
private function executeInserts($class)
{
- $className = $class->name;
- $persister = $this->getEntityPersister($className);
- $entities = array();
-
- $hasLifecycleCallbacks = isset($class->lifecycleCallbacks[Events::postPersist]);
- $hasListeners = $this->evm->hasListeners(Events::postPersist);
+ $entities = array();
+ $className = $class->name;
+ $persister = $this->getEntityPersister($className);
+ $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postPersist);
foreach ($this->entityInsertions as $oid => $entity) {
if ($this->em->getClassMetadata(get_class($entity))->name !== $className) {
@@ -924,7 +935,7 @@ private function executeInserts($class)
unset($this->entityInsertions[$oid]);
- if ($hasLifecycleCallbacks || $hasListeners) {
+ if ($invoke !== ListenersInvoker::INVOKE_NONE) {
$entities[] = $entity;
}
}
@@ -948,13 +959,7 @@ private function executeInserts($class)
}
foreach ($entities as $entity) {
- if ($hasLifecycleCallbacks) {
- $class->invokeLifecycleCallbacks(Events::postPersist, $entity);
- }
-
- if ($hasListeners) {
- $this->evm->dispatchEvent(Events::postPersist, new LifecycleEventArgs($entity, $this->em));
- }
+ $this->listenersInvoker->invoke($class, Events::postPersist, $entity, new LifecycleEventArgs($entity, $this->em), $invoke);
}
}
@@ -967,45 +972,29 @@ private function executeInserts($class)
*/
private function executeUpdates($class)
{
- $className = $class->name;
- $persister = $this->getEntityPersister($className);
-
- $hasPreUpdateLifecycleCallbacks = isset($class->lifecycleCallbacks[Events::preUpdate]);
- $hasPreUpdateListeners = $this->evm->hasListeners(Events::preUpdate);
-
- $hasPostUpdateLifecycleCallbacks = isset($class->lifecycleCallbacks[Events::postUpdate]);
- $hasPostUpdateListeners = $this->evm->hasListeners(Events::postUpdate);
+ $className = $class->name;
+ $persister = $this->getEntityPersister($className);
+ $preUpdateInvoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preUpdate);
+ $postUpdateInvoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postUpdate);
foreach ($this->entityUpdates as $oid => $entity) {
if ($this->em->getClassMetadata(get_class($entity))->name !== $className) {
continue;
}
- if ($hasPreUpdateLifecycleCallbacks) {
- $class->invokeLifecycleCallbacks(Events::preUpdate, $entity);
-
+ if ($preUpdateInvoke != ListenersInvoker::INVOKE_NONE) {
+ $this->listenersInvoker->invoke($class, Events::preUpdate, $entity, new PreUpdateEventArgs($entity, $this->em, $this->entityChangeSets[$oid]), $preUpdateInvoke);
$this->recomputeSingleEntityChangeSet($class, $entity);
}
- if ($hasPreUpdateListeners) {
- $this->evm->dispatchEvent(
- Events::preUpdate,
- new Event\PreUpdateEventArgs($entity, $this->em, $this->entityChangeSets[$oid])
- );
- }
-
- if (!empty($this->entityChangeSets[$oid])) {
+ if ( ! empty($this->entityChangeSets[$oid])) {
$persister->update($entity);
}
unset($this->entityUpdates[$oid]);
- if ($hasPostUpdateLifecycleCallbacks) {
- $class->invokeLifecycleCallbacks(Events::postUpdate, $entity);
- }
-
- if ($hasPostUpdateListeners) {
- $this->evm->dispatchEvent(Events::postUpdate, new LifecycleEventArgs($entity, $this->em));
+ if ($postUpdateInvoke != ListenersInvoker::INVOKE_NONE) {
+ $this->listenersInvoker->invoke($class, Events::postUpdate, $entity, new LifecycleEventArgs($entity, $this->em), $postUpdateInvoke);
}
}
}
@@ -1019,11 +1008,9 @@ private function executeUpdates($class)
*/
private function executeDeletions($class)
{
- $className = $class->name;
- $persister = $this->getEntityPersister($className);
-
- $hasLifecycleCallbacks = isset($class->lifecycleCallbacks[Events::postRemove]);
- $hasListeners = $this->evm->hasListeners(Events::postRemove);
+ $className = $class->name;
+ $persister = $this->getEntityPersister($className);
+ $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postRemove);
foreach ($this->entityDeletions as $oid => $entity) {
if ($this->em->getClassMetadata(get_class($entity))->name !== $className) {
@@ -1046,12 +1033,8 @@ private function executeDeletions($class)
$class->reflFields[$class->identifier[0]]->setValue($entity, null);
}
- if ($hasLifecycleCallbacks) {
- $class->invokeLifecycleCallbacks(Events::postRemove, $entity);
- }
-
- if ($hasListeners) {
- $this->evm->dispatchEvent(Events::postRemove, new LifecycleEventArgs($entity, $this->em));
+ if ($invoke !== ListenersInvoker::INVOKE_NONE) {
+ $this->listenersInvoker->invoke($class, Events::postRemove, $entity, new LifecycleEventArgs($entity, $this->em), $invoke);
}
}
}
@@ -1691,12 +1674,10 @@ private function doRemove($entity, array &$visited)
break;
case self::STATE_MANAGED:
- if (isset($class->lifecycleCallbacks[Events::preRemove])) {
- $class->invokeLifecycleCallbacks(Events::preRemove, $entity);
- }
+ $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preRemove);
- if ($this->evm->hasListeners(Events::preRemove)) {
- $this->evm->dispatchEvent(Events::preRemove, new LifecycleEventArgs($entity, $this->em));
+ if ($invoke !== ListenersInvoker::INVOKE_NONE) {
+ $this->listenersInvoker->invoke($class, Events::preRemove, $entity, new LifecycleEventArgs($entity, $this->em), $invoke);
}
$this->scheduleForDelete($entity);
@@ -2695,13 +2676,10 @@ public function createEntity($className, array $data, &$hints = array())
}
if ($overrideLocalValues) {
- if (isset($class->lifecycleCallbacks[Events::postLoad])) {
- $class->invokeLifecycleCallbacks(Events::postLoad, $entity);
- }
-
+ $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postLoad);
- if ($this->evm->hasListeners(Events::postLoad)) {
- $this->evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->em));
+ if ($invoke !== ListenersInvoker::INVOKE_NONE) {
+ $this->listenersInvoker->invoke($class, Events::postLoad, $entity, new LifecycleEventArgs($entity, $this->em), $invoke);
}
}
@@ -3177,14 +3155,14 @@ public function isReadOnly($object)
private function dispatchOnFlushEvent()
{
if ($this->evm->hasListeners(Events::onFlush)) {
- $this->evm->dispatchEvent(Events::onFlush, new Event\OnFlushEventArgs($this->em));
+ $this->evm->dispatchEvent(Events::onFlush, new OnFlushEventArgs($this->em));
}
}
private function dispatchPostFlushEvent()
{
if ($this->evm->hasListeners(Events::postFlush)) {
- $this->evm->dispatchEvent(Events::postFlush, new Event\PostFlushEventArgs($this->em));
+ $this->evm->dispatchEvent(Events::postFlush, new PostFlushEventArgs($this->em));
}
}
}
View
36 tests/Doctrine/Tests/Models/CMS/CmsAddress.php
@@ -59,6 +59,7 @@
* )
* })
*
+ * @EntityListeners({"CmsAddressListener"})
*/
class CmsAddress
{
@@ -127,6 +128,28 @@ public static function loadMetadata(\Doctrine\ORM\Mapping\ClassMetadataInfo $met
'name' => 'company_person',
));
+ $metadata->mapField(array (
+ 'id' => true,
+ 'fieldName' => 'id',
+ 'type' => 'integer',
+ ));
+
+ $metadata->mapField(array (
+ 'fieldName' => 'zip',
+ 'length' => 50,
+ ));
+
+ $metadata->mapField(array (
+ 'fieldName' => 'city',
+ 'length' => 50,
+ ));
+
+ $metadata->mapOneToOne(array(
+ 'fieldName' => 'user',
+ 'targetEntity' => 'CmsUser',
+ 'joinColumns' => array(array('referencedColumnName' => 'id'))
+ ));
+
$metadata->addNamedNativeQuery(array (
'name' => 'find-all',
'query' => 'SELECT id, country, city FROM cms_addresses',
@@ -145,7 +168,6 @@ public static function loadMetadata(\Doctrine\ORM\Mapping\ClassMetadataInfo $met
'resultSetMapping' => 'mapping-count',
));
-
$metadata->addSqlResultSetMapping(array (
'name' => 'mapping-find-all',
'columns' => array(),
@@ -187,5 +209,17 @@ public static function loadMetadata(\Doctrine\ORM\Mapping\ClassMetadataInfo $met
),
)
));
+
+ $metadata->addEntityListener(\Doctrine\ORM\Events::postPersist, 'CmsAddressListener', 'postPersist');
+ $metadata->addEntityListener(\Doctrine\ORM\Events::prePersist, 'CmsAddressListener', 'prePersist');
+
+ $metadata->addEntityListener(\Doctrine\ORM\Events::postUpdate, 'CmsAddressListener', 'postUpdate');
+ $metadata->addEntityListener(\Doctrine\ORM\Events::preUpdate, 'CmsAddressListener', 'preUpdate');
+
+ $metadata->addEntityListener(\Doctrine\ORM\Events::postRemove, 'CmsAddressListener', 'postRemove');
+ $metadata->addEntityListener(\Doctrine\ORM\Events::preRemove, 'CmsAddressListener', 'preRemove');
+
+ $metadata->addEntityListener(\Doctrine\ORM\Events::preFlush, 'CmsAddressListener', 'preFlush');
+ $metadata->addEntityListener(\Doctrine\ORM\Events::postLoad, 'CmsAddressListener', 'postLoad');
}
}
View
58 tests/Doctrine/Tests/Models/CMS/CmsAddressListener.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace Doctrine\Tests\Models\CMS;
+
+class CmsAddressListener
+{
+ public $calls;
+
+ public function prePersist()
+ {
+ $this->calls[__FUNCTION__][] = func_get_args();
+ }
+
+ public function postPersist()
+ {
+ $this->calls[__FUNCTION__][] = func_get_args();
+ }
+
+ public function preUpdate()
+ {
+ $this->calls[__FUNCTION__][] = func_get_args();
+ }
+
+ public function postUpdate()
+ {
+ $this->calls[__FUNCTION__][] = func_get_args();
+ }
+
+ public function preRemove()
+ {
+ $this->calls[__FUNCTION__][] = func_get_args();
+ }
+
+ public function postRemove()
+ {
+ $this->calls[__FUNCTION__][] = func_get_args();
+ }
+
+ public function postLoad()
+ {
+ $this->calls[__FUNCTION__][] = func_get_args();
+ }
+
+ public function preFlush()
+ {
+ $this->calls[__FUNCTION__][] = func_get_args();
+ }
+
+ protected function postPersistHandler()
+ {
+ throw new \BadMethodCallException("This is not a valid callback");
+ }
+
+ protected function prePersistHandler()
+ {
+ throw new \BadMethodCallException("This is not a valid callback");
+ }
+}
View
41 tests/Doctrine/Tests/Models/Company/CompanyContract.php
@@ -7,6 +7,7 @@
* @Table(name="company_contracts")
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
+ * @EntityListeners({"CompanyContractListener"})
* @DiscriminatorMap({
* "fix" = "CompanyFixContract",
* "flexible" = "CompanyFlexContract",
@@ -128,4 +129,44 @@ public function removeEngineer(CompanyEmployee $engineer)
}
abstract public function calculatePrice();
+
+ static public function loadMetadata(\Doctrine\ORM\Mapping\ClassMetadataInfo $metadata)
+ {
+ $metadata->setInheritanceType(\Doctrine\ORM\Mapping\ClassMetadata::INHERITANCE_TYPE_JOINED);
+ $metadata->setTableName( 'company_contracts');
+ $metadata->setDiscriminatorColumn(array(
+ 'name' => 'discr',
+ 'type' => 'string',
+ ));
+
+ $metadata->mapField(array(
+ 'id' => true,
+ 'name' => 'id',
+ 'fieldName' => 'id',
+ ));
+
+ $metadata->mapField(array(
+ 'type' => 'boolean',
+ 'name' => 'completed',
+ 'fieldName' => 'completed',
+ ));
+
+ $metadata->setDiscriminatorMap(array(
+ "fix" => "CompanyFixContract",
+ "flexible" => "CompanyFlexContract",
+ "flexultra" => "CompanyFlexUltraContract"
+ ));
+
+ $metadata->addEntityListener(\Doctrine\ORM\Events::postPersist, 'CompanyContractListener', 'postPersistHandler');
+ $metadata->addEntityListener(\Doctrine\ORM\Events::prePersist, 'CompanyContractListener', 'prePersistHandler');
+
+ $metadata->addEntityListener(\Doctrine\ORM\Events::postUpdate, 'CompanyContractListener', 'postUpdateHandler');
+ $metadata->addEntityListener(\Doctrine\ORM\Events::preUpdate, 'CompanyContractListener', 'preUpdateHandler');
+
+ $metadata->addEntityListener(\Doctrine\ORM\Events::postRemove, 'CompanyContractListener', 'postRemoveHandler');
+ $metadata->addEntityListener(\Doctrine\ORM\Events::preRemove, 'CompanyContractListener', 'preRemoveHandler');
+
+ $metadata->addEntityListener(\Doctrine\ORM\Events::preFlush, 'CompanyContractListener', 'preFlushHandler');
+ $metadata->addEntityListener(\Doctrine\ORM\Events::postLoad, 'CompanyContractListener', 'postLoadHandler');
+ }
}
View
84 tests/Doctrine/Tests/Models/Company/CompanyContractListener.php
@@ -0,0 +1,84 @@
+<?php
+
+namespace Doctrine\Tests\Models\Company;
+
+class CompanyContractListener
+{
+ public $postPersistCalls;
+ public $prePersistCalls;
+
+ public $postUpdateCalls;
+ public $preUpdateCalls;
+
+ public $postRemoveCalls;
+ public $preRemoveCalls;
+
+ public $preFlushCalls;
+
+ public $postLoadCalls;
+
+ /**
+ * @PostPersist
+ */
+ public function postPersistHandler(CompanyContract $contract)
+ {
+ $this->postPersistCalls[] = func_get_args();
+ }
+
+ /**
+ * @PrePersist
+ */
+ public function prePersistHandler(CompanyContract $contract)
+ {
+ $this->prePersistCalls[] = func_get_args();
+ }
+
+ /**
+ * @PostUpdate
+ */
+ public function postUpdateHandler(CompanyContract $contract)
+ {
+ $this->postUpdateCalls[] = func_get_args();
+ }
+
+ /**
+ * @PreUpdate
+ */
+ public function preUpdateHandler(CompanyContract $contract)
+ {
+ $this->preUpdateCalls[] = func_get_args();
+ }
+
+ /**
+ * @PostRemove
+ */
+ public function postRemoveHandler(CompanyContract $contract)
+ {
+ $this->postRemoveCalls[] = func_get_args();
+ }
+
+ /**
+ * @PreRemove
+ */
+ public function preRemoveHandler(CompanyContract $contract)
+ {
+ $this->preRemoveCalls[] = func_get_args();
+ }
+
+ /**
+ * @PreFlush
+ */
+ public function preFlushHandler(CompanyContract $contract)
+ {
+ $this->preFlushCalls[] = func_get_args();
+ }
+
+ /**
+ * @PostLoad
+ */
+ public function postLoadHandler(CompanyContract $contract)
+ {
+ $this->postLoadCalls[] = func_get_args();
+ }
+
+}
View
9 tests/Doctrine/Tests/Models/Company/CompanyFixContract.php
@@ -27,4 +27,13 @@ public function setFixPrice($fixPrice)
{
$this->fixPrice = $fixPrice;
}
+
+ static public function loadMetadata(\Doctrine\ORM\Mapping\ClassMetadataInfo $metadata)
+ {
+ $metadata->mapField(array(
+ 'type' => 'integer',
+ 'name' => 'fixPrice',
+ 'fieldName' => 'fixPrice',
+ ));
+ }
}
View
15 tests/Doctrine/Tests/Models/Company/CompanyFlexContract.php
@@ -107,4 +107,19 @@ public function removeManager(CompanyManager $manager)
{
$this->managers->removeElement($manager);
}
+
+ static public function loadMetadata(\Doctrine\ORM\Mapping\ClassMetadataInfo $metadata)
+ {
+ $metadata->mapField(array(
+ 'type' => 'integer',
+ 'name' => 'hoursWorked',
+ 'fieldName' => 'hoursWorked',
+ ));
+
+ $metadata->mapField(array(
+ 'type' => 'integer',
+ 'name' => 'pricePerHour',
+ 'fieldName' => 'pricePerHour',
+ ));
+ }
}
View
24 tests/Doctrine/Tests/Models/Company/CompanyFlexUltraContract.php
@@ -4,6 +4,7 @@
/**
* @Entity
+ * @EntityListeners({"CompanyContractListener","CompanyFlexUltraContractListener"})
*/
class CompanyFlexUltraContract extends CompanyFlexContract
{
@@ -27,4 +28,27 @@ public function setMaxPrice($maxPrice)
{
$this->maxPrice = $maxPrice;
}
+
+ static public function loadMetadata(\Doctrine\ORM\Mapping\ClassMetadataInfo $metadata)
+ {
+ $metadata->mapField(array(
+ 'type' => 'integer',
+ 'name' => 'maxPrice',
+ 'fieldName' => 'maxPrice',
+ ));
+ $metadata->addEntityListener(\Doctrine\ORM\Events::postPersist, 'CompanyContractListener', 'postPersistHandler');
+ $metadata->addEntityListener(\Doctrine\ORM\Events::prePersist, 'CompanyContractListener', 'prePersistHandler');
+
+ $metadata->addEntityListener(\Doctrine\ORM\Events::postUpdate, 'CompanyContractListener', 'postUpdateHandler');
+ $metadata->addEntityListener(\Doctrine\ORM\Events::preUpdate, 'CompanyContractListener', 'preUpdateHandler');
+
+ $metadata->addEntityListener(\Doctrine\ORM\Events::postRemove, 'CompanyContractListener', 'postRemoveHandler');
+ $metadata->addEntityListener(\Doctrine\ORM\Events::preRemove, 'CompanyContractListener', 'preRemoveHandler');
+
+ $metadata->addEntityListener(\Doctrine\ORM\Events::preFlush, 'CompanyContractListener', 'preFlushHandler');
+ $metadata->addEntityListener(\Doctrine\ORM\Events::postLoad, 'CompanyContractListener', 'postLoadHandler');
+
+ $metadata->addEntityListener(\Doctrine\ORM\Events::prePersist, 'CompanyFlexUltraContractListener', 'prePersistHandler1');
+ $metadata->addEntityListener(\Doctrine\ORM\Events::prePersist, 'CompanyFlexUltraContractListener', 'prePersistHandler2');
+ }
}
View
26 tests/Doctrine/Tests/Models/Company/CompanyFlexUltraContractListener.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Doctrine\Tests\Models\Company;
+
+use Doctrine\ORM\Event\LifecycleEventArgs;
+
+class CompanyFlexUltraContractListener
+{
+ public $prePersistCalls;
+
+ /**
+ * @PrePersist
+ */
+ public function prePersistHandler1(CompanyContract $contract, LifecycleEventArgs $args)
+ {
+ $this->prePersistCalls[] = func_get_args();
+ }
+
+ /**
+ * @PrePersist
+ */
+ public function prePersistHandler2(CompanyContract $contract, LifecycleEventArgs $args)
+ {
+ $this->prePersistCalls[] = func_get_args();
+ }
+}
View
12 tests/Doctrine/Tests/ORM/ConfigurationTest.php
@@ -260,6 +260,18 @@ public function testSetGetQuoteStrategy()
$this->configuration->setQuoteStrategy($quoteStrategy);
$this->assertSame($quoteStrategy, $this->configuration->getQuoteStrategy());
}
+
+ /**
+ * @group DDC-1955
+ */
+ public function testSetGetEntityListenerResolver()
+ {
+ $this->assertInstanceOf('Doctrine\ORM\Mapping\EntityListenerResolver', $this->configuration->getEntityListenerResolver());
+ $this->assertInstanceOf('Doctrine\ORM\Mapping\DefaultEntityListenerResolver', $this->configuration->getEntityListenerResolver());
+ $resolver = $this->getMock('Doctrine\ORM\Mapping\EntityListenerResolver');
+ $this->configuration->setEntityListenerResolver($resolver);
+ $this->assertSame($resolver, $this->configuration->getEntityListenerResolver());
+ }
}
class ConfigurationTestAnnotationReaderChecker
View
247 tests/Doctrine/Tests/ORM/Functional/EntityListenersTest.php
@@ -0,0 +1,247 @@
+<?php
+
+namespace Doctrine\Tests\ORM\Functional;
+
+use Doctrine\Tests\Models\Company\CompanyFixContract;
+
+/**
+* @group DDC-1955
+*/
+class EntityListenersTest extends \Doctrine\Tests\OrmFunctionalTestCase
+{
+
+ /**
+ * @var \Doctrine\Tests\Models\Company\CompanyContractListener
+ */
+ private $listener;
+
+ protected function setUp()
+ {
+ $this->useModelSet('company');
+ parent::setUp();
+
+ $this->listener = $this->_em->getConfiguration()
+ ->getEntityListenerResolver()
+ ->resolve('Doctrine\Tests\Models\Company\CompanyContractListener');
+ }
+
+ public function testPreFlushListeners()
+ {
+ $fix = new CompanyFixContract();
+ $fix->setFixPrice(2000);
+
+ $this->listener->preFlushCalls = array();
+
+ $this->_em->persist($fix);
+ $this->_em->flush();
+
+ $this->assertCount(1,$this->listener->preFlushCalls);
+
+ $this->assertSame($fix, $this->listener->preFlushCalls[0][0]);
+
+ $this->assertInstanceOf(
+ 'Doctrine\Tests\Models\Company\CompanyFixContract',
+ $this->listener->preFlushCalls[0][0]
+ );
+
+ $this->assertInstanceOf(
+ 'Doctrine\ORM\Event\PreFlushEventArgs',
+ $this->listener->preFlushCalls[0][1]
+ );
+ }
+
+ public function testPostLoadListeners()
+ {