Failed Conditions
Pull Request — master (#7766)
by
unknown
09:05
created

PersistentCollection::setOwner()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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

243
            /** @scrutinizer ignore-type */ array_combine(array_map('spl_object_id', $this->snapshot), $this->snapshot),
Loading history...
244 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

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

259
            /** @scrutinizer ignore-type */ array_combine(array_map('spl_object_id', $collectionItems), $collectionItems),
Loading history...
260 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

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

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

724
        $newObjectsThatWereNotLoaded = array_diff_key(/** @scrutinizer ignore-type */ $newObjectsByOid, $loadedObjectsByOid);
Loading history...
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

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