Completed
Pull Request — master (#1379)
by Maciej
16:13
created

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