Completed
Pull Request — master (#1411)
by Maciej
09:57
created

PersistentCollectionTrait::setMongoData()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

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 4
ccs 3
cts 3
cp 1
rs 10
cc 1
eloc 2
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 2
    public function setDocumentManager(DocumentManager $dm)
113
    {
114 2
        $this->dm = $dm;
115 2
        $this->uow = $dm->getUnitOfWork();
116 2
    }
117
118
    /** {@inheritdoc} */
119 176
    public function setMongoData(array $mongoData)
120
    {
121 176
        $this->mongoData = $mongoData;
122 176
    }
123
124
    /** {@inheritdoc} */
125 152
    public function getMongoData()
126
    {
127 152
        return $this->mongoData;
128
    }
129
130
    /** {@inheritdoc} */
131 260
    public function setHints(array $hints)
132
    {
133 260
        $this->hints = $hints;
134 260
    }
135
136
    /** {@inheritdoc} */
137 85
    public function getHints()
138
    {
139 85
        return $this->hints;
140
    }
141
142
    /** {@inheritdoc} */
143 378
    public function initialize()
144
    {
145 378
        if ($this->initialized || ! $this->mapping) {
146 371
            return;
147
        }
148
149 164
        $newObjects = array();
150
151 164
        if ($this->isDirty) {
152
            // Remember any NEW objects added through add()
153 22
            $newObjects = $this->coll->toArray();
154
        }
155
156 164
        $this->initialized = true;
157
158 164
        $this->coll->clear();
159 164
        $this->uow->loadCollection($this);
160 164
        $this->takeSnapshot();
161
162 164
        $this->mongoData = array();
163
164
        // Reattach any NEW objects added through add()
165 164
        if ($newObjects) {
166 22
            foreach ($newObjects as $key => $obj) {
167 22
                if (CollectionHelper::isHash($this->mapping['strategy'])) {
168
                    $this->coll->set($key, $obj);
169
                } else {
170 22
                    $this->coll->add($obj);
171
                }
172
            }
173
174 22
            $this->isDirty = true;
175
        }
176 164
    }
177
178
    /**
179
     * Marks this collection as changed/dirty.
180
     */
181 192
    private function changed()
182
    {
183 192
        if ($this->isDirty) {
184 125
            return;
185
        }
186
187 192
        $this->isDirty = true;
188
189 192
        if ($this->needsSchedulingForDirtyCheck()) {
190 2
            $this->uow->scheduleForDirtyCheck($this->owner);
191
        }
192 192
    }
193
194
    /** {@inheritdoc} */
195 393
    public function isDirty()
196
    {
197 393
        if ($this->isDirty) {
198 250
            return true;
199
        }
200 349
        if (! $this->initialized && count($this->coll)) {
201
            // not initialized collection with added elements
202
            return true;
203
        }
204 349
        if ($this->initialized) {
205
            // if initialized let's check with last known snapshot
206 339
            return $this->coll->toArray() !== $this->snapshot;
207
        }
208 94
        return false;
209
    }
210
211
    /** {@inheritdoc} */
212 385
    public function setDirty($dirty)
213
    {
214 385
        $this->isDirty = $dirty;
215 385
    }
216
217
    /** {@inheritdoc} */
218 404
    public function setOwner($document, array $mapping)
219
    {
220 404
        $this->owner = $document;
221 404
        $this->mapping = $mapping;
222 404
    }
223
224
    /** {@inheritdoc} */
225 274
    public function takeSnapshot()
226
    {
227 274
        if (CollectionHelper::isList($this->mapping['strategy'])) {
228 260
            $array = $this->coll->toArray();
229 260
            $this->coll->clear();
230 260
            foreach ($array as $document) {
231 235
                $this->coll->add($document);
232
            }
233
        }
234 274
        $this->snapshot = $this->coll->toArray();
235 274
        $this->isDirty = false;
236 274
    }
237
238
    /** {@inheritdoc} */
239 23
    public function clearSnapshot()
240
    {
241 23
        $this->snapshot = array();
242 23
        $this->isDirty = $this->coll->count() ? true : false;
243 23
    }
244
245
    /** {@inheritdoc} */
246
    public function getSnapshot()
247
    {
248
        return $this->snapshot;
249
    }
250
251
    /** {@inheritdoc} */
252 87 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...
253
    {
254 87
        return array_udiff_assoc(
255 87
            $this->snapshot,
256 87
            $this->coll->toArray(),
257
            function ($a, $b) { return $a === $b ? 0 : 1; }
258
        );
259
    }
260
261
    /** {@inheritdoc} */
262 145 View Code Duplication
    public function getDeletedDocuments()
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...
263
    {
264
        $compare = function ($a, $b) {
265 99
            $compareA = is_object($a) ? spl_object_hash($a) : $a;
266 99
            $compareb = is_object($b) ? spl_object_hash($b) : $b;
267 99
            return $compareA === $compareb ? 0 : ($compareA > $compareb ? 1 : -1);
268 145
        };
269 145
        return array_values(array_udiff(
270 145
            $this->snapshot,
271 145
            $this->coll->toArray(),
272
            $compare
273
        ));
274
    }
275
276
    /** {@inheritdoc} */
277 87 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...
278
    {
279 87
        return array_udiff_assoc(
280 87
            $this->coll->toArray(),
281 87
            $this->snapshot,
282
            function ($a, $b) { return $a === $b ? 0 : 1; }
283
        );
284
    }
285
286
    /** {@inheritdoc} */
287 View Code Duplication
    public function getInsertedDocuments()
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...
288
    {
289 4
        $compare = function ($a, $b) {
290 4
            $compareA = is_object($a) ? spl_object_hash($a) : $a;
291 4
            $compareb = is_object($b) ? spl_object_hash($b) : $b;
292 4
            return $compareA === $compareb ? 0 : ($compareA > $compareb ? 1 : -1);
293 4
        };
294 4
        return array_values(array_udiff(
295 4
            $this->coll->toArray(),
296 4
            $this->snapshot,
297
            $compare
298
        ));
299
    }
300
301
    /** {@inheritdoc} */
302 393
    public function getOwner()
303
    {
304 393
        return $this->owner;
305
    }
306
307
    /** {@inheritdoc} */
308 267
    public function getMapping()
309
    {
310 267
        return $this->mapping;
311
    }
312
313
    /** {@inheritdoc} */
314 5
    public function getTypeClass()
315
    {
316
        switch (true) {
317 5
            case (! empty($this->typeClass)):
318
                return $this->typeClass;
319 5
            case ($this->dm === null):
320 1
                throw new MongoDBException('No DocumentManager is associated with this PersistentCollection, please set one using setDocumentManager method.');
321
            case (empty($this->mapping)):
322 1
                throw new MongoDBException('No mapping is associated with this PersistentCollection, please set one using setOwner method.');
323 3
            case (empty($this->mapping['targetDocument'])):
324 1
                throw new MongoDBException('Specifying targetDocument is required for the ClassMetadata to be obtained.');
325
            default:
326 2
                return $this->typeClass = $this->dm->getClassMetadata($this->mapping['targetDocument']);
327
        }
328
    }
329
330
    /** {@inheritdoc} */
331 261
    public function setInitialized($bool)
332
    {
333 261
        $this->initialized = $bool;
334 261
    }
335
336
    /** {@inheritdoc} */
337 15
    public function isInitialized()
338
    {
339 15
        return $this->initialized;
340
    }
341
342
    /** {@inheritdoc} */
343 12
    public function first()
344
    {
345 12
        $this->initialize();
346 12
        return $this->coll->first();
347
    }
348
349
    /** {@inheritdoc} */
350 1
    public function last()
351
    {
352 1
        $this->initialize();
353 1
        return $this->coll->last();
354
    }
355
356
    /**
357
     * {@inheritdoc}
358
     */
359 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...
360
    {
361 21
        $this->initialize();
362 21
        $removed = $this->coll->remove($key);
363
364 21
        if ( ! $removed) {
365
            return $removed;
366
        }
367
368 21
        $this->changed();
369
370 21
        return $removed;
371
    }
372
373
    /**
374
     * {@inheritdoc}
375
     */
376 17 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...
377
    {
378 17
        $this->initialize();
379 17
        $removed = $this->coll->removeElement($element);
380
381 17
        if ( ! $removed) {
382
            return $removed;
383
        }
384
385 17
        $this->changed();
386
387 17
        return $removed;
388
    }
389
390
    /**
391
     * {@inheritdoc}
392
     */
393 1
    public function containsKey($key)
394
    {
395 1
        $this->initialize();
396 1
        return $this->coll->containsKey($key);
397
    }
398
399
    /**
400
     * {@inheritdoc}
401
     */
402 2
    public function contains($element)
403
    {
404 2
        $this->initialize();
405 2
        return $this->coll->contains($element);
406
    }
407
408
    /**
409
     * {@inheritdoc}
410
     */
411
    public function exists(\Closure $p)
412
    {
413
        $this->initialize();
414
        return $this->coll->exists($p);
415
    }
416
417
    /**
418
     * {@inheritdoc}
419
     */
420 2
    public function indexOf($element)
421
    {
422 2
        $this->initialize();
423 2
        return $this->coll->indexOf($element);
424
    }
425
426
    /**
427
     * {@inheritdoc}
428
     */
429 90
    public function get($key)
430
    {
431 90
        $this->initialize();
432 90
        return $this->coll->get($key);
433
    }
434
435
    /**
436
     * {@inheritdoc}
437
     */
438
    public function getKeys()
439
    {
440
        $this->initialize();
441
        return $this->coll->getKeys();
442
    }
443
444
    /**
445
     * {@inheritdoc}
446
     */
447
    public function getValues()
448
    {
449
        $this->initialize();
450
        return $this->coll->getValues();
451
    }
452
453
    /**
454
     * {@inheritdoc}
455
     */
456 374
    public function count()
457
    {
458 374
        $count = $this->coll->count();
459
460
        // If this collection is inversed and not initialized, add the count returned from the database
461 374
        if ($this->mapping['isInverseSide'] && ! $this->initialized) {
462 10
            $documentPersister = $this->uow->getDocumentPersister(get_class($this->owner));
463 10
            $count += empty($this->mapping['repositoryMethod'])
464 9
                ? $documentPersister->createReferenceManyInverseSideQuery($this)->count()
465 1
                : $documentPersister->createReferenceManyWithRepositoryMethodCursor($this)->count();
466
        }
467
468 374
        return $count + ($this->initialized ? 0 : count($this->mongoData));
469
    }
470
471
    /**
472
     * {@inheritdoc}
473
     */
474 35
    public function set($key, $value)
475
    {
476 35
        $this->coll->set($key, $value);
477
478
        // Handle orphanRemoval
479 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...
480 34
            $this->uow->unscheduleOrphanRemoval($value);
481
        }
482
483 35
        $this->changed();
484 35
    }
485
486
    /**
487
     * {@inheritdoc}
488
     */
489 166
    public function add($value)
490
    {
491
        /* Initialize the collection before calling add() so this append operation
492
         * uses the appropriate key. Otherwise, we risk overwriting original data
493
         * when $newObjects are re-added in a later call to initialize().
494
         */
495 166
        if (isset($this->mapping['strategy']) && CollectionHelper::isHash($this->mapping['strategy'])) {
496 12
            $this->initialize();
497
        }
498 166
        $this->coll->add($value);
499 166
        $this->changed();
500
501 166 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...
502 93
            $this->uow->unscheduleOrphanRemoval($value);
503
        }
504
505 166
        return true;
506
    }
507
508
    /**
509
     * {@inheritdoc}
510
     */
511 366
    public function isEmpty()
512
    {
513 366
        return $this->count() === 0;
514
    }
515
516
    /**
517
     * {@inheritdoc}
518
     */
519 321
    public function getIterator()
520
    {
521 321
        $this->initialize();
522 321
        return $this->coll->getIterator();
523
    }
524
525
    /**
526
     * {@inheritdoc}
527
     */
528 217
    public function map(\Closure $func)
529
    {
530 217
        $this->initialize();
531 217
        return $this->coll->map($func);
532
    }
533
534
    /**
535
     * {@inheritdoc}
536
     */
537
    public function filter(\Closure $p)
538
    {
539
        $this->initialize();
540
        return $this->coll->filter($p);
541
    }
542
543
    /**
544
     * {@inheritdoc}
545
     */
546
    public function forAll(\Closure $p)
547
    {
548
        $this->initialize();
549
        return $this->coll->forAll($p);
550
    }
551
552
    /**
553
     * {@inheritdoc}
554
     */
555
    public function partition(\Closure $p)
556
    {
557
        $this->initialize();
558
        return $this->coll->partition($p);
559
    }
560
561
    /**
562
     * {@inheritdoc}
563
     */
564 23
    public function toArray()
565
    {
566 23
        $this->initialize();
567 23
        return $this->coll->toArray();
568
    }
569
570
    /**
571
     * {@inheritdoc}
572
     */
573 28
    public function clear()
574
    {
575 28
        if ($this->initialized && $this->isEmpty()) {
576
            return;
577
        }
578
579 28
        if ($this->isOrphanRemovalEnabled()) {
580 27
            foreach ($this->coll as $element) {
581 25
                $this->uow->scheduleOrphanRemoval($element);
582
            }
583
        }
584
585 28
        $this->mongoData = array();
586 28
        $this->coll->clear();
587
588
        // Nothing to do for inverse-side collections
589 28
        if ( ! $this->mapping['isOwningSide']) {
590
            return;
591
        }
592
593
        // Nothing to do if the collection was initialized but contained no data
594 28
        if ($this->initialized && empty($this->snapshot)) {
595 2
            return;
596
        }
597
598 26
        $this->changed();
599 26
        $this->uow->scheduleCollectionDeletion($this);
600 26
        $this->takeSnapshot();
601 26
    }
602
603
    /**
604
     * {@inheritdoc}
605
     */
606 1
    public function slice($offset, $length = null)
607
    {
608 1
        $this->initialize();
609 1
        return $this->coll->slice($offset, $length);
610
    }
611
612
    /**
613
     * Called by PHP when this collection is serialized. Ensures that only the
614
     * elements are properly serialized.
615
     *
616
     * @internal Tried to implement Serializable first but that did not work well
617
     *           with circular references. This solution seems simpler and works well.
618
     */
619 4
    public function __sleep()
620
    {
621 4
        return array('coll', 'initialized');
622
    }
623
624
    /* ArrayAccess implementation */
625
626
    /**
627
     * @see containsKey()
628
     */
629 1
    public function offsetExists($offset)
630
    {
631 1
        return $this->containsKey($offset);
632
    }
633
634
    /**
635
     * @see get()
636
     */
637 78
    public function offsetGet($offset)
638
    {
639 78
        return $this->get($offset);
640
    }
641
642
    /**
643
     * @see add()
644
     * @see set()
645
     */
646 40
    public function offsetSet($offset, $value)
647
    {
648 40
        if ( ! isset($offset)) {
649 39
            return $this->add($value);
650
        }
651
652 2
        return $this->set($offset, $value);
653
    }
654
655
    /**
656
     * @see remove()
657
     */
658 18
    public function offsetUnset($offset)
659
    {
660 18
        return $this->remove($offset);
661
    }
662
663
    public function key()
664
    {
665
        return $this->coll->key();
666
    }
667
668
    /**
669
     * Gets the element of the collection at the current iterator position.
670
     */
671 1
    public function current()
672
    {
673 1
        return $this->coll->current();
674
    }
675
676
    /**
677
     * Moves the internal iterator position to the next element.
678
     */
679
    public function next()
680
    {
681
        return $this->coll->next();
682
    }
683
684
    /**
685
     * {@inheritdoc}
686
     */
687 394
    public function unwrap()
688
    {
689 394
        return $this->coll;
690
    }
691
692
    /**
693
     * Cleanup internal state of cloned persistent collection.
694
     *
695
     * The following problems have to be prevented:
696
     * 1. Added documents are added to old PersistentCollection
697
     * 2. New collection is not dirty, if reused on other document nothing
698
     * changes.
699
     * 3. Snapshot leads to invalid diffs being generated.
700
     * 4. Lazy loading grabs entities from old owner object.
701
     * 5. New collection is connected to old owner and leads to duplicate keys.
702
     */
703 8
    public function __clone()
704
    {
705 8
        if (is_object($this->coll)) {
706 8
            $this->coll = clone $this->coll;
707
        }
708
709 8
        $this->initialize();
710
711 8
        $this->owner = null;
712 8
        $this->snapshot = array();
713
714 8
        $this->changed();
715 8
    }
716
717
    /**
718
     * Returns whether or not this collection has orphan removal enabled.
719
     *
720
     * Embedded documents are automatically considered as "orphan removal enabled" because they might have references
721
     * that require to trigger cascade remove operations.
722
     *
723
     * @return boolean
724
     */
725 186
    private function isOrphanRemovalEnabled()
726
    {
727 186
        if ($this->mapping === null) {
728 10
            return false;
729
        }
730
731 176
        if (isset($this->mapping['embedded'])) {
732 104
            return true;
733
        }
734
735 77
        if (isset($this->mapping['reference']) && $this->mapping['isOwningSide'] && $this->mapping['orphanRemoval']) {
736 8
            return true;
737
        }
738
739 69
        return false;
740
    }
741
742
    /**
743
     * Checks whether collection owner needs to be scheduled for dirty change in case the collection is modified.
744
     *
745
     * @return bool
746
     */
747 192
    private function needsSchedulingForDirtyCheck()
748
    {
749 192
        return $this->owner && $this->dm && ! empty($this->mapping['isOwningSide'])
750 192
            && $this->dm->getClassMetadata(get_class($this->owner))->isChangeTrackingNotify();
751
    }
752
}
753