Completed
Pull Request — master (#1219)
by Maciej
09:25
created

PersistentCollectionTrait::isEmpty()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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