Completed
Push — master ( 8840ae...28ce39 )
by Andreas
09:22
created

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