PersistentCollection::removeElement()   B
last analyzed

Complexity

Conditions 10
Paths 5

Size

Total Lines 30
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 10.1371

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 17
c 1
b 0
f 0
nc 5
nop 1
dl 0
loc 30
ccs 16
cts 18
cp 0.8889
crap 10.1371
rs 7.6666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM;
6
7
use Doctrine\Common\Collections\AbstractLazyCollection;
8
use Doctrine\Common\Collections\ArrayCollection;
9
use Doctrine\Common\Collections\Collection;
10
use Doctrine\Common\Collections\Criteria;
11
use Doctrine\Common\Collections\Selectable;
12
use Doctrine\ORM\Mapping\AssociationMetadata;
13
use Doctrine\ORM\Mapping\ChangeTrackingPolicy;
14
use Doctrine\ORM\Mapping\ClassMetadata;
15
use Doctrine\ORM\Mapping\FetchMode;
16
use Doctrine\ORM\Mapping\ManyToManyAssociationMetadata;
17
use Doctrine\ORM\Mapping\OneToManyAssociationMetadata;
18
use Doctrine\ORM\Mapping\ToManyAssociationMetadata;
19
use RuntimeException;
20
use function array_combine;
21
use function array_diff_key;
22
use function array_map;
23
use function array_values;
24
use function array_walk;
25
use function get_class;
26
use function is_object;
27
use function spl_object_id;
28
29
/**
30
 * A PersistentCollection represents a collection of elements that have persistent state.
31
 *
32
 * Collections of entities represent only the associations (links) to those entities.
33
 * That means, if the collection is part of a many-many mapping and you remove
34
 * entities from the collection, only the links in the relation table are removed (on flush).
35
 * Similarly, if you remove entities from a collection that is part of a one-many
36
 * mapping this will only result in the nulling out of the foreign keys on flush.
37
 */
38
final class PersistentCollection extends AbstractLazyCollection implements Selectable
39
{
40
    /**
41
     * A snapshot of the collection at the moment it was fetched from the database.
42
     * This is used to create a diff of the collection at commit time.
43
     *
44
     * @var object[]
45
     */
46
    private $snapshot = [];
47
48
    /**
49
     * The entity that owns this collection.
50
     *
51
     * @var object
52
     */
53
    private $owner;
54
55
    /**
56
     * The association mapping the collection belongs to.
57
     * This is currently either a OneToManyMapping or a ManyToManyMapping.
58
     *
59
     * @var ToManyAssociationMetadata
60
     */
61
    private $association;
62
63
    /**
64
     * The EntityManager that manages the persistence of the collection.
65
     *
66
     * @var EntityManagerInterface
67
     */
68
    private $em;
69
70
    /**
71
     * The name of the field on the target entities that points to the owner
72
     * of the collection. This is only set if the association is bi-directional.
73
     *
74
     * @var string
75
     */
76
    private $backRefFieldName;
77
78
    /**
79
     * The class descriptor of the collection's entity type.
80
     *
81
     * @var ClassMetadata
82
     */
83
    private $typeClass;
84
85
    /**
86
     * Whether the collection is dirty and needs to be synchronized with the database
87
     * when the UnitOfWork that manages its persistent state commits.
88
     *
89
     * @var bool
90
     */
91
    private $isDirty = false;
92
93
    /**
94
     * Creates a new persistent collection.
95
     *
96
     * @param EntityManagerInterface $em         The EntityManager the collection will be associated with.
97
     * @param ClassMetadata          $class      The class descriptor of the entity type of this collection.
98
     * @param Collection|object[]    $collection The collection elements.
99
     */
100 866
    public function __construct(EntityManagerInterface $em, $class, Collection $collection)
101
    {
102 866
        $this->collection  = $collection;
103 866
        $this->em          = $em;
104 866
        $this->typeClass   = $class;
105 866
        $this->initialized = true;
106 866
    }
107
108
    /**
109
     * INTERNAL:
110
     * Sets the collection's owning entity together with the AssociationMapping that
111
     * describes the association between the owner and the elements of the collection.
112
     *
113
     * @param object $entity
114
     */
115 860
    public function setOwner($entity, ToManyAssociationMetadata $association)
116
    {
117 860
        $this->owner            = $entity;
118 860
        $this->association      = $association;
119 860
        $this->backRefFieldName = $association->getInversedBy() ?: $association->getMappedBy();
120 860
    }
121
122
    /**
123
     * INTERNAL:
124
     * Gets the collection owner.
125
     *
126
     * @return object
127
     */
128 514
    public function getOwner()
129
    {
130 514
        return $this->owner;
131
    }
132
133
    /**
134
     * @return Mapping\ClassMetadata
135
     */
136 327
    public function getTypeClass()
137
    {
138 327
        return $this->typeClass;
139
    }
140
141
    /**
142
     * INTERNAL:
143
     * Adds an element to a collection during hydration. This will automatically
144
     * complete bidirectional associations in the case of a one-to-many association.
145
     *
146
     * @param mixed $element The element to add.
147
     */
148 151
    public function hydrateAdd($element)
149
    {
150 151
        $this->collection->add($element);
151
152
        // If _backRefFieldName is set and its a one-to-many association,
153
        // we need to set the back reference.
154 151
        if ($this->backRefFieldName && $this->association instanceof OneToManyAssociationMetadata) {
155 99
            $inversedAssociation = $this->typeClass->getProperty($this->backRefFieldName);
156
157
            // Set back reference to owner
158 99
            $inversedAssociation->setValue($element, $this->owner);
159
160 99
            $this->em->getUnitOfWork()->setOriginalEntityProperty(
161 99
                spl_object_id($element),
162 99
                $this->backRefFieldName,
163 99
                $this->owner
164
            );
165
        }
166 151
    }
167
168
    /**
169
     * INTERNAL:
170
     * Sets a keyed element in the collection during hydration.
171
     *
172
     * @param mixed $key     The key to set.
173
     * @param mixed $element The element to set.
174
     */
175 34
    public function hydrateSet($key, $element)
176
    {
177 34
        $this->collection->set($key, $element);
178
179
        // If _backRefFieldName is set, then the association is bidirectional
180
        // and we need to set the back reference.
181 34
        if ($this->backRefFieldName && $this->association instanceof OneToManyAssociationMetadata) {
182 21
            $inversedAssociation = $this->typeClass->getProperty($this->backRefFieldName);
183
184
            // Set back reference to owner
185 21
            $inversedAssociation->setValue($element, $this->owner);
186
187 21
            $this->em->getUnitOfWork()->setOriginalEntityProperty(
188 21
                spl_object_id($element),
189 21
                $this->backRefFieldName,
190 21
                $this->owner
191
            );
192
        }
193 34
    }
194
195
    /**
196
     * Initializes the collection by loading its contents from the database
197
     * if the collection is not yet initialized.
198
     */
199 258
    public function initialize()
200
    {
201 258
        if ($this->initialized || ! $this->association) {
202 200
            return;
203
        }
204
205 138
        $this->doInitialize();
206
207 138
        $this->initialized = true;
208 138
    }
209
210
    /**
211
     * INTERNAL:
212
     * Tells this collection to take a snapshot of its current state.
213
     */
214 576
    public function takeSnapshot()
215
    {
216 576
        $this->snapshot = $this->collection->toArray();
217 576
        $this->isDirty  = false;
218 576
    }
219
220
    /**
221
     * INTERNAL:
222
     * Returns the last snapshot of the elements in the collection.
223
     *
224
     * @return object[] The last snapshot of the elements.
225
     */
226 24
    public function getSnapshot()
227
    {
228 24
        return $this->snapshot;
229
    }
230
231
    /**
232
     * INTERNAL:
233
     * getDeleteDiff
234
     *
235
     * @return object[]
236
     */
237 327
    public function getDeleteDiff()
238
    {
239 327
        $collectionItems = $this->collection->toArray();
240
241 327
        return array_values(array_diff_key(
242 327
            array_combine(array_map('spl_object_id', $this->snapshot), $this->snapshot),
0 ignored issues
show
Bug introduced by
It seems like array_combine(array_map(...shot), $this->snapshot) 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

242
            /** @scrutinizer ignore-type */ array_combine(array_map('spl_object_id', $this->snapshot), $this->snapshot),
Loading history...
243 327
            array_combine(array_map('spl_object_id', $collectionItems), $collectionItems)
0 ignored issues
show
Bug introduced by
It seems like array_combine(array_map(...ems), $collectionItems) 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

243
            /** @scrutinizer ignore-type */ array_combine(array_map('spl_object_id', $collectionItems), $collectionItems)
Loading history...
244
        ));
245
    }
246
247
    /**
248
     * INTERNAL:
249
     * getInsertDiff
250
     *
251
     * @return object[]
252
     */
253 327
    public function getInsertDiff()
254
    {
255 327
        $collectionItems = $this->collection->toArray();
256
257 327
        return array_values(array_diff_key(
258 327
            array_combine(array_map('spl_object_id', $collectionItems), $collectionItems),
0 ignored issues
show
Bug introduced by
It seems like array_combine(array_map(...ems), $collectionItems) 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

258
            /** @scrutinizer ignore-type */ array_combine(array_map('spl_object_id', $collectionItems), $collectionItems),
Loading history...
259 327
            array_combine(array_map('spl_object_id', $this->snapshot), $this->snapshot)
0 ignored issues
show
Bug introduced by
It seems like array_combine(array_map(...shot), $this->snapshot) 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

259
            /** @scrutinizer ignore-type */ array_combine(array_map('spl_object_id', $this->snapshot), $this->snapshot)
Loading history...
260
        ));
261
    }
262
263
    /**
264
     * INTERNAL: Gets the association mapping of the collection.
265
     *
266
     * @return AssociationMetadata
267
     */
268 542
    public function getMapping()
269
    {
270 542
        return $this->association;
271
    }
272
273
    /**
274
     * Marks this collection as changed/dirty.
275
     */
276 146
    private function changed()
277
    {
278 146
        if ($this->isDirty) {
279 74
            return;
280
        }
281
282 146
        $this->isDirty = true;
283
284 146
        if ($this->association instanceof ManyToManyAssociationMetadata &&
285 146
            $this->owner &&
286 146
            $this->association->isOwningSide() &&
287 146
            $this->em->getClassMetadata(get_class($this->owner))->changeTrackingPolicy === ChangeTrackingPolicy::NOTIFY) {
0 ignored issues
show
Bug introduced by
Accessing changeTrackingPolicy on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
288 1
            $this->em->getUnitOfWork()->scheduleForSynchronization($this->owner);
289
        }
290 146
    }
291
292
    /**
293
     * Gets a boolean flag indicating whether this collection is dirty which means
294
     * its state needs to be synchronized with the database.
295
     *
296
     * @return bool TRUE if the collection is dirty, FALSE otherwise.
297
     */
298 784
    public function isDirty()
299
    {
300 784
        return $this->isDirty;
301
    }
302
303
    /**
304
     * Sets a boolean flag, indicating whether this collection is dirty.
305
     *
306
     * @param bool $dirty Whether the collection should be marked dirty or not.
307
     */
308 817
    public function setDirty($dirty)
309
    {
310 817
        $this->isDirty = $dirty;
311 817
    }
312
313
    /**
314
     * Sets the initialized flag of the collection, forcing it into that state.
315
     *
316
     * @param bool $bool
317
     */
318 859
    public function setInitialized($bool)
319
    {
320 859
        $this->initialized = $bool;
321 859
    }
322
323
    /**
324
     * {@inheritdoc}
325
     */
326 16
    public function remove($key)
327
    {
328
        // TODO: If the keys are persistent as well (not yet implemented)
329
        //       and the collection is not initialized and orphanRemoval is
330
        //       not used we can issue a straight SQL delete/update on the
331
        //       association (table). Without initializing the collection.
332 16
        $removed = parent::remove($key);
333
334 16
        if (! $removed) {
335
            return $removed;
336
        }
337
338 16
        $this->changed();
339
340 16
        if ($this->association !== null &&
341 16
            $this->association instanceof ToManyAssociationMetadata &&
342 16
            $this->owner &&
343 16
            $this->association->isOrphanRemoval()) {
344 4
            $this->em->getUnitOfWork()->scheduleOrphanRemoval($removed);
345
        }
346
347 16
        return $removed;
348
    }
349
350
    /**
351
     * {@inheritdoc}
352
     */
353 25
    public function removeElement($element)
354
    {
355 25
        if (! $this->initialized &&
356 25
            $this->association !== null &&
357 25
            $this->association->getFetchMode() === FetchMode::EXTRA_LAZY) {
358 13
            if ($this->collection->contains($element)) {
359
                return $this->collection->removeElement($element);
360
            }
361
362 13
            $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
363
364 13
            return $persister->removeElement($this, $element);
365
        }
366
367 12
        $removed = parent::removeElement($element);
368
369 12
        if (! $removed) {
370
            return $removed;
371
        }
372
373 12
        $this->changed();
374
375 12
        if ($this->association !== null &&
376 12
            $this->association instanceof ToManyAssociationMetadata &&
377 12
            $this->owner &&
378 12
            $this->association->isOrphanRemoval()) {
379 3
            $this->em->getUnitOfWork()->scheduleOrphanRemoval($element);
380
        }
381
382 12
        return $removed;
383
    }
384
385
    /**
386
     * {@inheritdoc}
387
     */
388 28
    public function containsKey($key)
389
    {
390 28
        if (! $this->initialized &&
391 28
            $this->association !== null &&
392 28
            $this->association->getFetchMode() === FetchMode::EXTRA_LAZY &&
393 28
            $this->association->getIndexedBy()) {
394 11
            $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
395
396 11
            return $this->collection->containsKey($key) || $persister->containsKey($this, $key);
397
        }
398
399 17
        return parent::containsKey($key);
400
    }
401
402
    /**
403
     * {@inheritdoc}
404
     */
405 34
    public function contains($element)
406
    {
407 34
        if (! $this->initialized &&
408 34
            $this->association !== null &&
409 34
            $this->association->getFetchMode() === FetchMode::EXTRA_LAZY) {
410 17
            $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
411
412 17
            return $this->collection->contains($element) || $persister->contains($this, $element);
413
        }
414
415 17
        return parent::contains($element);
416
    }
417
418
    /**
419
     * {@inheritdoc}
420
     */
421 85
    public function get($key)
422
    {
423 85
        if (! $this->initialized &&
424 85
            $this->association !== null &&
425 85
            $this->association->getFetchMode() === FetchMode::EXTRA_LAZY &&
426 85
            $this->association->getIndexedBy()) {
427 5
            if (! $this->typeClass->isIdentifierComposite() && $this->typeClass->isIdentifier($this->association->getIndexedBy())) {
428 1
                return $this->em->find($this->typeClass->getClassName(), $key);
429
            }
430
431 4
            return $this->em->getUnitOfWork()->getCollectionPersister($this->association)->get($this, $key);
432
        }
433
434 80
        return parent::get($key);
435
    }
436
437
    /**
438
     * {@inheritdoc}
439
     */
440 165
    public function count()
441
    {
442 165
        if (! $this->initialized &&
443 165
            $this->association !== null &&
444 165
            $this->association->getFetchMode() === FetchMode::EXTRA_LAZY) {
445 32
            $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
446
447 32
            return $persister->count($this) + ($this->isDirty ? $this->collection->count() : 0);
448
        }
449
450 133
        return parent::count();
451
    }
452
453
    /**
454
     * {@inheritdoc}
455
     */
456 1
    public function set($key, $value)
457
    {
458 1
        parent::set($key, $value);
459
460 1
        $this->changed();
461
462 1
        if (is_object($value) && $this->em) {
463 1
            $this->em->getUnitOfWork()->cancelOrphanRemoval($value);
464
        }
465 1
    }
466
467
    /**
468
     * {@inheritdoc}
469
     */
470 114
    public function add($value)
471
    {
472 114
        $this->collection->add($value);
473
474 114
        $this->changed();
475
476 114
        if (is_object($value) && $this->em) {
477 113
            $this->em->getUnitOfWork()->cancelOrphanRemoval($value);
478
        }
479
480 114
        return true;
481
    }
482
483
    /* ArrayAccess implementation */
484
485
    /**
486
     * {@inheritdoc}
487
     */
488 17
    public function offsetExists($offset)
489
    {
490 17
        return $this->containsKey($offset);
491
    }
492
493
    /**
494
     * {@inheritdoc}
495
     */
496 61
    public function offsetGet($offset)
497
    {
498 61
        return $this->get($offset);
499
    }
500
501
    /**
502
     * {@inheritdoc}
503
     */
504 93
    public function offsetSet($offset, $value)
505
    {
506 93
        if (! isset($offset)) {
507 93
            $this->add($value);
508
509 93
            return;
510
        }
511
512
        $this->set($offset, $value);
513
    }
514
515
    /**
516
     * {@inheritdoc}
517
     */
518 6
    public function offsetUnset($offset)
519
    {
520 6
        return $this->remove($offset);
521
    }
522
523
    /**
524
     * {@inheritdoc}
525
     */
526 816
    public function isEmpty()
527
    {
528 816
        if ($this->initialized) {
529 816
            return $this->collection->isEmpty();
530
        }
531
532 1
        return $this->collection->isEmpty() && ! $this->count();
533
    }
534
535
    /**
536
     * {@inheritdoc}
537
     */
538 21
    public function clear()
539
    {
540 21
        if ($this->initialized && $this->isEmpty()) {
541 1
            $this->collection->clear();
542
543 1
            return;
544
        }
545
546 20
        $uow = $this->em->getUnitOfWork();
547
548 20
        if ($this->owner !== null &&
549 20
            $this->association !== null &&
550 20
            $this->association->isOrphanRemoval()) {
551
            // we need to initialize here, as orphan removal acts like implicit cascadeRemove,
552
            // hence for event listeners we need the objects in memory.
553 6
            $this->initialize();
554
555 6
            foreach ($this->collection as $element) {
556 6
                $uow->scheduleOrphanRemoval($element);
557
            }
558
        }
559
560 20
        $this->collection->clear();
561
562 20
        $this->initialized = true; // direct call, {@link initialize()} is too expensive
563
564 20
        if ($this->association->isOwningSide() && $this->owner) {
565 14
            $this->changed();
566
567 14
            $uow->scheduleCollectionDeletion($this);
568
569 14
            $this->takeSnapshot();
570
        }
571 20
    }
572
573
    /**
574
     * Called by PHP when this collection is serialized. Ensures that only the
575
     * elements are properly serialized.
576
     *
577
     * {@internal Tried to implement Serializable first but that did not work well
578
     *            with circular references. This solution seems simpler and works well. }}
579
     *
580
     * @return string[]
581
     */
582
    public function __sleep()
583
    {
584
        return ['collection', 'initialized'];
585
    }
586
587
    /**
588
     * Extracts a slice of $length elements starting at position $offset from the Collection.
589
     *
590
     * If $length is null it returns all elements from $offset to the end of the Collection.
591
     * Keys have to be preserved by this method. Calling this method will only return the
592
     * selected slice and NOT change the elements contained in the collection slice is called on.
593
     *
594
     * @param int      $offset
595
     * @param int|null $length
596
     *
597
     * @return object[]
598
     */
599 15
    public function slice($offset, $length = null)
600
    {
601 15
        if (! $this->initialized &&
602 15
            ! $this->isDirty &&
603 15
            $this->association !== null &&
604 15
            $this->association->getFetchMode() === FetchMode::EXTRA_LAZY) {
605 13
            $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
606
607 13
            return $persister->slice($this, $offset, $length);
608
        }
609
610 2
        return parent::slice($offset, $length);
611
    }
612
613
    /**
614
     * Cleans up internal state of cloned persistent collection.
615
     *
616
     * The following problems have to be prevented:
617
     * 1. Added entities are added to old PC
618
     * 2. New collection is not dirty, if reused on other entity nothing
619
     * changes.
620
     * 3. Snapshot leads to invalid diffs being generated.
621
     * 4. Lazy loading grabs entities from old owner object.
622
     * 5. New collection is connected to old owner and leads to duplicate keys.
623
     */
624 11
    public function __clone()
625
    {
626 11
        if (is_object($this->collection)) {
627 11
            $this->collection = clone $this->collection;
628
        }
629
630 11
        $this->initialize();
631
632 11
        $this->owner    = null;
633 11
        $this->snapshot = [];
634
635 11
        $this->changed();
636 11
    }
637
638
    /**
639
     * Selects all elements from a selectable that match the expression and
640
     * return a new collection containing these elements.
641
     *
642
     * @return Collection|object[]
643
     *
644
     * @throws RuntimeException
645
     */
646 19
    public function matching(Criteria $criteria)
647
    {
648 19
        if ($this->isDirty) {
649 3
            $this->initialize();
650
        }
651
652 19
        if ($this->initialized) {
653 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

653
            return $this->collection->/** @scrutinizer ignore-call */ matching($criteria);
Loading history...
654
        }
655
656 16
        if ($this->association instanceof ManyToManyAssociationMetadata) {
657 12
            $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
658
659 12
            return new ArrayCollection($persister->loadCriteria($this, $criteria));
660
        }
661
662 4
        $builder         = Criteria::expr();
663 4
        $ownerExpression = $builder->eq($this->backRefFieldName, $this->owner);
664 4
        $expression      = $criteria->getWhereExpression();
665 4
        $expression      = $expression ? $builder->andX($expression, $ownerExpression) : $ownerExpression;
666
667 4
        $criteria = clone $criteria;
668 4
        $criteria->where($expression);
669
670 4
        $persister = $this->em->getUnitOfWork()->getEntityPersister($this->association->getTargetEntity());
671
672 4
        return $this->association->getFetchMode() === FetchMode::EXTRA_LAZY
673 2
            ? new LazyCriteriaCollection($persister, $criteria)
674 4
            : new ArrayCollection($persister->loadCriteria($criteria));
675
    }
676
677
    /**
678
     * Retrieves the wrapped Collection instance.
679
     *
680
     * @return Collection|object[]
681
     */
682 777
    public function unwrap()
683
    {
684 777
        return $this->collection;
685
    }
686
687
    /**
688
     * {@inheritdoc}
689
     */
690 138
    protected function doInitialize()
691
    {
692
        // Has NEW objects added through add(). Remember them.
693 138
        $newlyAddedDirtyObjects = [];
694
695 138
        if ($this->isDirty) {
696 17
            $newlyAddedDirtyObjects = $this->collection->toArray();
697
        }
698
699 138
        $this->collection->clear();
700 138
        $this->em->getUnitOfWork()->loadCollection($this);
701 138
        $this->takeSnapshot();
702
703 138
        if ($newlyAddedDirtyObjects) {
704 17
            $this->restoreNewObjectsInDirtyCollection($newlyAddedDirtyObjects);
705
        }
706 138
    }
707
708
    /**
709
     * @param object[] $newObjects
710
     *
711
     * Note: the only reason why this entire looping/complexity is performed via `spl_object_id`
712
     *       is because we want to prevent using `array_udiff()`, which is likely to cause very
713
     *       high overhead (complexity of O(n^2)). `array_diff_key()` performs the operation in
714
     *       core, which is faster than using a callback for comparisons
715
     */
716 17
    private function restoreNewObjectsInDirtyCollection(array $newObjects) : void
717
    {
718 17
        $loadedObjects               = $this->collection->toArray();
719 17
        $newObjectsByOid             = array_combine(array_map('spl_object_id', $newObjects), $newObjects);
720 17
        $loadedObjectsByOid          = array_combine(array_map('spl_object_id', $loadedObjects), $loadedObjects);
721 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

721
        $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

721
        $newObjectsThatWereNotLoaded = array_diff_key(/** @scrutinizer ignore-type */ $newObjectsByOid, $loadedObjectsByOid);
Loading history...
722
723 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...
724
            // Reattach NEW objects added through add(), if any.
725 16
            array_walk($newObjectsThatWereNotLoaded, [$this->collection, 'add']);
726
727 16
            $this->isDirty = true;
728
        }
729 17
    }
730
}
731