Failed Conditions
Pull Request — master (#6591)
by Luís
14:08
created

PersistentCollection::getOwner()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\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
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
 * @since     2.0
39
 * @author    Konsta Vesterinen <[email protected]>
40
 * @author    Roman Borschel <[email protected]>
41
 * @author    Giorgio Sironi <[email protected]>
42
 * @author    Stefano Rodriguez <[email protected]>
43
 */
44
final class PersistentCollection extends AbstractLazyCollection implements Selectable
45
{
46
    /**
47
     * A snapshot of the collection at the moment it was fetched from the database.
48
     * This is used to create a diff of the collection at commit time.
49
     *
50
     * @var array
51
     */
52
    private $snapshot = [];
53
54
    /**
55
     * The entity that owns this collection.
56
     *
57
     * @var object
58
     */
59
    private $owner;
60
61
    /**
62
     * The association mapping the collection belongs to.
63
     * This is currently either a OneToManyMapping or a ManyToManyMapping.
64
     *
65
     * @var array
66
     */
67
    private $association;
68
69
    /**
70
     * The EntityManager that manages the persistence of the collection.
71
     *
72
     * @var \Doctrine\ORM\EntityManagerInterface
73
     */
74
    private $em;
75
76
    /**
77
     * The name of the field on the target entities that points to the owner
78
     * of the collection. This is only set if the association is bi-directional.
79
     *
80
     * @var string
81
     */
82
    private $backRefFieldName;
83
84
    /**
85
     * The class descriptor of the collection's entity type.
86
     *
87
     * @var ClassMetadata
88
     */
89
    private $typeClass;
90
91
    /**
92
     * Whether the collection is dirty and needs to be synchronized with the database
93
     * when the UnitOfWork that manages its persistent state commits.
94
     *
95
     * @var boolean
96
     */
97
    private $isDirty = false;
98
99
    /**
100
     * Creates a new persistent collection.
101
     *
102
     * @param EntityManagerInterface $em         The EntityManager the collection will be associated with.
103
     * @param ClassMetadata          $class      The class descriptor of the entity type of this collection.
104
     * @param Collection             $collection The collection elements.
105
     */
106 890
    public function __construct(EntityManagerInterface $em, $class, Collection $collection)
107
    {
108 890
        $this->collection  = $collection;
109 890
        $this->em          = $em;
110 890
        $this->typeClass   = $class;
111 890
        $this->initialized = true;
112 890
    }
113
114
    /**
115
     * INTERNAL:
116
     * Sets the collection's owning entity together with the AssociationMapping that
117
     * describes the association between the owner and the elements of the collection.
118
     *
119
     * @param object $entity
120
     * @param array  $assoc
121
     *
122
     * @return void
123
     */
124 884
    public function setOwner($entity, array $assoc)
125
    {
126 884
        $this->owner            = $entity;
127 884
        $this->association      = $assoc;
128 884
        $this->backRefFieldName = $assoc['inversedBy'] ?: $assoc['mappedBy'];
129 884
    }
130
131
    /**
132
     * INTERNAL:
133
     * Gets the collection owner.
134
     *
135
     * @return object
136
     */
137 526
    public function getOwner()
138
    {
139 526
        return $this->owner;
140
    }
141
142
    /**
143
     * @return Mapping\ClassMetadata
144
     */
145 21
    public function getTypeClass()
146
    {
147 21
        return $this->typeClass;
148
    }
149
150
    /**
151
     * INTERNAL:
152
     * Adds an element to a collection during hydration. This will automatically
153
     * complete bidirectional associations in the case of a one-to-many association.
154
     *
155
     * @param mixed $element The element to add.
156
     *
157
     * @return void
158
     */
159 158 View Code Duplication
    public function hydrateAdd($element)
160
    {
161 158
        $this->collection->add($element);
162
163 158
        if ($this->backRefFieldName && $this->association['type'] === ClassMetadata::ONE_TO_MANY) {
164 101
            $this->completeBidirectionalAssociationLink($element);
165
        }
166 158
    }
167
168
    /**
169
     * Sets the owner on the inverse side of the association, when the property was not set yet
170
     *
171
     * @param mixed $element
172
     */
173 114
    private function completeBidirectionalAssociationLink($element) : void
174
    {
175 114
        $field = $this->typeClass->reflFields[$this->backRefFieldName];
176
177 114
        if ($field->getValue($element) !== null) {
178 72
            return;
179
        }
180
181 49
        $field->setValue($element, $this->owner);
182
183 49
        $this->em->getUnitOfWork()->setOriginalEntityProperty(
184 49
            spl_object_hash($element),
185 49
            $this->backRefFieldName,
186 49
            $this->owner
187
        );
188 49
    }
189
190
    /**
191
     * INTERNAL:
192
     * Sets a keyed element in the collection during hydration.
193
     *
194
     * @param mixed $key     The key to set.
195
     * @param mixed $element The element to set.
196
     *
197
     * @return void
198
     */
199 39 View Code Duplication
    public function hydrateSet($key, $element)
200
    {
201 39
        $this->collection->set($key, $element);
202
203 39
        if ($this->backRefFieldName && $this->association['type'] === ClassMetadata::ONE_TO_MANY) {
204 23
            $this->completeBidirectionalAssociationLink($element);
205
        }
206 39
    }
207
208
    /**
209
     * Initializes the collection by loading its contents from the database
210
     * if the collection is not yet initialized.
211
     *
212
     * @return void
213
     */
214 767
    public function initialize()
215
    {
216 767
        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...
217 745
            return;
218
        }
219
220 147
        $this->doInitialize();
221
222 147
        $this->initialized = true;
223 147
    }
224
225
    /**
226
     * INTERNAL:
227
     * Tells this collection to take a snapshot of its current state.
228
     *
229
     * @return void
230
     */
231 588
    public function takeSnapshot()
232
    {
233 588
        $this->snapshot = $this->collection->toArray();
234 588
        $this->isDirty  = false;
235 588
    }
236
237
    /**
238
     * INTERNAL:
239
     * Returns the last snapshot of the elements in the collection.
240
     *
241
     * @return array The last snapshot of the elements.
242
     */
243 24
    public function getSnapshot()
244
    {
245 24
        return $this->snapshot;
246
    }
247
248
    /**
249
     * INTERNAL:
250
     * getDeleteDiff
251
     *
252
     * @return array
253
     */
254 328 View Code Duplication
    public function getDeleteDiff()
255
    {
256 328
        return array_udiff_assoc(
257 328
            $this->snapshot,
258 328
            $this->collection->toArray(),
259
            function($a, $b) { return $a === $b ? 0 : 1; }
260
        );
261
    }
262
263
    /**
264
     * INTERNAL:
265
     * getInsertDiff
266
     *
267
     * @return array
268
     */
269 328 View Code Duplication
    public function getInsertDiff()
270
    {
271 328
        return array_udiff_assoc(
272 328
            $this->collection->toArray(),
273 328
            $this->snapshot,
274
            function($a, $b) { return $a === $b ? 0 : 1; }
275
        );
276
    }
277
278
    /**
279
     * INTERNAL: Gets the association mapping of the collection.
280
     *
281
     * @return array
282
     */
283 554
    public function getMapping()
284
    {
285 554
        return $this->association;
286
    }
287
288
    /**
289
     * Marks this collection as changed/dirty.
290
     *
291
     * @return void
292
     */
293 154
    private function changed()
294
    {
295 154
        if ($this->isDirty) {
296 78
            return;
297
        }
298
299 154
        $this->isDirty = true;
300
301 154
        if ($this->association !== null &&
302 154
            $this->association['isOwningSide'] &&
303 154
            $this->association['type'] === ClassMetadata::MANY_TO_MANY &&
304 154
            $this->owner &&
305 154
            $this->em->getClassMetadata(get_class($this->owner))->isChangeTrackingNotify()) {
306 1
            $this->em->getUnitOfWork()->scheduleForDirtyCheck($this->owner);
307
        }
308 154
    }
309
310
    /**
311
     * Gets a boolean flag indicating whether this collection is dirty which means
312
     * its state needs to be synchronized with the database.
313
     *
314
     * @return boolean TRUE if the collection is dirty, FALSE otherwise.
315
     */
316 802
    public function isDirty()
317
    {
318 802
        return $this->isDirty;
319
    }
320
321
    /**
322
     * Sets a boolean flag, indicating whether this collection is dirty.
323
     *
324
     * @param boolean $dirty Whether the collection should be marked dirty or not.
325
     *
326
     * @return void
327
     */
328 790
    public function setDirty($dirty)
329
    {
330 790
        $this->isDirty = $dirty;
331 790
    }
332
333
    /**
334
     * Sets the initialized flag of the collection, forcing it into that state.
335
     *
336
     * @param boolean $bool
337
     *
338
     * @return void
339
     */
340 538
    public function setInitialized($bool)
341
    {
342 538
        $this->initialized = $bool;
343 538
    }
344
345
    /**
346
     * {@inheritdoc}
347
     */
348 16 View Code Duplication
    public function remove($key)
349
    {
350
        // TODO: If the keys are persistent as well (not yet implemented)
351
        //       and the collection is not initialized and orphanRemoval is
352
        //       not used we can issue a straight SQL delete/update on the
353
        //       association (table). Without initializing the collection.
354 16
        $removed = parent::remove($key);
355
356 16
        if ( ! $removed) {
357
            return $removed;
358
        }
359
360 16
        $this->changed();
361
362 16
        if ($this->association !== null &&
363 16
            $this->association['type'] & ClassMetadata::TO_MANY &&
364 16
            $this->owner &&
365 16
            $this->association['orphanRemoval']) {
366 4
            $this->em->getUnitOfWork()->scheduleOrphanRemoval($removed);
367
        }
368
369 16
        return $removed;
370
    }
371
372
    /**
373
     * {@inheritdoc}
374
     */
375 27
    public function removeElement($element)
376
    {
377 27
        if ( ! $this->initialized && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
378 13
            if ($this->collection->contains($element)) {
379
                return $this->collection->removeElement($element);
380
            }
381
382 13
            $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
383
384 13
            return $persister->removeElement($this, $element);
385
        }
386
387 14
        $removed = parent::removeElement($element);
388
389 14
        if ( ! $removed) {
390
            return $removed;
391
        }
392
393 14
        $this->changed();
394
395 14
        if ($this->association !== null &&
396 14
            $this->association['type'] & ClassMetadata::TO_MANY &&
397 14
            $this->owner &&
398 14
            $this->association['orphanRemoval']) {
399 4
            $this->em->getUnitOfWork()->scheduleOrphanRemoval($element);
400
        }
401
402 14
        return $removed;
403
    }
404
405
    /**
406
     * {@inheritdoc}
407
     */
408 28 View Code Duplication
    public function containsKey($key)
409
    {
410 28
        if (! $this->initialized && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY
411 28
            && isset($this->association['indexBy'])) {
412 11
            $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
413
414 11
            return $this->collection->containsKey($key) || $persister->containsKey($this, $key);
415
        }
416
417 17
        return parent::containsKey($key);
418
    }
419
420
    /**
421
     * {@inheritdoc}
422
     */
423 34 View Code Duplication
    public function contains($element)
424
    {
425 34
        if ( ! $this->initialized && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
426 17
            $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
427
428 17
            return $this->collection->contains($element) || $persister->contains($this, $element);
429
        }
430
431 17
        return parent::contains($element);
432
    }
433
434
    /**
435
     * {@inheritdoc}
436
     */
437 86
    public function get($key)
438
    {
439 86
        if ( ! $this->initialized
440 86
            && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY
441 86
            && isset($this->association['indexBy'])
442
        ) {
443 5
            if (!$this->typeClass->isIdentifierComposite && $this->typeClass->isIdentifier($this->association['indexBy'])) {
444 1
                return $this->em->find($this->typeClass->name, $key);
445
            }
446
447 4
            return $this->em->getUnitOfWork()->getCollectionPersister($this->association)->get($this, $key);
448
        }
449
450 81
        return parent::get($key);
451
    }
452
453
    /**
454
     * {@inheritdoc}
455
     */
456 735
    public function count()
457
    {
458 735
        if ( ! $this->initialized && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
459 32
            $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
460
461 32
            return $persister->count($this) + ($this->isDirty ? $this->collection->count() : 0);
462
        }
463
464 729
        return parent::count();
465
    }
466
467
    /**
468
     * {@inheritdoc}
469
     */
470 4 View Code Duplication
    public function set($key, $value)
471
    {
472 4
        parent::set($key, $value);
473
474 4
        $this->changed();
475
476 4
        if (is_object($value) && $this->em) {
477 4
            $this->em->getUnitOfWork()->cancelOrphanRemoval($value);
478
        }
479 4
    }
480
481
    /**
482
     * {@inheritdoc}
483
     */
484 118 View Code Duplication
    public function add($value)
485
    {
486 118
        $this->collection->add($value);
487
488 118
        $this->changed();
489
490 118
        if (is_object($value) && $this->em) {
491 117
            $this->em->getUnitOfWork()->cancelOrphanRemoval($value);
492
        }
493
494 118
        return true;
495
    }
496
497
    /* ArrayAccess implementation */
498
499
    /**
500
     * {@inheritdoc}
501
     */
502 17
    public function offsetExists($offset)
503
    {
504 17
        return $this->containsKey($offset);
505
    }
506
507
    /**
508
     * {@inheritdoc}
509
     */
510 62
    public function offsetGet($offset)
511
    {
512 62
        return $this->get($offset);
513
    }
514
515
    /**
516
     * {@inheritdoc}
517
     */
518 96
    public function offsetSet($offset, $value)
519
    {
520 96
        if ( ! isset($offset)) {
521 96
            $this->add($value);
522 96
            return;
523
        }
524
525
        $this->set($offset, $value);
526
    }
527
528
    /**
529
     * {@inheritdoc}
530
     */
531 6
    public function offsetUnset($offset)
532
    {
533 6
        return $this->remove($offset);
534
    }
535
536
    /**
537
     * {@inheritdoc}
538
     */
539 791
    public function isEmpty()
540
    {
541 791
        return $this->collection->isEmpty() && $this->count() === 0;
542
    }
543
544
    /**
545
     * {@inheritdoc}
546
     */
547 21
    public function clear()
548
    {
549 21
        if ($this->initialized && $this->isEmpty()) {
550 1
            $this->collection->clear();
551
552 1
            return;
553
        }
554
555 20
        $uow = $this->em->getUnitOfWork();
556
557 20
        if ($this->association['type'] & ClassMetadata::TO_MANY &&
558 20
            $this->association['orphanRemoval'] &&
559 20
            $this->owner) {
560
            // we need to initialize here, as orphan removal acts like implicit cascadeRemove,
561
            // hence for event listeners we need the objects in memory.
562 6
            $this->initialize();
563
564 6
            foreach ($this->collection as $element) {
565 6
                $uow->scheduleOrphanRemoval($element);
566
            }
567
        }
568
569 20
        $this->collection->clear();
570
571 20
        $this->initialized = true; // direct call, {@link initialize()} is too expensive
572
573 20
        if ($this->association['isOwningSide'] && $this->owner) {
574 14
            $this->changed();
575
576 14
            $uow->scheduleCollectionDeletion($this);
577
578 14
            $this->takeSnapshot();
579
        }
580 20
    }
581
582
    /**
583
     * Called by PHP when this collection is serialized. Ensures that only the
584
     * elements are properly serialized.
585
     *
586
     * Internal note: Tried to implement Serializable first but that did not work well
587
     *                with circular references. This solution seems simpler and works well.
588
     *
589
     * @return array
590
     */
591 2
    public function __sleep()
592
    {
593 2
        return ['collection', 'initialized'];
594
    }
595
596
    /**
597
     * Extracts a slice of $length elements starting at position $offset from the Collection.
598
     *
599
     * If $length is null it returns all elements from $offset to the end of the Collection.
600
     * Keys have to be preserved by this method. Calling this method will only return the
601
     * selected slice and NOT change the elements contained in the collection slice is called on.
602
     *
603
     * @param int      $offset
604
     * @param int|null $length
605
     *
606
     * @return array
607
     */
608 15 View Code Duplication
    public function slice($offset, $length = null)
609
    {
610 15
        if ( ! $this->initialized && ! $this->isDirty && $this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY) {
611 13
            $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
612
613 13
            return $persister->slice($this, $offset, $length);
614
        }
615
616 2
        return parent::slice($offset, $length);
617
    }
618
619
    /**
620
     * Cleans up internal state of cloned persistent collection.
621
     *
622
     * The following problems have to be prevented:
623
     * 1. Added entities are added to old PC
624
     * 2. New collection is not dirty, if reused on other entity nothing
625
     * changes.
626
     * 3. Snapshot leads to invalid diffs being generated.
627
     * 4. Lazy loading grabs entities from old owner object.
628
     * 5. New collection is connected to old owner and leads to duplicate keys.
629
     *
630
     * @return void
631
     */
632 11
    public function __clone()
633
    {
634 11
        if (is_object($this->collection)) {
635 11
            $this->collection = clone $this->collection;
636
        }
637
638 11
        $this->initialize();
639
640 11
        $this->owner    = null;
641 11
        $this->snapshot = [];
642
643 11
        $this->changed();
644 11
    }
645
646
    /**
647
     * Selects all elements from a selectable that match the expression and
648
     * return a new collection containing these elements.
649
     *
650
     * @param \Doctrine\Common\Collections\Criteria $criteria
651
     *
652
     * @return Collection
653
     *
654
     * @throws \RuntimeException
655
     */
656 15
    public function matching(Criteria $criteria)
657
    {
658 15
        if ($this->isDirty) {
659 3
            $this->initialize();
660
        }
661
662 15
        if ($this->initialized) {
663 3
            return $this->collection->matching($criteria);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Doctrine\Common\Collections\Collection as the method matching() does only exist in the following implementations of said interface: Doctrine\Common\Collections\ArrayCollection, Doctrine\ORM\LazyCriteriaCollection, Doctrine\ORM\PersistentCollection.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
664
        }
665
666 12
        if ($this->association['type'] === ClassMetadata::MANY_TO_MANY) {
667 7
            $persister = $this->em->getUnitOfWork()->getCollectionPersister($this->association);
668
669 7
            return new ArrayCollection($persister->loadCriteria($this, $criteria));
670
        }
671
672 5
        $builder         = Criteria::expr();
673 5
        $ownerExpression = $builder->eq($this->backRefFieldName, $this->owner);
674 5
        $expression      = $criteria->getWhereExpression();
675 5
        $expression      = $expression ? $builder->andX($expression, $ownerExpression) : $ownerExpression;
676
677 5
        $criteria = clone $criteria;
678 5
        $criteria->where($expression);
679
680 5
        $persister = $this->em->getUnitOfWork()->getEntityPersister($this->association['targetEntity']);
681
682 5
        return ($this->association['fetch'] === ClassMetadata::FETCH_EXTRA_LAZY)
683 2
            ? new LazyCriteriaCollection($persister, $criteria)
684 5
            : new ArrayCollection($persister->loadCriteria($criteria));
685
    }
686
687
    /**
688
     * Retrieves the wrapped Collection instance.
689
     *
690
     * @return \Doctrine\Common\Collections\Collection
691
     */
692 795
    public function unwrap()
693
    {
694 795
        return $this->collection;
695
    }
696
697
    /**
698
     * {@inheritdoc}
699
     */
700 147
    protected function doInitialize()
701
    {
702
        // Has NEW objects added through add(). Remember them.
703 147
        $newlyAddedDirtyObjects = [];
704
705 147
        if ($this->isDirty) {
706 17
            $newlyAddedDirtyObjects = $this->collection->toArray();
707
        }
708
709 147
        $this->collection->clear();
710 147
        $this->em->getUnitOfWork()->loadCollection($this);
711 147
        $this->takeSnapshot();
712
713 147
        if ($newlyAddedDirtyObjects) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $newlyAddedDirtyObjects 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...
714 17
            $this->restoreNewObjectsInDirtyCollection($newlyAddedDirtyObjects);
715
        }
716 147
    }
717
718
    /**
719
     * @param object[] $newObjects
720
     *
721
     * Note: the only reason why this entire looping/complexity is performed via `spl_object_hash`
722
     *       is because we want to prevent using `array_udiff()`, which is likely to cause very
723
     *       high overhead (complexity of O(n^2)). `array_diff_key()` performs the operation in
724
     *       core, which is faster than using a callback for comparisons
725
     */
726 17
    private function restoreNewObjectsInDirtyCollection(array $newObjects) : void
727
    {
728 17
        $loadedObjects               = $this->collection->toArray();
729 17
        $newObjectsByOid             = \array_combine(\array_map('spl_object_hash', $newObjects), $newObjects);
730 17
        $loadedObjectsByOid          = \array_combine(\array_map('spl_object_hash', $loadedObjects), $loadedObjects);
731 17
        $newObjectsThatWereNotLoaded = \array_diff_key($newObjectsByOid, $loadedObjectsByOid);
732
733 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...
734
            // Reattach NEW objects added through add(), if any.
735 16
            \array_walk($newObjectsThatWereNotLoaded, [$this->collection, 'add']);
736
737 16
            $this->isDirty = true;
738
        }
739 17
    }
740
}
741