Skip to content

Commit

Permalink
add lifecycle callback handling, based on mongodb odm
Browse files Browse the repository at this point in the history
  • Loading branch information
brki committed Mar 31, 2011
1 parent 7a71c15 commit bb9e02c
Show file tree
Hide file tree
Showing 8 changed files with 206 additions and 14 deletions.
3 changes: 2 additions & 1 deletion lib/Doctrine/ODM/PHPCR/Event.php
Expand Up @@ -26,8 +26,9 @@ final class Event
const preRemove = 'preRemove';
const preUpdate = 'preUpdate';
const postRemove = 'postRemove';
const postPersist = 'postPersist';
const postUpdate = 'postUpdate';
const postLoad = 'postLoad';

private function __construct() {}
}
}
4 changes: 4 additions & 0 deletions lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadata.php
Expand Up @@ -98,6 +98,10 @@ public function __sleep()
$serialized[] = 'versionField';
}

if ($this->lifecycleCallbacks) {
$serialized[] = 'lifecycleCallbacks';
}

return $serialized;
}

Expand Down
74 changes: 74 additions & 0 deletions lib/Doctrine/ODM/PHPCR/Mapping/ClassMetadataInfo.php
Expand Up @@ -163,6 +163,14 @@ class ClassMetadataInfo implements ClassMetadata
*/
public $alsoLoadMethods = array();

/**
* READ-ONLY: The registered lifecycle callbacks for documents of this class.
*
* @var array
*/
public $lifecycleCallbacks = array();


/**
* The ReflectionClass instance of the mapped class.
*
Expand Down Expand Up @@ -277,6 +285,72 @@ public function setCustomRepositoryClassName($repositoryClassName)
$this->customRepositoryClassName = $repositoryClassName;
}

/**
* Dispatches the lifecycle event of the given document to the registered
* lifecycle callbacks and lifecycle listeners.
*
* @param string $event The lifecycle event.
* @param Document $document The Document on which the event occured.
*/
public function invokeLifecycleCallbacks($lifecycleEvent, $document, array $arguments = null)
{
foreach ($this->lifecycleCallbacks[$lifecycleEvent] as $callback) {
if ($arguments !== null) {
call_user_func_array(array($document, $callback), $arguments);
} else {
$document->$callback();
}
}
}

/**
* Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event.
*
* @param string $lifecycleEvent
* @return boolean
*/
public function hasLifecycleCallbacks($lifecycleEvent)
{
return isset($this->lifecycleCallbacks[$lifecycleEvent]);
}

/**
* Gets the registered lifecycle callbacks for an event.
*
* @param string $event
* @return array
*/
public function getLifecycleCallbacks($event)
{
return isset($this->lifecycleCallbacks[$event]) ? $this->lifecycleCallbacks[$event] : array();
}

/**
* Adds a lifecycle callback for documents of this class.
*
* Note: If the same callback is registered more than once, the old one
* will be overridden.
*
* @param string $callback
* @param string $event
*/
public function addLifecycleCallback($callback, $event)
{
$this->lifecycleCallbacks[$event][] = $callback;
}

/**
* Sets the lifecycle callbacks for documents of this class.
* Any previously registered callbacks are overwritten.
*
* @param array $callbacks
*/
public function setLifecycleCallbacks(array $callbacks)
{
$this->lifecycleCallbacks = $callbacks;
}


/**
* @param string $alias
*/
Expand Down
42 changes: 42 additions & 0 deletions lib/Doctrine/ODM/PHPCR/Mapping/Driver/AnnotationDriver.php
Expand Up @@ -161,6 +161,48 @@ public function loadMetadataForClass($className, ClassMetadata $class)
}
}
}

$methods = $reflClass->getMethods();
if (isset($classAnnotations['Doctrine\ODM\PHPCR\Mapping\HasLifecycleCallbacks'])) {
foreach ($methods as $method) {
if ($method->isPublic()) {
$annotations = $this->reader->getMethodAnnotations($method);

if (isset($annotations['Doctrine\ODM\PHPCR\Mapping\PrePersist'])) {
$class->addLifecycleCallback($method->getName(), \Doctrine\ODM\PHPCR\Event::prePersist);
}

if (isset($annotations['Doctrine\ODM\PHPCR\Mapping\PostPersist'])) {
$class->addLifecycleCallback($method->getName(), \Doctrine\ODM\PHPCR\Event::postPersist);
}

if (isset($annotations['Doctrine\ODM\PHPCR\Mapping\PreUpdate'])) {
$class->addLifecycleCallback($method->getName(), \Doctrine\ODM\PHPCR\Event::preUpdate);
}

if (isset($annotations['Doctrine\ODM\PHPCR\Mapping\PostUpdate'])) {
$class->addLifecycleCallback($method->getName(), \Doctrine\ODM\PHPCR\Event::postUpdate);
}

if (isset($annotations['Doctrine\ODM\PHPCR\Mapping\PreRemove'])) {
$class->addLifecycleCallback($method->getName(), \Doctrine\ODM\PHPCR\Event::preRemove);
}

if (isset($annotations['Doctrine\ODM\PHPCR\Mapping\PostRemove'])) {
$class->addLifecycleCallback($method->getName(), \Doctrine\ODM\PHPCR\Event::postRemove);
}

if (isset($annotations['Doctrine\ODM\PHPCR\Mapping\PreLoad'])) {
$class->addLifecycleCallback($method->getName(), \Doctrine\ODM\PHPCR\Event::preLoad);
}

if (isset($annotations['Doctrine\ODM\PHPCR\Mapping\PostLoad'])) {
$class->addLifecycleCallback($method->getName(), \Doctrine\ODM\PHPCR\Event::postLoad);
}
}
}
}

}

/**
Expand Down
12 changes: 12 additions & 0 deletions lib/Doctrine/ODM/PHPCR/Mapping/Driver/DoctrineAnnotations.php
Expand Up @@ -82,3 +82,15 @@ final class ReferenceMany extends Reference
public $cascade = array();
public $mappedBy;
}

/* Annotations for lifecycle callbacks */
final class HasLifecycleCallbacks extends Annotation {}
final class PrePersist extends Annotation {}
final class PostPersist extends Annotation {}
final class PreUpdate extends Annotation {}
final class PostUpdate extends Annotation {}
final class PreRemove extends Annotation {}
final class PostRemove extends Annotation {}
final class PreLoad extends Annotation {}
final class PostLoad extends Annotation {}

7 changes: 7 additions & 0 deletions lib/Doctrine/ODM/PHPCR/Mapping/Driver/XmlDriver.php
Expand Up @@ -99,6 +99,13 @@ public function loadMetadataForClass($className, ClassMetadata $class)
$this->addReferenceMapping($class, $reference, 'one');
}
}

if (isset($xmlRoot->{'lifecycle-callbacks'})) {
foreach ($xmlRoot->{'lifecycle-callbacks'}->{'lifecycle-callback'} as $lifecycleCallback) {
$class->addLifecycleCallback((string) $lifecycleCallback['method'], constant('Doctrine\ODM\PHPCR\Event::' . (string) $lifecycleCallback['type']));
}
}

}

private function addPropertyMapping(ClassMetadata $class, $mapping)
Expand Down
8 changes: 8 additions & 0 deletions lib/Doctrine/ODM/PHPCR/Mapping/Driver/YamlDriver.php
Expand Up @@ -97,6 +97,14 @@ public function loadMetadataForClass($className, ClassMetadata $class)
$this->addMappingFromReference($class, $fieldName, $reference, 'many');
}
}
if (isset($element['lifecycleCallbacks'])) {
foreach ($element['lifecycleCallbacks'] as $type => $methods) {
foreach ($methods as $method) {
$class->addLifecycleCallback($method, constant('Doctrine\ODM\PHPCR\Event::' . $type));
}
}
}

}

private function addPropertyMapping(ClassMetadata $class, $mapping)
Expand Down
70 changes: 57 additions & 13 deletions lib/Doctrine/ODM/PHPCR/UnitOfWork.php
Expand Up @@ -258,6 +258,10 @@ public function createDocument($documentName, $node, array &$hints = array())
}
}

// Invoke the postLoad lifecycle callbacks and listeners
if (isset($metadata->lifecycleCallbacks[Event::postLoad])) {
$metadata->invokeLifecycleCallbacks(Event::postLoad, $document);
}
if ($this->evm->hasListeners(Event::postLoad)) {
$this->evm->dispatchEvent(Event::postLoad, new Events\LifecycleEventArgs($document, $this->dm));
}
Expand Down Expand Up @@ -358,6 +362,10 @@ public function scheduleRemove($document)
$this->scheduledRemovals[$oid] = $document;
$this->documentState[$oid] = self::STATE_REMOVED;

$class = $this->dm->getClassMetadata(get_class($document));
if (isset($class->lifecycleCallbacks[Event::preRemove])) {
$class->invokeLifecycleCallbacks(Event::preRemove, $document);
}
if ($this->evm->hasListeners(Event::preRemove)) {
$this->evm->dispatchEvent(Event::preRemove, new Events\LifecycleEventArgs($document, $this->dm));
}
Expand Down Expand Up @@ -487,6 +495,9 @@ public function persistNew($class, $document)

$this->registerManaged($document, $path, null);

if (isset($class->lifecycleCallbacks[Event::prePersist])) {
$class->invokeLifecycleCallbacks(Event::prePersist, $document);
}
if ($this->evm->hasListeners(Event::prePersist)) {
$this->evm->dispatchEvent(Event::prePersist, new Events\LifecycleEventArgs($document, $this->dm));
}
Expand Down Expand Up @@ -519,6 +530,10 @@ public function flush()
foreach ($this->scheduledInserts as $oid => $document) {
$class = $this->dm->getClassMetadata(get_class($document));

if (isset($class->lifecycleCallbacks[Event::preUpdate])) {
$class->invokeLifecycleCallbacks(Event::preUpdate, $document);
$this->computeChangeSet($class, $document); // TODO: prevent association computations in this case?
}
if ($this->evm->hasListeners(Event::preUpdate)) {
$this->evm->dispatchEvent(Event::preUpdate, new Events\LifecycleEventArgs($document, $this->dm));
$this->computeChangeSet($class, $document); // TODO: prevent association computations in this case?
Expand Down Expand Up @@ -559,6 +574,10 @@ public function flush()
$class = $this->dm->getClassMetadata(get_class($document));
$node = $this->nodesMap[$oid];

if (isset($class->lifecycleCallbacks[Event::preUpdate])) {
$class->invokeLifecycleCallbacks(Event::preUpdate, $document);
$this->computeChangeSet($class, $document); // TODO: prevent association computations in this case?
}
if ($this->evm->hasListeners(Event::preUpdate)) {
$this->evm->dispatchEvent(Event::preUpdate, new Events\LifecycleEventArgs($document, $this->dm));
$this->computeChangeSet($class, $document); // TODO: prevent association computations in this case?
Expand Down Expand Up @@ -613,30 +632,55 @@ public function flush()

$session->save();

$this->handlePostFlushEvents();

foreach ($this->visitedCollections as $col) {
$col->takeSnapshot();
}

$this->scheduledUpdates =
$this->scheduledRemovals =
$this->scheduledInserts =
$this->visitedCollections = array();
}

/**
* Invoke / dispatch events as necessary after a flush operation
*/
protected function handlePostFlushEvents()
{
foreach ($this->scheduledRemovals as $oid => $document) {
$class = $this->dm->getClassMetadata(get_class($document));

if (isset($class->lifecycleCallbacks[Event::postRemove])) {
$class->invokeLifecycleCallbacks(Event::postRemove, $document);
}
if ($this->evm->hasListeners(Event::postRemove)) {
$this->evm->dispatchEvent(Event::postRemove, new Events\LifecycleEventArgs($document, $this->dm));
}
}

foreach (array('scheduledInserts', 'scheduledUpdates') as $var) {
foreach ($this->$var as $oid => $document) {
$class = $this->dm->getClassMetadata(get_class($document));
foreach ($this->scheduledInserts as $oid => $document) {
$class = $this->dm->getClassMetadata(get_class($document));

if ($this->evm->hasListeners(Event::postUpdate)) {
$this->evm->dispatchEvent(Event::postUpdate, new Events\LifecycleEventArgs($document, $this->dm));
}
if (isset($class->lifecycleCallbacks[Event::postPersist])) {
$class->invokeLifecycleCallbacks(Event::postPersist, $document);
}
if ($this->evm->hasListeners(Event::postPersist)) {
$this->evm->dispatchEvent(Event::postPersist, new Events\LifecycleEventArgs($document, $this->dm));
}
}

foreach ($this->visitedCollections as $col) {
$col->takeSnapshot();
}
foreach ($this->scheduledUpdates as $oid => $document) {
$class = $this->dm->getClassMetadata(get_class($document));

$this->scheduledUpdates =
$this->scheduledRemovals =
$this->scheduledInserts =
$this->visitedCollections = array();
if (isset($class->lifecycleCallbacks[Event::postUpdate])) {
$class->invokeLifecycleCallbacks(Event::postUpdate, $document);
}
if ($this->evm->hasListeners(Event::postUpdate)) {
$this->evm->dispatchEvent(Event::postUpdate, new Events\LifecycleEventArgs($document, $this->dm));
}
}
}

/**
Expand Down

0 comments on commit bb9e02c

Please sign in to comment.