PersistentCollection::hydrateAdd()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 8
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 16
ccs 9
cts 9
cp 1
crap 3
rs 10
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