Completed
Pull Request — master (#1219)
by Maciej
24:16
created

PersistentCollectionTrait::offsetExists()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 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
    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
    public function getDeletedDocuments()
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_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
    public function getOwner()
285
    {
286
        return $this->owner;
287
    }
288
289
    /** {@inheritdoc} */
290
    public function getMapping()
291
    {
292
        return $this->mapping;
293
    }
294
295
    /** {@inheritdoc} */
296
    public function getTypeClass()
297
    {
298
        if (empty($this->typeClass)) {
299
            throw new MongoDBException('Specifying targetDocument is required for the ClassMetadata to be obtained.');
300
        }
301
302
        return $this->typeClass;
303
    }
304
305
    /** {@inheritdoc} */
306
    public function setInitialized($bool)
307
    {
308
        $this->initialized = $bool;
309
    }
310
311
    /** {@inheritdoc} */
312
    public function isInitialized()
313
    {
314
        return $this->initialized;
315
    }
316
317
    /** {@inheritdoc} */
318
    public function first()
319
    {
320
        $this->initialize();
321
        return $this->coll->first();
322
    }
323
324
    /** {@inheritdoc} */
325
    public function last()
326
    {
327
        $this->initialize();
328
        return $this->coll->last();
329
    }
330
331
    /**
332
     * {@inheritdoc}
333
     */
334 View Code Duplication
    public function remove($key)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

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

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

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

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

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

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

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

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

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

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