Completed
Pull Request — master (#1378)
by Maciej
25:59
created

PersistentCollectionTrait::getKeys()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 5
rs 9.4285
cc 1
eloc 3
nc 1
nop 0
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
    public function setMongoData(array $mongoData)
120
    {
121
        $this->mongoData = $mongoData;
122
    }
123
124
    /** {@inheritdoc} */
125
    public function getMongoData()
126
    {
127
        return $this->mongoData;
128
    }
129
130
    /** {@inheritdoc} */
131
    public function setHints(array $hints)
132
    {
133
        $this->hints = $hints;
134
    }
135
136
    /** {@inheritdoc} */
137
    public function getHints()
138
    {
139
        return $this->hints;
140
    }
141
142
    /** {@inheritdoc} */
143
    public function initialize()
144
    {
145
        if ($this->initialized || ! $this->mapping) {
146
            return;
147
        }
148
149
        $newObjects = array();
150
151
        if ($this->isDirty) {
152
            // Remember any NEW objects added through add()
153
            $newObjects = $this->coll->toArray();
154
        }
155
156
        $this->initialized = true;
157
158
        $this->coll->clear();
159
        $this->uow->loadCollection($this);
160
        $this->takeSnapshot();
161
162
        $this->mongoData = array();
163
164
        // Reattach any NEW objects added through add()
165
        if ($newObjects) {
166
            foreach ($newObjects as $key => $obj) {
167
                if (CollectionHelper::isHash($this->mapping['strategy'])) {
168
                    $this->coll->set($key, $obj);
169
                } else {
170
                    $this->coll->add($obj);
171
                }
172
            }
173
174
            $this->isDirty = true;
175
        }
176
    }
177
178
    /**
179
     * Marks this collection as changed/dirty.
180
     */
181
    private function changed()
182
    {
183
        if ($this->isDirty) {
184
            return;
185
        }
186
187
        $this->isDirty = true;
188
189
        if ($this->dm &&
190
            $this->mapping !== null &&
191
            $this->mapping['isOwningSide'] &&
192
            $this->owner &&
193
            $this->dm->getClassMetadata(get_class($this->owner))->isChangeTrackingNotify()) {
194
            $this->uow->scheduleForDirtyCheck($this->owner);
195
        }
196
    }
197
198
    /** {@inheritdoc} */
199
    public function isDirty()
200
    {
201
        return $this->isDirty;
202
    }
203
204
    /** {@inheritdoc} */
205
    public function setDirty($dirty)
206
    {
207
        $this->isDirty = $dirty;
208
    }
209
210
    /** {@inheritdoc} */
211
    public function setOwner($document, array $mapping)
212
    {
213
        $this->owner = $document;
214
        $this->mapping = $mapping;
215
216
        if ( ! empty($this->mapping['targetDocument'])) {
217
            $this->typeClass = $this->dm->getClassMetadata($this->mapping['targetDocument']);
218
        }
219
    }
220
221
    /** {@inheritdoc} */
222
    public function takeSnapshot()
223
    {
224
        if (CollectionHelper::isList($this->mapping['strategy'])) {
225
            $array = $this->coll->toArray();
226
            $this->coll->clear();
227
            foreach ($array as $document) {
228
                $this->coll->add($document);
229
            }
230
        }
231
        $this->snapshot = $this->coll->toArray();
232
        $this->isDirty = false;
233
    }
234
235
    /** {@inheritdoc} */
236
    public function clearSnapshot()
237
    {
238
        $this->snapshot = array();
239
        $this->isDirty = $this->coll->count() ? true : false;
240
    }
241
242
    /** {@inheritdoc} */
243
    public function getSnapshot()
244
    {
245
        return $this->snapshot;
246
    }
247
248
    /** {@inheritdoc} */
249 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
        return array_udiff_assoc(
252
            $this->snapshot,
253
            $this->coll->toArray(),
254
            function ($a, $b) { return $a === $b ? 0 : 1; }
255
        );
256
    }
257
258
    /** {@inheritdoc} */
259 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
            $compareA = is_object($a) ? spl_object_hash($a) : $a;
263
            $compareb = is_object($b) ? spl_object_hash($b) : $b;
264
            return $compareA === $compareb ? 0 : ($compareA > $compareb ? 1 : -1);
265
        };
266
        return array_values(array_udiff(
267
            $this->snapshot,
268
            $this->coll->toArray(),
269
            $compare
270
        ));
271
    }
272
273
    /** {@inheritdoc} */
274 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
        return array_udiff_assoc(
277
            $this->coll->toArray(),
278
            $this->snapshot,
279
            function ($a, $b) { return $a === $b ? 0 : 1; }
280
        );
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
        $compare = function ($a, $b) {
287
            $compareA = is_object($a) ? spl_object_hash($a) : $a;
288
            $compareb = is_object($b) ? spl_object_hash($b) : $b;
289
            return $compareA === $compareb ? 0 : ($compareA > $compareb ? 1 : -1);
290
        };
291
        return array_values(array_udiff(
292
            $this->coll->toArray(),
293
            $this->snapshot,
294
            $compare
295
        ));
296
    }
297
298
    /** {@inheritdoc} */
299
    public function getOwner()
300
    {
301
        return $this->owner;
302
    }
303
304
    /** {@inheritdoc} */
305
    public function getMapping()
306
    {
307
        return $this->mapping;
308
    }
309
310
    /** {@inheritdoc} */
311
    public function getTypeClass()
312
    {
313
        if (empty($this->typeClass)) {
314
            throw new MongoDBException('Specifying targetDocument is required for the ClassMetadata to be obtained.');
315
        }
316
317
        return $this->typeClass;
318
    }
319
320
    /** {@inheritdoc} */
321
    public function setInitialized($bool)
322
    {
323
        $this->initialized = $bool;
324
    }
325
326
    /** {@inheritdoc} */
327
    public function isInitialized()
328
    {
329
        return $this->initialized;
330
    }
331
332
    /** {@inheritdoc} */
333
    public function first()
334
    {
335
        $this->initialize();
336
        return $this->coll->first();
337
    }
338
339
    /** {@inheritdoc} */
340
    public function last()
341
    {
342
        $this->initialize();
343
        return $this->coll->last();
344
    }
345
346
    /**
347
     * {@inheritdoc}
348
     */
349 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
        $this->initialize();
352
        $removed = $this->coll->remove($key);
353
354
        if ( ! $removed) {
355
            return $removed;
356
        }
357
358
        $this->changed();
359
360
        return $removed;
361
    }
362
363
    /**
364
     * {@inheritdoc}
365
     */
366 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
        $this->initialize();
369
        $removed = $this->coll->removeElement($element);
370
371
        if ( ! $removed) {
372
            return $removed;
373
        }
374
375
        $this->changed();
376
377
        return $removed;
378
    }
379
380
    /**
381
     * {@inheritdoc}
382
     */
383
    public function containsKey($key)
384
    {
385
        $this->initialize();
386
        return $this->coll->containsKey($key);
387
    }
388
389
    /**
390
     * {@inheritdoc}
391
     */
392
    public function contains($element)
393
    {
394
        $this->initialize();
395
        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
    public function indexOf($element)
411
    {
412
        $this->initialize();
413
        return $this->coll->indexOf($element);
414
    }
415
416
    /**
417
     * {@inheritdoc}
418
     */
419
    public function get($key)
420
    {
421
        $this->initialize();
422
        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
    public function count()
447
    {
448
        $count = $this->coll->count();
449
450
        // If this collection is inversed and not initialized, add the count returned from the database
451
        if ($this->mapping['isInverseSide'] && ! $this->initialized) {
452
            $documentPersister = $this->uow->getDocumentPersister(get_class($this->owner));
453
            $count += empty($this->mapping['repositoryMethod'])
454
                ? $documentPersister->createReferenceManyInverseSideQuery($this)->count()
455
                : $documentPersister->createReferenceManyWithRepositoryMethodCursor($this)->count();
456
        }
457
458
        return count($this->mongoData) + $count;
459
    }
460
461
    /**
462
     * {@inheritdoc}
463
     */
464
    public function set($key, $value)
465
    {
466
        $this->coll->set($key, $value);
467
468
        // Handle orphanRemoval
469 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
            $this->uow->unscheduleOrphanRemoval($value);
471
        }
472
473
        $this->changed();
474
    }
475
476
    /**
477
     * {@inheritdoc}
478
     */
479
    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
        if (isset($this->mapping['strategy']) && CollectionHelper::isHash($this->mapping['strategy'])) {
486
            $this->initialize();
487
        }
488
        $this->coll->add($value);
489
        $this->changed();
490
491 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
            $this->uow->unscheduleOrphanRemoval($value);
493
        }
494
495
        return true;
496
    }
497
498
    /**
499
     * {@inheritdoc}
500
     */
501
    public function isEmpty()
502
    {
503
        return $this->count() === 0;
504
    }
505
506
    /**
507
     * {@inheritdoc}
508
     */
509
    public function getIterator()
510
    {
511
        $this->initialize();
512
        return $this->coll->getIterator();
513
    }
514
515
    /**
516
     * {@inheritdoc}
517
     */
518
    public function map(\Closure $func)
519
    {
520
        $this->initialize();
521
        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
    public function toArray()
555
    {
556
        $this->initialize();
557
        return $this->coll->toArray();
558
    }
559
560
    /**
561
     * {@inheritdoc}
562
     */
563
    public function clear()
564
    {
565
        if ($this->initialized && $this->isEmpty()) {
566
            return;
567
        }
568
569
        if ($this->isOrphanRemovalEnabled()) {
570
            foreach ($this->coll as $element) {
571
                $this->uow->scheduleOrphanRemoval($element);
572
            }
573
        }
574
575
        $this->mongoData = array();
576
        $this->coll->clear();
577
578
        // Nothing to do for inverse-side collections
579
        if ( ! $this->mapping['isOwningSide']) {
580
            return;
581
        }
582
583
        // Nothing to do if the collection was initialized but contained no data
584
        if ($this->initialized && empty($this->snapshot)) {
585
            return;
586
        }
587
588
        $this->changed();
589
        $this->uow->scheduleCollectionDeletion($this);
590
        $this->takeSnapshot();
591
    }
592
593
    /**
594
     * {@inheritdoc}
595
     */
596
    public function slice($offset, $length = null)
597
    {
598
        $this->initialize();
599
        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
    public function __sleep()
610
    {
611
        return array('coll', 'initialized');
612
    }
613
614
    /* ArrayAccess implementation */
615
616
    /**
617
     * @see containsKey()
618
     */
619
    public function offsetExists($offset)
620
    {
621
        return $this->containsKey($offset);
622
    }
623
624
    /**
625
     * @see get()
626
     */
627
    public function offsetGet($offset)
628
    {
629
        return $this->get($offset);
630
    }
631
632
    /**
633
     * @see add()
634
     * @see set()
635
     */
636
    public function offsetSet($offset, $value)
637
    {
638
        if ( ! isset($offset)) {
639
            return $this->add($value);
640
        }
641
642
        return $this->set($offset, $value);
643
    }
644
645
    /**
646
     * @see remove()
647
     */
648
    public function offsetUnset($offset)
649
    {
650
        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
    public function current()
662
    {
663
        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
    public function unwrap()
678
    {
679
        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
    public function __clone()
694
    {
695
        if (is_object($this->coll)) {
696
            $this->coll = clone $this->coll;
697
        }
698
699
        $this->initialize();
700
701
        $this->owner = null;
702
        $this->snapshot = array();
703
704
        $this->changed();
705
    }
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
    private function isOrphanRemovalEnabled()
716
    {
717
        if ($this->mapping === null) {
718
            return false;
719
        }
720
721
        if (isset($this->mapping['embedded'])) {
722
            return true;
723
        }
724
725
        if (isset($this->mapping['reference']) && $this->mapping['isOwningSide'] && $this->mapping['orphanRemoval']) {
726
            return true;
727
        }
728
729
        return false;
730
    }
731
}
732