Failed Conditions
Pull Request — 2.7 (#7940)
by Benjamin
07:27
created

PersistentCollection::remove()   A

Complexity

Conditions 6
Paths 3

Size

Total Lines 22
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 6.027

Importance

Changes 0
Metric Value
cc 6
eloc 10
nc 3
nop 1
dl 0
loc 22
ccs 10
cts 11
cp 0.9091
crap 6.027
rs 9.2222
c 0
b 0
f 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\ORM;
21
22
use Doctrine\Common\Collections\AbstractLazyCollection;
23
use Doctrine\Common\Collections\Collection;
24
use Doctrine\Common\Collections\ArrayCollection;
25
use Doctrine\Common\Collections\Selectable;
26
use Doctrine\Common\Collections\Criteria;
27
use Doctrine\ORM\Mapping\ClassMetadata;
28
use function get_class;
29
30
/**
31
 * A PersistentCollection represents a collection of elements that have persistent state.
32
 *
33
 * Collections of entities represent only the associations (links) to those entities.
34
 * That means, if the collection is part of a many-many mapping and you remove
35
 * entities from the collection, only the links in the relation table are removed (on flush).
36
 * Similarly, if you remove entities from a collection that is part of a one-many
37
 * mapping this will only result in the nulling out of the foreign keys on flush.
38
 *
39
 * @since     2.0
40
 * @author    Konsta Vesterinen <[email protected]>
41
 * @author    Roman Borschel <[email protected]>
42
 * @author    Giorgio Sironi <[email protected]>
43
 * @author    Stefano Rodriguez <[email protected]>
44
 */
45
final class PersistentCollection extends AbstractLazyCollection implements Selectable
46
{
47
    /**
48
     * A snapshot of the collection at the moment it was fetched from the database.
49
     * This is used to create a diff of the collection at commit time.
50
     *
51
     * @var array
52
     */
53
    private $snapshot = [];
54
55
    /**
56
     * The entity that owns this collection.
57
     *
58
     * @var object
59
     */
60
    private $owner;
61
62
    /**
63
     * The association mapping the collection belongs to.
64
     * This is currently either a OneToManyMapping or a ManyToManyMapping.
65
     *
66
     * @var array
67
     */
68
    private $association;
69
70
    /**
71
     * The EntityManager that manages the persistence of the collection.
72
     *
73
     * @var \Doctrine\ORM\EntityManagerInterface
74
     */
75
    private $em;
76
77
    /**
78
     * The name of the field on the target entities that points to the owner
79
     * of the collection. This is only set if the association is bi-directional.
80
     *
81
     * @var string
82
     */
83
    private $backRefFieldName;
84
85
    /**
86
     * The class descriptor of the collection's entity type.
87
     *
88
     * @var ClassMetadata
89
     */
90
    private $typeClass;
91
92
    /**
93
     * Whether the collection is dirty and needs to be synchronized with the database
94
     * when the UnitOfWork that manages its persistent state commits.
95
     *
96
     * @var boolean
97
     */
98
    private $isDirty = false;
99
100
    /**
101
     * Creates a new persistent collection.
102
     *
103
     * @param EntityManagerInterface $em         The EntityManager the collection will be associated with.
104
     * @param ClassMetadata          $class      The class descriptor of the entity type of this collection.
105
     * @param Collection             $collection The collection elements.
106
     */
107 923
    public function __construct(EntityManagerInterface $em, $class, Collection $collection)
108
    {
109 923
        $this->collection  = $collection;
110 923
        $this->em          = $em;
111 923
        $this->typeClass   = $class;
112 923
        $this->initialized = true;
113 923
    }
114
115
    /**
116
     * INTERNAL:
117
     * Sets the collection's owning entity together with the AssociationMapping that
118
     * describes the association between the owner and the elements of the collection.
119
     *
120
     * @param object $entity
121
     * @param array  $assoc
122
     *
123
     * @return void
124
     */
125 917
    public function setOwner($entity, array $assoc)
126
    {
127 917
        $this->owner            = $entity;
128 917
        $this->association      = $assoc;
129 917
        $this->backRefFieldName = $assoc['inversedBy'] ?: $assoc['mappedBy'];
130 917
    }
131
132
    /**
133
     * INTERNAL:
134
     * Gets the collection owner.
135
     *
136
     * @return object
137
     */
138 535
    public function getOwner()
139
    {
140 535
        return $this->owner;
141
    }
142
143
    /**
144
     * @return Mapping\ClassMetadata
145
     */
146 21
    public function getTypeClass()
147
    {
148 21
        return $this->typeClass;
149
    }
150
151
    /**
152
     * INTERNAL:
153
     * Adds an element to a collection during hydration. This will automatically
154
     * complete bidirectional associations in the case of a one-to-many association.
155
     *
156
     * @param mixed $element The element to add.
157
     *
158
     * @return void
159
     */
160 163
    public function hydrateAdd($element)
161
    {
162 163
        $this->collection->add($element);
163
164
        // If _backRefFieldName is set and its a one-to-many association,
165
        // we need to set the back reference.
166 163
        if ($this->backRefFieldName && $this->association['type'] === ClassMetadata::ONE_TO_MANY) {
167
            // Set back reference to owner
168 105
            $this->typeClass->reflFields[$this->backRefFieldName]->setValue(
169 105
                $element, $this->owner
170
            );
171
172 105
            $this->em->getUnitOfWork()->setOriginalEntityProperty(
173 105
                spl_object_hash($element), $this->backRefFieldName, $this->owner
174
            );
175
        }
176 163
    }
177
178
    /**
179
     * INTERNAL:
180
     * Sets a keyed element in the collection during hydration.
181
     *
182
     * @param mixed $key     The key to set.
183
     * @param mixed $element The element to set.
184
     *
185
     * @return void
186
     */
187 40
    public function hydrateSet($key, $element)
188
    {
189 40
        $this->collection->set($key, $element);
190
191
        // If _backRefFieldName is set, then the association is bidirectional
192
        // and we need to set the back reference.
193 40
        if ($this->backRefFieldName && $this->association['type'] === ClassMetadata::ONE_TO_MANY) {
194
            // Set back reference to owner
195 23
            $this->typeClass->reflFields[$this->backRefFieldName]->setValue(
196 23
                $element, $this->owner
197
            );
198
        }
199 40
    }
200
201
    /**
202
     * Initializes the collection by loading its contents from the database
203
     * if the collection is not yet initialized.
204
     *
205
     * @return void
206
     */
207 789
    public function initialize()
208
    {
209 789
        if ($this->initialized || ! $this->association) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->association of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
210 766
            return;
211
        }
212
213 154
        $this->doInitialize();
214
215 154
        $this->initialized = true;
216 154
    }
217
218
    /**
219
     * INTERNAL:
220
     * Tells this collection to take a snapshot of its current state.
221
     *
222
     * @return void
223
     */
224 602
    public function takeSnapshot()
225
    {
226 602
        $this->snapshot = $this->collection->toArray();
227 602
        $this->isDirty  = false;
228 602
    }
229
230
    /**
231
     * INTERNAL:
232
     * Returns the last snapshot of the elements in the collection.
233
     *
234
     * @return array The last snapshot of the elements.
235
     */
236 24
    public function getSnapshot()
237
    {
238 24
        return $this->snapshot;
239
    }
240
241
    /**
242
     * INTERNAL:
243
     * getDeleteDiff
244
     *
245
     * @return array
246
     */
247 333
    public function getDeleteDiff()
248
    {
249 333
        return array_udiff_assoc(
250 333
            $this->snapshot,
251 333
            $this->collection->toArray(),
252
            function($a, $b) { return $a === $b ? 0 : 1; }
253
        );
254
    }
255
256
    /**
257
     * INTERNAL:
258
     * getInsertDiff
259
     *
260
     * @return array
261
     */
262 333
    public function getInsertDiff()
263
    {
264 333
        return array_udiff_assoc(
265 333
            $this->collection->toArray(),
266 333
            $this->snapshot,
267
            function($a, $b) { return $a === $b ? 0 : 1; }
268
        );
269
    }
270
271
    /**
272
     * INTERNAL: Gets the association mapping of the collection.
273
     *
274
     * @return array
275
     */
276 567
    public function getMapping()
277
    {
278 567
        return $this->association;
279
    }
280
281
    /**
282
     * Marks this collection as changed/dirty.
283
     *
284
     * @return void
285
     */
286 161
    private function changed()
287
    {
288 161
        if ($this->isDirty) {
289 78
            return;
290
        }
291
292 161
        $this->isDirty = true;
293
294 161
        if ($this->association !== null &&
295 161
            $this->association['isOwningSide'] &&
296 161
            $this->association['type'] === ClassMetadata::MANY_TO_MANY &&
297 161
            $this->owner &&
298 161
            $this->em->getClassMetadata(get_class($this->owner))->isChangeTrackingNotify()) {
299 1
            $this->em->getUnitOfWork()->scheduleForDirtyCheck($this->owner);
300
        }
301 161
    }
302
303
    /**
304
     * Gets a boolean flag indicating whether this collection is dirty which means
305
     * its state needs to be synchronized with the database.
306
     *
307
     * @return boolean TRUE if the collection is dirty, FALSE otherwise.
308
     */
309 832
    public function isDirty()
310
    {
311 832
        return $this->isDirty;
312
    }
313
314
    /**
315
     * Sets a boolean flag, indicating whether this collection is dirty.
316
     *
317
     * @param boolean $dirty Whether the collection should be marked dirty or not.
318
     *
319
     * @return void
320
     */
321 820
    public function setDirty($dirty)
322
    {
323 820
        $this->isDirty = $dirty;
324 820
    }
325
326
    /**
327
     * Sets the initialized flag of the collection, forcing it into that state.
328
     *
329
     * @param boolean $bool
330
     *
331
     * @return void
332
     */
333 550
    public function setInitialized($bool)
334
    {
335 550
        $this->initialized = $bool;
336 550
    }
337
338
    /**
339
     * {@inheritdoc}
340
     */
341 16
    public function remove($key)
342
    {
343
        // TODO: If the keys are persistent as well (not yet implemented)
344
        //       and the collection is not initialized and orphanRemoval is
345
        //       not used we can issue a straight SQL delete/update on the
346
        //       association (table). Without initializing the collection.
347 16
        $removed = parent::remove($key);
348
349 16
        if ( ! $removed) {
350
            return $removed;
351
        }
352
353 16
        $this->changed();
354
355 16
        if ($this->association !== null &&
356 16
            $this->association['type'] & ClassMetadata::TO_MANY &&
357 16
            $this->owner &&
358 16
            $this->association['orphanRemoval']) {
359 4
            $this->em->getUnitOfWork()->scheduleOrphanRemoval($removed);
360
        }
361
362 16
        return $removed;
363
    }
364
365
    /**
366
     * {@inheritdoc}
367
     */
368 23
    public function removeElement($element)
369
    {
370 23
        if ( ! $this->initialized && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
371 10
            if ($this->collection->contains($element)) {
372 1
                return $this->collection->removeElement($element);
373
            }
374
375 9
            if ($this->em->getUnitOfWork()->getEntityState($element) === UnitOfWork::STATE_NEW) {
376 3
                return true;
377
            }
378
379 8
            $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
380
381 8
            if ($persister->removeElement($this, $element) === true) {
382
                // only  owning side or orphan removal triggers successful
383
                // delete so that we can keep this collection unitialized.
384 4
                return true;
385
            }
386
        }
387
388 18
        $removed = parent::removeElement($element);
389
390 18
        if ( ! $removed) {
391 1
            return $removed;
392
        }
393
394 17
        $this->changed();
395
396 17
        if ($this->association !== null &&
397 17
            $this->association['type'] & ClassMetadata::TO_MANY &&
398 17
            $this->owner &&
399 17
            $this->association['orphanRemoval']) {
400 4
            $this->em->getUnitOfWork()->scheduleOrphanRemoval($element);
401
        }
402
403 17
        return $removed;
404
    }
405
406
    /**
407
     * {@inheritdoc}
408
     */
409 28
    public function containsKey($key)
410
    {
411 28
        if (! $this->initialized && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY
412 28
            && isset($this->association['indexBy'])) {
413 11
            $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
414
415 11
            return $this->collection->containsKey($key) || $persister->containsKey($this, $key);
416
        }
417
418 17
        return parent::containsKey($key);
419
    }
420
421
    /**
422
     * {@inheritdoc}
423
     */
424 33
    public function contains($element)
425
    {
426 33
        if ( ! $this->initialized && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
427 16
            $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
428
429 16
            return $this->collection->contains($element) || $persister->contains($this, $element);
430
        }
431
432 17
        return parent::contains($element);
433
    }
434
435
    /**
436
     * {@inheritdoc}
437
     */
438 87
    public function get($key)
439
    {
440 87
        if ( ! $this->initialized
441 87
            && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY
442 87
            && isset($this->association['indexBy'])
443
        ) {
444 5
            if (!$this->typeClass->isIdentifierComposite && $this->typeClass->isIdentifier($this->association['indexBy'])) {
445 1
                return $this->em->find($this->typeClass->name, $key);
446
            }
447
448 4
            return $this->em->getUnitOfWork()->getCollectionPersister($this->association)->get($this, $key);
449
        }
450
451 82
        return parent::get($key);
452
    }
453
454
    /**
455
     * {@inheritdoc}
456
     */
457 754
    public function count()
458
    {
459 754
        if (! $this->initialized && $this->association !== null && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
460 32
            $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
461
462 32
            return $persister->count($this) + ($this->isDirty ? $this->collection->count() : 0);
463
        }
464
465 748
        return parent::count();
466
    }
467
468
    /**
469
     * {@inheritdoc}
470
     */
471 4
    public function set($key, $value)
472
    {
473 4
        parent::set($key, $value);
474
475 4
        $this->changed();
476
477 4
        if (is_object($value) && $this->em) {
478 4
            $this->em->getUnitOfWork()->cancelOrphanRemoval($value);
479
        }
480 4
    }
481
482
    /**
483
     * {@inheritdoc}
484
     */
485 118
    public function add($value)
486
    {
487 118
        $this->collection->add($value);
488
489 118
        $this->changed();
490
491 118
        if (is_object($value) && $this->em) {
492 117
            $this->em->getUnitOfWork()->cancelOrphanRemoval($value);
493
        }
494
495 118
        return true;
496
    }
497
498
    /* ArrayAccess implementation */
499
500
    /**
501
     * {@inheritdoc}
502
     */
503 17
    public function offsetExists($offset)
504
    {
505 17
        return $this->containsKey($offset);
506
    }
507
508
    /**
509
     * {@inheritdoc}
510
     */
511 63
    public function offsetGet($offset)
512
    {
513 63
        return $this->get($offset);
514
    }
515
516
    /**
517
     * {@inheritdoc}
518
     */
519 96
    public function offsetSet($offset, $value)
520
    {
521 96
        if ( ! isset($offset)) {
522 96
            $this->add($value);
523 96
            return;
524
        }
525
526
        $this->set($offset, $value);
527
    }
528
529
    /**
530
     * {@inheritdoc}
531
     */
532 6
    public function offsetUnset($offset)
533
    {
534 6
        return $this->remove($offset);
535
    }
536
537
    /**
538
     * {@inheritdoc}
539
     */
540 821
    public function isEmpty()
541
    {
542 821
        return $this->collection->isEmpty() && $this->count() === 0;
543
    }
544
545
    /**
546
     * {@inheritdoc}
547
     */
548 24
    public function clear()
549
    {
550 24
        if ($this->initialized && $this->isEmpty()) {
551 1
            $this->collection->clear();
552
553 1
            return;
554
        }
555
556 23
        $uow = $this->em->getUnitOfWork();
557
558 23
        if ($this->association['type'] & ClassMetadata::TO_MANY &&
559 23
            $this->association['orphanRemoval'] &&
560 23
            $this->owner) {
561
            // we need to initialize here, as orphan removal acts like implicit cascadeRemove,
562
            // hence for event listeners we need the objects in memory.
563 6
            $this->initialize();
564
565 6
            foreach ($this->collection as $element) {
566 6
                $uow->scheduleOrphanRemoval($element);
567
            }
568
        }
569
570 23
        $this->collection->clear();
571
572 23
        $this->initialized = true; // direct call, {@link initialize()} is too expensive
573
574 23
        if ($this->association['isOwningSide'] && $this->owner) {
575 17
            $this->changed();
576
577 17
            $uow->scheduleCollectionDeletion($this);
578
579 17
            $this->takeSnapshot();
580
        }
581 23
    }
582
583
    /**
584
     * Called by PHP when this collection is serialized. Ensures that only the
585
     * elements are properly serialized.
586
     *
587
     * Internal note: Tried to implement Serializable first but that did not work well
588
     *                with circular references. This solution seems simpler and works well.
589
     *
590
     * @return array
591
     */
592 2
    public function __sleep()
593
    {
594 2
        return ['collection', 'initialized'];
595
    }
596
597
    /**
598
     * Extracts a slice of $length elements starting at position $offset from the Collection.
599
     *
600
     * If $length is null it returns all elements from $offset to the end of the Collection.
601
     * Keys have to be preserved by this method. Calling this method will only return the
602
     * selected slice and NOT change the elements contained in the collection slice is called on.
603
     *
604
     * @param int      $offset
605
     * @param int|null $length
606
     *
607
     * @return array
608
     */
609 15
    public function slice($offset, $length = null)
610
    {
611 15
        if ( ! $this->initialized && ! $this->isDirty && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
612 13
            $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
613
614 13
            return $persister->slice($this, $offset, $length);
615
        }
616
617 2
        return parent::slice($offset, $length);
618
    }
619
620
    /**
621
     * Cleans up internal state of cloned persistent collection.
622
     *
623
     * The following problems have to be prevented:
624
     * 1. Added entities are added to old PC
625
     * 2. New collection is not dirty, if reused on other entity nothing
626
     * changes.
627
     * 3. Snapshot leads to invalid diffs being generated.
628
     * 4. Lazy loading grabs entities from old owner object.
629
     * 5. New collection is connected to old owner and leads to duplicate keys.
630
     *
631
     * @return void
632
     */
633 11
    public function __clone()
634
    {
635 11
        if (is_object($this->collection)) {
636 11
            $this->collection = clone $this->collection;
637
        }
638
639 11
        $this->initialize();
640
641 11
        $this->owner    = null;
642 11
        $this->snapshot = [];
643
644 11
        $this->changed();
645 11
    }
646
647
    /**
648
     * Selects all elements from a selectable that match the expression and
649
     * return a new collection containing these elements.
650
     *
651
     * @param \Doctrine\Common\Collections\Criteria $criteria
652
     *
653
     * @return Collection
654
     *
655
     * @throws \RuntimeException
656
     */
657 25
    public function matching(Criteria $criteria)
658
    {
659 25
        if ($this->isDirty) {
660 3
            $this->initialize();
661
        }
662
663 25
        if ($this->initialized) {
664 3
            return $this->collection->matching($criteria);
0 ignored issues
show
Bug introduced by
The method matching() does not exist on Doctrine\Common\Collections\Collection. It seems like you code against a sub-type of said class. However, the method does not exist in Doctrine\Common\Collections\AbstractLazyCollection. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

664
            return $this->collection->/** @scrutinizer ignore-call */ matching($criteria);
Loading history...
665
        }
666
667 22
        if ($this->association['type'] === ClassMetadata::MANY_TO_MANY) {
668 12
            $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
669
670 12
            return new ArrayCollection($persister->loadCriteria($this, $criteria));
671
        }
672
673 10
        $builder         = Criteria::expr();
674 10
        $ownerExpression = $builder->eq($this->backRefFieldName, $this->owner);
675 10
        $expression      = $criteria->getWhereExpression();
676 10
        $expression      = $expression ? $builder->andX($expression, $ownerExpression) : $ownerExpression;
677
678 10
        $criteria = clone $criteria;
679 10
        $criteria->where($expression);
680 10
        $criteria->orderBy($criteria->getOrderings() ?: $this->association['orderBy'] ?? []);
681
682 10
        $persister = $this->em->getUnitOfWork()->getEntityPersister($this->association['targetEntity']);
683
684 10
        return ($this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY)
685 7
            ? new LazyCriteriaCollection($persister, $criteria)
686 10
            : new ArrayCollection($persister->loadCriteria($criteria));
687
    }
688
689
    /**
690
     * Retrieves the wrapped Collection instance.
691
     *
692
     * @return \Doctrine\Common\Collections\Collection
693
     */
694 825
    public function unwrap()
695
    {
696 825
        return $this->collection;
697
    }
698
699
    /**
700
     * {@inheritdoc}
701
     */
702 154
    protected function doInitialize()
703
    {
704
        // Has NEW objects added through add(). Remember them.
705 154
        $newlyAddedDirtyObjects = [];
706
707 154
        if ($this->isDirty) {
708 17
            $newlyAddedDirtyObjects = $this->collection->toArray();
709
        }
710
711 154
        $this->collection->clear();
712 154
        $this->em->getUnitOfWork()->loadCollection($this);
713 154
        $this->takeSnapshot();
714
715 154
        if ($newlyAddedDirtyObjects) {
716 17
            $this->restoreNewObjectsInDirtyCollection($newlyAddedDirtyObjects);
717
        }
718 154
    }
719
720
    /**
721
     * @param object[] $newObjects
722
     *
723
     * Note: the only reason why this entire looping/complexity is performed via `spl_object_hash`
724
     *       is because we want to prevent using `array_udiff()`, which is likely to cause very
725
     *       high overhead (complexity of O(n^2)). `array_diff_key()` performs the operation in
726
     *       core, which is faster than using a callback for comparisons
727
     */
728 17
    private function restoreNewObjectsInDirtyCollection(array $newObjects) : void
729
    {
730 17
        $loadedObjects               = $this->collection->toArray();
731 17
        $newObjectsByOid             = \array_combine(\array_map('spl_object_hash', $newObjects), $newObjects);
732 17
        $loadedObjectsByOid          = \array_combine(\array_map('spl_object_hash', $loadedObjects), $loadedObjects);
733 17
        $newObjectsThatWereNotLoaded = \array_diff_key($newObjectsByOid, $loadedObjectsByOid);
0 ignored issues
show
Bug introduced by
It seems like $loadedObjectsByOid can also be of type false; however, parameter $array2 of array_diff_key() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

733
        $newObjectsThatWereNotLoaded = \array_diff_key($newObjectsByOid, /** @scrutinizer ignore-type */ $loadedObjectsByOid);
Loading history...
Bug introduced by
It seems like $newObjectsByOid can also be of type false; however, parameter $array1 of array_diff_key() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

733
        $newObjectsThatWereNotLoaded = \array_diff_key(/** @scrutinizer ignore-type */ $newObjectsByOid, $loadedObjectsByOid);
Loading history...
734
735 17
        if ($newObjectsThatWereNotLoaded) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $newObjectsThatWereNotLoaded of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
736
            // Reattach NEW objects added through add(), if any.
737 16
            \array_walk($newObjectsThatWereNotLoaded, [$this->collection, 'add']);
738
739 16
            $this->isDirty = true;
740
        }
741 17
    }
742
}
743