Completed
Pull Request — master (#1219)
by Maciej
13:47
created

PersistentCollectionTrait::get()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
crap 1
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 MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ODM\MongoDB\PersistentCollection;
21
22
use Doctrine\Common\Collections\Collection as BaseCollection;
23
use Doctrine\ODM\MongoDB\DocumentManager;
24
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
25
use Doctrine\ODM\MongoDB\MongoDBException;
26
use Doctrine\ODM\MongoDB\UnitOfWork;
27
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
28
29
/**
30
 * Trait with methods needed to implement PersistentCollectionInterface.
31
 *
32
 * @since 1.1
33
 */
34
trait PersistentCollectionTrait
35
{
36
    /**
37
     * A snapshot of the collection at the moment it was fetched from the database.
38
     * This is used to create a diff of the collection at commit time.
39
     *
40
     * @var array
41
     */
42
    private $snapshot = array();
43
44
    /**
45
     * Collection's owning entity
46
     *
47
     * @var object
48
     */
49
    private $owner;
50
51
    /**
52
     * @var array
53
     */
54
    private $mapping;
55
56
    /**
57
     * Whether the collection is dirty and needs to be synchronized with the database
58
     * when the UnitOfWork that manages its persistent state commits.
59
     *
60
     * @var boolean
61
     */
62
    private $isDirty = false;
63
64
    /**
65
     * Whether the collection has already been initialized.
66
     *
67
     * @var boolean
68
     */
69
    private $initialized = true;
70
71
    /**
72
     * The wrapped Collection instance.
73
     *
74
     * @var BaseCollection
75
     */
76
    private $coll;
77
78
    /**
79
     * The DocumentManager that manages the persistence of the collection.
80
     *
81
     * @var DocumentManager
82
     */
83
    private $dm;
84
85
    /**
86
     * The UnitOfWork that manages the persistence of the collection.
87
     *
88
     * @var UnitOfWork
89
     */
90
    private $uow;
91
92
    /**
93
     * The raw mongo data that will be used to initialize this collection.
94
     *
95
     * @var array
96
     */
97
    private $mongoData = array();
98
99
    /**
100
     * Any hints to account for during reconstitution/lookup of the documents.
101
     *
102
     * @var array
103
     */
104
    private $hints = array();
105
106
    /**
107
     * @var ClassMetadata
108
     */
109
    private $typeClass;
110
111
    /** {@inheritdoc} */
112
    public function setDocumentManager(DocumentManager $dm)
113
    {
114
        $this->dm = $dm;
115
        $this->uow = $dm->getUnitOfWork();
116
    }
117
118
    /** {@inheritdoc} */
119 171
    public function setMongoData(array $mongoData)
120
    {
121 171
        $this->mongoData = $mongoData;
122 171
    }
123
124
    /** {@inheritdoc} */
125 147
    public function getMongoData()
126
    {
127 147
        return $this->mongoData;
128
    }
129
130
    /** {@inheritdoc} */
131 254
    public function setHints(array $hints)
132
    {
133 254
        $this->hints = $hints;
134 254
    }
135
136
    /** {@inheritdoc} */
137 82
    public function getHints()
138
    {
139 82
        return $this->hints;
140
    }
141
142
    /** {@inheritdoc} */
143 361
    public function initialize()
144
    {
145 361
        if ($this->initialized || ! $this->mapping) {
146 354
            return;
147
        }
148
149 159
        $newObjects = array();
150
151 159
        if ($this->isDirty) {
152
            // Remember any NEW objects added through add()
153 20
            $newObjects = $this->coll->toArray();
154 20
        }
155
156 159
        $this->initialized = true;
157
158 159
        $this->coll->clear();
159 159
        $this->uow->loadCollection($this);
160 159
        $this->takeSnapshot();
161
162 159
        $this->mongoData = array();
163
164
        // Reattach any NEW objects added through add()
165 159
        if ($newObjects) {
166 20
            foreach ($newObjects as $key => $obj) {
167 20
                if (CollectionHelper::isHash($this->mapping['strategy'])) {
168
                    $this->coll->set($key, $obj);
169
                } else {
170 20
                    $this->coll->add($obj);
171
                }
172 20
            }
173
174 20
            $this->isDirty = true;
175 20
        }
176 159
    }
177
178
    /**
179
     * Marks this collection as changed/dirty.
180
     */
181 180
    private function changed()
182
    {
183 180
        if ($this->isDirty) {
184 117
            return;
185
        }
186
187 180
        $this->isDirty = true;
188
189 180
        if ($this->dm &&
190 180
            $this->mapping !== null &&
191 180
            $this->mapping['isOwningSide'] &&
192 180
            $this->owner &&
193 180
            $this->dm->getClassMetadata(get_class($this->owner))->isChangeTrackingNotify()) {
194 1
            $this->uow->scheduleForDirtyCheck($this->owner);
195 1
        }
196 180
    }
197
198
    /** {@inheritdoc} */
199 377
    public function isDirty()
200
    {
201 377
        return $this->isDirty;
202
    }
203
204
    /** {@inheritdoc} */
205 370
    public function setDirty($dirty)
206
    {
207 370
        $this->isDirty = $dirty;
208 370
    }
209
210
    /** {@inheritdoc} */
211 386
    public function setOwner($document, array $mapping)
212
    {
213 386
        $this->owner = $document;
214 386
        $this->mapping = $mapping;
215
216 386
        if ( ! empty($this->mapping['targetDocument'])) {
217 372
            $this->typeClass = $this->dm->getClassMetadata($this->mapping['targetDocument']);
218 372
        }
219 386
    }
220
221
    /** {@inheritdoc} */
222 259
    public function takeSnapshot()
223
    {
224 259
        if (CollectionHelper::isList($this->mapping['strategy'])) {
225 245
            $array = $this->coll->toArray();
226 245
            $this->coll->clear();
227 245
            foreach ($array as $document) {
228 220
                $this->coll->add($document);
229 245
            }
230 245
        }
231 259
        $this->snapshot = $this->coll->toArray();
232 259
        $this->isDirty = false;
233 259
    }
234
235
    /** {@inheritdoc} */
236 23
    public function clearSnapshot()
237
    {
238 23
        $this->snapshot = array();
239 23
        $this->isDirty = $this->coll->count() ? true : false;
240 23
    }
241
242
    /** {@inheritdoc} */
243
    public function getSnapshot()
244
    {
245
        return $this->snapshot;
246
    }
247
248
    /** {@inheritdoc} */
249 81 View Code Duplication
    public function getDeleteDiff()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
250
    {
251 81
        return array_udiff_assoc(
252 81
            $this->snapshot,
253 81
            $this->coll->toArray(),
254
            function ($a, $b) { return $a === $b ? 0 : 1; }
255 81
        );
256
    }
257
258
    /** {@inheritdoc} */
259 141
    public function getDeletedDocuments()
260
    {
261
        $compare = function ($a, $b) {
262 97
            $compareA = is_object($a) ? spl_object_hash($a) : $a;
263 97
            $compareb = is_object($b) ? spl_object_hash($b) : $b;
264 97
            return $compareA === $compareb ? 0 : ($compareA > $compareb ? 1 : -1);
265 141
        };
266 141
        return array_udiff(
267 141
            $this->snapshot,
268 141
            $this->coll->toArray(),
269
            $compare
270 141
        );
271
    }
272
273
    /** {@inheritdoc} */
274 81 View Code Duplication
    public function getInsertDiff()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
275
    {
276 81
        return array_udiff_assoc(
277 81
            $this->coll->toArray(),
278 81
            $this->snapshot,
279
            function ($a, $b) { return $a === $b ? 0 : 1; }
280 81
        );
281
    }
282
283
    /** {@inheritdoc} */
284 377
    public function getOwner()
285
    {
286 377
        return $this->owner;
287
    }
288
289
    /** {@inheritdoc} */
290 257
    public function getMapping()
291
    {
292 257
        return $this->mapping;
293
    }
294
295
    /** {@inheritdoc} */
296 2
    public function getTypeClass()
297
    {
298 2
        if (empty($this->typeClass)) {
299 1
            throw new MongoDBException('Specifying targetDocument is required for the ClassMetadata to be obtained.');
300
        }
301
302 1
        return $this->typeClass;
303
    }
304
305
    /** {@inheritdoc} */
306 255
    public function setInitialized($bool)
307
    {
308 255
        $this->initialized = $bool;
309 255
    }
310
311
    /** {@inheritdoc} */
312 15
    public function isInitialized()
313
    {
314 15
        return $this->initialized;
315
    }
316
317
    /** {@inheritdoc} */
318 12
    public function first()
319
    {
320 12
        $this->initialize();
321 12
        return $this->coll->first();
322
    }
323
324
    /** {@inheritdoc} */
325 1
    public function last()
326
    {
327 1
        $this->initialize();
328 1
        return $this->coll->last();
329
    }
330
331
    /**
332
     * {@inheritdoc}
333
     */
334 21 View Code Duplication
    public function remove($key)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
335
    {
336 21
        $this->initialize();
337 21
        $removed = $this->coll->remove($key);
338
339 21
        if ( ! $removed) {
340
            return $removed;
341
        }
342
343 21
        $this->changed();
344
345 21
        return $removed;
346
    }
347
348
    /**
349
     * {@inheritdoc}
350
     */
351 14 View Code Duplication
    public function removeElement($element)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
352
    {
353 14
        $this->initialize();
354 14
        $removed = $this->coll->removeElement($element);
355
356 14
        if ( ! $removed) {
357
            return $removed;
358
        }
359
360 14
        $this->changed();
361
362 14
        return $removed;
363
    }
364
365
    /**
366
     * {@inheritdoc}
367
     */
368 1
    public function containsKey($key)
369
    {
370 1
        $this->initialize();
371 1
        return $this->coll->containsKey($key);
372
    }
373
374
    /**
375
     * {@inheritdoc}
376
     */
377 2
    public function contains($element)
378
    {
379 2
        $this->initialize();
380 2
        return $this->coll->contains($element);
381
    }
382
383
    /**
384
     * {@inheritdoc}
385
     */
386
    public function exists(\Closure $p)
387
    {
388
        $this->initialize();
389
        return $this->coll->exists($p);
390
    }
391
392
    /**
393
     * {@inheritdoc}
394
     */
395 2
    public function indexOf($element)
396
    {
397 2
        $this->initialize();
398 2
        return $this->coll->indexOf($element);
399
    }
400
401
    /**
402
     * {@inheritdoc}
403
     */
404 87
    public function get($key)
405
    {
406 87
        $this->initialize();
407 87
        return $this->coll->get($key);
408
    }
409
410
    /**
411
     * {@inheritdoc}
412
     */
413
    public function getKeys()
414
    {
415
        $this->initialize();
416
        return $this->coll->getKeys();
417
    }
418
419
    /**
420
     * {@inheritdoc}
421
     */
422
    public function getValues()
423
    {
424
        $this->initialize();
425
        return $this->coll->getValues();
426
    }
427
428
    /**
429
     * {@inheritdoc}
430
     */
431 358
    public function count()
432
    {
433 358
        $count = $this->coll->count();
434
435
        // If this collection is inversed and not initialized, add the count returned from the database
436 358
        if ($this->mapping['isInverseSide'] && ! $this->initialized) {
437 10
            $documentPersister = $this->uow->getDocumentPersister(get_class($this->owner));
438 10
            $count += empty($this->mapping['repositoryMethod'])
439 10
                ? $documentPersister->createReferenceManyInverseSideQuery($this)->count()
440 10
                : $documentPersister->createReferenceManyWithRepositoryMethodCursor($this)->count();
441 10
        }
442
443 358
        return count($this->mongoData) + $count;
444
    }
445
446
    /**
447
     * {@inheritdoc}
448
     */
449 35
    public function set($key, $value)
450
    {
451 35
        $this->coll->set($key, $value);
452
453
        // Handle orphanRemoval
454 35 View Code Duplication
        if ($this->uow !== null && $this->isOrphanRemovalEnabled() && $value !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
455 34
            $this->uow->unscheduleOrphanRemoval($value);
456 34
        }
457
458 35
        $this->changed();
459 35
    }
460
461
    /**
462
     * {@inheritdoc}
463
     */
464 154
    public function add($value)
465
    {
466
        /* Initialize the collection before calling add() so this append operation
467
         * uses the appropriate key. Otherwise, we risk overwriting original data
468
         * when $newObjects are re-added in a later call to initialize().
469
         */
470 154
        if (isset($this->mapping['strategy']) && CollectionHelper::isHash($this->mapping['strategy'])) {
471 12
            $this->initialize();
472 12
        }
473 154
        $this->coll->add($value);
474 154
        $this->changed();
475
476 154 View Code Duplication
        if ($this->uow !== null && $this->isOrphanRemovalEnabled() && $value !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
477 91
            $this->uow->unscheduleOrphanRemoval($value);
478 91
        }
479
480 154
        return true;
481
    }
482
483
    /**
484
     * {@inheritdoc}
485
     */
486 351
    public function isEmpty()
487
    {
488 351
        return $this->count() === 0;
489
    }
490
491
    /**
492
     * {@inheritdoc}
493
     */
494 311
    public function getIterator()
495
    {
496 311
        $this->initialize();
497 311
        return $this->coll->getIterator();
498
    }
499
500
    /**
501
     * {@inheritdoc}
502
     */
503 210
    public function map(\Closure $func)
504
    {
505 210
        $this->initialize();
506 210
        return $this->coll->map($func);
507
    }
508
509
    /**
510
     * {@inheritdoc}
511
     */
512
    public function filter(\Closure $p)
513
    {
514
        $this->initialize();
515
        return $this->coll->filter($p);
516
    }
517
518
    /**
519
     * {@inheritdoc}
520
     */
521
    public function forAll(\Closure $p)
522
    {
523
        $this->initialize();
524
        return $this->coll->forAll($p);
525
    }
526
527
    /**
528
     * {@inheritdoc}
529
     */
530
    public function partition(\Closure $p)
531
    {
532
        $this->initialize();
533
        return $this->coll->partition($p);
534
    }
535
536
    /**
537
     * {@inheritdoc}
538
     */
539 23
    public function toArray()
540
    {
541 23
        $this->initialize();
542 23
        return $this->coll->toArray();
543
    }
544
545
    /**
546
     * {@inheritdoc}
547
     */
548 28
    public function clear()
549
    {
550 28
        if ($this->initialized && $this->isEmpty()) {
551
            return;
552
        }
553
554 28
        if ($this->isOrphanRemovalEnabled()) {
555 27
            foreach ($this->coll as $element) {
556 25
                $this->uow->scheduleOrphanRemoval($element);
557 27
            }
558 27
        }
559
560 28
        $this->mongoData = array();
561 28
        $this->coll->clear();
562
563
        // Nothing to do for inverse-side collections
564 28
        if ( ! $this->mapping['isOwningSide']) {
565
            return;
566
        }
567
568
        // Nothing to do if the collection was initialized but contained no data
569 28
        if ($this->initialized && empty($this->snapshot)) {
570 2
            return;
571
        }
572
573 26
        $this->changed();
574 26
        $this->uow->scheduleCollectionDeletion($this);
575 26
        $this->takeSnapshot();
576 26
    }
577
578
    /**
579
     * {@inheritdoc}
580
     */
581 1
    public function slice($offset, $length = null)
582
    {
583 1
        $this->initialize();
584 1
        return $this->coll->slice($offset, $length);
585
    }
586
587
    /**
588
     * Called by PHP when this collection is serialized. Ensures that only the
589
     * elements are properly serialized.
590
     *
591
     * @internal Tried to implement Serializable first but that did not work well
592
     *           with circular references. This solution seems simpler and works well.
593
     */
594 1
    public function __sleep()
595
    {
596 1
        return array('coll', 'initialized');
597
    }
598
599
    /* ArrayAccess implementation */
600
601
    /**
602
     * @see containsKey()
603
     */
604 1
    public function offsetExists($offset)
605
    {
606 1
        return $this->containsKey($offset);
607
    }
608
609
    /**
610
     * @see get()
611
     */
612 75
    public function offsetGet($offset)
613
    {
614 75
        return $this->get($offset);
615
    }
616
617
    /**
618
     * @see add()
619
     * @see set()
620
     */
621 38
    public function offsetSet($offset, $value)
622
    {
623 38
        if ( ! isset($offset)) {
624 37
            return $this->add($value);
625
        }
626
627 2
        return $this->set($offset, $value);
628
    }
629
630
    /**
631
     * @see remove()
632
     */
633 18
    public function offsetUnset($offset)
634
    {
635 18
        return $this->remove($offset);
636
    }
637
638
    public function key()
639
    {
640
        return $this->coll->key();
641
    }
642
643
    /**
644
     * Gets the element of the collection at the current iterator position.
645
     */
646 1
    public function current()
647
    {
648 1
        return $this->coll->current();
649
    }
650
651
    /**
652
     * Moves the internal iterator position to the next element.
653
     */
654
    public function next()
655
    {
656
        return $this->coll->next();
657
    }
658
659
    /**
660
     * Retrieves the wrapped Collection instance.
661
     */
662 378
    public function unwrap()
663
    {
664 378
        return $this->coll;
665
    }
666
667
    /**
668
     * Cleanup internal state of cloned persistent collection.
669
     *
670
     * The following problems have to be prevented:
671
     * 1. Added documents are added to old PersistentCollection
672
     * 2. New collection is not dirty, if reused on other document nothing
673
     * changes.
674
     * 3. Snapshot leads to invalid diffs being generated.
675
     * 4. Lazy loading grabs entities from old owner object.
676
     * 5. New collection is connected to old owner and leads to duplicate keys.
677
     */
678 8
    public function __clone()
679
    {
680 8
        if (is_object($this->coll)) {
681 8
            $this->coll = clone $this->coll;
682 8
        }
683
684 8
        $this->initialize();
685
686 8
        $this->owner = null;
687 8
        $this->snapshot = array();
688
689 8
        $this->changed();
690 8
    }
691
692
    /**
693
     * Returns whether or not this collection has orphan removal enabled.
694
     *
695
     * Embedded documents are automatically considered as "orphan removal enabled" because they might have references
696
     * that require to trigger cascade remove operations.
697
     *
698
     * @return boolean
699
     */
700 174
    private function isOrphanRemovalEnabled()
701
    {
702 174
        if ($this->mapping === null) {
703 5
            return false;
704
        }
705
706 169
        if (isset($this->mapping['embedded'])) {
707 102
            return true;
708
        }
709
710 72
        if (isset($this->mapping['reference']) && $this->mapping['isOwningSide'] && $this->mapping['orphanRemoval']) {
711 8
            return true;
712
        }
713
714 64
        return false;
715
    }
716
}
717