1
0
mirror of synced 2025-01-18 06:21:40 +03:00

[2.0] added entity lifecycle events.

This commit is contained in:
romanb 2009-07-18 18:06:30 +00:00
parent 227667c95d
commit 59cf1f745d
7 changed files with 269 additions and 21 deletions

View File

@ -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;
}
}

View File

@ -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';
}

View File

@ -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);
}
}
}
}
}
/**

View File

@ -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 {}

View File

@ -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;
}

View File

@ -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());

View File

@ -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!';
}
}