1
0
mirror of synced 2024-12-05 03:06:05 +03:00

PersistentCollection now extends AbstractLazyCollection.

This commit is contained in:
Guilherme Blanco 2015-01-21 05:10:05 +00:00
parent 6e40361fe7
commit 19f18fa069
3 changed files with 106 additions and 286 deletions

View File

@ -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 <roman@code-factory.org>
* @author Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
* @author Stefano Rodriguez <stefano.rodriguez@fubles.com>
* @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;
}
}
}

View File

@ -12,6 +12,7 @@ class DriverConnectionMock implements \Doctrine\DBAL\Driver\Connection
*/
public function prepare($prepareString)
{
return new StatementMock();
}
/**

View File

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