[2.0] added entity lifecycle events.
This commit is contained in:
parent
227667c95d
commit
59cf1f745d
@ -7,7 +7,7 @@ class LifecycleEventArgs extends \Doctrine\Common\EventArgs
|
||||
private $_em;
|
||||
private $_entity;
|
||||
|
||||
public function __construct($entity, \Doctrine\ORM\EntityManager $em)
|
||||
public function __construct($entity)
|
||||
{
|
||||
$this->_entity = $entity;
|
||||
}
|
||||
@ -16,9 +16,4 @@ class LifecycleEventArgs extends \Doctrine\Common\EventArgs
|
||||
{
|
||||
return $this->_entity;
|
||||
}
|
||||
|
||||
public function getEntityManager()
|
||||
{
|
||||
return $this->_em;
|
||||
}
|
||||
}
|
@ -32,13 +32,76 @@ namespace Doctrine\ORM;
|
||||
final class Events
|
||||
{
|
||||
private function __construct() {}
|
||||
|
||||
/**
|
||||
* The preDelete event occurs for a given entity before the respective
|
||||
* EntityManager delete operation for that entity is executed.
|
||||
*
|
||||
* This is an entity lifecycle event.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const preDelete = 'preDelete';
|
||||
/**
|
||||
* The postDelete event occurs for an entity after the entity has
|
||||
* been deleted. It will be invoked after the database delete operations.
|
||||
*
|
||||
* This is an entity lifecycle event.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const postDelete = 'postDelete';
|
||||
const preInsert = 'preInsert';
|
||||
const postInsert = 'postInsert';
|
||||
/**
|
||||
* The preSave event occurs for a given entity before the respective
|
||||
* EntityManager save operation for that entity is executed.
|
||||
*
|
||||
* This is an entity lifecycle event.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const preSave = 'preSave';
|
||||
/**
|
||||
* The postSave event occurs for an entity after the entity has
|
||||
* been made persistent. It will be invoked after the database insert operations.
|
||||
* Generated primary key values are available in the postSave event.
|
||||
*
|
||||
* This is an entity lifecycle event.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const postSave = 'postSave';
|
||||
/**
|
||||
* The preUpdate event occurs before the database update operations to
|
||||
* entity data.
|
||||
*
|
||||
* This is an entity lifecycle event.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const preUpdate = 'preUpdate';
|
||||
/**
|
||||
* The postUpdate event occurs after the database update operations to
|
||||
* entity data.
|
||||
*
|
||||
* This is an entity lifecycle event.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const postUpdate = 'postUpdate';
|
||||
/**
|
||||
* The postLoad event occurs for an entity after the entity has been loaded
|
||||
* into the current EntityManager from the database or after the refresh operation
|
||||
* has been applied to it.
|
||||
*
|
||||
* This is an entity lifecycle event.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const postLoad = 'postLoad';
|
||||
/**
|
||||
* The loadClassMetadata event occurs after the mapping metadata for a class
|
||||
* has been loaded from a mapping source (annotations/xml/yaml).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const loadClassMetadata = 'loadClassMetadata';
|
||||
}
|
@ -231,9 +231,39 @@ class AnnotationDriver implements Driver
|
||||
$mapping['mappedBy'] = $manyToManyAnnot->mappedBy;
|
||||
$mapping['cascade'] = $manyToManyAnnot->cascade;
|
||||
$metadata->mapManyToMany($mapping);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate LifecycleListener annotation
|
||||
if (($lifecycleListenerAnnot = $this->_reader->getClassAnnotation($class, 'Doctrine\ORM\Mapping\LifecycleListener'))) {
|
||||
foreach ($class->getMethods() as $method) {
|
||||
if ($method->isPublic()) {
|
||||
$annotations = $this->_reader->getMethodAnnotations($method);
|
||||
if (isset($annotations['Doctrine\ORM\Mapping\PreSave'])) {
|
||||
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::preSave);
|
||||
}
|
||||
if (isset($annotations['Doctrine\ORM\Mapping\PostSave'])) {
|
||||
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postSave);
|
||||
}
|
||||
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\PreDelete'])) {
|
||||
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::preDelete);
|
||||
}
|
||||
if (isset($annotations['Doctrine\ORM\Mapping\PostDelete'])) {
|
||||
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postDelete);
|
||||
}
|
||||
if (isset($annotations['Doctrine\ORM\Mapping\PostLoad'])) {
|
||||
$metadata->addLifecycleCallback($method->getName(), \Doctrine\ORM\Events::postLoad);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -103,6 +103,7 @@ final class SequenceGenerator extends \Doctrine\Common\Annotations\Annotation {
|
||||
final class ChangeTrackingPolicy extends \Doctrine\Common\Annotations\Annotation {}
|
||||
|
||||
/* Annotations for lifecycle callbacks */
|
||||
final class LifecycleListener extends \Doctrine\Common\Annotations\Annotation {}
|
||||
final class PreSave extends \Doctrine\Common\Annotations\Annotation {}
|
||||
final class PostSave extends \Doctrine\Common\Annotations\Annotation {}
|
||||
final class PreUpdate extends \Doctrine\Common\Annotations\Annotation {}
|
||||
|
@ -25,6 +25,7 @@ use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\DoctrineException;
|
||||
use Doctrine\Common\PropertyChangedListener;
|
||||
use Doctrine\ORM\Events;
|
||||
use Doctrine\ORM\Event\LifecycleEventArgs;
|
||||
use Doctrine\ORM\Internal\CommitOrderCalculator;
|
||||
use Doctrine\ORM\Internal\CommitOrderNode;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
@ -634,18 +635,28 @@ class UnitOfWork implements PropertyChangedListener
|
||||
{
|
||||
$className = $class->name;
|
||||
$persister = $this->getEntityPersister($className);
|
||||
|
||||
$hasLifecycleCallbacks = isset($class->lifecycleCallbacks[Events::postSave]);
|
||||
$hasListeners = $this->_evm->hasListeners(Events::postSave);
|
||||
if ($hasLifecycleCallbacks || $hasListeners) {
|
||||
$entities = array();
|
||||
}
|
||||
|
||||
foreach ($this->_entityInsertions as $oid => $entity) {
|
||||
if (get_class($entity) == $className) {
|
||||
$persister->addInsert($entity);
|
||||
unset($this->_entityInsertions[$oid]);
|
||||
if ($hasLifecycleCallbacks || $hasListeners) {
|
||||
$entities[] = $entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$postInsertIds = $persister->executeInserts();
|
||||
|
||||
if ($postInsertIds) {
|
||||
// Persister returned a post-insert IDs
|
||||
foreach ($postInsertIds as $id => $entity) {
|
||||
// Persister returned a post-insert ID
|
||||
$oid = spl_object_hash($entity);
|
||||
$idField = $class->identifier[0];
|
||||
$class->reflFields[$idField]->setValue($entity, $id);
|
||||
@ -655,6 +666,17 @@ class UnitOfWork implements PropertyChangedListener
|
||||
$this->addToIdentityMap($entity);
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasLifecycleCallbacks || $hasListeners) {
|
||||
foreach ($entities as $entity) {
|
||||
if ($hasLifecycleCallbacks) {
|
||||
$class->invokeLifecycleCallbacks(Events::postSave, $entity);
|
||||
}
|
||||
if ($hasListeners) {
|
||||
$this->_evm->dispatchEvent(Events::postSave, new LifecycleEventArgs($entity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -666,14 +688,36 @@ class UnitOfWork implements PropertyChangedListener
|
||||
{
|
||||
$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);
|
||||
|
||||
foreach ($this->_entityUpdates as $oid => $entity) {
|
||||
if (get_class($entity) == $className) {
|
||||
//TODO: Fire preUpdate
|
||||
if ($hasPreUpdateLifecycleCallbacks) {
|
||||
$class->invokeLifecycleCallbacks(Events::preUpdate, $entity);
|
||||
if ( ! $hasPreUpdateListeners) {
|
||||
// Need to recompute entity changeset to detect changes made in the callback.
|
||||
$this->computeSingleEntityChangeSet($class, $entity);
|
||||
}
|
||||
}
|
||||
if ($hasPreUpdateListeners) {
|
||||
$this->_evm->dispatchEvent(Events::preUpdate, new LifecycleEventArgs($entity));
|
||||
// Need to recompute entity changeset to detect changes made in the listener.
|
||||
$this->computeSingleEntityChangeSet($class, $entity);
|
||||
}
|
||||
|
||||
$persister->update($entity);
|
||||
unset($this->_entityUpdates[$oid]);
|
||||
|
||||
//TODO: Fire postUpdate
|
||||
if ($hasPostUpdateLifecycleCallbacks) {
|
||||
$class->invokeLifecycleCallbacks(Events::postUpdate, $entity);
|
||||
}
|
||||
if ($hasPostUpdateListeners) {
|
||||
$this->_evm->dispatchEvent(Events::postUpdate, new LifecycleEventArgs($entity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -687,12 +731,21 @@ class UnitOfWork implements PropertyChangedListener
|
||||
{
|
||||
$className = $class->name;
|
||||
$persister = $this->getEntityPersister($className);
|
||||
|
||||
$hasLifecycleCallbacks = isset($class->lifecycleCallbacks[Events::postDelete]);
|
||||
$hasListeners = $this->_evm->hasListeners(Events::postDelete);
|
||||
|
||||
foreach ($this->_entityDeletions as $oid => $entity) {
|
||||
if (get_class($entity) == $className) {
|
||||
$persister->delete($entity);
|
||||
unset($this->_entityDeletions[$oid]);
|
||||
|
||||
//TODO: Fire postDelete
|
||||
if ($hasLifecycleCallbacks) {
|
||||
$class->invokeLifecycleCallbacks(Events::postDelete, $entity);
|
||||
}
|
||||
if ($hasListeners) {
|
||||
$this->_evm->dispatchEvent(Events::postDelete, new LifecycleEventArgs($entity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1109,7 +1162,12 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
break;
|
||||
case self::STATE_NEW:
|
||||
//TODO: Fire preSave lifecycle event
|
||||
if (isset($class->lifecycleCallbacks[Events::preSave])) {
|
||||
$class->invokeLifecycleCallbacks(Events::preSave, $entity);
|
||||
}
|
||||
if ($this->_evm->hasListeners(Events::preSave)) {
|
||||
$this->_evm->dispatchEvent(Events::preSave, new LifecycleEventArgs($entity));
|
||||
}
|
||||
|
||||
$idGen = $class->idGenerator;
|
||||
if ($idGen->isPostInsertGenerator()) {
|
||||
@ -1172,15 +1230,20 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
|
||||
$visited[$oid] = $entity; // mark visited
|
||||
|
||||
//TODO: Fire preDelete
|
||||
|
||||
$class = $this->_em->getClassMetadata(get_class($entity));
|
||||
switch ($this->getEntityState($entity)) {
|
||||
case self::STATE_NEW:
|
||||
case self::STATE_DELETED:
|
||||
// nothing to do
|
||||
break;
|
||||
case self::STATE_MANAGED:
|
||||
if (isset($class->lifecycleCallbacks[Events::preDelete])) {
|
||||
$class->invokeLifecycleCallbacks(Events::preDelete, $entity);
|
||||
}
|
||||
if ($this->_evm->hasListeners(Events::preDelete)) {
|
||||
$this->_evm->dispatchEvent(Events::preDelete, new LifecycleEventArgs($entity));
|
||||
}
|
||||
$this->registerDeleted($entity);
|
||||
break;
|
||||
case self::STATE_DETACHED:
|
||||
@ -1227,6 +1290,15 @@ class UnitOfWork implements PropertyChangedListener
|
||||
} else {
|
||||
$managedCopy = $this->_em->find($class->name, $id);
|
||||
}
|
||||
|
||||
if ($class->isVersioned) {
|
||||
$managedCopyVersion = $class->reflFields[$class->versionField]->getValue($managedCopy);
|
||||
$entityVersion = $class->reflFields[$class->versionField]->getValue($entity);
|
||||
// Throw exception if versions dont match.
|
||||
if ($managedCopyVersion != $entity) {
|
||||
throw OptimisticLockException::versionMismatch();
|
||||
}
|
||||
}
|
||||
|
||||
// Merge state of $entity into existing (managed) entity
|
||||
foreach ($class->reflFields as $name => $prop) {
|
||||
@ -1463,13 +1535,13 @@ class UnitOfWork implements PropertyChangedListener
|
||||
}
|
||||
}
|
||||
|
||||
/*if (isset($class->lifecycleCallbacks[Events::postLoad])) {
|
||||
if (isset($class->lifecycleCallbacks[Events::postLoad])) {
|
||||
$class->invokeLifecycleCallbacks(Events::postLoad, $entity);
|
||||
}
|
||||
if ($this->_evm->hasListeners(Events::postLoad)) {
|
||||
$this->_evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity, $this->_em));
|
||||
$this->_evm->dispatchEvent(Events::postLoad, new LifecycleEventArgs($entity));
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ class AllTests
|
||||
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\OneToManySelfReferentialAssociationTest');
|
||||
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\ManyToManySelfReferentialAssociationTest');
|
||||
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\ReferenceProxyTest');
|
||||
$suite->addTestSuite('Doctrine\Tests\ORM\Functional\LifecycleCallbackTest');
|
||||
|
||||
$suite->addTest(Locking\AllTests::suite());
|
||||
|
||||
|
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\Tests\ORM\Functional;
|
||||
|
||||
require_once __DIR__ . '/../../TestInit.php';
|
||||
|
||||
class LifecycleCallbackTest extends \Doctrine\Tests\OrmFunctionalTestCase
|
||||
{
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
try {
|
||||
$this->_schemaTool->createSchema(array(
|
||||
$this->_em->getClassMetadata('Doctrine\Tests\ORM\Functional\LifecycleCallbackTestEntity')
|
||||
));
|
||||
} catch (\Exception $e) {
|
||||
// Swallow all exceptions. We do not test the schema tool here.
|
||||
}
|
||||
}
|
||||
|
||||
public function testPreSavePostSaveCallbacksAreInvoked()
|
||||
{
|
||||
$entity = new LifecycleCallbackTestEntity;
|
||||
$entity->value = 'hello';
|
||||
$this->_em->save($entity);
|
||||
$this->_em->flush();
|
||||
|
||||
$this->assertTrue($entity->preSaveCallbackInvoked);
|
||||
$this->assertTrue($entity->postSaveCallbackInvoked);
|
||||
|
||||
$this->_em->clear();
|
||||
|
||||
$query = $this->_em->createQuery("select e from Doctrine\Tests\ORM\Functional\LifecycleCallbackTestEntity e");
|
||||
$result = $query->getResultList();
|
||||
$this->assertTrue($result[0]->postLoadCallbackInvoked);
|
||||
|
||||
$result[0]->value = 'hello again';
|
||||
|
||||
$this->_em->flush();
|
||||
|
||||
$this->assertEquals('changed from preUpdate callback!', $result[0]->value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Entity
|
||||
* @LifecycleListener
|
||||
* @Table(name="lifecycle_callback_test_entity")
|
||||
*/
|
||||
class LifecycleCallbackTestEntity
|
||||
{
|
||||
/* test stuff */
|
||||
public $preSaveCallbackInvoked = false;
|
||||
public $postSaveCallbackInvoked = false;
|
||||
public $postLoadCallbackInvoked = false;
|
||||
|
||||
/**
|
||||
* @Id @Column(type="integer")
|
||||
* @GeneratedValue(strategy="AUTO")
|
||||
*/
|
||||
private $id;
|
||||
/**
|
||||
* @Column(type="string", length=255)
|
||||
*/
|
||||
public $value;
|
||||
|
||||
/** @PreSave */
|
||||
public function doStuffOnPreSave() {
|
||||
$this->preSaveCallbackInvoked = true;
|
||||
}
|
||||
|
||||
/** @PostSave */
|
||||
public function doStuffOnPostSave() {
|
||||
$this->postSaveCallbackInvoked = true;
|
||||
}
|
||||
|
||||
/** @PostLoad */
|
||||
public function doStuffOnPostLoad() {
|
||||
$this->postLoadCallbackInvoked = true;
|
||||
}
|
||||
|
||||
/** @PreUpdate */
|
||||
public function doStuffOnPreUpdate() {
|
||||
$this->value = 'changed from preUpdate callback!';
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user