source: pro-violet-viettel/sourcecode/application/libraries/Doctrine/ORM/PersistentCollection.php @ 345

Last change on this file since 345 was 345, checked in by quyenla, 11 years ago

collaborator page

File size: 19.4 KB
Line 
1<?php
2/*
3 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14 *
15 * This software consists of voluntary contributions made by many individuals
16 * and is licensed under the LGPL. For more information, see
17 * <http://www.doctrine-project.org>.
18 */
19
20namespace Doctrine\ORM;
21
22use Doctrine\ORM\Mapping\ClassMetadata,
23    Doctrine\Common\Collections\Collection,
24    Doctrine\Common\Collections\ArrayCollection,
25    Closure;
26
27/**
28 * A PersistentCollection represents a collection of elements that have persistent state.
29 *
30 * Collections of entities represent only the associations (links) to those entities.
31 * That means, if the collection is part of a many-many mapping and you remove
32 * entities from the collection, only the links in the relation table are removed (on flush).
33 * Similarly, if you remove entities from a collection that is part of a one-many
34 * mapping this will only result in the nulling out of the foreign keys on flush.
35 *
36 * @since     2.0
37 * @author    Konsta Vesterinen <kvesteri@cc.hut.fi>
38 * @author    Roman Borschel <roman@code-factory.org>
39 * @author    Giorgio Sironi <piccoloprincipeazzurro@gmail.com>
40 * @todo Design for inheritance to allow custom implementations?
41 */
42final class PersistentCollection implements Collection
43{
44    /**
45     * A snapshot of the collection at the moment it was fetched from the database.
46     * This is used to create a diff of the collection at commit time.
47     *
48     * @var array
49     */
50    private $snapshot = array();
51
52    /**
53     * The entity that owns this collection.
54     *
55     * @var object
56     */
57    private $owner;
58
59    /**
60     * The association mapping the collection belongs to.
61     * This is currently either a OneToManyMapping or a ManyToManyMapping.
62     *
63     * @var array
64     */
65    private $association;
66
67    /**
68     * The EntityManager that manages the persistence of the collection.
69     *
70     * @var \Doctrine\ORM\EntityManager
71     */
72    private $em;
73
74    /**
75     * The name of the field on the target entities that points to the owner
76     * of the collection. This is only set if the association is bi-directional.
77     *
78     * @var string
79     */
80    private $backRefFieldName;
81
82    /**
83     * The class descriptor of the collection's entity type.
84     */
85    private $typeClass;
86
87    /**
88     * Whether the collection is dirty and needs to be synchronized with the database
89     * when the UnitOfWork that manages its persistent state commits.
90     *
91     * @var boolean
92     */
93    private $isDirty = false;
94
95    /**
96     * Whether the collection has already been initialized.
97     *
98     * @var boolean
99     */
100    private $initialized = true;
101
102    /**
103     * The wrapped Collection instance.
104     *
105     * @var Collection
106     */
107    private $coll;
108
109    /**
110     * Creates a new persistent collection.
111     *
112     * @param EntityManager $em The EntityManager the collection will be associated with.
113     * @param ClassMetadata $class The class descriptor of the entity type of this collection.
114     * @param array The collection elements.
115     */
116    public function __construct(EntityManager $em, $class, $coll)
117    {
118        $this->coll      = $coll;
119        $this->em        = $em;
120        $this->typeClass = $class;
121    }
122
123    /**
124     * INTERNAL:
125     * Sets the collection's owning entity together with the AssociationMapping that
126     * describes the association between the owner and the elements of the collection.
127     *
128     * @param object $entity
129     * @param AssociationMapping $assoc
130     */
131    public function setOwner($entity, array $assoc)
132    {
133        $this->owner            = $entity;
134        $this->association      = $assoc;
135        $this->backRefFieldName = $assoc['inversedBy'] ?: $assoc['mappedBy'];
136    }
137
138    /**
139     * INTERNAL:
140     * Gets the collection owner.
141     *
142     * @return object
143     */
144    public function getOwner()
145    {
146        return $this->owner;
147    }
148
149    public function getTypeClass()
150    {
151        return $this->typeClass;
152    }
153
154    /**
155     * INTERNAL:
156     * Adds an element to a collection during hydration. This will automatically
157     * complete bidirectional associations in the case of a one-to-many association.
158     *
159     * @param mixed $element The element to add.
160     */
161    public function hydrateAdd($element)
162    {
163        $this->coll->add($element);
164
165        // If _backRefFieldName is set and its a one-to-many association,
166        // we need to set the back reference.
167        if ($this->backRefFieldName && $this->association['type'] === ClassMetadata::ONE_TO_MANY) {
168            // Set back reference to owner
169            $this->typeClass->reflFields[$this->backRefFieldName]->setValue(
170                $element, $this->owner
171            );
172
173            $this->em->getUnitOfWork()->setOriginalEntityProperty(
174                spl_object_hash($element), $this->backRefFieldName, $this->owner
175            );
176        }
177    }
178
179    /**
180     * INTERNAL:
181     * Sets a keyed element in the collection during hydration.
182     *
183     * @param mixed $key The key to set.
184     * $param mixed $value The element to set.
185     */
186    public function hydrateSet($key, $element)
187    {
188        $this->coll->set($key, $element);
189
190        // If _backRefFieldName is set, then the association is bidirectional
191        // and we need to set the back reference.
192        if ($this->backRefFieldName && $this->association['type'] === ClassMetadata::ONE_TO_MANY) {
193            // Set back reference to owner
194            $this->typeClass->reflFields[$this->backRefFieldName]->setValue(
195                $element, $this->owner
196            );
197        }
198    }
199
200    /**
201     * Initializes the collection by loading its contents from the database
202     * if the collection is not yet initialized.
203     */
204    public function initialize()
205    {
206        if ($this->initialized || ! $this->association) {
207            return;
208        }
209
210        // Has NEW objects added through add(). Remember them.
211        $newObjects = array();
212
213        if ($this->isDirty) {
214            $newObjects = $this->coll->toArray();
215        }
216
217        $this->coll->clear();
218        $this->em->getUnitOfWork()->loadCollection($this);
219        $this->takeSnapshot();
220
221        // Reattach NEW objects added through add(), if any.
222        if ($newObjects) {
223            foreach ($newObjects as $obj) {
224                $this->coll->add($obj);
225            }
226
227            $this->isDirty = true;
228        }
229
230        $this->initialized = true;
231    }
232
233    /**
234     * INTERNAL:
235     * Tells this collection to take a snapshot of its current state.
236     */
237    public function takeSnapshot()
238    {
239        $this->snapshot = $this->coll->toArray();
240        $this->isDirty  = false;
241    }
242
243    /**
244     * INTERNAL:
245     * Returns the last snapshot of the elements in the collection.
246     *
247     * @return array The last snapshot of the elements.
248     */
249    public function getSnapshot()
250    {
251        return $this->snapshot;
252    }
253
254    /**
255     * INTERNAL:
256     * getDeleteDiff
257     *
258     * @return array
259     */
260    public function getDeleteDiff()
261    {
262        return array_udiff_assoc(
263            $this->snapshot,
264            $this->coll->toArray(),
265            function($a, $b) { return $a === $b ? 0 : 1; }
266        );
267    }
268
269    /**
270     * INTERNAL:
271     * getInsertDiff
272     *
273     * @return array
274     */
275    public function getInsertDiff()
276    {
277        return array_udiff_assoc(
278            $this->coll->toArray(),
279            $this->snapshot,
280            function($a, $b) { return $a === $b ? 0 : 1; }
281        );
282    }
283
284    /**
285     * INTERNAL: Gets the association mapping of the collection.
286     *
287     * @return array
288     */
289    public function getMapping()
290    {
291        return $this->association;
292    }
293
294    /**
295     * Marks this collection as changed/dirty.
296     */
297    private function changed()
298    {
299        if ($this->isDirty) {
300            return;
301        }
302
303        $this->isDirty = true;
304
305        if ($this->association !== null &&
306            $this->association['isOwningSide'] &&
307            $this->association['type'] === ClassMetadata::MANY_TO_MANY &&
308            $this->owner &&
309            $this->em->getClassMetadata(get_class($this->owner))->isChangeTrackingNotify()) {
310            $this->em->getUnitOfWork()->scheduleForDirtyCheck($this->owner);
311        }
312    }
313
314    /**
315     * Gets a boolean flag indicating whether this collection is dirty which means
316     * its state needs to be synchronized with the database.
317     *
318     * @return boolean TRUE if the collection is dirty, FALSE otherwise.
319     */
320    public function isDirty()
321    {
322        return $this->isDirty;
323    }
324
325    /**
326     * Sets a boolean flag, indicating whether this collection is dirty.
327     *
328     * @param boolean $dirty Whether the collection should be marked dirty or not.
329     */
330    public function setDirty($dirty)
331    {
332        $this->isDirty = $dirty;
333    }
334
335    /**
336     * Sets the initialized flag of the collection, forcing it into that state.
337     *
338     * @param boolean $bool
339     */
340    public function setInitialized($bool)
341    {
342        $this->initialized = $bool;
343    }
344
345    /**
346     * Checks whether this collection has been initialized.
347     *
348     * @return boolean
349     */
350    public function isInitialized()
351    {
352        return $this->initialized;
353    }
354
355    /** {@inheritdoc} */
356    public function first()
357    {
358        $this->initialize();
359
360        return $this->coll->first();
361    }
362
363    /** {@inheritdoc} */
364    public function last()
365    {
366        $this->initialize();
367
368        return $this->coll->last();
369    }
370
371    /**
372     * {@inheritdoc}
373     */
374    public function remove($key)
375    {
376        // TODO: If the keys are persistent as well (not yet implemented)
377        //       and the collection is not initialized and orphanRemoval is
378        //       not used we can issue a straight SQL delete/update on the
379        //       association (table). Without initializing the collection.
380        $this->initialize();
381
382        $removed = $this->coll->remove($key);
383
384        if ( ! $removed) {
385            return $removed;
386        }
387
388        $this->changed();
389
390        if ($this->association !== null &&
391            $this->association['type'] & ClassMetadata::TO_MANY &&
392            $this->owner &&
393            $this->association['orphanRemoval']) {
394            $this->em->getUnitOfWork()->scheduleOrphanRemoval($removed);
395        }
396
397        return $removed;
398    }
399
400    /**
401     * {@inheritdoc}
402     */
403    public function removeElement($element)
404    {
405        if ( ! $this->initialized && $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) {
406            if ($this->coll->contains($element)) {
407                return $this->coll->removeElement($element);
408            }
409
410            $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
411
412            if ($persister->removeElement($this, $element)) {
413                return $element;
414            }
415
416            return null;
417        }
418
419        $this->initialize();
420
421        $removed = $this->coll->removeElement($element);
422
423        if ( ! $removed) {
424            return $removed;
425        }
426
427        $this->changed();
428
429        if ($this->association !== null &&
430            $this->association['type'] & ClassMetadata::TO_MANY &&
431            $this->owner &&
432            $this->association['orphanRemoval']) {
433            $this->em->getUnitOfWork()->scheduleOrphanRemoval($element);
434        }
435
436        return $removed;
437    }
438
439    /**
440     * {@inheritdoc}
441     */
442    public function containsKey($key)
443    {
444        $this->initialize();
445
446        return $this->coll->containsKey($key);
447    }
448
449    /**
450     * {@inheritdoc}
451     */
452    public function contains($element)
453    {
454        if ( ! $this->initialized && $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) {
455            $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
456
457            return $this->coll->contains($element) || $persister->contains($this, $element);
458        }
459
460        $this->initialize();
461
462        return $this->coll->contains($element);
463    }
464
465    /**
466     * {@inheritdoc}
467     */
468    public function exists(Closure $p)
469    {
470        $this->initialize();
471
472        return $this->coll->exists($p);
473    }
474
475    /**
476     * {@inheritdoc}
477     */
478    public function indexOf($element)
479    {
480        $this->initialize();
481
482        return $this->coll->indexOf($element);
483    }
484
485    /**
486     * {@inheritdoc}
487     */
488    public function get($key)
489    {
490        $this->initialize();
491
492        return $this->coll->get($key);
493    }
494
495    /**
496     * {@inheritdoc}
497     */
498    public function getKeys()
499    {
500        $this->initialize();
501
502        return $this->coll->getKeys();
503    }
504
505    /**
506     * {@inheritdoc}
507     */
508    public function getValues()
509    {
510        $this->initialize();
511
512        return $this->coll->getValues();
513    }
514
515    /**
516     * {@inheritdoc}
517     */
518    public function count()
519    {
520        if ( ! $this->initialized && $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) {
521            $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
522
523            return $persister->count($this) + ($this->isDirty ? $this->coll->count() : 0);
524        }
525
526        $this->initialize();
527
528        return $this->coll->count();
529    }
530
531    /**
532     * {@inheritdoc}
533     */
534    public function set($key, $value)
535    {
536        $this->initialize();
537
538        $this->coll->set($key, $value);
539
540        $this->changed();
541    }
542
543    /**
544     * {@inheritdoc}
545     */
546    public function add($value)
547    {
548        $this->coll->add($value);
549
550        $this->changed();
551
552        return true;
553    }
554
555    /**
556     * {@inheritdoc}
557     */
558    public function isEmpty()
559    {
560        $this->initialize();
561
562        return $this->coll->isEmpty();
563    }
564
565    /**
566     * {@inheritdoc}
567     */
568    public function getIterator()
569    {
570        $this->initialize();
571
572        return $this->coll->getIterator();
573    }
574
575    /**
576     * {@inheritdoc}
577     */
578    public function map(Closure $func)
579    {
580        $this->initialize();
581
582        return $this->coll->map($func);
583    }
584
585    /**
586     * {@inheritdoc}
587     */
588    public function filter(Closure $p)
589    {
590        $this->initialize();
591
592        return $this->coll->filter($p);
593    }
594
595    /**
596     * {@inheritdoc}
597     */
598    public function forAll(Closure $p)
599    {
600        $this->initialize();
601
602        return $this->coll->forAll($p);
603    }
604
605    /**
606     * {@inheritdoc}
607     */
608    public function partition(Closure $p)
609    {
610        $this->initialize();
611
612        return $this->coll->partition($p);
613    }
614
615    /**
616     * {@inheritdoc}
617     */
618    public function toArray()
619    {
620        $this->initialize();
621
622        return $this->coll->toArray();
623    }
624
625    /**
626     * {@inheritdoc}
627     */
628    public function clear()
629    {
630        if ($this->initialized && $this->isEmpty()) {
631            return;
632        }
633
634        $uow = $this->em->getUnitOfWork();
635
636        if ($this->association['type'] & ClassMetadata::TO_MANY &&
637            $this->association['orphanRemoval'] &&
638            $this->owner) {
639            // we need to initialize here, as orphan removal acts like implicit cascadeRemove,
640            // hence for event listeners we need the objects in memory.
641            $this->initialize();
642
643            foreach ($this->coll as $element) {
644                $uow->scheduleOrphanRemoval($element);
645            }
646        }
647
648        $this->coll->clear();
649
650        $this->initialized = true; // direct call, {@link initialize()} is too expensive
651
652        if ($this->association['isOwningSide']) {
653            $this->changed();
654
655            $uow->scheduleCollectionDeletion($this);
656
657            $this->takeSnapshot();
658        }
659    }
660
661    /**
662     * Called by PHP when this collection is serialized. Ensures that only the
663     * elements are properly serialized.
664     *
665     * @internal Tried to implement Serializable first but that did not work well
666     *           with circular references. This solution seems simpler and works well.
667     */
668    public function __sleep()
669    {
670        return array('coll', 'initialized');
671    }
672
673    /* ArrayAccess implementation */
674
675    /**
676     * @see containsKey()
677     */
678    public function offsetExists($offset)
679    {
680        return $this->containsKey($offset);
681    }
682
683    /**
684     * @see get()
685     */
686    public function offsetGet($offset)
687    {
688        return $this->get($offset);
689    }
690
691    /**
692     * @see add()
693     * @see set()
694     */
695    public function offsetSet($offset, $value)
696    {
697        if ( ! isset($offset)) {
698            return $this->add($value);
699        }
700
701        return $this->set($offset, $value);
702    }
703
704    /**
705     * @see remove()
706     */
707    public function offsetUnset($offset)
708    {
709        return $this->remove($offset);
710    }
711
712    public function key()
713    {
714        return $this->coll->key();
715    }
716
717    /**
718     * Gets the element of the collection at the current iterator position.
719     */
720    public function current()
721    {
722        return $this->coll->current();
723    }
724
725    /**
726     * Moves the internal iterator position to the next element.
727     */
728    public function next()
729    {
730        return $this->coll->next();
731    }
732
733    /**
734     * Retrieves the wrapped Collection instance.
735     *
736     * @return \Doctrine\Common\Collections\Collection
737     */
738    public function unwrap()
739    {
740        return $this->coll;
741    }
742
743    /**
744     * Extract a slice of $length elements starting at position $offset from the Collection.
745     *
746     * If $length is null it returns all elements from $offset to the end of the Collection.
747     * Keys have to be preserved by this method. Calling this method will only return the
748     * selected slice and NOT change the elements contained in the collection slice is called on.
749     *
750     * @param int $offset
751     * @param int $length
752     *
753     * @return array
754     */
755    public function slice($offset, $length = null)
756    {
757        if ( ! $this->initialized && ! $this->isDirty && $this->association['fetch'] === Mapping\ClassMetadataInfo::FETCH_EXTRA_LAZY) {
758            $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
759
760            return $persister->slice($this, $offset, $length);
761        }
762
763        $this->initialize();
764
765        return $this->coll->slice($offset, $length);
766    }
767
768    /**
769     * Cleanup internal state of cloned persistent collection.
770     *
771     * The following problems have to be prevented:
772     * 1. Added entities are added to old PC
773     * 2. New collection is not dirty, if reused on other entity nothing
774     * changes.
775     * 3. Snapshot leads to invalid diffs being generated.
776     * 4. Lazy loading grabs entities from old owner object.
777     * 5. New collection is connected to old owner and leads to duplicate keys.
778     */
779    public function __clone()
780    {
781        if (is_object($this->coll)) {
782            $this->coll = clone $this->coll;
783        }
784
785        $this->initialize();
786        $this->owner = null;
787
788        $this->snapshot = array();
789
790        $this->changed();
791    }
792}
Note: See TracBrowser for help on using the repository browser.