1
0
mirror of synced 2025-03-23 00:13:50 +03:00

Merge pull request #423 from FabioBatSilva/DDC-1955

DDC-1955 - @EntityListeners
This commit is contained in:
Benjamin Eberlei 2013-02-02 11:47:46 -08:00
commit 71a68a5c6f
46 changed files with 2164 additions and 166 deletions

View File

@ -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 File

@ -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 File

@ -29,6 +29,8 @@ use Doctrine\ORM\Mapping\QuoteStrategy;
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 @@ class Configuration extends \Doctrine\DBAL\Configuration
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 File

@ -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 File

@ -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 File

@ -156,6 +156,10 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
$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 @@ class ClassMetadataFactory extends AbstractClassMetadataFactory
$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 File

@ -26,6 +26,7 @@ use Doctrine\DBAL\Types\Type;
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,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.
*
@ -1313,10 +1321,7 @@ class ClassMetadataInfo implements ClassMetadata
$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 @@ class ClassMetadataInfo implements ClassMetadata
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 @@ class ClassMetadataInfo implements ClassMetadata
$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 @@ class ClassMetadataInfo implements ClassMetadata
$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 @@ class ClassMetadataInfo implements ClassMetadata
*/
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.
*
@ -2492,6 +2489,33 @@ class ClassMetadataInfo implements ClassMetadata
$this->lifecycleCallbacks = $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.
*
@ -2557,10 +2581,7 @@ class ClassMetadataInfo implements ClassMetadata
*/
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 @@ class ClassMetadataInfo implements ClassMetadata
}
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 File

@ -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 File

@ -23,8 +23,10 @@ use Doctrine\Common\Annotations\AnnotationReader;
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 @@ class AnnotationDriver extends AbstractAnnotationDriver
// 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 @@ class AnnotationDriver extends AbstractAnnotationDriver
}
}
// Evaluate EntityListeners annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\EntityListeners'])) {
$entityListenersAnnot = $classAnnotations['Doctrine\ORM\Mapping\EntityListeners'];
foreach ($entityListenersAnnot->value as $item) {
$listenerClassName = $metadata->fullyQualifiedClassName($item);
if ( ! class_exists($listenerClassName)) {
throw MappingException::entityListenerClassNotFound($listenerClassName, $className);
}
$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));
foreach ($callbacks as $value) {
$metadata->addEntityListener($value[1], $listenerClassName, $value[0]);
}
}
// Evaluate the listener using naming convention.
if ( ! $hasMapping ) {
EntityListenerBuilder::bindEntityListener($metadata, $listenerClassName);
}
}
}
// Evaluate @HasLifecycleCallbacks annotation
if (isset($classAnnotations['Doctrine\ORM\Mapping\HasLifecycleCallbacks'])) {
/* @var $method \ReflectionMethod */
foreach ($class->getMethods() as $method) {
foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC) 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 ($method->getDeclaringClass()->name !== $class->name) {
continue;
}
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);
}
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 (isset($annotations['Doctrine\ORM\Mapping\PreRemove'])) {
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::preRemove);
}
if (isset($annotations['Doctrine\ORM\Mapping\PostRemove'])) {
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postRemove);
}
if (isset($annotations['Doctrine\ORM\Mapping\PostLoad'])) {
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postLoad);
}
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 @@ class AnnotationDriver extends AbstractAnnotationDriver
}
/**
* 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 File

@ -64,3 +64,4 @@ require_once __DIR__.'/../AssociationOverride.php';
require_once __DIR__.'/../AssociationOverrides.php';
require_once __DIR__.'/../AttributeOverride.php';
require_once __DIR__.'/../AttributeOverrides.php';
require_once __DIR__.'/../EntityListeners.php';

View File

@ -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 @@ class XmlDriver extends FileDriver
$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 File

@ -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 @@ class YamlDriver extends FileDriver
}
}
}
// 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 File

@ -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 File

@ -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 File

@ -684,6 +684,29 @@ class MappingException extends \Doctrine\ORM\ORMException
return new self("Entity '" . $className . "' has no method '" . $methodName . "' to be registered as lifecycle callback.");
}
/**
* @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

View File

@ -22,15 +22,22 @@ namespace Doctrine\ORM;
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
@ -212,6 +219,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.
*
@ -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 @@ class UnitOfWork implements PropertyChangedListener
{
// 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 @@ class UnitOfWork implements PropertyChangedListener
$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 @@ class UnitOfWork implements PropertyChangedListener
}
/**
* @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 @@ class UnitOfWork implements PropertyChangedListener
*/
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 @@ class UnitOfWork implements PropertyChangedListener
unset($this->entityInsertions[$oid]);
if ($hasLifecycleCallbacks || $hasListeners) {
if ($invoke !== ListenersInvoker::INVOKE_NONE) {
$entities[] = $entity;
}
}
@ -948,13 +959,7 @@ class UnitOfWork implements PropertyChangedListener
}
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 @@ class UnitOfWork implements PropertyChangedListener
*/
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 @@ class UnitOfWork implements PropertyChangedListener
*/
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 @@ class UnitOfWork implements PropertyChangedListener
$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 @@ class UnitOfWork implements PropertyChangedListener
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 @@ class UnitOfWork implements PropertyChangedListener
}
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 @@ class UnitOfWork implements PropertyChangedListener
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 File

@ -59,6 +59,7 @@ namespace Doctrine\Tests\Models\CMS;
* )
* })
*
* @EntityListeners({"CmsAddressListener"})
*/
class CmsAddress
{
@ -127,6 +128,28 @@ class CmsAddress
'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 @@ class CmsAddress
'resultSetMapping' => 'mapping-count',
));
$metadata->addSqlResultSetMapping(array (
'name' => 'mapping-find-all',
'columns' => array(),
@ -187,5 +209,17 @@ class CmsAddress
),
)
));
$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 File

@ -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 File

@ -7,6 +7,7 @@ namespace Doctrine\Tests\Models\Company;
* @Table(name="company_contracts")
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="discr", type="string")
* @EntityListeners({"CompanyContractListener"})
* @DiscriminatorMap({
* "fix" = "CompanyFixContract",
* "flexible" = "CompanyFlexContract",
@ -128,4 +129,44 @@ abstract class CompanyContract
}
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 File

@ -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 File

@ -27,4 +27,13 @@ class CompanyFixContract extends CompanyContract
{
$this->fixPrice = $fixPrice;
}
static public function loadMetadata(\Doctrine\ORM\Mapping\ClassMetadataInfo $metadata)
{
$metadata->mapField(array(
'type' => 'integer',
'name' => 'fixPrice',
'fieldName' => 'fixPrice',
));
}
}

View File

@ -107,4 +107,19 @@ class CompanyFlexContract extends CompanyContract
{
$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 File

@ -4,6 +4,7 @@ namespace Doctrine\Tests\Models\Company;
/**
* @Entity
* @EntityListeners({"CompanyContractListener","CompanyFlexUltraContractListener"})
*/
class CompanyFlexUltraContract extends CompanyFlexContract
{
@ -27,4 +28,27 @@ class CompanyFlexUltraContract extends CompanyFlexContract
{
$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 File

@ -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 File

@ -260,6 +260,18 @@ class ConfigurationTest extends PHPUnit_Framework_TestCase
$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 File

@ -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()
{
$fix = new CompanyFixContract();
$fix->setFixPrice(2000);
$this->_em->persist($fix);
$this->_em->flush();
$this->_em->clear();
$this->listener->postLoadCalls = array();
$dql = "SELECT f FROM Doctrine\Tests\Models\Company\CompanyFixContract f WHERE f.id = ?1";
$fix = $this->_em->createQuery($dql)->setParameter(1, $fix->getId())->getSingleResult();
$this->assertCount(1,$this->listener->postLoadCalls);
$this->assertSame($fix, $this->listener->postLoadCalls[0][0]);
$this->assertInstanceOf(
'Doctrine\Tests\Models\Company\CompanyFixContract',
$this->listener->postLoadCalls[0][0]
);
$this->assertInstanceOf(
'Doctrine\ORM\Event\LifecycleEventArgs',
$this->listener->postLoadCalls[0][1]
);
}
public function testPrePersistListeners()
{
$fix = new CompanyFixContract();
$fix->setFixPrice(2000);
$this->listener->prePersistCalls = array();
$this->_em->persist($fix);
$this->_em->flush();
$this->assertCount(1,$this->listener->prePersistCalls);
$this->assertSame($fix, $this->listener->prePersistCalls[0][0]);
$this->assertInstanceOf(
'Doctrine\Tests\Models\Company\CompanyFixContract',
$this->listener->prePersistCalls[0][0]
);
$this->assertInstanceOf(
'Doctrine\ORM\Event\LifecycleEventArgs',
$this->listener->prePersistCalls[0][1]
);
}
public function testPostPersistListeners()
{
$fix = new CompanyFixContract();
$fix->setFixPrice(2000);
$this->listener->postPersistCalls = array();
$this->_em->persist($fix);
$this->_em->flush();
$this->assertCount(1,$this->listener->postPersistCalls);
$this->assertSame($fix, $this->listener->postPersistCalls[0][0]);
$this->assertInstanceOf(
'Doctrine\Tests\Models\Company\CompanyFixContract',
$this->listener->postPersistCalls[0][0]
);
$this->assertInstanceOf(
'Doctrine\ORM\Event\LifecycleEventArgs',
$this->listener->postPersistCalls[0][1]
);
}
public function testPreUpdateListeners()
{
$fix = new CompanyFixContract();
$fix->setFixPrice(1000);
$this->_em->persist($fix);
$this->_em->flush();
$this->listener->preUpdateCalls = array();
$fix->setFixPrice(2000);
$this->_em->persist($fix);
$this->_em->flush();
$this->assertCount(1,$this->listener->preUpdateCalls);
$this->assertSame($fix, $this->listener->preUpdateCalls[0][0]);
$this->assertInstanceOf(
'Doctrine\Tests\Models\Company\CompanyFixContract',
$this->listener->preUpdateCalls[0][0]
);
$this->assertInstanceOf(
'Doctrine\ORM\Event\PreUpdateEventArgs',
$this->listener->preUpdateCalls[0][1]
);
}
public function testPostUpdateListeners()
{
$fix = new CompanyFixContract();
$fix->setFixPrice(1000);
$this->_em->persist($fix);
$this->_em->flush();
$this->listener->postUpdateCalls = array();
$fix->setFixPrice(2000);
$this->_em->persist($fix);
$this->_em->flush();
$this->assertCount(1,$this->listener->postUpdateCalls);
$this->assertSame($fix, $this->listener->postUpdateCalls[0][0]);
$this->assertInstanceOf(
'Doctrine\Tests\Models\Company\CompanyFixContract',
$this->listener->postUpdateCalls[0][0]
);
$this->assertInstanceOf(
'Doctrine\ORM\Event\LifecycleEventArgs',
$this->listener->postUpdateCalls[0][1]
);
}
public function testPreRemoveListeners()
{
$fix = new CompanyFixContract();
$fix->setFixPrice(1000);
$this->_em->persist($fix);
$this->_em->flush();
$this->listener->preRemoveCalls = array();
$this->_em->remove($fix);
$this->_em->flush();
$this->assertCount(1,$this->listener->preRemoveCalls);
$this->assertSame($fix, $this->listener->preRemoveCalls[0][0]);
$this->assertInstanceOf(
'Doctrine\Tests\Models\Company\CompanyFixContract',
$this->listener->preRemoveCalls[0][0]
);
$this->assertInstanceOf(
'Doctrine\ORM\Event\LifecycleEventArgs',
$this->listener->preRemoveCalls[0][1]
);
}
public function testPostRemoveListeners()
{
$fix = new CompanyFixContract();
$fix->setFixPrice(1000);
$this->_em->persist($fix);
$this->_em->flush();
$this->listener->postRemoveCalls = array();
$this->_em->remove($fix);
$this->_em->flush();
$this->assertCount(1,$this->listener->postRemoveCalls);
$this->assertSame($fix, $this->listener->postRemoveCalls[0][0]);
$this->assertInstanceOf(
'Doctrine\Tests\Models\Company\CompanyFixContract',
$this->listener->postRemoveCalls[0][0]
);
$this->assertInstanceOf(
'Doctrine\ORM\Event\LifecycleEventArgs',
$this->listener->postRemoveCalls[0][1]
);
}
}

View File

@ -11,6 +11,7 @@ class LifecycleCallbackTest extends \Doctrine\Tests\OrmFunctionalTestCase
parent::setUp();
try {
$this->_schemaTool->createSchema(array(
$this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\LifecycleCallbackEventArgEntity'),
$this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\LifecycleCallbackTestEntity'),
$this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\LifecycleCallbackTestUser'),
$this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\LifecycleCallbackCascader'),
@ -182,6 +183,77 @@ class LifecycleCallbackTest extends \Doctrine\Tests\OrmFunctionalTestCase
$this->assertEquals('Bob', $bob->getName());
}
/**
* @group DDC-1955
*/
public function testLifecycleCallbackEventArgs()
{
$e = new LifecycleCallbackEventArgEntity;
$e->value = 'foo';
$this->_em->persist($e);
$this->_em->flush();
$e->value = 'var';
$this->_em->persist($e);
$this->_em->flush();
$this->_em->refresh($e);
$this->_em->remove($e);
$this->_em->flush();
$this->assertArrayHasKey('preFlushHandler', $e->calls);
$this->assertArrayHasKey('postLoadHandler', $e->calls);
$this->assertArrayHasKey('prePersistHandler', $e->calls);
$this->assertArrayHasKey('postPersistHandler', $e->calls);
$this->assertArrayHasKey('preUpdateHandler', $e->calls);
$this->assertArrayHasKey('postUpdateHandler', $e->calls);
$this->assertArrayHasKey('preRemoveHandler', $e->calls);
$this->assertArrayHasKey('postRemoveHandler', $e->calls);
$this->assertInstanceOf(
'Doctrine\ORM\Event\PreFlushEventArgs',
$e->calls['preFlushHandler']
);
$this->assertInstanceOf(
'Doctrine\ORM\Event\LifecycleEventArgs',
$e->calls['postLoadHandler']
);
$this->assertInstanceOf(
'Doctrine\ORM\Event\LifecycleEventArgs',
$e->calls['prePersistHandler']
);
$this->assertInstanceOf(
'Doctrine\ORM\Event\LifecycleEventArgs',
$e->calls['postPersistHandler']
);
$this->assertInstanceOf(
'Doctrine\ORM\Event\PreUpdateEventArgs',
$e->calls['preUpdateHandler']
);
$this->assertInstanceOf(
'Doctrine\ORM\Event\LifecycleEventArgs',
$e->calls['postUpdateHandler']
);
$this->assertInstanceOf(
'Doctrine\ORM\Event\LifecycleEventArgs',
$e->calls['preRemoveHandler']
);
$this->assertInstanceOf(
'Doctrine\ORM\Event\LifecycleEventArgs',
$e->calls['postRemoveHandler']
);
}
}
/** @Entity @HasLifecycleCallbacks */
@ -309,3 +381,79 @@ class LifecycleListenerPreUpdate
$eventArgs->setNewValue('name', 'Bob');
}
}
/** @Entity @HasLifecycleCallbacks */
class LifecycleCallbackEventArgEntity
{
/** @Id @Column(type="integer") @GeneratedValue */
public $id;
/** @Column() */
public $value;
public $calls = array();
/**
* @PostPersist
*/
public function postPersistHandler(\Doctrine\ORM\Event\LifecycleEventArgs $event)
{
$this->calls[__FUNCTION__] = $event;
}
/**
* @PrePersist
*/
public function prePersistHandler(\Doctrine\ORM\Event\LifecycleEventArgs $event)
{
$this->calls[__FUNCTION__] = $event;
}
/**
* @PostUpdate
*/
public function postUpdateHandler(\Doctrine\ORM\Event\LifecycleEventArgs $event)
{
$this->calls[__FUNCTION__] = $event;
}
/**
* @PreUpdate
*/
public function preUpdateHandler(\Doctrine\ORM\Event\PreUpdateEventArgs $event)
{
$this->calls[__FUNCTION__] = $event;
}
/**
* @PostRemove
*/
public function postRemoveHandler(\Doctrine\ORM\Event\LifecycleEventArgs $event)
{
$this->calls[__FUNCTION__] = $event;
}
/**
* @PreRemove
*/
public function preRemoveHandler(\Doctrine\ORM\Event\LifecycleEventArgs $event)
{
$this->calls[__FUNCTION__] = $event;
}
/**
* @PreFlush
*/
public function preFlushHandler(\Doctrine\ORM\Event\PreFlushEventArgs $event)
{
$this->calls[__FUNCTION__] = $event;
}
/**
* @PostLoad
*/
public function postLoadHandler(\Doctrine\ORM\Event\LifecycleEventArgs $event)
{
$this->calls[__FUNCTION__] = $event;
}
}

View File

@ -2,7 +2,7 @@
namespace Doctrine\Tests\ORM\Functional\Ticket;
use Doctrine\ORM\UnitOfWork;
use Doctrine\ORM\Event\LifecycleEventArgs;
/**
* @group DDC-1707
@ -25,9 +25,11 @@ class DDC1707Test extends \Doctrine\Tests\OrmFunctionalTestCase
public function testPostLoadOnChild()
{
$class = $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1707Child');
$class = $this->_em->getClassMetadata(__NAMESPACE__ . '\DDC1707Child');
$entity = new DDC1707Child();
$class->invokeLifecycleCallbacks(\Doctrine\ORM\Events::postLoad, $entity);
$event = new LifecycleEventArgs($entity, $this->_em);
$class->invokeLifecycleCallbacks(\Doctrine\ORM\Events::postLoad, $entity, $event);
$this->assertTrue($entity->postLoad);
}

View File

@ -2,10 +2,13 @@
namespace Doctrine\Tests\ORM\Mapping;
use Doctrine\ORM\Mapping\ClassMetadata,
Doctrine\ORM\Mapping\ClassMetadataInfo,
Doctrine\ORM\Mapping\Driver\XmlDriver,
Doctrine\ORM\Mapping\Driver\YamlDriver;
use Doctrine\ORM\Events;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\Tests\Models\Company\CompanyFixContract;
use Doctrine\Tests\Models\Company\CompanyFlexContract;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
require_once __DIR__ . '/../../TestInit.php';
@ -748,6 +751,126 @@ abstract class AbstractMappingDriverTest extends \Doctrine\Tests\OrmTestCase
$this->assertTrue($guestMetadata->fieldMappings['name']['unique']);
}
/**
* @group DDC-1955
*/
public function testEntityListeners()
{
$em = $this->_getTestEntityManager();
$factory = $this->createClassMetadataFactory($em);
$superClass = $factory->getMetadataFor('Doctrine\Tests\Models\Company\CompanyContract');
$flexClass = $factory->getMetadataFor('Doctrine\Tests\Models\Company\CompanyFixContract');
$fixClass = $factory->getMetadataFor('Doctrine\Tests\Models\Company\CompanyFlexContract');
$ultraClass = $factory->getMetadataFor('Doctrine\Tests\Models\Company\CompanyFlexUltraContract');
$this->assertArrayHasKey(Events::prePersist, $superClass->entityListeners);
$this->assertArrayHasKey(Events::postPersist, $superClass->entityListeners);
$this->assertCount(1, $superClass->entityListeners[Events::prePersist]);
$this->assertCount(1, $superClass->entityListeners[Events::postPersist]);
$postPersist = $superClass->entityListeners[Events::postPersist][0];
$prePersist = $superClass->entityListeners[Events::prePersist][0];
$this->assertEquals('Doctrine\Tests\Models\Company\CompanyContractListener', $postPersist['class']);
$this->assertEquals('Doctrine\Tests\Models\Company\CompanyContractListener', $prePersist['class']);
$this->assertEquals('postPersistHandler', $postPersist['method']);
$this->assertEquals('prePersistHandler', $prePersist['method']);
//Inherited listeners
$this->assertEquals($fixClass->entityListeners, $superClass->entityListeners);
$this->assertEquals($flexClass->entityListeners, $superClass->entityListeners);
}
/**
* @group DDC-1955
*/
public function testEntityListenersOverride()
{
$em = $this->_getTestEntityManager();
$factory = $this->createClassMetadataFactory($em);
$ultraClass = $factory->getMetadataFor('Doctrine\Tests\Models\Company\CompanyFlexUltraContract');
//overrited listeners
$this->assertArrayHasKey(Events::postPersist, $ultraClass->entityListeners);
$this->assertArrayHasKey(Events::prePersist, $ultraClass->entityListeners);
$this->assertCount(1, $ultraClass->entityListeners[Events::postPersist]);
$this->assertCount(3, $ultraClass->entityListeners[Events::prePersist]);
$postPersist = $ultraClass->entityListeners[Events::postPersist][0];
$prePersist = $ultraClass->entityListeners[Events::prePersist][0];
$this->assertEquals('Doctrine\Tests\Models\Company\CompanyContractListener', $postPersist['class']);
$this->assertEquals('Doctrine\Tests\Models\Company\CompanyContractListener', $prePersist['class']);
$this->assertEquals('postPersistHandler', $postPersist['method']);
$this->assertEquals('prePersistHandler', $prePersist['method']);
$prePersist = $ultraClass->entityListeners[Events::prePersist][1];
$this->assertEquals('Doctrine\Tests\Models\Company\CompanyFlexUltraContractListener', $prePersist['class']);
$this->assertEquals('prePersistHandler1', $prePersist['method']);
$prePersist = $ultraClass->entityListeners[Events::prePersist][2];
$this->assertEquals('Doctrine\Tests\Models\Company\CompanyFlexUltraContractListener', $prePersist['class']);
$this->assertEquals('prePersistHandler2', $prePersist['method']);
}
/**
* @group DDC-1955
*/
public function testEntityListenersNamingConvention()
{
$em = $this->_getTestEntityManager();
$factory = $this->createClassMetadataFactory($em);
$metadata = $factory->getMetadataFor('Doctrine\Tests\Models\CMS\CmsAddress');
$this->assertArrayHasKey(Events::postPersist, $metadata->entityListeners);
$this->assertArrayHasKey(Events::prePersist, $metadata->entityListeners);
$this->assertArrayHasKey(Events::postUpdate, $metadata->entityListeners);
$this->assertArrayHasKey(Events::preUpdate, $metadata->entityListeners);
$this->assertArrayHasKey(Events::postRemove, $metadata->entityListeners);
$this->assertArrayHasKey(Events::preRemove, $metadata->entityListeners);
$this->assertArrayHasKey(Events::postLoad, $metadata->entityListeners);
$this->assertArrayHasKey(Events::preFlush, $metadata->entityListeners);
$this->assertCount(1, $metadata->entityListeners[Events::postPersist]);
$this->assertCount(1, $metadata->entityListeners[Events::prePersist]);
$this->assertCount(1, $metadata->entityListeners[Events::postUpdate]);
$this->assertCount(1, $metadata->entityListeners[Events::preUpdate]);
$this->assertCount(1, $metadata->entityListeners[Events::postRemove]);
$this->assertCount(1, $metadata->entityListeners[Events::preRemove]);
$this->assertCount(1, $metadata->entityListeners[Events::postLoad]);
$this->assertCount(1, $metadata->entityListeners[Events::preFlush]);
$postPersist = $metadata->entityListeners[Events::postPersist][0];
$prePersist = $metadata->entityListeners[Events::prePersist][0];
$postUpdate = $metadata->entityListeners[Events::postUpdate][0];
$preUpdate = $metadata->entityListeners[Events::preUpdate][0];
$postRemove = $metadata->entityListeners[Events::postRemove][0];
$preRemove = $metadata->entityListeners[Events::preRemove][0];
$postLoad = $metadata->entityListeners[Events::postLoad][0];
$preFlush = $metadata->entityListeners[Events::preFlush][0];
$this->assertEquals('Doctrine\Tests\Models\CMS\CmsAddressListener', $postPersist['class']);
$this->assertEquals('Doctrine\Tests\Models\CMS\CmsAddressListener', $prePersist['class']);
$this->assertEquals('Doctrine\Tests\Models\CMS\CmsAddressListener', $postUpdate['class']);
$this->assertEquals('Doctrine\Tests\Models\CMS\CmsAddressListener', $preUpdate['class']);
$this->assertEquals('Doctrine\Tests\Models\CMS\CmsAddressListener', $postRemove['class']);
$this->assertEquals('Doctrine\Tests\Models\CMS\CmsAddressListener', $preRemove['class']);
$this->assertEquals('Doctrine\Tests\Models\CMS\CmsAddressListener', $postLoad['class']);
$this->assertEquals('Doctrine\Tests\Models\CMS\CmsAddressListener', $preFlush['class']);
$this->assertEquals(Events::postPersist, $postPersist['method']);
$this->assertEquals(Events::prePersist, $prePersist['method']);
$this->assertEquals(Events::postUpdate, $postUpdate['method']);
$this->assertEquals(Events::preUpdate, $preUpdate['method']);
$this->assertEquals(Events::postRemove, $postRemove['method']);
$this->assertEquals(Events::preRemove, $preRemove['method']);
$this->assertEquals(Events::postLoad, $postLoad['method']);
$this->assertEquals(Events::preFlush, $preFlush['method']);
}
}
/**

View File

@ -997,6 +997,34 @@ class ClassMetadataTest extends \Doctrine\Tests\OrmTestCase
$cm->setAttributeOverride('name', array('type'=>'date'));
}
/**
* @group DDC-1955
*
* @expectedException Doctrine\ORM\Mapping\MappingException
* @expectedExceptionMessage Entity Listener "\InvalidClassName" declared on "Doctrine\Tests\Models\CMS\CmsUser" not found.
*/
public function testInvalidEntityListenerClassException()
{
$cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser');
$cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService);
$cm->addEntityListener(Events::postLoad, '\InvalidClassName', 'postLoadHandler');
}
/**
* @group DDC-1955
*
* @expectedException Doctrine\ORM\Mapping\MappingException
* @expectedExceptionMessage Entity Listener "\Doctrine\Tests\Models\Company\CompanyContractListener" declared on "Doctrine\Tests\Models\CMS\CmsUser" has no method "invalidMethod".
*/
public function testInvalidEntityListenerMethodException()
{
$cm = new ClassMetadata('Doctrine\Tests\Models\CMS\CmsUser');
$cm->initializeReflection(new \Doctrine\Common\Persistence\Mapping\RuntimeReflectionService);
$cm->addEntityListener(Events::postLoad, '\Doctrine\Tests\Models\Company\CompanyContractListener', 'invalidMethod');
}
}
class MyNamespacedNamingStrategy extends \Doctrine\ORM\Mapping\DefaultNamingStrategy

View File

@ -0,0 +1,97 @@
<?php
namespace Doctrine\Tests\ORM\Mapping;
use Doctrine\ORM\Mapping\DefaultEntityListenerResolver;
/**
* @group DDC-1955
*/
class EntityListenerResolverTest extends \Doctrine\Tests\OrmTestCase
{
/**
* @var \Doctrine\ORM\Mapping\DefaultEntityListenerResolver
*/
private $resolver;
protected function setUp()
{
parent::setUp();
$this->resolver = new DefaultEntityListenerResolver();
}
public function testResolve()
{
$className = '\Doctrine\Tests\Models\Company\CompanyContractListener';
$object = $this->resolver->resolve($className);
$this->assertInstanceOf($className, $object);
$this->assertSame($object, $this->resolver->resolve($className));
}
public function testRegisterAndResolve()
{
$className = '\Doctrine\Tests\Models\Company\CompanyContractListener';
$object = new $className();
$this->resolver->register($object);
$this->assertSame($object, $this->resolver->resolve($className));
}
public function testClearOne()
{
$className1 = '\Doctrine\Tests\Models\Company\CompanyContractListener';
$className2 = '\Doctrine\Tests\Models\Company\CompanyFlexUltraContractListener';
$obj1 = $this->resolver->resolve($className1);
$obj2 = $this->resolver->resolve($className2);
$this->assertInstanceOf($className1, $obj1);
$this->assertInstanceOf($className2, $obj2);
$this->assertSame($obj1, $this->resolver->resolve($className1));
$this->assertSame($obj2, $this->resolver->resolve($className2));
$this->resolver->clear($className1);
$this->assertInstanceOf($className1, $this->resolver->resolve($className1));
$this->assertInstanceOf($className2, $this->resolver->resolve($className2));
$this->assertNotSame($obj1, $this->resolver->resolve($className1));
$this->assertSame($obj2, $this->resolver->resolve($className2));
}
public function testClearAll()
{
$className1 = '\Doctrine\Tests\Models\Company\CompanyContractListener';
$className2 = '\Doctrine\Tests\Models\Company\CompanyFlexUltraContractListener';
$obj1 = $this->resolver->resolve($className1);
$obj2 = $this->resolver->resolve($className2);
$this->assertInstanceOf($className1, $obj1);
$this->assertInstanceOf($className2, $obj2);
$this->assertSame($obj1, $this->resolver->resolve($className1));
$this->assertSame($obj2, $this->resolver->resolve($className2));
$this->resolver->clear();
$this->assertInstanceOf($className1, $this->resolver->resolve($className1));
$this->assertInstanceOf($className2, $this->resolver->resolve($className2));
$this->assertNotSame($obj1, $this->resolver->resolve($className1));
$this->assertNotSame($obj2, $this->resolver->resolve($className2));
}
/**
* @expectedException InvalidArgumentException
* @expectedExceptionMessage An object was expected, but got "string".
*/
public function testRegisterStringException()
{
$this->resolver->register('CompanyContractListener');
}
}

View File

@ -6,6 +6,27 @@ $metadata->setPrimaryTable(array(
'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',
@ -66,4 +87,16 @@ $metadata->addSqlResultSetMapping(array (
'name' => 'count',
),
)
));
));
$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 File

@ -0,0 +1,40 @@
<?php
use Doctrine\ORM\Mapping\ClassMetadataInfo;
$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 File

@ -0,0 +1,7 @@
<?php
$metadata->mapField(array(
'type' => 'integer',
'name' => 'fixPrice',
'fieldName' => 'fixPrice',
));

View File

@ -0,0 +1,13 @@
<?php
$metadata->mapField(array(
'type' => 'integer',
'name' => 'hoursWorked',
'fieldName' => 'hoursWorked',
));
$metadata->mapField(array(
'type' => 'integer',
'name' => 'pricePerHour',
'fieldName' => 'pricePerHour',
));

View File

@ -0,0 +1,21 @@
<?php
$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 File

@ -7,6 +7,10 @@
<entity name="Doctrine\Tests\Models\CMS\CmsAddress" table="cms_users">
<entity-listeners>
<entity-listener class="CmsAddressListener"/>
</entity-listeners>
<named-native-queries>
<named-native-query name="find-all" result-set-mapping="mapping-find-all">
<query>SELECT id, country, city FROM cms_addresses</query>

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\Models\Company\CompanyContract" table="company_contracts" inheritance-type="SINGLE_TABLE">
<discriminator-map>
<discriminator-mapping value="fix" class="CompanyFixContract" />
<discriminator-mapping value="flexible" class="CompanyFlexContract" />
<discriminator-mapping value="flexultra" class="CompanyFlexUltraContract" />
</discriminator-map>
<entity-listeners>
<entity-listener class="CompanyContractListener">
<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>
<id name="id" type="integer" column="id">
<generator strategy="AUTO"/>
</id>
<field name="completed" column="completed" type="boolean"/>
<!-- Other mappings -->
</entity>
</doctrine-mapping>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\Models\Company\CompanyFixContract">
<field name="fixPrice" column="fixPrice" type="integer"/>
<!-- Other mappings -->
</entity>
</doctrine-mapping>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\Models\Company\CompanyFlexContract">
<field name="hoursWorked" column="hoursWorked" type="integer"/>
<field name="pricePerHour" column="pricePerHour" type="integer"/>
<!-- Other mappings -->
</entity>
</doctrine-mapping>

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="Doctrine\Tests\Models\Company\CompanyFlexUltraContract">
<entity-listeners>
<entity-listener class="CompanyContractListener">
<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-listener class="CompanyFlexUltraContractListener">
<lifecycle-callback type="prePersist" method="prePersistHandler1"/>
<lifecycle-callback type="prePersist" method="prePersistHandler2"/>
</entity-listener>
</entity-listeners>
<field name="maxPrice" column="maxPrice" type="integer"/>
<!-- Other mappings -->
</entity>
</doctrine-mapping>

View File

@ -1,6 +1,8 @@
Doctrine\Tests\Models\CMS\CmsAddress:
type: entity
table: cms_address
entityListeners:
CmsAddressListener:
namedNativeQueries:
find-all:
resultSetMapping: mapping-find-all
@ -55,7 +57,7 @@ Doctrine\Tests\Models\CMS\CmsAddress:
type: string
length: 50
oneToOne:
address:
user:
targetEntity: CmsUser
inversedBy: address
joinColumn:

View File

@ -0,0 +1,32 @@
Doctrine\Tests\Models\Company\CompanyContract:
type: entity
table: company_contracts
inheritanceType: SINGLE_TABLE
discriminatorMap:
fix: CompanyFixContract
flexible: CompanyFlexContract
flexultra: CompanyFlexUltraContract
entityListeners:
CompanyContractListener:
preFlush: [preFlushHandler]
postLoad: [postLoadHandler]
postPersist: [postPersistHandler]
prePersist: [prePersistHandler]
postUpdate: [postUpdateHandler]
preUpdate: [preUpdateHandler]
postRemove: [postRemoveHandler]
preRemove: [preRemoveHandler]
id:
id:
type: integer
generator:
strategy: AUTO
fields:
completed:
type: boolean

View File

@ -0,0 +1,5 @@
Doctrine\Tests\Models\Company\CompanyFixContract:
type: entity
fields:
fixPrice:
type: integer

View File

@ -0,0 +1,7 @@
Doctrine\Tests\Models\Company\CompanyFlexContract:
type: entity
fields:
hoursWorked:
type: integer
pricePerHour:
type: integer

View File

@ -0,0 +1,25 @@
Doctrine\Tests\Models\Company\CompanyFlexUltraContract:
type: entity
entityListeners:
CompanyContractListener:
preFlush: [preFlushHandler]
postLoad: [postLoadHandler]
postPersist: [postPersistHandler]
prePersist: [prePersistHandler]
postUpdate: [postUpdateHandler]
preUpdate: [preUpdateHandler]
postRemove: [postRemoveHandler]
preRemove: [preRemoveHandler]
CompanyFlexUltraContractListener:
prePersist: [prePersistHandler1, prePersistHandler2]
fields:
maxPrice:
type: integer