From 19f18fa069da520907605580c80cb31081c1cf01 Mon Sep 17 00:00:00 2001 From: Guilherme Blanco Date: Wed, 21 Jan 2015 05:10:05 +0000 Subject: [PATCH] PersistentCollection now extends AbstractLazyCollection. --- lib/Doctrine/ORM/PersistentCollection.php | 389 +++++------------- .../Tests/Mocks/DriverConnectionMock.php | 1 + .../ORM/Functional/Ticket/DDC1452Test.php | 2 +- 3 files changed, 106 insertions(+), 286 deletions(-) diff --git a/lib/Doctrine/ORM/PersistentCollection.php b/lib/Doctrine/ORM/PersistentCollection.php index 22404bbc7..87addf197 100644 --- a/lib/Doctrine/ORM/PersistentCollection.php +++ b/lib/Doctrine/ORM/PersistentCollection.php @@ -20,6 +20,7 @@ namespace Doctrine\ORM; use Closure; +use Doctrine\Common\Collections\AbstractLazyCollection; use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Selectable; @@ -41,9 +42,8 @@ use Doctrine\ORM\Mapping\ClassMetadataInfo; * @author Roman Borschel * @author Giorgio Sironi * @author Stefano Rodriguez - * @todo Design for inheritance to allow custom implementations? */ -final class PersistentCollection implements Collection, Selectable +final class PersistentCollection extends AbstractLazyCollection implements Selectable { /** * A snapshot of the collection at the moment it was fetched from the database. @@ -98,20 +98,6 @@ final class PersistentCollection implements Collection, Selectable */ private $isDirty = false; - /** - * Whether the collection has already been initialized. - * - * @var boolean - */ - private $initialized = true; - - /** - * The wrapped Collection instance. - * - * @var Collection - */ - private $coll; - /** * Creates a new persistent collection. * @@ -121,9 +107,10 @@ final class PersistentCollection implements Collection, Selectable */ public function __construct(EntityManagerInterface $em, $class, $coll) { - $this->coll = $coll; - $this->em = $em; - $this->typeClass = $class; + $this->collection = $coll; + $this->em = $em; + $this->typeClass = $class; + $this->initialized = true; } /** @@ -173,7 +160,7 @@ final class PersistentCollection implements Collection, Selectable */ public function hydrateAdd($element) { - $this->coll->add($element); + $this->collection->add($element); // If _backRefFieldName is set and its a one-to-many association, // we need to set the back reference. @@ -200,7 +187,7 @@ final class PersistentCollection implements Collection, Selectable */ public function hydrateSet($key, $element) { - $this->coll->set($key, $element); + $this->collection->set($key, $element); // If _backRefFieldName is set, then the association is bidirectional // and we need to set the back reference. @@ -224,25 +211,7 @@ final class PersistentCollection implements Collection, Selectable return; } - // Has NEW objects added through add(). Remember them. - $newObjects = array(); - - if ($this->isDirty) { - $newObjects = $this->coll->toArray(); - } - - $this->coll->clear(); - $this->em->getUnitOfWork()->loadCollection($this); - $this->takeSnapshot(); - - // Reattach NEW objects added through add(), if any. - if ($newObjects) { - foreach ($newObjects as $obj) { - $this->coll->add($obj); - } - - $this->isDirty = true; - } + $this->doInitialize(); $this->initialized = true; } @@ -255,7 +224,7 @@ final class PersistentCollection implements Collection, Selectable */ public function takeSnapshot() { - $this->snapshot = $this->coll->toArray(); + $this->snapshot = $this->collection->toArray(); $this->isDirty = false; } @@ -280,7 +249,7 @@ final class PersistentCollection implements Collection, Selectable { return array_udiff_assoc( $this->snapshot, - $this->coll->toArray(), + $this->collection->toArray(), function($a, $b) { return $a === $b ? 0 : 1; } ); } @@ -294,7 +263,7 @@ final class PersistentCollection implements Collection, Selectable public function getInsertDiff() { return array_udiff_assoc( - $this->coll->toArray(), + $this->collection->toArray(), $this->snapshot, function($a, $b) { return $a === $b ? 0 : 1; } ); @@ -367,36 +336,6 @@ final class PersistentCollection implements Collection, Selectable $this->initialized = $bool; } - /** - * Checks whether this collection has been initialized. - * - * @return boolean - */ - public function isInitialized() - { - return $this->initialized; - } - - /** - * {@inheritdoc} - */ - public function first() - { - $this->initialize(); - - return $this->coll->first(); - } - - /** - * {@inheritdoc} - */ - public function last() - { - $this->initialize(); - - return $this->coll->last(); - } - /** * {@inheritdoc} */ @@ -406,9 +345,7 @@ final class PersistentCollection implements Collection, Selectable // and the collection is not initialized and orphanRemoval is // not used we can issue a straight SQL delete/update on the // association (table). Without initializing the collection. - $this->initialize(); - - $removed = $this->coll->remove($key); + $removed = parent::remove($key); if ( ! $removed) { return $removed; @@ -432,8 +369,8 @@ final class PersistentCollection implements Collection, Selectable public function removeElement($element) { if ( ! $this->initialized && $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { - if ($this->coll->contains($element)) { - return $this->coll->removeElement($element); + if ($this->collection->contains($element)) { + return $this->collection->removeElement($element); } $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association); @@ -445,9 +382,7 @@ final class PersistentCollection implements Collection, Selectable return null; } - $this->initialize(); - - $removed = $this->coll->removeElement($element); + $removed = parent::removeElement($element); if ( ! $removed) { return $removed; @@ -470,16 +405,14 @@ final class PersistentCollection implements Collection, Selectable */ public function containsKey($key) { - if (! $this->initialized && $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY && isset($this->association['indexBy'])) { $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association); - return $this->coll->containsKey($key) || $persister->containsKey($this, $key); + return $this->collection->containsKey($key) || $persister->containsKey($this, $key); } - $this->initialize(); - return $this->coll->containsKey($key); + return parent::containsKey($key); } /** @@ -490,32 +423,10 @@ final class PersistentCollection implements Collection, Selectable if ( ! $this->initialized && $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association); - return $this->coll->contains($element) || $persister->contains($this, $element); + return $this->collection->contains($element) || $persister->contains($this, $element); } - $this->initialize(); - - return $this->coll->contains($element); - } - - /** - * {@inheritdoc} - */ - public function exists(Closure $p) - { - $this->initialize(); - - return $this->coll->exists($p); - } - - /** - * {@inheritdoc} - */ - public function indexOf($element) - { - $this->initialize(); - - return $this->coll->indexOf($element); + return parent::contains($element); } /** @@ -534,29 +445,7 @@ final class PersistentCollection implements Collection, Selectable return $this->em->getUnitOfWork()->getCollectionPersister($this->association)->get($this, $key); } - $this->initialize(); - - return $this->coll->get($key); - } - - /** - * {@inheritdoc} - */ - public function getKeys() - { - $this->initialize(); - - return $this->coll->getKeys(); - } - - /** - * {@inheritdoc} - */ - public function getValues() - { - $this->initialize(); - - return $this->coll->getValues(); + return parent::get($key); } /** @@ -567,12 +456,10 @@ final class PersistentCollection implements Collection, Selectable if ( ! $this->initialized && $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) { $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association); - return $persister->count($this) + ($this->isDirty ? $this->coll->count() : 0); + return $persister->count($this) + ($this->isDirty ? $this->collection->count() : 0); } - $this->initialize(); - - return $this->coll->count(); + return parent::count(); } /** @@ -580,9 +467,7 @@ final class PersistentCollection implements Collection, Selectable */ public function set($key, $value) { - $this->initialize(); - - $this->coll->set($key, $value); + parent::set($key, $value); $this->changed(); } @@ -592,131 +477,13 @@ final class PersistentCollection implements Collection, Selectable */ public function add($value) { - $this->coll->add($value); + $this->collection->add($value); $this->changed(); return true; } - /** - * {@inheritdoc} - */ - public function isEmpty() - { - return $this->coll->isEmpty() && $this->count() === 0; - } - - /** - * {@inheritdoc} - */ - public function getIterator() - { - $this->initialize(); - - return $this->coll->getIterator(); - } - - /** - * {@inheritdoc} - */ - public function map(Closure $func) - { - $this->initialize(); - - return $this->coll->map($func); - } - - /** - * {@inheritdoc} - */ - public function filter(Closure $p) - { - $this->initialize(); - - return $this->coll->filter($p); - } - - /** - * {@inheritdoc} - */ - public function forAll(Closure $p) - { - $this->initialize(); - - return $this->coll->forAll($p); - } - - /** - * {@inheritdoc} - */ - public function partition(Closure $p) - { - $this->initialize(); - - return $this->coll->partition($p); - } - - /** - * {@inheritdoc} - */ - public function toArray() - { - $this->initialize(); - - return $this->coll->toArray(); - } - - /** - * {@inheritdoc} - */ - public function clear() - { - if ($this->initialized && $this->isEmpty()) { - return; - } - - $uow = $this->em->getUnitOfWork(); - - if ($this->association['type'] & ClassMetadata::TO_MANY && - $this->association['orphanRemoval'] && - $this->owner) { - // we need to initialize here, as orphan removal acts like implicit cascadeRemove, - // hence for event listeners we need the objects in memory. - $this->initialize(); - - foreach ($this->coll as $element) { - $uow->scheduleOrphanRemoval($element); - } - } - - $this->coll->clear(); - - $this->initialized = true; // direct call, {@link initialize()} is too expensive - - if ($this->association['isOwningSide'] && $this->owner) { - $this->changed(); - - $uow->scheduleCollectionDeletion($this); - - $this->takeSnapshot(); - } - } - - /** - * Called by PHP when this collection is serialized. Ensures that only the - * elements are properly serialized. - * - * @return array - * - * @internal Tried to implement Serializable first but that did not work well - * with circular references. This solution seems simpler and works well. - */ - public function __sleep() - { - return array('coll', 'initialized'); - } - /* ArrayAccess implementation */ /** @@ -758,41 +525,59 @@ final class PersistentCollection implements Collection, Selectable /** * {@inheritdoc} */ - public function key() + public function isEmpty() { - $this->initialize(); - - return $this->coll->key(); + return $this->collection->isEmpty() && $this->count() === 0; } /** * {@inheritdoc} */ - public function current() + public function clear() { - $this->initialize(); + if ($this->initialized && $this->isEmpty()) { + return; + } - return $this->coll->current(); + $uow = $this->em->getUnitOfWork(); + + if ($this->association['type'] & ClassMetadata::TO_MANY && + $this->association['orphanRemoval'] && + $this->owner) { + // we need to initialize here, as orphan removal acts like implicit cascadeRemove, + // hence for event listeners we need the objects in memory. + $this->initialize(); + + foreach ($this->collection as $element) { + $uow->scheduleOrphanRemoval($element); + } + } + + $this->collection->clear(); + + $this->initialized = true; // direct call, {@link initialize()} is too expensive + + if ($this->association['isOwningSide'] && $this->owner) { + $this->changed(); + + $uow->scheduleCollectionDeletion($this); + + $this->takeSnapshot(); + } } /** - * {@inheritdoc} - */ - public function next() - { - $this->initialize(); - - return $this->coll->next(); - } - - /** - * Retrieves the wrapped Collection instance. + * Called by PHP when this collection is serialized. Ensures that only the + * elements are properly serialized. * - * @return \Doctrine\Common\Collections\Collection + * @return array + * + * @internal Tried to implement Serializable first but that did not work well + * with circular references. This solution seems simpler and works well. */ - public function unwrap() + public function __sleep() { - return $this->coll; + return array('collection', 'initialized'); } /** @@ -815,9 +600,7 @@ final class PersistentCollection implements Collection, Selectable return $persister->slice($this, $offset, $length); } - $this->initialize(); - - return $this->coll->slice($offset, $length); + return parent::slice($offset, $length); } /** @@ -835,8 +618,8 @@ final class PersistentCollection implements Collection, Selectable */ public function __clone() { - if (is_object($this->coll)) { - $this->coll = clone $this->coll; + if (is_object($this->collection)) { + $this->collection = clone $this->collection; } $this->initialize(); @@ -864,7 +647,7 @@ final class PersistentCollection implements Collection, Selectable } if ($this->initialized) { - return $this->coll->matching($criteria); + return $this->collection->matching($criteria); } if ($this->association['type'] === ClassMetadata::MANY_TO_MANY) { @@ -887,4 +670,40 @@ final class PersistentCollection implements Collection, Selectable ? new LazyCriteriaCollection($persister, $criteria) : new ArrayCollection($persister->loadCriteria($criteria)); } + + /** + * Retrieves the wrapped Collection instance. + * + * @return \Doctrine\Common\Collections\Collection + */ + public function unwrap() + { + return $this->collection; + } + + /** + * {@inheritdoc} + */ + protected function doInitialize() + { + // Has NEW objects added through add(). Remember them. + $newObjects = array(); + + if ($this->isDirty) { + $newObjects = $this->collection->toArray(); + } + + $this->collection->clear(); + $this->em->getUnitOfWork()->loadCollection($this); + $this->takeSnapshot(); + + // Reattach NEW objects added through add(), if any. + if ($newObjects) { + foreach ($newObjects as $obj) { + $this->collection->add($obj); + } + + $this->isDirty = true; + } + } } diff --git a/tests/Doctrine/Tests/Mocks/DriverConnectionMock.php b/tests/Doctrine/Tests/Mocks/DriverConnectionMock.php index 631fa0356..6acb89ba7 100644 --- a/tests/Doctrine/Tests/Mocks/DriverConnectionMock.php +++ b/tests/Doctrine/Tests/Mocks/DriverConnectionMock.php @@ -12,6 +12,7 @@ class DriverConnectionMock implements \Doctrine\DBAL\Driver\Connection */ public function prepare($prepareString) { + return new StatementMock(); } /** diff --git a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1452Test.php b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1452Test.php index 6d6429b93..d3811f1f3 100644 --- a/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1452Test.php +++ b/tests/Doctrine/Tests/ORM/Functional/Ticket/DDC1452Test.php @@ -92,7 +92,7 @@ class DDC1452EntityA public $id; /** @Column */ public $title; - /** @ManyToMany(targetEntity="DDC1452EntityB", mappedBy="entityAFrom") */ + /** @OneToMany(targetEntity="DDC1452EntityB", mappedBy="entityAFrom") */ public $entitiesB; public function __construct()