diff --git a/lib/Doctrine/ODM/PHPCR/Event.php b/lib/Doctrine/ODM/PHPCR/Event.php index c7fe9cd5b..90ab4e5dd 100644 --- a/lib/Doctrine/ODM/PHPCR/Event.php +++ b/lib/Doctrine/ODM/PHPCR/Event.php @@ -33,6 +33,7 @@ final class Event const preFlush = 'preFlush'; const postFlush = 'postFlush'; + const endFlush = 'endFlush'; const onFlush = 'onFlush'; const onClear = 'onClear'; const loadClassMetadata = 'loadClassMetadata'; diff --git a/lib/Doctrine/ODM/PHPCR/UnitOfWork.php b/lib/Doctrine/ODM/PHPCR/UnitOfWork.php index f667f4380..56c00e40d 100644 --- a/lib/Doctrine/ODM/PHPCR/UnitOfWork.php +++ b/lib/Doctrine/ODM/PHPCR/UnitOfWork.php @@ -1157,18 +1157,20 @@ public function computeChangeSet(ClassMetadata $class, $document) $this->changesetComputed[] = $oid; $actualData = $this->getDocumentActualData($class, $document); + $changeSet = $actualData; + $id = $this->getDocumentId($document, false); $isNew = !isset($this->originalData[$oid]); if ($isNew) { // Document is New and should be inserted - $this->originalData[$oid] = $actualData; - $this->documentChangesets[$oid] = array('fields' => $actualData, 'reorderings' => array()); + $this->originalData[$oid] = $changeSet; + $this->documentChangesets[$oid] = array('fields' => $changeSet, 'reorderings' => array()); $this->scheduledInserts[$oid] = $document; } - if ($class->parentMapping && isset($actualData[$class->parentMapping])) { - $parent = $actualData[$class->parentMapping]; + if ($class->parentMapping && isset($changeSet[$class->parentMapping])) { + $parent = $changeSet[$class->parentMapping]; $parentClass = $this->dm->getClassMetadata(get_class($parent)); $state = $this->getDocumentState($parent); @@ -1178,33 +1180,34 @@ public function computeChangeSet(ClassMetadata $class, $document) } foreach ($class->childMappings as $fieldName) { - if ($actualData[$fieldName]) { - if (is_array($actualData[$fieldName]) || $actualData[$fieldName] instanceof Collection) { + if ($changeSet[$fieldName]) { + if (is_array($changeSet[$fieldName]) || $changeSet[$fieldName] instanceof Collection) { throw PHPCRException::childFieldIsArray( self::objToStr($document, $this->dm), $fieldName ); } - if (!is_object($actualData[$fieldName])) { + + if (!is_object($changeSet[$fieldName])) { throw PHPCRException::childFieldNoObject( self::objToStr($document, $this->dm), $fieldName, - gettype($actualData[$fieldName]) + gettype($changeSet[$fieldName]) ); } $mapping = $class->mappings[$fieldName]; - $actualData[$fieldName] = $this->computeChildChanges($mapping, $actualData[$fieldName], $id, $mapping['nodeName']); + $changeSet[$fieldName] = $this->computeChildChanges($mapping, $changeSet[$fieldName], $id, $mapping['nodeName']); } } - $this->computeAssociationChanges($class, $oid, $isNew, $actualData, 'reference'); - $this->computeAssociationChanges($class, $oid, $isNew, $actualData, 'referrer'); + $this->computeAssociationChanges($class, $oid, $isNew, $changeSet, 'reference'); + $this->computeAssociationChanges($class, $oid, $isNew, $changeSet, 'referrer'); foreach ($class->mixedReferrersMappings as $fieldName) { - if ($actualData[$fieldName] - && $actualData[$fieldName] instanceof PersistentCollection - && $actualData[$fieldName]->isDirty() + if ($changeSet[$fieldName] + && $changeSet[$fieldName] instanceof PersistentCollection + && $changeSet[$fieldName]->isDirty() ) { throw new PHPCRException("The immutable mixed referrer collection in field $fieldName is dirty"); } @@ -1213,8 +1216,8 @@ public function computeChangeSet(ClassMetadata $class, $document) if ($isNew) { // much simpler handling for children foreach ($class->childrenMappings as $fieldName) { - if ($actualData[$fieldName]) { - if (!is_array($actualData[$fieldName]) && !$actualData[$fieldName] instanceof Collection) { + if ($changeSet[$fieldName]) { + if (!is_array($changeSet[$fieldName]) && !$changeSet[$fieldName] instanceof Collection) { throw PHPCRException::childrenFieldNoArray( self::objToStr($document, $this->dm), $fieldName @@ -1222,7 +1225,7 @@ public function computeChangeSet(ClassMetadata $class, $document) } $mapping = $class->mappings[$fieldName]; - foreach ($actualData[$fieldName] as $originalNodename => $child) { + foreach ($changeSet[$fieldName] as $originalNodename => $child) { if (!is_object($child)) { throw PHPCRException::childrenContainsNonObject( self::objToStr($document, $this->dm), @@ -1232,9 +1235,9 @@ public function computeChangeSet(ClassMetadata $class, $document) } $nodename = $this->getChildNodename($id, $originalNodename, $child, $document); - $actualData[$fieldName][$nodename] = $this->computeChildChanges($mapping, $child, $id, $nodename, $document); + $changeSet[$fieldName][$nodename] = $this->computeChildChanges($mapping, $child, $id, $nodename, $document); if (0 !== strcmp($originalNodename, $nodename)) { - unset($actualData[$fieldName][$originalNodename]); + unset($changeSet[$fieldName][$originalNodename]); } $childNames[] = $nodename; } @@ -1245,30 +1248,30 @@ public function computeChangeSet(ClassMetadata $class, $document) $destPath = $destName = false; if (isset($this->originalData[$oid][$class->parentMapping]) - && isset($actualData[$class->parentMapping]) - && $this->originalData[$oid][$class->parentMapping] !== $actualData[$class->parentMapping] + && isset($changeSet[$class->parentMapping]) + && $this->originalData[$oid][$class->parentMapping] !== $changeSet[$class->parentMapping] ) { - $destPath = $this->getDocumentId($actualData[$class->parentMapping]); + $destPath = $this->getDocumentId($changeSet[$class->parentMapping]); } if (isset($this->originalData[$oid][$class->nodename]) - && isset($actualData[$class->nodename]) - && $this->originalData[$oid][$class->nodename] !== $actualData[$class->nodename] + && isset($changeSet[$class->nodename]) + && $this->originalData[$oid][$class->nodename] !== $changeSet[$class->nodename] ) { - $destName = $actualData[$class->nodename]; + $destName = $changeSet[$class->nodename]; } // there was assignment move if ($destPath || $destName) { // add the other field if only one was changed if (false === $destPath) { - $destPath = isset($actualData[$class->parentMapping]) - ? $this->getDocumentId($actualData[$class->parentMapping]) + $destPath = isset($changeSet[$class->parentMapping]) + ? $this->getDocumentId($changeSet[$class->parentMapping]) : PathHelper::getParentPath($this->getDocumentId($document)); } if (false === $destName) { - $destName = $actualData[$class->nodename] - ? $actualData[$class->nodename] + $destName = $changeSet[$class->nodename] + ? $changeSet[$class->nodename] : PathHelper::getNodeName($this->getDocumentId($document)); } @@ -1284,35 +1287,36 @@ public function computeChangeSet(ClassMetadata $class, $document) } if (isset($this->originalData[$oid][$class->identifier]) - && isset($actualData[$class->identifier]) - && $this->originalData[$oid][$class->identifier] !== $actualData[$class->identifier] + && isset($changeSet[$class->identifier]) + && $this->originalData[$oid][$class->identifier] !== $changeSet[$class->identifier] ) { - throw new PHPCRException('The Id is immutable ('.$this->originalData[$oid][$class->identifier].' !== '.$actualData[$class->identifier].'). Please use DocumentManager::move to move the document: '.self::objToStr($document, $this->dm)); + throw new PHPCRException('The Id is immutable ('.$this->originalData[$oid][$class->identifier].' !== '.$changeSet[$class->identifier].'). Please use DocumentManager::move to move the document: '.self::objToStr($document, $this->dm)); } foreach ($class->childrenMappings as $fieldName) { - if ($actualData[$fieldName] instanceof PersistentCollection) { - if (!$actualData[$fieldName]->isInitialized()) { + if ($changeSet[$fieldName] instanceof PersistentCollection) { + if (!$changeSet[$fieldName]->isInitialized()) { continue; } - $coid = spl_object_hash($actualData[$fieldName]); - $this->visitedCollections[$coid] = $actualData[$fieldName]; + $coid = spl_object_hash($changeSet[$fieldName]); + $this->visitedCollections[$coid] = $changeSet[$fieldName]; } + $mapping = $class->mappings[$fieldName]; $childNames = array(); $movedChildNames = array(); - if ($actualData[$fieldName]) { - if (!is_array($actualData[$fieldName]) && !$actualData[$fieldName] instanceof Collection) { + if ($changeSet[$fieldName]) { + if (!is_array($changeSet[$fieldName]) && !$changeSet[$fieldName] instanceof Collection) { throw PHPCRException::childrenFieldNoArray( self::objToStr($document, $this->dm), $fieldName ); } - foreach ($actualData[$fieldName] as $originalNodename => $child) { + foreach ($changeSet[$fieldName] as $originalNodename => $child) { if (!is_object($child)) { throw PHPCRException::childrenContainsNonObject( self::objToStr($document, $this->dm), @@ -1322,9 +1326,9 @@ public function computeChangeSet(ClassMetadata $class, $document) } $nodename = $this->getChildNodename($id, $originalNodename, $child, $document); - $actualData[$fieldName][$nodename] = $this->computeChildChanges($mapping, $child, $id, $nodename, $document); + $changeSet[$fieldName][$nodename] = $this->computeChildChanges($mapping, $child, $id, $nodename, $document); if (0 !== strcmp($originalNodename, $nodename)) { - unset($actualData[$fieldName][$originalNodename]); + unset($changeSet[$fieldName][$originalNodename]); $movedChildNames[] = (string) $originalNodename; } $childNames[] = $nodename; @@ -1370,8 +1374,8 @@ public function computeChangeSet(ClassMetadata $class, $document) if (!isset($this->documentLocales[$oid]) || $this->documentLocales[$oid]['current'] === $this->documentLocales[$oid]['original'] ) { - // remove anything from $actualData that did not change - foreach ($actualData as $fieldName => $fieldValue) { + // remove anything from $changeSet that did not change + foreach ($changeSet as $fieldName => $fieldValue) { if (isset($class->mappings[$fieldName])) { if ($this->originalData[$oid][$fieldName] !== $fieldValue) { continue; @@ -1385,17 +1389,20 @@ public function computeChangeSet(ClassMetadata $class, $document) } } - unset($actualData[$fieldName]); + unset($changeSet[$fieldName]); } } - if (count($actualData)) { + if (count($changeSet)) { if (empty($this->documentChangesets[$oid])) { - $this->documentChangesets[$oid] = array('fields' => $actualData, 'reorderings' => array()); - } else { - $this->documentChangesets[$oid]['fields'] = $actualData; + $this->documentChangesets[$oid] = array( + 'fields' => array(), + 'reorderings' => array() + ); } + $this->documentChangesets[$oid]['fields'] = $changeSet; + $this->originalData[$oid] = $actualData; $this->scheduledUpdates[$oid] = $document; } elseif (isset($this->documentChangesets[$oid])) { // make sure we don't keep an old changeset if an event changed @@ -1942,6 +1949,22 @@ public function commit($document = null) } } + if (empty($this->scheduledInserts) && + empty($this->scheduledUpdates) && + empty($this->scheduledRemovals) && + empty($this->scheduledReorders) && + empty($this->documentTranslations) && + empty($this->scheduledMoves)) { + $this->invokeGlobalEvent(Event::onFlush, new ManagerEventArgs($this->dm)); + $this->invokeGlobalEvent(Event::postFlush, new ManagerEventArgs($this->dm)); + + // @deprecated This is to maintain BC with the old behavior, where users may call + // flush instead of PHPCR\SessionInterface#save + $this->session->save(); + + return; // Nothing to do. + } + $this->invokeGlobalEvent(Event::onFlush, new ManagerEventArgs($this->dm)); try { @@ -2014,6 +2037,8 @@ public function commit($document = null) $this->visitedCollections = $this->documentChangesets = $this->changesetComputed = array(); + + $this->invokeGlobalEvent(Event::endFlush, new ManagerEventArgs($this->dm)); } /** diff --git a/tests/Doctrine/Tests/ODM/PHPCR/Functional/EventManagerTest.php b/tests/Doctrine/Tests/ODM/PHPCR/Functional/EventManagerTest.php index 6bc8d16cf..ee6ede29c 100644 --- a/tests/Doctrine/Tests/ODM/PHPCR/Functional/EventManagerTest.php +++ b/tests/Doctrine/Tests/ODM/PHPCR/Functional/EventManagerTest.php @@ -57,6 +57,7 @@ public function testTriggerEvents() Event::preFlush, Event::preMove, Event::postMove, + Event::endFlush, ), $this->listener ); @@ -70,12 +71,14 @@ public function testTriggerEvents() $this->assertTrue($this->listener->pagePrePersist); $this->assertFalse($this->listener->itemPrePersist); $this->assertFalse($this->listener->postFlush); + $this->assertFalse($this->listener->endFlush); $this->assertFalse($this->listener->preFlush); $this->dm->flush(); $this->assertTrue($this->listener->onFlush); $this->assertTrue($this->listener->postFlush); + $this->assertTrue($this->listener->endFlush); $this->assertTrue($this->listener->preFlush); $this->assertFalse($this->listener->preUpdate); $this->assertFalse($this->listener->postUpdate); @@ -205,6 +208,7 @@ class TestPersistenceListener public $itemPostRemove = false; public $onFlush = false; public $postFlush = false; + public $endFlush = false; public $preFlush = false; public $itemPreMove = false; public $itemPostMove = false; @@ -306,6 +310,16 @@ public function postFlush(ManagerEventArgs $e) $this->postFlush = true; } + public function endFlush(ManagerEventArgs $e) + { + $this->endFlush = true; + $dm = $e->getObjectManager(); + + // endFlush can call ->flush(). The UOW should exit early if there is nothing + // to do, avoiding an infinite recursion. + $dm->flush(); + } + public function preFlush(ManagerEventArgs $e) { $this->preFlush = true; diff --git a/tests/Doctrine/Tests/ODM/PHPCR/Functional/Translation/AttributeTranslationStrategyTest.php b/tests/Doctrine/Tests/ODM/PHPCR/Functional/Translation/AttributeTranslationStrategyTest.php index b3c968823..8dfee4ae6 100644 --- a/tests/Doctrine/Tests/ODM/PHPCR/Functional/Translation/AttributeTranslationStrategyTest.php +++ b/tests/Doctrine/Tests/ODM/PHPCR/Functional/Translation/AttributeTranslationStrategyTest.php @@ -344,7 +344,7 @@ public function testQueryBuilder() ); $strategy->saveTranslation($data, $node, $this->metadata, 'fr'); - $this->dm->flush(); + $this->dm->getPhpcrSession()->save(); $qb = $this->dm->createQueryBuilder(); $qb->from()->document('Doctrine\Tests\Models\Translation\Article', 'a'); diff --git a/tests/Doctrine/Tests/ODM/PHPCR/Functional/Translation/ChildTranslationStrategyTest.php b/tests/Doctrine/Tests/ODM/PHPCR/Functional/Translation/ChildTranslationStrategyTest.php index 5cd738766..dc4a585e3 100644 --- a/tests/Doctrine/Tests/ODM/PHPCR/Functional/Translation/ChildTranslationStrategyTest.php +++ b/tests/Doctrine/Tests/ODM/PHPCR/Functional/Translation/ChildTranslationStrategyTest.php @@ -64,7 +64,7 @@ public function testSaveTranslation() $data['topic'] = 'Un sujet intéressant'; $strategy->saveTranslation($data, $node, $this->metadata, 'fr'); - $this->dm->flush(); + $this->dm->getPhpcrSession()->save(); // Then test we have what we expect in the content repository $node_en = $this->session->getNode($this->nodeNameForLocale('en'));