Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #268 from doctrine/collection_lazy_loading

improved lazy loading for ChildrenCollection's
  • Loading branch information...
commit 84ad950df08b8d6c23f98d12004384764335bb1e 2 parents b7f40b9 + 659df23
@lsmith77 lsmith77 authored
View
19 docs/doctrine-phpcr-odm.html
@@ -183,7 +183,7 @@
/** @PHPCR\ReferenceOne */
public $reference;
- /** @PHPCR\Referrers */
+ /** @PHPCR\MixedReferrers */
public $referrers;
...
@@ -238,7 +238,7 @@
$query->setLimit($limit);
$query->setOffset($offset);
- $collection = $dm->getDocumentsByQuery($query);
+ $collection = $dm->getDocumentsByPhpcrQuery($query);
</pre>
</div>
@@ -248,14 +248,16 @@
$qb = $dm->createQueryBuilder();
$factory = $qb->getQOMFactory();
- // SELECT * FROM nt:unstructured WHERE name NOT IS NULL
- $qb->from($factory->selector('nt:unstructured'))
- ->where($factory->propertyExistence('name'))
- ->setFirstResult(10)
- ->setMaxResults(10)
+ // SELECT * FROM [nt:unstructured] WHERE text = 'foo' ORDER BY title
+ $qb = $conn->createQueryBuilder()
+ ->nodeType('nt:unstructured')
+ ->where($qb->expr()->eq('text', 'foo'))
+ ->orderBy('title')
+ ->setMaxResults($limit)
+ ->setFirstResult($offset)
->execute();
- $collection = $dm->getDocumentsByQuery($qb->getQuery());
+ $collection = $dm->getDocumentsByPhpcrQuery($qb->getQuery());
</pre>
</div>
@@ -356,7 +358,6 @@
$locales = $dm->getLocalesFor($article);
var_dump($locales); // en, fr
-
</pre>
</div>
View
143 lib/Doctrine/ODM/PHPCR/ChildrenCollection.php
@@ -20,6 +20,7 @@
namespace Doctrine\ODM\PHPCR;
use Doctrine\Common\Collections\ArrayCollection;
+use PHPCR\Util\PathHelper;
/**
* Children collection class
@@ -33,7 +34,8 @@ class ChildrenCollection extends PersistentCollection
private $document;
private $filter;
private $fetchDepth;
- private $originalNodeNames = array();
+ private $originalNodeNames;
+ private $node;
/**
* Creates a new persistent collection.
@@ -41,7 +43,7 @@ class ChildrenCollection extends PersistentCollection
* @param DocumentManager $dm The DocumentManager the collection will be associated with.
* @param object $document Document instance
* @param string $filter filter string
- * @param integer $fetchDepth optional fetch depth if supported by the PHPCR session
+ * @param null|int $fetchDepth optional fetch depth
* @param string $locale the locale to use during the loading of this collection
*/
public function __construct(DocumentManager $dm, $document, $filter = null, $fetchDepth = null, $locale = null)
@@ -53,6 +55,29 @@ public function __construct(DocumentManager $dm, $document, $filter = null, $fet
$this->locale = $locale;
}
+ private function getNode()
+ {
+ if (null === $this->node) {
+ $path = $this->dm->getUnitOfWork()->getDocumentId($this->document);
+ $this->node = $this->dm->getPhpcrSession()->getNode($path, $this->fetchDepth);
+ }
+
+ return $this->node;
+ }
+
+ private function getChildren($childNodes)
+ {
+ $uow = $this->dm->getUnitOfWork();
+ $locale = $this->locale ?: $uow->getCurrentLocale($this->document);
+
+ $childDocuments = array();
+ foreach ($childNodes as $childNode) {
+ $childDocuments[$childNode->getName()] = $uow->getOrCreateProxyFromNode($childNode, $locale);
+ }
+
+ return $childDocuments;
+ }
+
/**
* Initializes the collection by loading its contents from the database
* if the collection is not yet initialized.
@@ -60,33 +85,117 @@ public function __construct(DocumentManager $dm, $document, $filter = null, $fet
public function initialize()
{
if (!$this->initialized) {
+ $this->getOriginalNodeNames();
+ $childNodes = $this->getNode()->getNodes($this->filter, $this->fetchDepth);
+ $this->collection = new ArrayCollection($this->getChildren($childNodes));
$this->initialized = true;
- $this->collection = $this->dm->getChildren($this->document, $this->filter, $this->fetchDepth, $this->locale);
- $this->originalNodeNames = $this->collection->getKeys();
}
}
- /**
- * Return the ordered list of node names of children that existed when the collection was initialized
- *
- * @return array
- */
- public function getOriginalNodeNames()
+ /** {@inheritDoc} */
+ public function contains($element)
{
- $this->initialize();
+ if (!$this->initialized) {
+ $uow = $this->dm->getUnitOfWork();
- return $this->originalNodeNames;
+ // Shortcut for new documents
+ $documentState = $uow->getDocumentState($element);
+
+ if ($documentState === UnitOfWork::STATE_NEW) {
+ return false;
+ }
+
+ // Document is scheduled for inclusion
+ if ($documentState === UnitOfWork::STATE_MANAGED && $uow->isScheduledForInsert($element)) {
+ return false;
+ }
+
+ $documentId = $uow->getDocumentId($element);
+ if (PathHelper::getParentPath($documentId) !== PathHelper::getParentPath($uow->getDocumentId($this->document))) {
+ return false;
+ }
+
+ $nodeName = PathHelper::getNodeName($documentId);
+ return in_array($nodeName, $this->getOriginalNodeNames());
+ }
+
+ return parent::contains($element);
+ }
+
+ /** {@inheritDoc} */
+ public function containsKey($key)
+ {
+ if (!$this->initialized) {
+ return in_array($key, $this->getOriginalNodeNames());
+ }
+
+ return parent::containsKey($key);
+ }
+
+ /** {@inheritDoc} */
+ public function count()
+ {
+ if (!$this->initialized) {
+ return count($this->getOriginalNodeNames());
+ }
+
+ return parent::count();
+ }
+
+ /** {@inheritDoc} */
+ public function isEmpty()
+ {
+ if (!$this->initialized) {
+ return !$this->count();
+ }
+
+ return parent::isEmpty();
+ }
+
+ /** {@inheritDoc} */
+ public function slice($offset, $length = null)
+ {
+ if (!$this->initialized) {
+ $nodeNames = $this->getOriginalNodeNames();
+ if (!is_numeric($offset)) {
+ $offset = array_search($offset, $nodeNames);
+ if (false === $offset) {
+ return new ArrayCollection();
+ }
+ }
+
+ $nodeNames = array_slice($nodeNames, $offset, $length);
+ $parentPath = $this->getNode()->getPath();
+ array_walk($nodeNames, function (&$nodeName) use ($parentPath) {
+ $nodeName = "$parentPath/$nodeName";
+ });
+
+ $childNodes = $this->dm->getPhpcrSession()->getNodes($nodeNames);
+ return $this->getChildren($childNodes);
+ }
+
+ if (!is_numeric($offset)) {
+ $nodeNames = $this->collection->getKeys();
+ $offset = array_search($offset, $nodeNames);
+ if (false === $offset) {
+ return new ArrayCollection();
+ }
+ }
+
+ return parent::slice($offset, $length);
}
/**
- * @return ArrayCollection The collection
+ * Return the ordered list of node names of children that existed when the collection was initialized
+ *
+ * @return array
*/
- public function unwrap()
+ public function getOriginalNodeNames()
{
- if (!$this->initialized) {
- return new ArrayCollection();
+ if (null === $this->originalNodeNames) {
+ $this->originalNodeNames = (array) $this->getNode()->getNodeNames($this->filter);
}
- return parent::unwrap();
+ return $this->originalNodeNames;
}
}
View
12 lib/Doctrine/ODM/PHPCR/DocumentManager.php
@@ -515,7 +515,7 @@ public function createQuery($statement, $language)
/**
* Create the fluent query builder.
*
- * After building your query, use DocumentManager::getDocumentsByQuery with the
+ * After building your query, use DocumentManager::getDocumentsByPhpcrQuery with the
* query returned by QueryBuilder::getQuery()
*
* @return QueryBuilder
@@ -804,10 +804,10 @@ public function refresh($document)
*
* @param object $document document instance which children should be loaded
* @param string|array $filter optional filter to filter on children names
- * @param integer $fetchDepth optional fetch depth if supported by the PHPCR session
+ * @param integer $fetchDepth optional fetch depth
* @param string $locale the locale to use during the loading of this collection
*
- * @return \Doctrine\Common\Collections\Collection collection of child documents
+ * @return ChildrenCollection collection of child documents
*/
public function getChildren($document, $filter = null, $fetchDepth = null, $locale = null)
{
@@ -817,7 +817,7 @@ public function getChildren($document, $filter = null, $fetchDepth = null, $loca
$this->errorIfClosed();
- return $this->unitOfWork->getChildren($document, $filter, $fetchDepth, $locale);
+ return new ChildrenCollection($this, $document, $filter, $fetchDepth, $locale);
}
/**
@@ -835,7 +835,7 @@ public function getChildren($document, $filter = null, $fetchDepth = null, $loca
* @param string|null $name optional PHPCR property name that holds the reference
* @param string $locale the locale to use during the loading of this collection
*
- * @return \Doctrine\Common\Collections\Collection collection of referrer documents
+ * @return ReferrersCollection collection of referrer documents
*/
public function getReferrers($document, $type = null, $name = null, $locale = null)
{
@@ -845,7 +845,7 @@ public function getReferrers($document, $type = null, $name = null, $locale = nu
$this->errorIfClosed();
- return $this->unitOfWork->getReferrers($document, $type, $name, $locale);
+ return new ReferrersCollection($this, $document, $type, $name, $locale);
}
/**
View
11 lib/Doctrine/ODM/PHPCR/ReferenceManyCollection.php
@@ -55,8 +55,6 @@ public function __construct(DocumentManager $dm, array $referencedNodes, $target
public function initialize()
{
if (!$this->initialized) {
- $this->initialized = true;
-
$referencedDocs = array();
$referencedNodes = $this->dm->getPhpcrSession()->getNodesByIdentifier($this->referencedNodes);
$uow = $this->dm->getUnitOfWork();
@@ -73,9 +71,11 @@ public function initialize()
}
$this->collection = new ArrayCollection($referencedDocs);
+ $this->initialized = true;
}
}
+ /** {@inheritDoc} */
public function count()
{
if (!$this->initialized) {
@@ -85,8 +85,13 @@ public function count()
return parent::count();
}
+ /** {@inheritDoc} */
public function isEmpty()
{
- return !$this->count();
+ if (!$this->initialized) {
+ return !$this->count();
+ }
+
+ return parent::isEmpty();
}
}
View
44 lib/Doctrine/ODM/PHPCR/ReferrersCollection.php
@@ -50,21 +50,39 @@ public function __construct(DocumentManager $dm, $document, $type = null, $name
public function initialize()
{
if (!$this->initialized) {
- $this->initialized = true;
+ $uow = $this->dm->getUnitOfWork();
+ $node = $this->dm->getPhpcrSession()->getNode($uow->getDocumentId($this->document));
- $this->collection = $this->dm->getReferrers($this->document, $this->type, $this->name, $this->locale);
- }
- }
+ $referrerDocuments = array();
+ $referrerPropertiesW = array();
+ $referrerPropertiesH = array();
- /**
- * @return ArrayCollection The collection
- */
- public function unwrap()
- {
- if (!$this->initialized) {
- return new ArrayCollection();
- }
+ switch ($this->type) {
+ case 'weak':
+ $referrerPropertiesW = $node->getWeakReferences($this->name);
+ break;
+ case 'hard':
+ $referrerPropertiesH = $node->getReferences($this->name);
+ break;
+ default:
+ $referrerPropertiesW = $node->getWeakReferences($this->name);
+ $referrerPropertiesH = $node->getReferences($this->name);
+ }
+
+ $locale = $this->locale ?: $uow->getCurrentLocale($this->document);
+
+ foreach ($referrerPropertiesW as $referrerProperty) {
+ $referrerNode = $referrerProperty->getParent();
+ $referrerDocuments[] = $uow->getOrCreateProxyFromNode($referrerNode, $locale);
+ }
- return parent::unwrap();
+ foreach ($referrerPropertiesH as $referrerProperty) {
+ $referrerNode = $referrerProperty->getParent();
+ $referrerDocuments[] = $uow->getOrCreateProxyFromNode($referrerNode, $locale);
+ }
+
+ $this->collection = new ArrayCollection($referrerDocuments);
+ $this->initialized = true;
+ }
}
}
View
93 lib/Doctrine/ODM/PHPCR/UnitOfWork.php
@@ -882,6 +882,18 @@ public function getDocumentState($document)
}
/**
+ * Checks whether an document is scheduled for insertion.
+ *
+ * @param object $document
+ *
+ * @return boolean
+ */
+ public function isScheduledForInsert($document)
+ {
+ return isset($this->scheduledInserts[spl_object_hash($document)]);
+ }
+
+ /**
* Detects the changes for a single document
*
* @param object $document
@@ -1133,6 +1145,7 @@ public function computeChangeSet(ClassMetadata $class, $document)
}
if ($this->originalData[$oid][$fieldName] instanceof ChildrenCollection) {
+ $this->originalData[$oid][$fieldName]->initialize();
$originalNames = $this->originalData[$oid][$fieldName]->getOriginalNodenames();
foreach ($originalNames as $key => $childName) {
if (!in_array($childName, $childNames)) {
@@ -2521,86 +2534,6 @@ public function getDocumentById($id)
}
/**
- * Get the child documents of a given document using an optional filter.
- *
- * This methods gets all child nodes as a collection of documents that matches
- * a given filter (same as PHPCR Node::getNodes)
- *
- * @param object $document document instance which children should be loaded
- * @param string|array $filter optional filter to filter on children's names
- * @param integer $fetchDepth optional fetch depth if supported by the PHPCR session
- * @param string $locale the locale to use during the loading of this collection
- *
- * @return ArrayCollection a collection of child documents
- */
- public function getChildren($document, $filter = null, $fetchDepth = null, $locale = null)
- {
- $oldFetchDepth = $this->setFetchDepth($fetchDepth);
- $node = $this->session->getNode($this->getDocumentId($document));
- $this->setFetchDepth($oldFetchDepth);
-
- $locale = $locale ?: $this->getCurrentLocale($document);
-
- $childNodes = $node->getNodes($filter);
- $childDocuments = array();
- foreach ($childNodes as $name => $childNode) {
- $childDocuments[$name] = $this->getOrCreateProxyFromNode($childNode, $locale);
- }
-
- return new ArrayCollection($childDocuments);
- }
-
- /**
- * Get all the documents that refer a given document using an optional name
- * and an optional reference type.
- *
- * This methods gets all nodes as a collection of documents that refer (weak
- * and hard) the given document. The property of the referrer node that refers
- * the document needs to match the given name and must store a reference of the
- * given type.
- *
- * @param object $document document instance which referrers should be loaded
- * @param string $type optional type of the reference the referrer should
- * have ('weak' or 'hard'). null to get both
- * @param string $name optional name to match on referrers reference
- * property name
- * @param string $locale the locale to use during the loading of this collection
- *
- * @return ArrayCollection a collection of referrer documents
- */
- public function getReferrers($document, $type = null, $name = null, $locale = null)
- {
- $node = $this->session->getNode($this->getDocumentId($document));
-
- $referrerDocuments = array();
- $referrerPropertiesW = array();
- $referrerPropertiesH = array();
-
- if ($type === null) {
- $referrerPropertiesW = $node->getWeakReferences($name);
- $referrerPropertiesH = $node->getReferences($name);
- } elseif ($type === 'weak') {
- $referrerPropertiesW = $node->getWeakReferences($name);
- } elseif ($type === 'hard') {
- $referrerPropertiesH = $node->getReferences($name);
- }
-
- $locale = $locale ?: $this->getCurrentLocale($document);
-
- foreach ($referrerPropertiesW as $referrerProperty) {
- $referrerNode = $referrerProperty->getParent();
- $referrerDocuments[] = $this->getOrCreateProxyFromNode($referrerNode, $locale);
- }
-
- foreach ($referrerPropertiesH as $referrerProperty) {
- $referrerNode = $referrerProperty->getParent();
- $referrerDocuments[] = $this->getOrCreateProxyFromNode($referrerNode, $locale);
- }
-
- return new ArrayCollection($referrerDocuments);
- }
-
- /**
* Get the object ID for the given document
*
* @param object|string $document document instance or document object hash
View
28 tests/Doctrine/Tests/ODM/PHPCR/Functional/ChildrenTest.php
@@ -103,12 +103,27 @@ public function testNoChildrenInitOnFlush()
public function testLazyLoading()
{
$parent = $this->dm->find('Doctrine\Tests\ODM\PHPCR\Functional\ChildrenTestObj', '/functional/parent');
+
// lazy loaded
$this->assertCount(0, $parent->aChildren->unwrap());
$this->assertCount(0, $parent->allChildren->unwrap());
- // loaded
$this->assertCount(1, $parent->aChildren);
$this->assertCount(4, $parent->allChildren);
+ $this->assertFalse($parent->aChildren->contains(new ChildrenTestObj()));
+ $this->assertTrue($parent->allChildren->containsKey('Child D'));
+ $this->assertEquals('Child B', key($parent->allChildren->slice('Child B', 2)));
+ $this->assertCount(2, $parent->allChildren->slice('Child B', 2));
+ $this->assertFalse($parent->aChildren->isInitialized());
+ $this->assertFalse($parent->allChildren->isInitialized());
+
+ // loaded
+ $parent->aChildren[] = new ChildrenTestObj();
+ $this->assertCount(2, $parent->aChildren);
+ $this->assertTrue($parent->aChildren->isInitialized());
+
+ $parent->allChildren->remove('Child C');
+ $this->assertCount(3, $parent->allChildren);
+ $this->assertTrue($parent->allChildren->isInitialized());
}
public function testChildrenOfReference()
@@ -163,6 +178,17 @@ public function testCreateChildren()
$this->assertCount(2, $parent->allChildren);
}
+ public function testSliceChildrenCollection()
+ {
+ $parent = $this->dm->find('Doctrine\Tests\ODM\PHPCR\Functional\ChildrenTestObj', '/functional/parent');
+ $collection = $parent->allChildren->slice('Child B', 2);
+ $this->assertEquals(array('Child B', 'Child C'), array_keys($collection));
+
+ $parent->allChildren->initialize();
+ $collection = $parent->allChildren->slice('Child B', 2);
+ $this->assertEquals(array('Child B', 'Child C'), array_keys($collection));
+ }
+
public function testRemoveChildParent()
{
$parent = $this->dm->find('Doctrine\Tests\ODM\PHPCR\Functional\ChildrenTestObj', '/functional/parent');
View
1  tests/Doctrine/Tests/ODM/PHPCR/Functional/MergeTest.php
@@ -209,7 +209,6 @@ public function testMergeWithChildren()
$mergedUser = $this->dm->merge($mergableUser);
$this->assertSame($mergedUser, $user);
- $this->assertTrue($mergedUser->children->isInitialized());
$this->assertCount(1, $mergedUser->children);
$this->dm->flush();
Please sign in to comment.
Something went wrong with that request. Please try again.