Permalink
Browse files

entity listeners mapping

  • Loading branch information...
1 parent 3d1956d commit 368cf73f892e266fce695bb3e2ab8a75aa603bc9 @FabioBatSilva committed with fabio.silva Jul 29, 2012
@@ -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) {
@@ -411,7 +415,7 @@ private function addInheritedSqlResultSetMappings(ClassMetadata $subClass, Class
}
}
- /**
+ /**
* Completes the ID generator mapping. If "auto" is specified we choose the generator
* most appropriate for the targeted database platform.
*
@@ -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
@@ -436,6 +437,18 @@ class ClassMetadataInfo implements ClassMetadata
*/
public $lifecycleCallbacks = array();
+ /**
+ * READ-ONLY: The registered entity listeners.
+ *
+ * @var array
+ */
+ public $entityListeners = array();
+
+ /**
+ * @var array entity listeners instances.
+ */
+ static private $entityListenerInstances = array();
+
/**
* READ-ONLY: The association mappings of this class.
*
@@ -2492,6 +2505,41 @@ public function setLifecycleCallbacks(array $callbacks)
$this->lifecycleCallbacks = $callbacks;
}
+ /**
+ * Adds a entity listener for entities of this class.
+ *
+ * @param string $callback
+ * @param string $eventName
+ */
+ public function addEntityListener($eventName, $class, $method)
+ {
+ $this->entityListeners[$eventName][] = array(
+ 'class' => $class,
+ 'method' => $method
+ );
+ }
+
+ /**
+ * Call the entity listeners.
+ *
+ * @param string $eventName The event name.
+ * @param object $entity An instance of the mapped entity
+ * @param \Doctrine\Common\EventArgs $arg The Event args
+ */
+ public function dispatchEntityListeners($eventName, $entity, EventArgs $arg)
+ {
+ foreach ($this->entityListeners[$eventName] as $listener) {
+ $class = $listener['class'];
+ $method = $listener['method'];
+
+ if ( ! isset(self::$entityListenerInstances[$class])) {
+ self::$entityListenerInstances[$class] = new $class();
+ }
+
+ self::$entityListenerInstances[$class]->{$method}($entity, $arg);
+ }
+ }
+
/**
* Sets the discriminator column definition.
*
@@ -25,6 +25,7 @@
use Doctrine\ORM\Mapping\Column;
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.
@@ -415,54 +416,39 @@ 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;
- }
- }
-
- if (isset($annotations['Doctrine\ORM\Mapping\PrePersist'])) {
- $metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::prePersist);
- }
-
- if (isset($annotations['Doctrine\ORM\Mapping\PostPersist'])) {
- $metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postPersist);
- }
+ // Evaluate EntityListeners annotation
+ if (isset($classAnnotations['Doctrine\ORM\Mapping\EntityListeners'])) {
+ $entityListenersAnnot = $classAnnotations['Doctrine\ORM\Mapping\EntityListeners'];
- if (isset($annotations['Doctrine\ORM\Mapping\PreUpdate'])) {
- $metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::preUpdate);
- }
+ foreach ($entityListenersAnnot->value as $listener) {
- if (isset($annotations['Doctrine\ORM\Mapping\PostUpdate'])) {
- $metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postUpdate);
- }
+ if ( ! class_exists($listener)) {
+ throw new \InvalidArgumentException("Indefined class \"$listener\"");
+ }
- if (isset($annotations['Doctrine\ORM\Mapping\PreRemove'])) {
- $metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::preRemove);
- }
+ $listener = new \ReflectionClass($listener);
- if (isset($annotations['Doctrine\ORM\Mapping\PostRemove'])) {
- $metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postRemove);
+ foreach ($listener->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
+ foreach ($this->getMethodCallbacks($method) as $value) {
+ list($callback, $event) = $value;
+ $metadata->addEntityListener($event, $listener->name, $callback);
}
+ }
+ }
+ }
- 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) {
+ list($callback, $event) = $value;
+ $metadata->addLifecycleCallback($callback, $event);
}
}
}
@@ -488,9 +474,55 @@ 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
*/
@@ -64,3 +64,4 @@
require_once __DIR__.'/../AssociationOverrides.php';
require_once __DIR__.'/../AttributeOverride.php';
require_once __DIR__.'/../AttributeOverrides.php';
+require_once __DIR__.'/../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;
+}
@@ -7,6 +7,7 @@
* @Table(name="company_contracts")
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
+ * @EntityListeners({"Doctrine\Tests\Models\Company\ContractSubscriber"})
* @DiscriminatorMap({
* "fix" = "CompanyFixContract",
* "flexible" = "CompanyFlexContract",
@@ -0,0 +1,31 @@
+<?php
+
+namespace Doctrine\Tests\Models\Company;
+
+class ContractSubscriber
+{
+ static public $prePersistCalls;
+ static public $postPersisCalls;
+ static public $instances;
+
+ public function __construct()
+ {
+ self::$instances[] = $this;
+ }
+
+ /**
+ * @PostPersist
+ */
+ public function postPersist(CompanyContract $contract)
+ {
+ self::$postPersisCalls[] = func_get_args();
+ }
+
+ /**
+ * @PrePersist
+ */
+ public function prePersist(CompanyContract $contract)
+ {
+ self::$prePersistCalls[] = func_get_args();
+ }
+}
@@ -0,0 +1,43 @@
+<?php
+
+namespace Doctrine\Tests\ORM\Functional;
+
+use Doctrine\ORM\Events;
+use Doctrine\ORM\Event\LifecycleEventArgs;
+use Doctrine\Tests\Models\Company\CompanyFixContract;
+use Doctrine\Tests\Models\Company\CompanyFlexContract;
+use Doctrine\Tests\Models\Company\ContractSubscriber;
+
+require_once __DIR__ . '/../../TestInit.php';
+
+class EntityListenersDispatcherTest extends \Doctrine\Tests\OrmFunctionalTestCase
+{
+
+ /**
+ * @group DDC-1955
+ */
+ public function testEntityListeners()
+ {
+ $flexClass = $this->_em->getClassMetadata('Doctrine\Tests\Models\Company\CompanyFixContract');
+ $fixClass = $this->_em->getClassMetadata('Doctrine\Tests\Models\Company\CompanyFlexContract');
+
+ $this->assertNull(ContractSubscriber::$instances);
+ $this->assertNull(ContractSubscriber::$prePersistCalls);
+ $this->assertNull(ContractSubscriber::$postPersisCalls);
+
+ $fix = new CompanyFixContract();
+ $fixArg = new LifecycleEventArgs($fix, $this->_em);
+
+ $flex = new CompanyFlexContract();
+ $flexArg = new LifecycleEventArgs($fix, $this->_em);
+
+ $fixClass->dispatchEntityListeners(Events::prePersist, $fix, $fixArg);
+ $flexClass->dispatchEntityListeners(Events::prePersist, $flex, $flexArg);
+
+ $this->assertSame($fix, ContractSubscriber::$prePersistCalls[0][0]);
+ $this->assertSame($fixArg, ContractSubscriber::$prePersistCalls[0][1]);
+
+ $this->assertCount(1, ContractSubscriber::$instances);
+ $this->assertNull(ContractSubscriber::$postPersisCalls);
+ }
+}

0 comments on commit 368cf73

Please sign in to comment.