Passed
Pull Request — develop (#6909)
by
unknown
26:42
created

UnitOfWork::recomputeSingleEntityChangeSet()   C

Complexity

Conditions 18
Paths 158

Size

Total Lines 63
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 342

Importance

Changes 0
Metric Value
cc 18
eloc 36
nc 158
nop 2
dl 0
loc 63
ccs 0
cts 36
cp 0
crap 342
rs 5.5128
c 0
b 0
f 0

How to fix   Long Method    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\Collection;
8
use Doctrine\Common\NotifyPropertyChanged;
9
use Doctrine\Common\PropertyChangedListener;
10
use Doctrine\DBAL\LockMode;
11
use Doctrine\Instantiator\Instantiator;
12
use Doctrine\ORM\Cache\Persister\CachedPersister;
13
use Doctrine\ORM\Event\LifecycleEventArgs;
14
use Doctrine\ORM\Event\ListenersInvoker;
15
use Doctrine\ORM\Event\OnFlushEventArgs;
16
use Doctrine\ORM\Event\PostFlushEventArgs;
17
use Doctrine\ORM\Event\PreFlushEventArgs;
18
use Doctrine\ORM\Event\PreUpdateEventArgs;
19
use Doctrine\ORM\Internal\HydrationCompleteHandler;
20
use Doctrine\ORM\Mapping\AssociationMetadata;
21
use Doctrine\ORM\Mapping\ChangeTrackingPolicy;
22
use Doctrine\ORM\Mapping\ClassMetadata;
23
use Doctrine\ORM\Mapping\FetchMode;
24
use Doctrine\ORM\Mapping\FieldMetadata;
25
use Doctrine\ORM\Mapping\GeneratorType;
26
use Doctrine\ORM\Mapping\InheritanceType;
27
use Doctrine\ORM\Mapping\JoinColumnMetadata;
28
use Doctrine\ORM\Mapping\ManyToManyAssociationMetadata;
29
use Doctrine\ORM\Mapping\OneToManyAssociationMetadata;
30
use Doctrine\ORM\Mapping\OneToOneAssociationMetadata;
31
use Doctrine\ORM\Mapping\ToManyAssociationMetadata;
32
use Doctrine\ORM\Mapping\ToOneAssociationMetadata;
33
use Doctrine\ORM\Mapping\VersionFieldMetadata;
34
use Doctrine\ORM\Persisters\Collection\ManyToManyPersister;
35
use Doctrine\ORM\Persisters\Collection\OneToManyPersister;
36
use Doctrine\ORM\Persisters\Entity\BasicEntityPersister;
37
use Doctrine\ORM\Persisters\Entity\JoinedSubclassPersister;
38
use Doctrine\ORM\Persisters\Entity\SingleTablePersister;
39
use Doctrine\ORM\Utility\NormalizeIdentifier;
40
use InvalidArgumentException;
41
use ProxyManager\Proxy\GhostObjectInterface;
42
use UnexpectedValueException;
43
44
/**
45
 * The UnitOfWork is responsible for tracking changes to objects during an
46
 * "object-level" transaction and for writing out changes to the database
47
 * in the correct order.
48
 *
49
 * Internal note: This class contains highly performance-sensitive code.
50
 *
51
 * @since       2.0
52
 * @author      Benjamin Eberlei <[email protected]>
53
 * @author      Guilherme Blanco <[email protected]>
54
 * @author      Jonathan Wage <[email protected]>
55
 * @author      Roman Borschel <[email protected]>
56
 * @author      Rob Caiger <[email protected]>
57
 */
58
class UnitOfWork implements PropertyChangedListener
59
{
60
    /**
61
     * An entity is in MANAGED state when its persistence is managed by an EntityManager.
62
     */
63
    const STATE_MANAGED = 1;
64
65
    /**
66
     * An entity is new if it has just been instantiated (i.e. using the "new" operator)
67
     * and is not (yet) managed by an EntityManager.
68
     */
69
    const STATE_NEW = 2;
70
71
    /**
72
     * A detached entity is an instance with persistent state and identity that is not
73
     * (or no longer) associated with an EntityManager (and a UnitOfWork).
74
     */
75
    const STATE_DETACHED = 3;
76
77
    /**
78
     * A removed entity instance is an instance with a persistent identity,
79
     * associated with an EntityManager, whose persistent state will be deleted
80
     * on commit.
81
     */
82
    const STATE_REMOVED = 4;
83
84
    /**
85
     * Hint used to collect all primary keys of associated entities during hydration
86
     * and execute it in a dedicated query afterwards
87
     * @see https://doctrine-orm.readthedocs.org/en/latest/reference/dql-doctrine-query-language.html?highlight=eager#temporarily-change-fetch-mode-in-dql
88
     */
89
    const HINT_DEFEREAGERLOAD = 'deferEagerLoad';
90
91
    /**
92
     * The identity map that holds references to all managed entities that have
93
     * an identity. The entities are grouped by their class name.
94
     * Since all classes in a hierarchy must share the same identifier set,
95
     * we always take the root class name of the hierarchy.
96
     *
97
     * @var array
98
     */
99
    private $identityMap = [];
100
101
    /**
102
     * Map of all identifiers of managed entities.
103
     * This is a 2-dimensional data structure (map of maps). Keys are object ids (spl_object_id).
104
     * Values are maps of entity identifiers, where its key is the column name and the value is the raw value.
105
     *
106
     * @var array
107
     */
108
    private $entityIdentifiers = [];
109
110
    /**
111
     * Map of the original entity data of managed entities.
112
     * This is a 2-dimensional data structure (map of maps). Keys are object ids (spl_object_id).
113
     * Values are maps of entity data, where its key is the field name and the value is the converted
114
     * (convertToPHPValue) value.
115
     * This structure is used for calculating changesets at commit time.
116
     *
117
     * Internal: Note that PHPs "copy-on-write" behavior helps a lot with memory usage.
118
     *           A value will only really be copied if the value in the entity is modified by the user.
119
     *
120
     * @var array
121
     */
122
    private $originalEntityData = [];
123
124
    /**
125
     * Map of entity changes. Keys are object ids (spl_object_id).
126
     * Filled at the beginning of a commit of the UnitOfWork and cleaned at the end.
127
     *
128
     * @var array
129
     */
130
    private $entityChangeSets = [];
131
132
    /**
133
     * The (cached) states of any known entities.
134
     * Keys are object ids (spl_object_id).
135
     *
136
     * @var array
137
     */
138
    private $entityStates = [];
139
140
    /**
141
     * Map of entities that are scheduled for dirty checking at commit time.
142
     * This is only used for entities with a change tracking policy of DEFERRED_EXPLICIT.
143
     * Keys are object ids (spl_object_id).
144
     *
145
     * @var array
146
     */
147
    private $scheduledForSynchronization = [];
148
149
    /**
150
     * A list of all pending entity insertions.
151
     *
152
     * @var array
153
     */
154
    private $entityInsertions = [];
155
156
    /**
157
     * A list of all pending entity updates.
158
     *
159
     * @var array
160
     */
161
    private $entityUpdates = [];
162
163
    /**
164
     * Any pending extra updates that have been scheduled by persisters.
165
     *
166
     * @var array
167
     */
168
    private $extraUpdates = [];
169
170
    /**
171
     * A list of all pending entity deletions.
172
     *
173
     * @var array
174
     */
175
    private $entityDeletions = [];
176
177
    /**
178
     * New entities that were discovered through relationships that were not
179
     * marked as cascade-persist. During flush, this array is populated and
180
     * then pruned of any entities that were discovered through a valid
181
     * cascade-persist path. (Leftovers cause an error.)
182
     *
183
     * Keys are OIDs, payload is a two-item array describing the association
184
     * and the entity.
185
     *
186
     * @var object[][]|array[][] indexed by respective object spl_object_id()
187
     */
188
    private $nonCascadedNewDetectedEntities = [];
189
190
    /**
191
     * All pending collection deletions.
192
     *
193
     * @var array
194
     */
195
    private $collectionDeletions = [];
196
197
    /**
198
     * All pending collection updates.
199
     *
200
     * @var array
201
     */
202
    private $collectionUpdates = [];
203
204
    /**
205
     * List of collections visited during changeset calculation on a commit-phase of a UnitOfWork.
206
     * At the end of the UnitOfWork all these collections will make new snapshots
207
     * of their data.
208
     *
209
     * @var array
210
     */
211
    private $visitedCollections = [];
212
213
    /**
214
     * The EntityManager that "owns" this UnitOfWork instance.
215
     *
216
     * @var EntityManagerInterface
217
     */
218
    private $em;
219
220
221
    /**
222
     * The entity persister instances used to persist entity instances.
223
     *
224
     * @var array
225
     */
226
    private $entityPersisters = [];
227
228
    /**
229
     * The collection persister instances used to persist collections.
230
     *
231
     * @var array
232
     */
233
    private $collectionPersisters = [];
234
235
    /**
236
     * The EventManager used for dispatching events.
237
     *
238
     * @var \Doctrine\Common\EventManager
239
     */
240
    private $eventManager;
241
242
    /**
243
     * The ListenersInvoker used for dispatching events.
244
     *
245
     * @var \Doctrine\ORM\Event\ListenersInvoker
246
     */
247
    private $listenersInvoker;
248
249
    /**
250
     * @var Instantiator
251
     */
252
    private $instantiator;
253
254
    /**
255
     * Orphaned entities that are scheduled for removal.
256
     *
257
     * @var array
258
     */
259
    private $orphanRemovals = [];
260
261
    /**
262
     * Read-Only objects are never evaluated
263
     *
264
     * @var array
265
     */
266
    private $readOnlyObjects = [];
267
268
    /**
269
     * Map of Entity Class-Names and corresponding IDs that should eager loaded when requested.
270
     *
271
     * @var array
272
     */
273
    private $eagerLoadingEntities = [];
274
275
    /**
276
     * @var boolean
277
     */
278
    protected $hasCache = false;
279
280
    /**
281
     * Helper for handling completion of hydration
282
     *
283
     * @var HydrationCompleteHandler
284
     */
285
    private $hydrationCompleteHandler;
286
287
    /**
288
     * @var NormalizeIdentifier
289
     */
290
    private $normalizeIdentifier;
291
292
    /**
293
     * Initializes a new UnitOfWork instance, bound to the given EntityManager.
294
     *
295
     * @param EntityManagerInterface $em
296
     */
297 1120
    public function __construct(EntityManagerInterface $em)
298
    {
299 1120
        $this->em                       = $em;
300 1120
        $this->eventManager             = $em->getEventManager();
301 1120
        $this->listenersInvoker         = new ListenersInvoker($em);
302 1120
        $this->hasCache                 = $em->getConfiguration()->isSecondLevelCacheEnabled();
303 1120
        $this->instantiator             = new Instantiator();
304 1120
        $this->hydrationCompleteHandler = new HydrationCompleteHandler($this->listenersInvoker, $em);
305 1120
        $this->normalizeIdentifier      = new NormalizeIdentifier();
306 1120
    }
307
308
    /**
309
     * Commits the UnitOfWork, executing all operations that have been postponed
310
     * up to this point. The state of all managed entities will be synchronized with
311
     * the database.
312
     *
313
     * The operations are executed in the following order:
314
     *
315
     * 1) All entity insertions
316
     * 2) All entity updates
317
     * 3) All collection deletions
318
     * 4) All collection updates
319
     * 5) All entity deletions
320
     *
321
     * @return void
322
     *
323
     * @throws \Exception
324
     */
325 5
    public function commit()
326
    {
327
        // Raise preFlush
328 5
        if ($this->eventManager->hasListeners(Events::preFlush)) {
329 1
            $this->eventManager->dispatchEvent(Events::preFlush, new PreFlushEventArgs($this->em));
330
        }
331
332 5
        $this->computeChangeSets();
333
334 5
        if ( ! ($this->entityInsertions ||
335 5
                $this->entityDeletions ||
336 5
                $this->entityUpdates ||
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->entityUpdates 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...
337 5
                $this->collectionUpdates ||
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->collectionUpdates of type array<mixed,mixed|Doctri...M\PersistentCollection> 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...
338 5
                $this->collectionDeletions ||
339 5
                $this->orphanRemovals)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->orphanRemovals 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...
340 5
            $this->dispatchOnFlushEvent();
341 5
            $this->dispatchPostFlushEvent();
342
343 5
            return; // Nothing to do.
344
        }
345
346
        $this->assertThatThereAreNoUnintentionallyNonPersistedAssociations();
347
348
        if ($this->orphanRemovals) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->orphanRemovals 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...
349
            foreach ($this->orphanRemovals as $orphan) {
350
                $this->remove($orphan);
351
            }
352
        }
353
354
        $this->dispatchOnFlushEvent();
355
356
        // Now we need a commit order to maintain referential integrity
357
        $commitOrder = $this->getCommitOrder();
358
359
        $conn = $this->em->getConnection();
360
        $conn->beginTransaction();
361
362
        try {
363
            // Collection deletions (deletions of complete collections)
364
            foreach ($this->collectionDeletions as $collectionToDelete) {
365
                $this->getCollectionPersister($collectionToDelete->getMapping())->delete($collectionToDelete);
366
            }
367
368
            if ($this->entityInsertions) {
369
                foreach ($commitOrder as $class) {
370
                    $this->executeInserts($class);
371
                }
372
            }
373
374
            if ($this->entityUpdates) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->entityUpdates 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...
375
                foreach ($commitOrder as $class) {
376
                    $this->executeUpdates($class);
377
                }
378
            }
379
380
            // Extra updates that were requested by persisters.
381
            if ($this->extraUpdates) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->extraUpdates 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...
382
                $this->executeExtraUpdates();
383
            }
384
385
            // Collection updates (deleteRows, updateRows, insertRows)
386
            foreach ($this->collectionUpdates as $collectionToUpdate) {
387
                $this->getCollectionPersister($collectionToUpdate->getMapping())->update($collectionToUpdate);
388
            }
389
390
            // Entity deletions come last and need to be in reverse commit order
391
            if ($this->entityDeletions) {
392
                foreach (array_reverse($commitOrder) as $committedEntityName) {
393
                    if (! $this->entityDeletions) {
394
                        break; // just a performance optimisation
395
                    }
396
397
                    $this->executeDeletions($committedEntityName);
398
                }
399
            }
400
401
            $conn->commit();
402
        } catch (\Throwable $e) {
403
            $this->em->close();
404
            $conn->rollBack();
405
406
            $this->afterTransactionRolledBack();
407
408
            throw $e;
409
        }
410
411
        $this->afterTransactionComplete();
412
413
        // Take new snapshots from visited collections
414
        foreach ($this->visitedCollections as $coll) {
415
            $coll->takeSnapshot();
416
        }
417
418
        $this->dispatchPostFlushEvent();
419
420
        // Clean up
421
        $this->entityInsertions =
422
        $this->entityUpdates =
423
        $this->entityDeletions =
424
        $this->extraUpdates =
425
        $this->entityChangeSets =
426
        $this->collectionUpdates =
427
        $this->collectionDeletions =
428
        $this->visitedCollections =
429
        $this->scheduledForSynchronization =
430
        $this->orphanRemovals = [];
431
    }
432
433
    /**
434
     * Computes the changesets of all entities scheduled for insertion.
435
     *
436
     * @return void
437
     */
438 5
    private function computeScheduleInsertsChangeSets()
439
    {
440 5
        foreach ($this->entityInsertions as $entity) {
441
            $class = $this->em->getClassMetadata(get_class($entity));
442
443
            $this->computeChangeSet($class, $entity);
0 ignored issues
show
Bug introduced by
$class of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $class of Doctrine\ORM\UnitOfWork::computeChangeSet(). ( Ignorable by Annotation )

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

443
            $this->computeChangeSet(/** @scrutinizer ignore-type */ $class, $entity);
Loading history...
444
        }
445 5
    }
446
447
    /**
448
     * Executes any extra updates that have been scheduled.
449
     */
450
    private function executeExtraUpdates()
451
    {
452
        foreach ($this->extraUpdates as $oid => $update) {
453
            list ($entity, $changeset) = $update;
454
455
            $this->entityChangeSets[$oid] = $changeset;
456
457
//            echo 'Extra update: ';
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
458
//            \Doctrine\Common\Util\Debug::dump($changeset, 3);
459
460
            $this->getEntityPersister(get_class($entity))->update($entity);
461
        }
462
463
        $this->extraUpdates = [];
464
    }
465
466
    /**
467
     * Gets the changeset for an entity.
468
     *
469
     * @param object $entity
470
     *
471
     * @return array
472
     */
473
    public function & getEntityChangeSet($entity)
474
    {
475
        $oid  = spl_object_id($entity);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

475
        $oid  = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
476
        $data = [];
477
478
        if (!isset($this->entityChangeSets[$oid])) {
479
            return $data;
480
        }
481
482
        return $this->entityChangeSets[$oid];
483
    }
484
485
    /**
486
     * Computes the changes that happened to a single entity.
487
     *
488
     * Modifies/populates the following properties:
489
     *
490
     * {@link originalEntityData}
491
     * If the entity is NEW or MANAGED but not yet fully persisted (only has an id)
492
     * then it was not fetched from the database and therefore we have no original
493
     * entity data yet. All of the current entity data is stored as the original entity data.
494
     *
495
     * {@link entityChangeSets}
496
     * The changes detected on all properties of the entity are stored there.
497
     * A change is a tuple array where the first entry is the old value and the second
498
     * entry is the new value of the property. Changesets are used by persisters
499
     * to INSERT/UPDATE the persistent entity state.
500
     *
501
     * {@link entityUpdates}
502
     * If the entity is already fully MANAGED (has been fetched from the database before)
503
     * and any changes to its properties are detected, then a reference to the entity is stored
504
     * there to mark it for an update.
505
     *
506
     * {@link collectionDeletions}
507
     * If a PersistentCollection has been de-referenced in a fully MANAGED entity,
508
     * then this collection is marked for deletion.
509
     *
510
     * @ignore
511
     *
512
     * @internal Don't call from the outside.
513
     *
514
     * @param ClassMetadata $class  The class descriptor of the entity.
515
     * @param object        $entity The entity for which to compute the changes.
516
     *
517
     * @return void
518
     */
519
    public function computeChangeSet(ClassMetadata $class, $entity)
520
    {
521
        $oid = spl_object_id($entity);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

521
        $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
522
523
        if (isset($this->readOnlyObjects[$oid])) {
524
            return;
525
        }
526
527
        if ($class->inheritanceType !== InheritanceType::NONE) {
528
            $class = $this->em->getClassMetadata(get_class($entity));
529
        }
530
531
        $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preFlush) & ~ListenersInvoker::INVOKE_MANAGER;
0 ignored issues
show
Bug introduced by
It seems like $class can also be of type Doctrine\Common\Persistence\Mapping\ClassMetadata; however, parameter $metadata of Doctrine\ORM\Event\Liste...:getSubscribedSystems() does only seem to accept Doctrine\ORM\Mapping\ClassMetadata, 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

531
        $invoke = $this->listenersInvoker->getSubscribedSystems(/** @scrutinizer ignore-type */ $class, Events::preFlush) & ~ListenersInvoker::INVOKE_MANAGER;
Loading history...
532
533
        if ($invoke !== ListenersInvoker::INVOKE_NONE) {
534
            $this->listenersInvoker->invoke($class, Events::preFlush, $entity, new PreFlushEventArgs($this->em), $invoke);
0 ignored issues
show
Bug introduced by
It seems like $class can also be of type Doctrine\Common\Persistence\Mapping\ClassMetadata; however, parameter $metadata of Doctrine\ORM\Event\ListenersInvoker::invoke() does only seem to accept Doctrine\ORM\Mapping\ClassMetadata, 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

534
            $this->listenersInvoker->invoke(/** @scrutinizer ignore-type */ $class, Events::preFlush, $entity, new PreFlushEventArgs($this->em), $invoke);
Loading history...
535
        }
536
537
        $actualData = [];
538
539
        foreach ($class->getDeclaredPropertiesIterator() as $name => $property) {
0 ignored issues
show
Bug introduced by
The method getDeclaredPropertiesIterator() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

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

539
        foreach ($class->/** @scrutinizer ignore-call */ getDeclaredPropertiesIterator() as $name => $property) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
540
            $value = $property->getValue($entity);
541
542
            if ($property instanceof ToManyAssociationMetadata && $value !== null) {
543
                if ($value instanceof PersistentCollection && $value->getOwner() === $entity) {
544
                    continue;
545
                }
546
547
                $value = $property->wrap($entity, $value, $this->em);
548
549
                $property->setValue($entity, $value);
550
551
                $actualData[$name] = $value;
552
553
                continue;
554
            }
555
556
            if (
557
                ( ! $class->isIdentifier($name)
558
                    || ! $class->getProperty($name) instanceof FieldMetadata
0 ignored issues
show
Bug introduced by
The method getProperty() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

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

558
                    || ! $class->/** @scrutinizer ignore-call */ getProperty($name) instanceof FieldMetadata

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
559
                    || ! $class->getProperty($name)->hasValueGenerator()
0 ignored issues
show
Bug introduced by
The method hasValueGenerator() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\FieldMetadata. ( Ignorable by Annotation )

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

559
                    || ! $class->getProperty($name)->/** @scrutinizer ignore-call */ hasValueGenerator()
Loading history...
560
                    || $class->getProperty($name)->getValueGenerator()->getType() !== GeneratorType::IDENTITY
0 ignored issues
show
Bug introduced by
The method getValueGenerator() does not exist on Doctrine\ORM\Mapping\Property. Did you maybe mean getValue()? ( Ignorable by Annotation )

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

560
                    || $class->getProperty($name)->/** @scrutinizer ignore-call */ getValueGenerator()->getType() !== GeneratorType::IDENTITY

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
561
                ) && (! $class->isVersioned() || $name !== $class->versionProperty->getName())) {
0 ignored issues
show
Bug introduced by
The method isVersioned() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

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

561
                ) && (! $class->/** @scrutinizer ignore-call */ isVersioned() || $name !== $class->versionProperty->getName())) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
562
                $actualData[$name] = $value;
563
            }
564
        }
565
566
        if ( ! isset($this->originalEntityData[$oid])) {
567
            // Entity is either NEW or MANAGED but not yet fully persisted (only has an id).
568
            // These result in an INSERT.
569
            $this->originalEntityData[$oid] = $actualData;
570
            $changeSet = [];
571
572
            foreach ($actualData as $propName => $actualValue) {
573
                $property = $class->getProperty($propName);
574
575
                if (($property instanceof FieldMetadata) ||
576
                    ($property instanceof ToOneAssociationMetadata && $property->isOwningSide())) {
577
                    $changeSet[$propName] = [null, $actualValue];
578
                }
579
            }
580
581
            $this->entityChangeSets[$oid] = $changeSet;
582
        } else {
583
            // Entity is "fully" MANAGED: it was already fully persisted before
584
            // and we have a copy of the original data
585
            $originalData           = $this->originalEntityData[$oid];
586
            $isChangeTrackingNotify = $class->changeTrackingPolicy === ChangeTrackingPolicy::NOTIFY;
587
            $changeSet              = ($isChangeTrackingNotify && isset($this->entityChangeSets[$oid]))
588
                ? $this->entityChangeSets[$oid]
589
                : [];
590
591
            foreach ($actualData as $propName => $actualValue) {
592
                // skip field, its a partially omitted one!
593
                if ( ! (isset($originalData[$propName]) || array_key_exists($propName, $originalData))) {
594
                    continue;
595
                }
596
597
                $orgValue = $originalData[$propName];
598
599
                // skip if value haven't changed
600
                if ($orgValue === $actualValue) {
601
                    continue;
602
                }
603
604
                $property = $class->getProperty($propName);
605
606
                // Persistent collection was exchanged with the "originally"
607
                // created one. This can only mean it was cloned and replaced
608
                // on another entity.
609
                if ($actualValue instanceof PersistentCollection) {
610
                    $owner = $actualValue->getOwner();
611
612
                    if ($owner === null) { // cloned
613
                        $actualValue->setOwner($entity, $property);
614
                    } elseif ($owner !== $entity) { // no clone, we have to fix
615
                        if (! $actualValue->isInitialized()) {
616
                            $actualValue->initialize(); // we have to do this otherwise the cols share state
617
                        }
618
619
                        $newValue = clone $actualValue;
620
621
                        $newValue->setOwner($entity, $property);
622
623
                        $property->setValue($entity, $newValue);
624
                    }
625
                }
626
627
                switch (true) {
628
                    case ($property instanceof FieldMetadata):
629
                        if ($isChangeTrackingNotify) {
630
                            // Continue inside switch behaves as break.
631
                            // We are required to use continue 2, since we need to continue to next $actualData item
632
                            continue 2;
633
                        }
634
635
                        $changeSet[$propName] = [$orgValue, $actualValue];
636
                        break;
637
638
                    case ($property instanceof ToOneAssociationMetadata):
639
                        if ($property->isOwningSide()) {
640
                            $changeSet[$propName] = [$orgValue, $actualValue];
641
                        }
642
643
                        if ($orgValue !== null && $property->isOrphanRemoval()) {
644
                            $this->scheduleOrphanRemoval($orgValue);
645
                        }
646
647
                        break;
648
649
                    case ($property instanceof ToManyAssociationMetadata):
650
                        // Check if original value exists
651
                        if ($orgValue instanceof PersistentCollection) {
652
                            // A PersistentCollection was de-referenced, so delete it.
653
                            if (! $this->isCollectionScheduledForDeletion($orgValue)) {
654
                                $this->scheduleCollectionDeletion($orgValue);
655
656
                                $changeSet[$propName] = $orgValue; // Signal changeset, to-many associations will be ignored
657
                            }
658
                        }
659
660
                        break;
661
662
                    default:
663
                        // Do nothing
664
                }
665
            }
666
667
            if ($changeSet) {
668
                $this->entityChangeSets[$oid]   = $changeSet;
669
                $this->originalEntityData[$oid] = $actualData;
670
                $this->entityUpdates[$oid]      = $entity;
671
            }
672
        }
673
674
        // Look for changes in associations of the entity
675
        foreach ($class->getDeclaredPropertiesIterator() as $property) {
676
            if (! ($property instanceof AssociationMetadata) || ($value = $property->getValue($entity)) === null) {
677
                continue;
678
            }
679
680
            $this->computeAssociationChanges($property, $value);
681
682
            if ($property instanceof ManyToManyAssociationMetadata &&
683
                $value instanceof PersistentCollection &&
684
                ! isset($this->entityChangeSets[$oid]) &&
685
                $property->isOwningSide() &&
686
                $value->isDirty()) {
687
688
                $this->entityChangeSets[$oid]   = [];
689
                $this->originalEntityData[$oid] = $actualData;
690
                $this->entityUpdates[$oid]      = $entity;
691
            }
692
        }
693
    }
694
695
    /**
696
     * Computes all the changes that have been done to entities and collections
697
     * since the last commit and stores these changes in the _entityChangeSet map
698
     * temporarily for access by the persisters, until the UoW commit is finished.
699
     *
700
     * @return void
701
     */
702 5
    public function computeChangeSets()
703
    {
704
        // Compute changes for INSERTed entities first. This must always happen.
705 5
        $this->computeScheduleInsertsChangeSets();
706
707
        // Compute changes for other MANAGED entities. Change tracking policies take effect here.
708 5
        foreach ($this->identityMap as $className => $entities) {
709
            $class = $this->em->getClassMetadata($className);
710
711
            // Skip class if instances are read-only
712
            if ($class->isReadOnly()) {
0 ignored issues
show
Bug introduced by
The method isReadOnly() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

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

712
            if ($class->/** @scrutinizer ignore-call */ isReadOnly()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
713
                continue;
714
            }
715
716
            // If change tracking is explicit or happens through notification, then only compute
717
            // changes on entities of that type that are explicitly marked for synchronization.
718
            switch (true) {
719
                case ($class->changeTrackingPolicy === ChangeTrackingPolicy::DEFERRED_IMPLICIT):
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...
720
                    $entitiesToProcess = $entities;
721
                    break;
722
723
                case (isset($this->scheduledForSynchronization[$className])):
724
                    $entitiesToProcess = $this->scheduledForSynchronization[$className];
725
                    break;
726
727
                default:
728
                    $entitiesToProcess = [];
729
730
            }
731
732
            foreach ($entitiesToProcess as $entity) {
733
                // Ignore uninitialized proxy objects
734
                if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
735
                    continue;
736
                }
737
738
                // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION OR DELETION are processed here.
739
                $oid = spl_object_id($entity);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

739
                $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
740
741
                if ( ! isset($this->entityInsertions[$oid]) && ! isset($this->entityDeletions[$oid]) && isset($this->entityStates[$oid])) {
742
                    $this->computeChangeSet($class, $entity);
0 ignored issues
show
Bug introduced by
$class of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $class of Doctrine\ORM\UnitOfWork::computeChangeSet(). ( Ignorable by Annotation )

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

742
                    $this->computeChangeSet(/** @scrutinizer ignore-type */ $class, $entity);
Loading history...
743
                }
744
            }
745
        }
746 5
    }
747
748
    /**
749
     * Computes the changes of an association.
750
     *
751
     * @param AssociationMetadata $association The association mapping.
752
     * @param mixed               $value       The value of the association.
753
     *
754
     * @throws ORMInvalidArgumentException
755
     * @throws ORMException
756
     *
757
     * @return void
758
     */
759
    private function computeAssociationChanges(AssociationMetadata $association, $value)
760
    {
761
        if ($value instanceof GhostObjectInterface && ! $value->isProxyInitialized()) {
762
            return;
763
        }
764
765
        if ($value instanceof PersistentCollection && $value->isDirty()) {
766
            $coid = spl_object_id($value);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

766
            $coid = /** @scrutinizer ignore-call */ spl_object_id($value);
Loading history...
767
768
            $this->collectionUpdates[$coid] = $value;
769
            $this->visitedCollections[$coid] = $value;
770
        }
771
772
        // Look through the entities, and in any of their associations,
773
        // for transient (new) entities, recursively. ("Persistence by reachability")
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
774
        // Unwrap. Uninitialized collections will simply be empty.
775
        $unwrappedValue = ($association instanceof ToOneAssociationMetadata) ? [$value] : $value->unwrap();
776
        $targetEntity   = $association->getTargetEntity();
777
        $targetClass    = $this->em->getClassMetadata($targetEntity);
778
779
        foreach ($unwrappedValue as $key => $entry) {
780
            if (! ($entry instanceof $targetEntity)) {
781
                throw ORMInvalidArgumentException::invalidAssociation($targetClass, $association, $entry);
0 ignored issues
show
Bug introduced by
$targetClass of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $targetClass of Doctrine\ORM\ORMInvalidA...n::invalidAssociation(). ( Ignorable by Annotation )

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

781
                throw ORMInvalidArgumentException::invalidAssociation(/** @scrutinizer ignore-type */ $targetClass, $association, $entry);
Loading history...
782
            }
783
784
            $state = $this->getEntityState($entry, self::STATE_NEW);
785
786
            if (! ($entry instanceof $targetEntity)) {
787
                throw ORMException::unexpectedAssociationValue(
788
                    $association->getSourceEntity(),
789
                    $association->getName(),
790
                    get_class($entry),
791
                    $targetEntity
792
                );
793
            }
794
795
            switch ($state) {
796
                case self::STATE_NEW:
797
                    if ( ! in_array('persist', $association->getCascade())) {
798
                        $this->nonCascadedNewDetectedEntities[\spl_object_id($entry)] = [$association, $entry];
799
800
                        break;
801
                    }
802
803
                    $this->persistNew($targetClass, $entry);
0 ignored issues
show
Bug introduced by
$targetClass of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $class of Doctrine\ORM\UnitOfWork::persistNew(). ( Ignorable by Annotation )

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

803
                    $this->persistNew(/** @scrutinizer ignore-type */ $targetClass, $entry);
Loading history...
804
                    $this->computeChangeSet($targetClass, $entry);
0 ignored issues
show
Bug introduced by
$targetClass of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $class of Doctrine\ORM\UnitOfWork::computeChangeSet(). ( Ignorable by Annotation )

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

804
                    $this->computeChangeSet(/** @scrutinizer ignore-type */ $targetClass, $entry);
Loading history...
805
806
                    break;
807
808
                case self::STATE_REMOVED:
809
                    // Consume the $value as array (it's either an array or an ArrayAccess)
810
                    // and remove the element from Collection.
811
                    if ($association instanceof ToManyAssociationMetadata) {
812
                        unset($value[$key]);
813
                    }
814
                    break;
815
816
                case self::STATE_DETACHED:
817
                    // Can actually not happen right now as we assume STATE_NEW,
818
                    // so the exception will be raised from the DBAL layer (constraint violation).
819
                    throw ORMInvalidArgumentException::detachedEntityFoundThroughRelationship($association, $entry);
820
                    break;
821
822
                default:
823
                    // MANAGED associated entities are already taken into account
824
                    // during changeset calculation anyway, since they are in the identity map.
825
            }
826
        }
827
    }
828
829
    /**
830
     * @param \Doctrine\ORM\Mapping\ClassMetadata $class
831
     * @param object                              $entity
832
     *
833
     * @return void
834
     */
835
    private function persistNew($class, $entity)
836
    {
837
        $oid    = spl_object_id($entity);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

837
        $oid    = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
838
        $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::prePersist);
839
840
        if ($invoke !== ListenersInvoker::INVOKE_NONE) {
841
            $this->listenersInvoker->invoke($class, Events::prePersist, $entity, new LifecycleEventArgs($entity, $this->em), $invoke);
842
        }
843
844
        $generationPlan = $class->getValueGenerationPlan();
845
        $persister = $this->getEntityPersister($class->getClassName());
846
        $generationPlan->executeImmediate($this->em, $entity);
847
848
        if (! $generationPlan->containsDeferred()) {
849
            $id = $this->em->getIdentifierFlattener()->flattenIdentifier($class, $persister->getIdentifier($entity));
850
            $this->entityIdentifiers[$oid] = $id;
851
        }
852
853
        $this->entityStates[$oid] = self::STATE_MANAGED;
854
855
        $this->scheduleForInsert($entity);
856
    }
857
858
    /**
859
     * INTERNAL:
860
     * Computes the changeset of an individual entity, independently of the
861
     * computeChangeSets() routine that is used at the beginning of a UnitOfWork#commit().
862
     *
863
     * The passed entity must be a managed entity. If the entity already has a change set
864
     * because this method is invoked during a commit cycle then the change sets are added.
865
     * whereby changes detected in this method prevail.
866
     *
867
     * @ignore
868
     *
869
     * @param ClassMetadata $class  The class descriptor of the entity.
870
     * @param object        $entity The entity for which to (re)calculate the change set.
871
     *
872
     * @return void
873
     *
874
     * @throws ORMInvalidArgumentException If the passed entity is not MANAGED.
875
     * @throws \RuntimeException
876
     */
877
    public function recomputeSingleEntityChangeSet(ClassMetadata $class, $entity) : void
878
    {
879
        $oid = spl_object_id($entity);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

879
        $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
880
881
        if (! isset($this->entityStates[$oid]) || $this->entityStates[$oid] !== self::STATE_MANAGED) {
882
            throw ORMInvalidArgumentException::entityNotManaged($entity);
883
        }
884
885
        // skip if change tracking is "NOTIFY"
886
        if ($class->changeTrackingPolicy === ChangeTrackingPolicy::NOTIFY) {
887
            return;
888
        }
889
890
        if ($class->inheritanceType !== InheritanceType::NONE) {
891
            $class = $this->em->getClassMetadata(get_class($entity));
892
        }
893
894
        $actualData = [];
895
896
        foreach ($class->getDeclaredPropertiesIterator() as $name => $property) {
897
            switch (true) {
898
                case ($property instanceof VersionFieldMetadata):
899
                    // Ignore version field
900
                    break;
901
902
                case ($property instanceof FieldMetadata):
903
                    if (! $property->isPrimaryKey()
904
                        || ! $property->getValueGenerator()
905
                        || $property->getValueGenerator()->getType() !== GeneratorType::IDENTITY) {
906
                        $actualData[$name] = $property->getValue($entity);
907
                    }
908
909
                    break;
910
911
                case ($property instanceof ToOneAssociationMetadata):
912
                    $actualData[$name] = $property->getValue($entity);
913
                    break;
914
            }
915
        }
916
917
        if ( ! isset($this->originalEntityData[$oid])) {
918
            throw new \RuntimeException('Cannot call recomputeSingleEntityChangeSet before computeChangeSet on an entity.');
919
        }
920
921
        $originalData = $this->originalEntityData[$oid];
922
        $changeSet = [];
923
924
        foreach ($actualData as $propName => $actualValue) {
925
            $orgValue = $originalData[$propName] ?? null;
926
927
            if ($orgValue !== $actualValue) {
928
                $changeSet[$propName] = [$orgValue, $actualValue];
929
            }
930
        }
931
932
        if ($changeSet) {
933
            if (isset($this->entityChangeSets[$oid])) {
934
                $this->entityChangeSets[$oid] = array_merge($this->entityChangeSets[$oid], $changeSet);
935
            } elseif ( ! isset($this->entityInsertions[$oid])) {
936
                $this->entityChangeSets[$oid] = $changeSet;
937
                $this->entityUpdates[$oid]    = $entity;
938
            }
939
            $this->originalEntityData[$oid] = $actualData;
940
        }
941
    }
942
943
    /**
944
     * Executes all entity insertions for entities of the specified type.
945
     *
946
     * @param ClassMetadata $class
947
     *
948
     * @return void
949
     */
950
    private function executeInserts(ClassMetadata $class) : void
951
    {
952
        $className      = $class->getClassName();
953
        $persister      = $this->getEntityPersister($className);
954
        $invoke         = $this->listenersInvoker->getSubscribedSystems($class, Events::postPersist);
955
        $generationPlan = $class->getValueGenerationPlan();
956
957
        foreach ($this->entityInsertions as $oid => $entity) {
958
            if ($this->em->getClassMetadata(get_class($entity))->getClassName() !== $className) {
0 ignored issues
show
Bug introduced by
The method getClassName() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

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

958
            if ($this->em->getClassMetadata(get_class($entity))->/** @scrutinizer ignore-call */ getClassName() !== $className) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
959
                continue;
960
            }
961
962
            $persister->insert($entity);
963
964
            if ($generationPlan->containsDeferred()) {
965
                // Entity has post-insert IDs
966
                $oid = spl_object_id($entity);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

966
                $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
967
                $id  = $this->em->getIdentifierFlattener()->flattenIdentifier($class, $persister->getIdentifier($entity));
968
969
                $this->entityIdentifiers[$oid] = $id;
970
                $this->entityStates[$oid] = self::STATE_MANAGED;
971
                $this->originalEntityData[$oid] = $id + $this->originalEntityData[$oid];
972
973
                $this->addToIdentityMap($entity);
974
            }
975
976
            unset($this->entityInsertions[$oid]);
977
978
            if ($invoke !== ListenersInvoker::INVOKE_NONE) {
979
                $eventArgs = new LifecycleEventArgs($entity, $this->em);
980
981
                $this->listenersInvoker->invoke($class, Events::postPersist, $entity, $eventArgs, $invoke);
982
            }
983
        }
984
    }
985
986
    /**
987
     * Executes all entity updates for entities of the specified type.
988
     *
989
     * @param \Doctrine\ORM\Mapping\ClassMetadata $class
990
     *
991
     * @return void
992
     */
993
    private function executeUpdates($class)
994
    {
995
        $className          = $class->getClassName();
996
        $persister          = $this->getEntityPersister($className);
997
        $preUpdateInvoke    = $this->listenersInvoker->getSubscribedSystems($class, Events::preUpdate);
998
        $postUpdateInvoke   = $this->listenersInvoker->getSubscribedSystems($class, Events::postUpdate);
999
1000
        foreach ($this->entityUpdates as $oid => $entity) {
1001
            if ($this->em->getClassMetadata(get_class($entity))->getClassName() !== $className) {
1002
                continue;
1003
            }
1004
1005
            if ($preUpdateInvoke != ListenersInvoker::INVOKE_NONE) {
1006
                $this->listenersInvoker->invoke($class, Events::preUpdate, $entity, new PreUpdateEventArgs($entity, $this->em, $this->getEntityChangeSet($entity)), $preUpdateInvoke);
1007
1008
                $this->recomputeSingleEntityChangeSet($class, $entity);
1009
            }
1010
1011
            if ( ! empty($this->entityChangeSets[$oid])) {
1012
//                echo 'Update: ';
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1013
//                \Doctrine\Common\Util\Debug::dump($this->entityChangeSets[$oid], 3);
1014
1015
                $persister->update($entity);
1016
            }
1017
1018
            unset($this->entityUpdates[$oid]);
1019
1020
            if ($postUpdateInvoke != ListenersInvoker::INVOKE_NONE) {
1021
                $this->listenersInvoker->invoke($class, Events::postUpdate, $entity, new LifecycleEventArgs($entity, $this->em), $postUpdateInvoke);
1022
            }
1023
        }
1024
    }
1025
1026
    /**
1027
     * Executes all entity deletions for entities of the specified type.
1028
     *
1029
     * @param \Doctrine\ORM\Mapping\ClassMetadata $class
1030
     *
1031
     * @return void
1032
     */
1033
    private function executeDeletions($class)
1034
    {
1035
        $className  = $class->getClassName();
1036
        $persister  = $this->getEntityPersister($className);
1037
        $invoke     = $this->listenersInvoker->getSubscribedSystems($class, Events::postRemove);
1038
1039
        foreach ($this->entityDeletions as $oid => $entity) {
1040
            if ($this->em->getClassMetadata(get_class($entity))->getClassName() !== $className) {
1041
                continue;
1042
            }
1043
1044
            $persister->delete($entity);
1045
1046
            unset(
1047
                $this->entityDeletions[$oid],
1048
                $this->entityIdentifiers[$oid],
1049
                $this->originalEntityData[$oid],
1050
                $this->entityStates[$oid]
1051
            );
1052
1053
            // Entity with this $oid after deletion treated as NEW, even if the $oid
1054
            // is obtained by a new entity because the old one went out of scope.
1055
            //$this->entityStates[$oid] = self::STATE_NEW;
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1056
            if (! $class->isIdentifierComposite()) {
1057
                $property = $class->getProperty($class->getSingleIdentifierFieldName());
1058
1059
                if ($property instanceof FieldMetadata && $property->hasValueGenerator()) {
1060
                    $property->setValue($entity, null);
1061
                }
1062
            }
1063
1064
            if ($invoke !== ListenersInvoker::INVOKE_NONE) {
1065
                $eventArgs = new LifecycleEventArgs($entity, $this->em);
1066
1067
                $this->listenersInvoker->invoke($class, Events::postRemove, $entity, $eventArgs, $invoke);
1068
            }
1069
        }
1070
    }
1071
1072
    /**
1073
     * Gets the commit order.
1074
     *
1075
     * @return array
1076
     */
1077
    private function getCommitOrder()
1078
    {
1079
        $calc = new Internal\CommitOrderCalculator();
1080
1081
        // See if there are any new classes in the changeset, that are not in the
1082
        // commit order graph yet (don't have a node).
1083
        // We have to inspect changeSet to be able to correctly build dependencies.
1084
        // It is not possible to use IdentityMap here because post inserted ids
1085
        // are not yet available.
1086
        $newNodes = [];
1087
1088
        foreach (\array_merge($this->entityInsertions, $this->entityUpdates, $this->entityDeletions) as $entity) {
1089
            $class = $this->em->getClassMetadata(get_class($entity));
1090
1091
            if ($calc->hasNode($class->getClassName())) {
1092
                continue;
1093
            }
1094
1095
            $calc->addNode($class->getClassName(), $class);
1096
1097
            $newNodes[] = $class;
1098
        }
1099
1100
        // Calculate dependencies for new nodes
1101
        while ($class = array_pop($newNodes)) {
1102
            foreach ($class->getDeclaredPropertiesIterator() as $property) {
1103
                if (! ($property instanceof ToOneAssociationMetadata && $property->isOwningSide())) {
1104
                    continue;
1105
                }
1106
1107
                $targetClass = $this->em->getClassMetadata($property->getTargetEntity());
1108
1109
                if ( ! $calc->hasNode($targetClass->getClassName())) {
1110
                    $calc->addNode($targetClass->getClassName(), $targetClass);
1111
1112
                    $newNodes[] = $targetClass;
1113
                }
1114
1115
                $weight = ! array_filter(
1116
                    $property->getJoinColumns(),
1117
                    function (JoinColumnMetadata $joinColumn) { return $joinColumn->isNullable(); }
1118
                );
1119
1120
                $calc->addDependency($targetClass->getClassName(), $class->getClassName(), $weight);
0 ignored issues
show
Bug introduced by
$weight of type boolean is incompatible with the type integer expected by parameter $weight of Doctrine\ORM\Internal\Co...ulator::addDependency(). ( Ignorable by Annotation )

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

1120
                $calc->addDependency($targetClass->getClassName(), $class->getClassName(), /** @scrutinizer ignore-type */ $weight);
Loading history...
1121
1122
                // If the target class has mapped subclasses, these share the same dependency.
1123
                if ( ! $targetClass->getSubClasses()) {
0 ignored issues
show
Bug introduced by
The method getSubClasses() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

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

1123
                if ( ! $targetClass->/** @scrutinizer ignore-call */ getSubClasses()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1124
                    continue;
1125
                }
1126
1127
                foreach ($targetClass->getSubClasses() as $subClassName) {
1128
                    $targetSubClass = $this->em->getClassMetadata($subClassName);
1129
1130
                    if ( ! $calc->hasNode($subClassName)) {
1131
                        $calc->addNode($targetSubClass->getClassName(), $targetSubClass);
1132
1133
                        $newNodes[] = $targetSubClass;
1134
                    }
1135
1136
                    $calc->addDependency($targetSubClass->getClassName(), $class->getClassName(), 1);
1137
                }
1138
            }
1139
        }
1140
1141
        return $calc->sort();
1142
    }
1143
1144
    /**
1145
     * Schedules an entity for insertion into the database.
1146
     * If the entity already has an identifier, it will be added to the identity map.
1147
     *
1148
     * @param object $entity The entity to schedule for insertion.
1149
     *
1150
     * @return void
1151
     *
1152
     * @throws ORMInvalidArgumentException
1153
     * @throws \InvalidArgumentException
1154
     */
1155
    public function scheduleForInsert($entity)
1156
    {
1157
        $oid = spl_object_id($entity);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1157
        $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
1158
1159
        if (isset($this->entityUpdates[$oid])) {
1160
            throw new InvalidArgumentException("Dirty entity can not be scheduled for insertion.");
1161
        }
1162
1163
        if (isset($this->entityDeletions[$oid])) {
1164
            throw ORMInvalidArgumentException::scheduleInsertForRemovedEntity($entity);
1165
        }
1166
        if (isset($this->originalEntityData[$oid]) && ! isset($this->entityInsertions[$oid])) {
1167
            throw ORMInvalidArgumentException::scheduleInsertForManagedEntity($entity);
1168
        }
1169
1170
        if (isset($this->entityInsertions[$oid])) {
1171
            throw ORMInvalidArgumentException::scheduleInsertTwice($entity);
1172
        }
1173
1174
        $this->entityInsertions[$oid] = $entity;
1175
1176
        if (isset($this->entityIdentifiers[$oid])) {
1177
            $this->addToIdentityMap($entity);
1178
        }
1179
1180
        if ($entity instanceof NotifyPropertyChanged) {
1181
            $entity->addPropertyChangedListener($this);
1182
        }
1183
    }
1184
1185
    /**
1186
     * Checks whether an entity is scheduled for insertion.
1187
     *
1188
     * @param object $entity
1189
     *
1190
     * @return boolean
1191
     */
1192
    public function isScheduledForInsert($entity)
1193
    {
1194
        return isset($this->entityInsertions[spl_object_id($entity)]);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1194
        return isset($this->entityInsertions[/** @scrutinizer ignore-call */ spl_object_id($entity)]);
Loading history...
1195
    }
1196
1197
    /**
1198
     * Schedules an entity for being updated.
1199
     *
1200
     * @param object $entity The entity to schedule for being updated.
1201
     *
1202
     * @return void
1203
     *
1204
     * @throws ORMInvalidArgumentException
1205
     */
1206
    public function scheduleForUpdate($entity) : void
1207
    {
1208
        $oid = spl_object_id($entity);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1208
        $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
1209
1210
        if ( ! isset($this->entityIdentifiers[$oid])) {
1211
            throw ORMInvalidArgumentException::entityHasNoIdentity($entity, "scheduling for update");
1212
        }
1213
1214
        if (isset($this->entityDeletions[$oid])) {
1215
            throw ORMInvalidArgumentException::entityIsRemoved($entity, "schedule for update");
1216
        }
1217
1218
        if ( ! isset($this->entityUpdates[$oid]) && ! isset($this->entityInsertions[$oid])) {
1219
            $this->entityUpdates[$oid] = $entity;
1220
        }
1221
    }
1222
1223
    /**
1224
     * INTERNAL:
1225
     * Schedules an extra update that will be executed immediately after the
1226
     * regular entity updates within the currently running commit cycle.
1227
     *
1228
     * Extra updates for entities are stored as (entity, changeset) tuples.
1229
     *
1230
     * @ignore
1231
     *
1232
     * @param object $entity    The entity for which to schedule an extra update.
1233
     * @param array  $changeset The changeset of the entity (what to update).
1234
     *
1235
     * @return void
1236
     */
1237
    public function scheduleExtraUpdate($entity, array $changeset) : void
1238
    {
1239
        $oid         = spl_object_id($entity);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1239
        $oid         = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
1240
        $extraUpdate = [$entity, $changeset];
1241
1242
        if (isset($this->extraUpdates[$oid])) {
1243
            [$unused, $changeset2] = $this->extraUpdates[$oid];
1244
1245
            $extraUpdate = [$entity, $changeset + $changeset2];
1246
        }
1247
1248
        $this->extraUpdates[$oid] = $extraUpdate;
1249
    }
1250
1251
    /**
1252
     * Checks whether an entity is registered as dirty in the unit of work.
1253
     * Note: Is not very useful currently as dirty entities are only registered
1254
     * at commit time.
1255
     *
1256
     * @param object $entity
1257
     *
1258
     * @return boolean
1259
     */
1260
    public function isScheduledForUpdate($entity) : bool
1261
    {
1262
        return isset($this->entityUpdates[spl_object_id($entity)]);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1262
        return isset($this->entityUpdates[/** @scrutinizer ignore-call */ spl_object_id($entity)]);
Loading history...
1263
    }
1264
1265
    /**
1266
     * Checks whether an entity is registered to be checked in the unit of work.
1267
     *
1268
     * @param object $entity
1269
     *
1270
     * @return boolean
1271
     */
1272
    public function isScheduledForDirtyCheck($entity) : bool
1273
    {
1274
        $rootEntityName = $this->em->getClassMetadata(get_class($entity))->getRootClassName();
0 ignored issues
show
Bug introduced by
The method getRootClassName() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

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

1274
        $rootEntityName = $this->em->getClassMetadata(get_class($entity))->/** @scrutinizer ignore-call */ getRootClassName();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1275
1276
        return isset($this->scheduledForSynchronization[$rootEntityName][spl_object_id($entity)]);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1276
        return isset($this->scheduledForSynchronization[$rootEntityName][/** @scrutinizer ignore-call */ spl_object_id($entity)]);
Loading history...
1277
    }
1278
1279
    /**
1280
     * INTERNAL:
1281
     * Schedules an entity for deletion.
1282
     *
1283
     * @param object $entity
1284
     *
1285
     * @return void
1286
     */
1287
    public function scheduleForDelete($entity)
1288
    {
1289
        $oid = spl_object_id($entity);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1289
        $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
1290
1291
        if (isset($this->entityInsertions[$oid])) {
1292
            if ($this->isInIdentityMap($entity)) {
1293
                $this->removeFromIdentityMap($entity);
1294
            }
1295
1296
            unset($this->entityInsertions[$oid], $this->entityStates[$oid]);
1297
1298
            return; // entity has not been persisted yet, so nothing more to do.
1299
        }
1300
1301
        if ( ! $this->isInIdentityMap($entity)) {
1302
            return;
1303
        }
1304
1305
        $this->removeFromIdentityMap($entity);
1306
1307
        unset($this->entityUpdates[$oid]);
1308
1309
        if ( ! isset($this->entityDeletions[$oid])) {
1310
            $this->entityDeletions[$oid] = $entity;
1311
            $this->entityStates[$oid]    = self::STATE_REMOVED;
1312
        }
1313
    }
1314
1315
    /**
1316
     * Checks whether an entity is registered as removed/deleted with the unit
1317
     * of work.
1318
     *
1319
     * @param object $entity
1320
     *
1321
     * @return boolean
1322
     */
1323
    public function isScheduledForDelete($entity)
1324
    {
1325
        return isset($this->entityDeletions[spl_object_id($entity)]);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1325
        return isset($this->entityDeletions[/** @scrutinizer ignore-call */ spl_object_id($entity)]);
Loading history...
1326
    }
1327
1328
    /**
1329
     * Checks whether an entity is scheduled for insertion, update or deletion.
1330
     *
1331
     * @param object $entity
1332
     *
1333
     * @return boolean
1334
     */
1335
    public function isEntityScheduled($entity)
1336
    {
1337
        $oid = spl_object_id($entity);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1337
        $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
1338
1339
        return isset($this->entityInsertions[$oid])
1340
            || isset($this->entityUpdates[$oid])
1341
            || isset($this->entityDeletions[$oid]);
1342
    }
1343
1344
    /**
1345
     * INTERNAL:
1346
     * Registers an entity in the identity map.
1347
     * Note that entities in a hierarchy are registered with the class name of
1348
     * the root entity.
1349
     *
1350
     * @ignore
1351
     *
1352
     * @param object $entity The entity to register.
1353
     *
1354
     * @return boolean TRUE if the registration was successful, FALSE if the identity of
1355
     *                 the entity in question is already managed.
1356
     *
1357
     * @throws ORMInvalidArgumentException
1358
     */
1359
    public function addToIdentityMap($entity)
1360
    {
1361
        $classMetadata = $this->em->getClassMetadata(get_class($entity));
1362
        $identifier    = $this->entityIdentifiers[spl_object_id($entity)];
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1362
        $identifier    = $this->entityIdentifiers[/** @scrutinizer ignore-call */ spl_object_id($entity)];
Loading history...
1363
1364
        if (empty($identifier) || in_array(null, $identifier, true)) {
1365
            throw ORMInvalidArgumentException::entityWithoutIdentity($classMetadata->getClassName(), $entity);
1366
        }
1367
1368
        $idHash    = implode(' ', $identifier);
1369
        $className = $classMetadata->getRootClassName();
1370
1371
        if (isset($this->identityMap[$className][$idHash])) {
1372
            return false;
1373
        }
1374
1375
        $this->identityMap[$className][$idHash] = $entity;
1376
1377
        return true;
1378
    }
1379
1380
    /**
1381
     * Gets the state of an entity with regard to the current unit of work.
1382
     *
1383
     * @param object   $entity
1384
     * @param int|null $assume The state to assume if the state is not yet known (not MANAGED or REMOVED).
1385
     *                         This parameter can be set to improve performance of entity state detection
1386
     *                         by potentially avoiding a database lookup if the distinction between NEW and DETACHED
1387
     *                         is either known or does not matter for the caller of the method.
1388
     *
1389
     * @return int The entity state.
1390
     */
1391 1
    public function getEntityState($entity, $assume = null)
1392
    {
1393 1
        $oid = spl_object_id($entity);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1393
        $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
1394
1395
        if (isset($this->entityStates[$oid])) {
1396
            return $this->entityStates[$oid];
1397
        }
1398
1399
        if ($assume !== null) {
1400
            return $assume;
1401
        }
1402
1403
        // State can only be NEW or DETACHED, because MANAGED/REMOVED states are known.
1404
        // Note that you can not remember the NEW or DETACHED state in _entityStates since
1405
        // the UoW does not hold references to such objects and the object hash can be reused.
1406
        // More generally because the state may "change" between NEW/DETACHED without the UoW being aware of it.
1407
        $class     = $this->em->getClassMetadata(get_class($entity));
1408
        $persister = $this->getEntityPersister($class->getClassName());
1409
        $id        = $persister->getIdentifier($entity);
1410
1411
        if (! $id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $id 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...
1412
            return self::STATE_NEW;
1413
        }
1414
1415
        $flatId = $this->em->getIdentifierFlattener()->flattenIdentifier($class, $id);
1416
1417
        if ($class->isIdentifierComposite()
1418
            || ! $class->getProperty($class->getSingleIdentifierFieldName()) instanceof FieldMetadata
1419
            || ! $class->getProperty($class->getSingleIdentifierFieldName())->hasValueGenerator()
1420
        ) {
1421
            // Check for a version field, if available, to avoid a db lookup.
1422
            if ($class->isVersioned()) {
1423
                return $class->versionProperty->getValue($entity)
1424
                    ? self::STATE_DETACHED
1425
                    : self::STATE_NEW;
1426
            }
1427
1428
            // Last try before db lookup: check the identity map.
1429
            if ($this->tryGetById($flatId, $class->getRootClassName())) {
1430
                return self::STATE_DETACHED;
1431
            }
1432
1433
            // db lookup
1434
            if ($this->getEntityPersister($class->getClassName())->exists($entity)) {
1435
                return self::STATE_DETACHED;
1436
            }
1437
1438
            return self::STATE_NEW;
1439
        }
1440
1441
        if ($class->isIdentifierComposite()
1442
            || ! $class->getProperty($class->getSingleIdentifierFieldName()) instanceof FieldMetadata
1443
            || ! $class->getValueGenerationPlan()->containsDeferred()) {
1444
            // if we have a pre insert generator we can't be sure that having an id
1445
            // really means that the entity exists. We have to verify this through
1446
            // the last resort: a db lookup
1447
1448
            // Last try before db lookup: check the identity map.
1449
            if ($this->tryGetById($flatId, $class->getRootClassName())) {
1450
                return self::STATE_DETACHED;
1451
            }
1452
1453
            // db lookup
1454
            if ($this->getEntityPersister($class->getClassName())->exists($entity)) {
1455
                return self::STATE_DETACHED;
1456
            }
1457
1458
            return self::STATE_NEW;
1459
        }
1460
1461
        return self::STATE_DETACHED;
1462
    }
1463
1464
    /**
1465
     * INTERNAL:
1466
     * Removes an entity from the identity map. This effectively detaches the
1467
     * entity from the persistence management of Doctrine.
1468
     *
1469
     * @ignore
1470
     *
1471
     * @param object $entity
1472
     *
1473
     * @return boolean
1474
     *
1475
     * @throws ORMInvalidArgumentException
1476
     */
1477
    public function removeFromIdentityMap($entity)
1478
    {
1479
        $oid           = spl_object_id($entity);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1479
        $oid           = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
1480
        $classMetadata = $this->em->getClassMetadata(get_class($entity));
1481
        $idHash        = implode(' ', $this->entityIdentifiers[$oid]);
1482
1483
        if ($idHash === '') {
1484
            throw ORMInvalidArgumentException::entityHasNoIdentity($entity, "remove from identity map");
1485
        }
1486
1487
        $className = $classMetadata->getRootClassName();
1488
1489
        if (isset($this->identityMap[$className][$idHash])) {
1490
            unset($this->identityMap[$className][$idHash], $this->readOnlyObjects[$oid]);
1491
1492
            //$this->entityStates[$oid] = self::STATE_DETACHED;
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1493
1494
            return true;
1495
        }
1496
1497
        return false;
1498
    }
1499
1500
    /**
1501
     * INTERNAL:
1502
     * Gets an entity in the identity map by its identifier hash.
1503
     *
1504
     * @ignore
1505
     *
1506
     * @param string $idHash
1507
     * @param string $rootClassName
1508
     *
1509
     * @return object
1510
     */
1511
    public function getByIdHash($idHash, $rootClassName)
1512
    {
1513
        return $this->identityMap[$rootClassName][$idHash];
1514
    }
1515
1516
    /**
1517
     * INTERNAL:
1518
     * Tries to get an entity by its identifier hash. If no entity is found for
1519
     * the given hash, FALSE is returned.
1520
     *
1521
     * @ignore
1522
     *
1523
     * @param mixed  $idHash        (must be possible to cast it to string)
1524
     * @param string $rootClassName
1525
     *
1526
     * @return object|bool The found entity or FALSE.
1527
     */
1528
    public function tryGetByIdHash($idHash, $rootClassName)
1529
    {
1530
        $stringIdHash = (string) $idHash;
1531
1532
        return $this->identityMap[$rootClassName][$stringIdHash] ?? false;
1533
    }
1534
1535
    /**
1536
     * Checks whether an entity is registered in the identity map of this UnitOfWork.
1537
     *
1538
     * @param object $entity
1539
     *
1540
     * @return boolean
1541
     */
1542 2
    public function isInIdentityMap($entity)
1543
    {
1544 2
        $oid = spl_object_id($entity);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1544
        $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
1545
1546
        if (empty($this->entityIdentifiers[$oid])) {
1547
            return false;
1548
        }
1549
1550
        $classMetadata = $this->em->getClassMetadata(get_class($entity));
1551
        $idHash        = implode(' ', $this->entityIdentifiers[$oid]);
1552
1553
        return isset($this->identityMap[$classMetadata->getRootClassName()][$idHash]);
1554
    }
1555
1556
    /**
1557
     * INTERNAL:
1558
     * Checks whether an identifier hash exists in the identity map.
1559
     *
1560
     * @ignore
1561
     *
1562
     * @param string $idHash
1563
     * @param string $rootClassName
1564
     *
1565
     * @return boolean
1566
     */
1567
    public function containsIdHash($idHash, $rootClassName)
1568
    {
1569
        return isset($this->identityMap[$rootClassName][$idHash]);
1570
    }
1571
1572
    /**
1573
     * Persists an entity as part of the current unit of work.
1574
     *
1575
     * @param object $entity The entity to persist.
1576
     *
1577
     * @return void
1578
     */
1579 26
    public function persist($entity)
1580
    {
1581 26
        $visited = [];
1582
1583 26
        $this->doPersist($entity, $visited);
1584
    }
1585
1586
    /**
1587
     * Persists an entity as part of the current unit of work.
1588
     *
1589
     * This method is internally called during persist() cascades as it tracks
1590
     * the already visited entities to prevent infinite recursions.
1591
     *
1592
     * @param object $entity  The entity to persist.
1593
     * @param array  $visited The already visited entities.
1594
     *
1595
     * @return void
1596
     *
1597
     * @throws ORMInvalidArgumentException
1598
     * @throws UnexpectedValueException
1599
     */
1600 26
    private function doPersist($entity, array &$visited)
1601
    {
1602 26
        $oid = spl_object_id($entity);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1602
        $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
1603
1604
        if (isset($visited[$oid])) {
1605
            return; // Prevent infinite recursion
1606
        }
1607
1608
        $visited[$oid] = $entity; // Mark visited
1609
1610
        $class = $this->em->getClassMetadata(get_class($entity));
1611
1612
        // We assume NEW, so DETACHED entities result in an exception on flush (constraint violation).
1613
        // If we would detect DETACHED here we would throw an exception anyway with the same
1614
        // consequences (not recoverable/programming error), so just assuming NEW here
1615
        // lets us avoid some database lookups for entities with natural identifiers.
1616
        $entityState = $this->getEntityState($entity, self::STATE_NEW);
1617
1618
        switch ($entityState) {
1619
            case self::STATE_MANAGED:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1620
                // Nothing to do, except if policy is "deferred explicit"
1621
                if ($class->changeTrackingPolicy === ChangeTrackingPolicy::DEFERRED_EXPLICIT) {
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...
1622
                    $this->scheduleForSynchronization($entity);
1623
                }
1624
                break;
1625
1626
            case self::STATE_NEW:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1627
                $this->persistNew($class, $entity);
0 ignored issues
show
Bug introduced by
$class of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $class of Doctrine\ORM\UnitOfWork::persistNew(). ( Ignorable by Annotation )

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

1627
                $this->persistNew(/** @scrutinizer ignore-type */ $class, $entity);
Loading history...
1628
                break;
1629
1630
            case self::STATE_REMOVED:
1631
                // Entity becomes managed again
1632
                unset($this->entityDeletions[$oid]);
1633
                $this->addToIdentityMap($entity);
1634
1635
                $this->entityStates[$oid] = self::STATE_MANAGED;
1636
                break;
1637
1638
            case self::STATE_DETACHED:
1639
                // Can actually not happen right now since we assume STATE_NEW.
1640
                throw ORMInvalidArgumentException::detachedEntityCannot($entity, "persisted");
1641
1642
            default:
1643
                throw new UnexpectedValueException("Unexpected entity state: $entityState." . self::objToStr($entity));
1644
        }
1645
1646
        $this->cascadePersist($entity, $visited);
1647
    }
1648
1649
    /**
1650
     * Deletes an entity as part of the current unit of work.
1651
     *
1652
     * @param object $entity The entity to remove.
1653
     *
1654
     * @return void
1655
     */
1656
    public function remove($entity)
1657
    {
1658
        $visited = [];
1659
1660
        $this->doRemove($entity, $visited);
1661
    }
1662
1663
    /**
1664
     * Deletes an entity as part of the current unit of work.
1665
     *
1666
     * This method is internally called during delete() cascades as it tracks
1667
     * the already visited entities to prevent infinite recursions.
1668
     *
1669
     * @param object $entity  The entity to delete.
1670
     * @param array  $visited The map of the already visited entities.
1671
     *
1672
     * @return void
1673
     *
1674
     * @throws ORMInvalidArgumentException If the instance is a detached entity.
1675
     * @throws UnexpectedValueException
1676
     */
1677
    private function doRemove($entity, array &$visited)
1678
    {
1679
        $oid = spl_object_id($entity);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1679
        $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
1680
1681
        if (isset($visited[$oid])) {
1682
            return; // Prevent infinite recursion
1683
        }
1684
1685
        $visited[$oid] = $entity; // mark visited
1686
1687
        // Cascade first, because scheduleForDelete() removes the entity from the identity map, which
1688
        // can cause problems when a lazy proxy has to be initialized for the cascade operation.
1689
        $this->cascadeRemove($entity, $visited);
1690
1691
        $class       = $this->em->getClassMetadata(get_class($entity));
1692
        $entityState = $this->getEntityState($entity);
1693
1694
        switch ($entityState) {
1695
            case self::STATE_NEW:
1696
            case self::STATE_REMOVED:
1697
                // nothing to do
1698
                break;
1699
1700
            case self::STATE_MANAGED:
1701
                $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preRemove);
0 ignored issues
show
Bug introduced by
$class of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $metadata of Doctrine\ORM\Event\Liste...:getSubscribedSystems(). ( Ignorable by Annotation )

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

1701
                $invoke = $this->listenersInvoker->getSubscribedSystems(/** @scrutinizer ignore-type */ $class, Events::preRemove);
Loading history...
1702
1703
                if ($invoke !== ListenersInvoker::INVOKE_NONE) {
1704
                    $this->listenersInvoker->invoke($class, Events::preRemove, $entity, new LifecycleEventArgs($entity, $this->em), $invoke);
0 ignored issues
show
Bug introduced by
$class of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $metadata of Doctrine\ORM\Event\ListenersInvoker::invoke(). ( Ignorable by Annotation )

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

1704
                    $this->listenersInvoker->invoke(/** @scrutinizer ignore-type */ $class, Events::preRemove, $entity, new LifecycleEventArgs($entity, $this->em), $invoke);
Loading history...
1705
                }
1706
1707
                $this->scheduleForDelete($entity);
1708
                break;
1709
1710
            case self::STATE_DETACHED:
1711
                throw ORMInvalidArgumentException::detachedEntityCannot($entity, "removed");
1712
            default:
1713
                throw new UnexpectedValueException("Unexpected entity state: $entityState." . self::objToStr($entity));
1714
        }
1715
1716
    }
1717
1718
    /**
1719
     * Refreshes the state of the given entity from the database, overwriting
1720
     * any local, unpersisted changes.
1721
     *
1722
     * @param object $entity The entity to refresh.
1723
     *
1724
     * @return void
1725
     *
1726
     * @throws InvalidArgumentException If the entity is not MANAGED.
1727
     */
1728
    public function refresh($entity)
1729
    {
1730
        $visited = [];
1731
1732
        $this->doRefresh($entity, $visited);
1733
    }
1734
1735
    /**
1736
     * Executes a refresh operation on an entity.
1737
     *
1738
     * @param object $entity  The entity to refresh.
1739
     * @param array  $visited The already visited entities during cascades.
1740
     *
1741
     * @return void
1742
     *
1743
     * @throws ORMInvalidArgumentException If the entity is not MANAGED.
1744
     */
1745
    private function doRefresh($entity, array &$visited)
1746
    {
1747
        $oid = spl_object_id($entity);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1747
        $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
1748
1749
        if (isset($visited[$oid])) {
1750
            return; // Prevent infinite recursion
1751
        }
1752
1753
        $visited[$oid] = $entity; // mark visited
1754
1755
        $class = $this->em->getClassMetadata(get_class($entity));
1756
1757
        if ($this->getEntityState($entity) !== self::STATE_MANAGED) {
1758
            throw ORMInvalidArgumentException::entityNotManaged($entity);
1759
        }
1760
1761
        $this->getEntityPersister($class->getClassName())->refresh(
1762
            array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
1763
            $entity
1764
        );
1765
1766
        $this->cascadeRefresh($entity, $visited);
1767
    }
1768
1769
    /**
1770
     * Cascades a refresh operation to associated entities.
1771
     *
1772
     * @param object $entity
1773
     * @param array  $visited
1774
     *
1775
     * @return void
1776
     */
1777
    private function cascadeRefresh($entity, array &$visited)
1778
    {
1779
        $class = $this->em->getClassMetadata(get_class($entity));
1780
1781
        foreach ($class->getDeclaredPropertiesIterator() as $association) {
1782
            if (! ($association instanceof AssociationMetadata && in_array('refresh', $association->getCascade()))) {
1783
                continue;
1784
            }
1785
1786
            $relatedEntities = $association->getValue($entity);
1787
1788
            switch (true) {
1789
                case ($relatedEntities instanceof PersistentCollection):
1790
                    // Unwrap so that foreach() does not initialize
1791
                    $relatedEntities = $relatedEntities->unwrap();
1792
                    // break; is commented intentionally!
1793
1794
                case ($relatedEntities instanceof Collection):
1795
                case (is_array($relatedEntities)):
1796
                    foreach ($relatedEntities as $relatedEntity) {
1797
                        $this->doRefresh($relatedEntity, $visited);
1798
                    }
1799
                    break;
1800
1801
                case ($relatedEntities !== null):
1802
                    $this->doRefresh($relatedEntities, $visited);
1803
                    break;
1804
1805
                default:
1806
                    // Do nothing
1807
            }
1808
        }
1809
    }
1810
1811
    /**
1812
     * Cascades the save operation to associated entities.
1813
     *
1814
     * @param object $entity
1815
     * @param array  $visited
1816
     *
1817
     * @throws ORMInvalidArgumentException
1818
     *
1819
     * @return void
1820
     */
1821
    private function cascadePersist($entity, array &$visited)
1822
    {
1823
        $class = $this->em->getClassMetadata(get_class($entity));
1824
1825
        if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
1826
            // nothing to do - proxy is not initialized, therefore we don't do anything with it
1827
            return;
1828
        }
1829
1830
        foreach ($class->getDeclaredPropertiesIterator() as $association) {
1831
            if (! ($association instanceof AssociationMetadata && in_array('persist', $association->getCascade()))) {
1832
                continue;
1833
            }
1834
1835
            /** @var AssociationMetadata $association */
1836
            $relatedEntities = $association->getValue($entity);
1837
            $targetEntity    = $association->getTargetEntity();
1838
1839
            switch (true) {
1840
                case ($relatedEntities instanceof PersistentCollection):
1841
                    // Unwrap so that foreach() does not initialize
1842
                    $relatedEntities = $relatedEntities->unwrap();
1843
                    // break; is commented intentionally!
1844
1845
                case ($relatedEntities instanceof Collection):
1846
                case (is_array($relatedEntities)):
1847
                    if (! ($association instanceof ToManyAssociationMetadata)) {
1848
                        throw ORMInvalidArgumentException::invalidAssociation(
1849
                            $this->em->getClassMetadata($targetEntity),
0 ignored issues
show
Bug introduced by
$this->em->getClassMetadata($targetEntity) of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $targetClass of Doctrine\ORM\ORMInvalidA...n::invalidAssociation(). ( Ignorable by Annotation )

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

1849
                            /** @scrutinizer ignore-type */ $this->em->getClassMetadata($targetEntity),
Loading history...
1850
                            $association,
1851
                            $relatedEntities
1852
                        );
1853
                    }
1854
1855
                    foreach ($relatedEntities as $relatedEntity) {
1856
                        $this->doPersist($relatedEntity, $visited);
1857
                    }
1858
1859
                    break;
1860
1861
                case ($relatedEntities !== null):
1862
                    if (! $relatedEntities instanceof $targetEntity) {
1863
                        throw ORMInvalidArgumentException::invalidAssociation(
1864
                            $this->em->getClassMetadata($targetEntity),
1865
                            $association,
1866
                            $relatedEntities
1867
                        );
1868
                    }
1869
1870
                    $this->doPersist($relatedEntities, $visited);
1871
                    break;
1872
1873
                default:
1874
                    // Do nothing
1875
            }
1876
        }
1877
    }
1878
1879
    /**
1880
     * Cascades the delete operation to associated entities.
1881
     *
1882
     * @param object $entity
1883
     * @param array  $visited
1884
     *
1885
     * @return void
1886
     */
1887
    private function cascadeRemove($entity, array &$visited)
1888
    {
1889
        $entitiesToCascade = [];
1890
        $class             = $this->em->getClassMetadata(get_class($entity));
1891
1892
        foreach ($class->getDeclaredPropertiesIterator() as $association) {
1893
            if (! ($association instanceof AssociationMetadata && in_array('remove', $association->getCascade()))) {
1894
                continue;
1895
            }
1896
1897
            if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
1898
                $entity->initializeProxy();
1899
            }
1900
1901
            $relatedEntities = $association->getValue($entity);
1902
1903
            switch (true) {
1904
                case ($relatedEntities instanceof Collection):
1905
                case (\is_array($relatedEntities)):
1906
                    // If its a PersistentCollection initialization is intended! No unwrap!
1907
                    foreach ($relatedEntities as $relatedEntity) {
1908
                        $entitiesToCascade[] = $relatedEntity;
1909
                    }
1910
                    break;
1911
1912
                case ($relatedEntities !== null):
1913
                    $entitiesToCascade[] = $relatedEntities;
1914
                    break;
1915
1916
                default:
1917
                    // Do nothing
1918
            }
1919
        }
1920
1921
        foreach ($entitiesToCascade as $relatedEntity) {
1922
            $this->doRemove($relatedEntity, $visited);
1923
        }
1924
    }
1925
1926
    /**
1927
     * Acquire a lock on the given entity.
1928
     *
1929
     * @param object $entity
1930
     * @param int    $lockMode
1931
     * @param int    $lockVersion
1932
     *
1933
     * @return void
1934
     *
1935
     * @throws ORMInvalidArgumentException
1936
     * @throws TransactionRequiredException
1937
     * @throws OptimisticLockException
1938
     * @throws \InvalidArgumentException
1939
     */
1940 2
    public function lock($entity, $lockMode, $lockVersion = null)
1941
    {
1942 2
        if ($entity === null) {
1943 1
            throw new \InvalidArgumentException("No entity passed to UnitOfWork#lock().");
1944
        }
1945
1946 1
        if ($this->getEntityState($entity, self::STATE_DETACHED) !== self::STATE_MANAGED) {
1947
            throw ORMInvalidArgumentException::entityNotManaged($entity);
1948
        }
1949
1950
        $class = $this->em->getClassMetadata(get_class($entity));
1951
1952
        switch (true) {
1953
            case LockMode::OPTIMISTIC === $lockMode:
1954
                if ( ! $class->isVersioned()) {
1955
                    throw OptimisticLockException::notVersioned($class->getClassName());
1956
                }
1957
1958
                if ($lockVersion === null) {
1959
                    return;
1960
                }
1961
1962
                if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
1963
                    $entity->initializeProxy();
1964
                }
1965
1966
                $entityVersion = $class->versionProperty->getValue($entity);
0 ignored issues
show
Bug introduced by
Accessing versionProperty on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
1967
1968
                if ($entityVersion !== $lockVersion) {
1969
                    throw OptimisticLockException::lockFailedVersionMismatch($entity, $lockVersion, $entityVersion);
1970
                }
1971
1972
                break;
1973
1974
            case LockMode::NONE === $lockMode:
1975
            case LockMode::PESSIMISTIC_READ === $lockMode:
1976
            case LockMode::PESSIMISTIC_WRITE === $lockMode:
1977
                if (!$this->em->getConnection()->isTransactionActive()) {
1978
                    throw TransactionRequiredException::transactionRequired();
1979
                }
1980
1981
                $oid = spl_object_id($entity);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1981
                $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
1982
1983
                $this->getEntityPersister($class->getClassName())->lock(
1984
                    array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
1985
                    $lockMode
1986
                );
1987
                break;
1988
1989
            default:
1990
                // Do nothing
1991
        }
1992
    }
1993
1994
    /**
1995
     * Clears the UnitOfWork.
1996
     *
1997
     * @return void
1998
     */
1999 207
    public function clear()
2000
    {
2001 207
        $this->entityPersisters =
2002 207
        $this->collectionPersisters =
2003 207
        $this->eagerLoadingEntities =
2004 207
        $this->identityMap =
2005 207
        $this->entityIdentifiers =
2006 207
        $this->originalEntityData =
2007 207
        $this->entityChangeSets =
2008 207
        $this->entityStates =
2009 207
        $this->scheduledForSynchronization =
2010 207
        $this->entityInsertions =
2011 207
        $this->entityUpdates =
2012 207
        $this->entityDeletions =
2013 207
        $this->collectionDeletions =
2014 207
        $this->collectionUpdates =
2015 207
        $this->extraUpdates =
2016 207
        $this->readOnlyObjects =
2017 207
        $this->visitedCollections =
2018 207
        $this->nonCascadedNewDetectedEntities =
2019 207
        $this->orphanRemovals = [];
2020 207
    }
2021
2022
    /**
2023
     * INTERNAL:
2024
     * Schedules an orphaned entity for removal. The remove() operation will be
2025
     * invoked on that entity at the beginning of the next commit of this
2026
     * UnitOfWork.
2027
     *
2028
     * @ignore
2029
     *
2030
     * @param object $entity
2031
     *
2032
     * @return void
2033
     */
2034
    public function scheduleOrphanRemoval($entity)
2035
    {
2036
        $this->orphanRemovals[spl_object_id($entity)] = $entity;
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2036
        $this->orphanRemovals[/** @scrutinizer ignore-call */ spl_object_id($entity)] = $entity;
Loading history...
2037
    }
2038
2039
    /**
2040
     * INTERNAL:
2041
     * Cancels a previously scheduled orphan removal.
2042
     *
2043
     * @ignore
2044
     *
2045
     * @param object $entity
2046
     *
2047
     * @return void
2048
     */
2049 1
    public function cancelOrphanRemoval($entity)
2050
    {
2051 1
        unset($this->orphanRemovals[spl_object_id($entity)]);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2051
        unset($this->orphanRemovals[/** @scrutinizer ignore-call */ spl_object_id($entity)]);
Loading history...
2052
    }
2053
2054
    /**
2055
     * INTERNAL:
2056
     * Schedules a complete collection for removal when this UnitOfWork commits.
2057
     *
2058
     * @param PersistentCollection $coll
2059
     *
2060
     * @return void
2061
     */
2062
    public function scheduleCollectionDeletion(PersistentCollection $coll)
2063
    {
2064
        $coid = spl_object_id($coll);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2064
        $coid = /** @scrutinizer ignore-call */ spl_object_id($coll);
Loading history...
2065
2066
        // TODO: if $coll is already scheduled for recreation ... what to do?
2067
        // Just remove $coll from the scheduled recreations?
2068
        unset($this->collectionUpdates[$coid]);
2069
2070
        $this->collectionDeletions[$coid] = $coll;
2071
    }
2072
2073
    /**
2074
     * @param PersistentCollection $coll
2075
     *
2076
     * @return bool
2077
     */
2078
    public function isCollectionScheduledForDeletion(PersistentCollection $coll)
2079
    {
2080
        return isset($this->collectionDeletions[spl_object_id($coll)]);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2080
        return isset($this->collectionDeletions[/** @scrutinizer ignore-call */ spl_object_id($coll)]);
Loading history...
2081
    }
2082
2083
    /**
2084
     * INTERNAL:
2085
     * Creates a new instance of the mapped class, without invoking the constructor.
2086
     * This is only meant to be used internally, and should not be consumed by end users.
2087
     *
2088
     * @ignore
2089
     *
2090
     * @param ClassMetadata $class
2091
     *
2092
     * @return EntityManagerAware|object
2093
     */
2094 4
    public function newInstance(ClassMetadata $class)
2095
    {
2096 4
        $entity = $this->instantiator->instantiate($class->getClassName());
2097
2098 4
        if ($entity instanceof EntityManagerAware) {
2099
            $entity->injectEntityManager($this->em, $class);
2100
        }
2101
2102 4
        return $entity;
2103
    }
2104
2105
    /**
2106
     * INTERNAL:
2107
     * Creates an entity. Used for reconstitution of persistent entities.
2108
     *
2109
     * Internal note: Highly performance-sensitive method.
2110
     *
2111
     * @ignore
2112
     *
2113
     * @param string $className The name of the entity class.
2114
     * @param array  $data      The data for the entity.
2115
     * @param array  $hints     Any hints to account for during reconstitution/lookup of the entity.
2116
     *
2117
     * @return object The managed entity instance.
2118
     *
2119
     * @todo Rename: getOrCreateEntity
2120
     */
2121 2
    public function createEntity($className, array $data, &$hints = [])
2122
    {
2123 2
        $class  = $this->em->getClassMetadata($className);
2124 2
        $id     = $this->em->getIdentifierFlattener()->flattenIdentifier($class, $data);
0 ignored issues
show
Bug introduced by
$class of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $class of Doctrine\ORM\Utility\Ide...er::flattenIdentifier(). ( Ignorable by Annotation )

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

2124
        $id     = $this->em->getIdentifierFlattener()->flattenIdentifier(/** @scrutinizer ignore-type */ $class, $data);
Loading history...
2125 2
        $idHash = implode(' ', $id);
2126
2127 2
        if (isset($this->identityMap[$class->getRootClassName()][$idHash])) {
2128
            $entity = $this->identityMap[$class->getRootClassName()][$idHash];
2129
            $oid = spl_object_id($entity);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2129
            $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
2130
2131
            if (
2132
                isset($hints[Query::HINT_REFRESH], $hints[Query::HINT_REFRESH_ENTITY])
2133
                && ($unmanagedProxy = $hints[Query::HINT_REFRESH_ENTITY]) !== $entity
2134
                && $unmanagedProxy instanceof GhostObjectInterface
2135
                && $this->isIdentifierEquals($unmanagedProxy, $entity)
2136
            ) {
2137
                // We will hydrate the given un-managed proxy anyway:
2138
                // continue work, but consider it the entity from now on
2139
                $entity = $unmanagedProxy;
2140
            }
2141
2142
            if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
2143
                $entity->setProxyInitializer(null);
2144
2145
                if ($entity instanceof NotifyPropertyChanged) {
2146
                    $entity->addPropertyChangedListener($this);
2147
                }
2148
            } else {
2149
                if ( ! isset($hints[Query::HINT_REFRESH])
2150
                    || (isset($hints[Query::HINT_REFRESH_ENTITY]) && $hints[Query::HINT_REFRESH_ENTITY] !== $entity)) {
2151
                    return $entity;
2152
                }
2153
            }
2154
2155
            // inject EntityManager upon refresh.
2156
            if ($entity instanceof EntityManagerAware) {
2157
                $entity->injectEntityManager($this->em, $class);
0 ignored issues
show
Bug introduced by
$class of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $classMetadata of Doctrine\ORM\EntityManag...::injectEntityManager(). ( Ignorable by Annotation )

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

2157
                $entity->injectEntityManager($this->em, /** @scrutinizer ignore-type */ $class);
Loading history...
2158
            }
2159
2160
            $this->originalEntityData[$oid] = $data;
2161
        } else {
2162 2
            $entity = $this->newInstance($class);
0 ignored issues
show
Bug introduced by
$class of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $class of Doctrine\ORM\UnitOfWork::newInstance(). ( Ignorable by Annotation )

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

2162
            $entity = $this->newInstance(/** @scrutinizer ignore-type */ $class);
Loading history...
2163 2
            $oid    = spl_object_id($entity);
2164
2165
            $this->entityIdentifiers[$oid]  = $id;
2166
            $this->entityStates[$oid]       = self::STATE_MANAGED;
2167
            $this->originalEntityData[$oid] = $data;
2168
2169
            $this->identityMap[$class->getRootClassName()][$idHash] = $entity;
2170
        }
2171
2172
        if ($entity instanceof NotifyPropertyChanged) {
2173
            $entity->addPropertyChangedListener($this);
2174
        }
2175
2176
        foreach ($data as $field => $value) {
2177
            $property = $class->getProperty($field);
2178
2179
            if ($property instanceof FieldMetadata) {
2180
                $property->setValue($entity, $value);
2181
            }
2182
        }
2183
2184
        // Loading the entity right here, if its in the eager loading map get rid of it there.
2185
        unset($this->eagerLoadingEntities[$class->getRootClassName()][$idHash]);
2186
2187
        if (isset($this->eagerLoadingEntities[$class->getRootClassName()]) && ! $this->eagerLoadingEntities[$class->getRootClassName()]) {
2188
            unset($this->eagerLoadingEntities[$class->getRootClassName()]);
2189
        }
2190
2191
        // Properly initialize any unfetched associations, if partial objects are not allowed.
2192
        if (isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) {
2193
            return $entity;
2194
        }
2195
2196
        foreach ($class->getDeclaredPropertiesIterator() as $field => $association) {
2197
            if (! ($association instanceof AssociationMetadata)) {
2198
                continue;
2199
            }
2200
2201
            // Check if the association is not among the fetch-joined associations already.
2202
            if (isset($hints['fetchAlias'], $hints['fetched'][$hints['fetchAlias']][$field])) {
2203
                continue;
2204
            }
2205
2206
            $targetEntity = $association->getTargetEntity();
2207
            $targetClass  = $this->em->getClassMetadata($targetEntity);
2208
2209
            if ($association instanceof ToManyAssociationMetadata) {
2210
                // Ignore if its a cached collection
2211
                if (isset($hints[Query::HINT_CACHE_ENABLED]) &&
2212
                    $association->getValue($entity) instanceof PersistentCollection) {
2213
                    continue;
2214
                }
2215
2216
                $hasDataField = isset($data[$field]);
2217
2218
                // use the given collection
2219
                if ($hasDataField && $data[$field] instanceof PersistentCollection) {
2220
                    $data[$field]->setOwner($entity, $association);
2221
2222
                    $association->setValue($entity, $data[$field]);
2223
2224
                    $this->originalEntityData[$oid][$field] = $data[$field];
2225
2226
                    continue;
2227
                }
2228
2229
                // Inject collection
2230
                $pColl = $association->wrap($entity, $hasDataField ? $data[$field] : [], $this->em);
2231
2232
                $pColl->setInitialized($hasDataField);
2233
2234
                $association->setValue($entity, $pColl);
2235
2236
                if ($association->getFetchMode() === FetchMode::EAGER) {
2237
                    $this->loadCollection($pColl);
2238
                    $pColl->takeSnapshot();
2239
                }
2240
2241
                $this->originalEntityData[$oid][$field] = $pColl;
2242
2243
                continue;
2244
            }
2245
2246
            if (! $association->isOwningSide()) {
2247
                // use the given entity association
2248
                if (isset($data[$field]) && is_object($data[$field]) &&
2249
                    isset($this->entityStates[spl_object_id($data[$field])])) {
2250
                    $inverseAssociation = $targetClass->getProperty($association->getMappedBy());
2251
2252
                    $association->setValue($entity, $data[$field]);
2253
                    $inverseAssociation->setValue($data[$field], $entity);
2254
2255
                    $this->originalEntityData[$oid][$field] = $data[$field];
2256
2257
                    continue;
2258
                }
2259
2260
                // Inverse side of x-to-one can never be lazy
2261
                $persister = $this->getEntityPersister($targetEntity);
2262
2263
                $association->setValue($entity, $persister->loadToOneEntity($association, $entity));
2264
2265
                continue;
2266
            }
2267
2268
            // use the entity association
2269
            if (isset($data[$field]) && is_object($data[$field]) && isset($this->entityStates[spl_object_id($data[$field])])) {
2270
                $association->setValue($entity, $data[$field]);
2271
2272
                $this->originalEntityData[$oid][$field] = $data[$field];
2273
2274
                continue;
2275
            }
2276
2277
            $associatedId = [];
2278
2279
            // TODO: Is this even computed right in all cases of composite keys?
2280
            foreach ($association->getJoinColumns() as $joinColumn) {
0 ignored issues
show
Bug introduced by
The method getJoinColumns() does not exist on Doctrine\ORM\Mapping\AssociationMetadata. It seems like you code against a sub-type of Doctrine\ORM\Mapping\AssociationMetadata such as Doctrine\ORM\Mapping\ToOneAssociationMetadata. ( Ignorable by Annotation )

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

2280
            foreach ($association->/** @scrutinizer ignore-call */ getJoinColumns() as $joinColumn) {
Loading history...
2281
                /** @var JoinColumnMetadata $joinColumn */
2282
                $joinColumnName = $joinColumn->getColumnName();
2283
                $joinColumnValue = $data[$joinColumnName] ?? null;
2284
                $targetField     = $targetClass->fieldNames[$joinColumn->getReferencedColumnName()];
0 ignored issues
show
Bug introduced by
Accessing fieldNames on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2285
2286
                if ($joinColumnValue === null && in_array($targetField, $targetClass->identifier, true)) {
0 ignored issues
show
Bug introduced by
Accessing identifier on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2287
                    // the missing key is part of target's entity primary key
2288
                    $associatedId = [];
2289
2290
                    continue;
2291
                }
2292
2293
                $associatedId[$targetField] = $joinColumnValue;
2294
            }
2295
2296
            if (! $associatedId) {
2297
                // Foreign key is NULL
2298
                $association->setValue($entity, null);
2299
                $this->originalEntityData[$oid][$field] = null;
2300
2301
                continue;
2302
            }
2303
2304
            // @todo guilhermeblanco Can we remove the need of this somehow?
2305
            if (!isset($hints['fetchMode'][$class->getClassName()][$field])) {
2306
                $hints['fetchMode'][$class->getClassName()][$field] = $association->getFetchMode();
2307
            }
2308
2309
            // Foreign key is set
2310
            // Check identity map first
2311
            // FIXME: Can break easily with composite keys if join column values are in
2312
            //        wrong order. The correct order is the one in ClassMetadata#identifier.
2313
            $relatedIdHash = implode(' ', $associatedId);
2314
2315
            switch (true) {
2316
                case (isset($this->identityMap[$targetClass->getRootClassName()][$relatedIdHash])):
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
2317
                    $newValue = $this->identityMap[$targetClass->getRootClassName()][$relatedIdHash];
2318
2319
                    // If this is an uninitialized proxy, we are deferring eager loads,
2320
                    // this association is marked as eager fetch, and its an uninitialized proxy (wtf!)
2321
                    // then we can append this entity for eager loading!
2322
                    if (!$targetClass->isIdentifierComposite() &&
2323
                        $newValue instanceof GhostObjectInterface &&
2324
                        isset($hints[self::HINT_DEFEREAGERLOAD]) &&
2325
                        $hints['fetchMode'][$class->getClassName()][$field] === FetchMode::EAGER &&
2326
                        ! $newValue->isProxyInitialized()
2327
                    ) {
2328
2329
                        $this->eagerLoadingEntities[$targetClass->getRootClassName()][$relatedIdHash] = current($associatedId);
2330
                    }
2331
2332
                    break;
2333
2334
                case ($targetClass->getSubClasses()):
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
2335
                    // If it might be a subtype, it can not be lazy. There isn't even
2336
                    // a way to solve this with deferred eager loading, which means putting
2337
                    // an entity with subclasses at a *-to-one location is really bad! (performance-wise)
2338
                    $persister = $this->getEntityPersister($targetEntity);
2339
                    $newValue  = $persister->loadToOneEntity($association, $entity, $associatedId);
2340
                    break;
2341
2342
                default:
2343
                    // Proxies do not carry any kind of original entity data until they're fully loaded/initialized
2344
                    $managedData = [];
2345
2346
                    $normalizedAssociatedId = $this->normalizeIdentifier->__invoke(
2347
                        $this->em,
2348
                        $targetClass,
2349
                        $associatedId
2350
                    );
2351
2352
                    switch (true) {
2353
                        // We are negating the condition here. Other cases will assume it is valid!
2354
                        case ($hints['fetchMode'][$class->getClassName()][$field] !== FetchMode::EAGER):
2355
                            $newValue = $this->em->getProxyFactory()->getProxy($targetClass, $normalizedAssociatedId);
2356
                            break;
2357
2358
                        // Deferred eager load only works for single identifier classes
2359
                        case (isset($hints[self::HINT_DEFEREAGERLOAD]) && !$targetClass->isIdentifierComposite()):
2360
                            // TODO: Is there a faster approach?
2361
                            $this->eagerLoadingEntities[$targetClass->getRootClassName()][$relatedIdHash] = current($normalizedAssociatedId);
2362
2363
                            $newValue = $this->em->getProxyFactory()->getProxy($targetClass, $normalizedAssociatedId);
2364
                            break;
2365
2366
                        default:
2367
                            // TODO: This is very imperformant, ignore it?
2368
                            $newValue    = $this->em->find($targetEntity, $normalizedAssociatedId);
2369
                            // Needed to re-assign original entity data for freshly loaded entity
2370
                            $managedData = $this->originalEntityData[spl_object_id($newValue)];
2371
                            break;
2372
                    }
2373
2374
                    // @TODO using `$associatedId` here seems to be risky.
2375
                    $this->registerManaged($newValue, $associatedId, $managedData);
2376
2377
                    break;
2378
            }
2379
2380
            $this->originalEntityData[$oid][$field] = $newValue;
2381
            $association->setValue($entity, $newValue);
2382
2383
            if (
2384
                $association->getInversedBy()
2385
                && $association instanceof OneToOneAssociationMetadata
2386
                // @TODO refactor this
2387
                // we don't want to set any values in un-initialized proxies
2388
                && ! (
2389
                    $newValue instanceof GhostObjectInterface
2390
                    && ! $newValue->isProxyInitialized()
2391
                )
2392
            ) {
2393
                $inverseAssociation = $targetClass->getProperty($association->getInversedBy());
2394
2395
                $inverseAssociation->setValue($newValue, $entity);
2396
            }
2397
        }
2398
2399
        // defer invoking of postLoad event to hydration complete step
2400
        $this->hydrationCompleteHandler->deferPostLoadInvoking($class, $entity);
0 ignored issues
show
Bug introduced by
$class of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $class of Doctrine\ORM\Internal\Hy...deferPostLoadInvoking(). ( Ignorable by Annotation )

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

2400
        $this->hydrationCompleteHandler->deferPostLoadInvoking(/** @scrutinizer ignore-type */ $class, $entity);
Loading history...
2401
2402
        return $entity;
2403
    }
2404
2405
    /**
2406
     * @return void
2407
     */
2408 37
    public function triggerEagerLoads()
2409
    {
2410 37
        if ( ! $this->eagerLoadingEntities) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->eagerLoadingEntities 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...
2411 37
            return;
2412
        }
2413
2414
        // avoid infinite recursion
2415
        $eagerLoadingEntities       = $this->eagerLoadingEntities;
2416
        $this->eagerLoadingEntities = [];
2417
2418
        foreach ($eagerLoadingEntities as $entityName => $ids) {
2419
            if ( ! $ids) {
2420
                continue;
2421
            }
2422
2423
            $class = $this->em->getClassMetadata($entityName);
2424
2425
            $this->getEntityPersister($entityName)->loadAll(
2426
                array_combine($class->identifier, [array_values($ids)])
0 ignored issues
show
Bug introduced by
Accessing identifier on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2427
            );
2428
        }
2429
    }
2430
2431
    /**
2432
     * Initializes (loads) an uninitialized persistent collection of an entity.
2433
     *
2434
     * @param \Doctrine\ORM\PersistentCollection $collection The collection to initialize.
2435
     *
2436
     * @return void
2437
     *
2438
     * @todo Maybe later move to EntityManager#initialize($proxyOrCollection). See DDC-733.
2439
     */
2440 4
    public function loadCollection(PersistentCollection $collection)
2441
    {
2442 4
        $association = $collection->getMapping();
2443 4
        $persister   = $this->getEntityPersister($association->getTargetEntity());
2444
2445 4
        if ($association instanceof OneToManyAssociationMetadata) {
2446
            $persister->loadOneToManyCollection($association, $collection->getOwner(), $collection);
2447
        } else {
2448 4
            $persister->loadManyToManyCollection($association, $collection->getOwner(), $collection);
2449
        }
2450
2451 4
        $collection->setInitialized(true);
2452 4
    }
2453
2454
    /**
2455
     * Gets the identity map of the UnitOfWork.
2456
     *
2457
     * @return array
2458
     */
2459
    public function getIdentityMap()
2460
    {
2461
        return $this->identityMap;
2462
    }
2463
2464
    /**
2465
     * Gets the original data of an entity. The original data is the data that was
2466
     * present at the time the entity was reconstituted from the database.
2467
     *
2468
     * @param object $entity
2469
     *
2470
     * @return array
2471
     */
2472
    public function getOriginalEntityData($entity)
2473
    {
2474
        $oid = spl_object_id($entity);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2474
        $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
2475
2476
        return $this->originalEntityData[$oid] ?? [];
2477
    }
2478
2479
    /**
2480
     * @ignore
2481
     *
2482
     * @param object $entity
2483
     * @param array  $data
2484
     *
2485
     * @return void
2486
     */
2487
    public function setOriginalEntityData($entity, array $data)
2488
    {
2489
        $this->originalEntityData[spl_object_id($entity)] = $data;
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2489
        $this->originalEntityData[/** @scrutinizer ignore-call */ spl_object_id($entity)] = $data;
Loading history...
2490
    }
2491
2492
    /**
2493
     * INTERNAL:
2494
     * Sets a property value of the original data array of an entity.
2495
     *
2496
     * @ignore
2497
     *
2498
     * @param string $oid
2499
     * @param string $property
2500
     * @param mixed  $value
2501
     *
2502
     * @return void
2503
     */
2504
    public function setOriginalEntityProperty($oid, $property, $value)
2505
    {
2506
        $this->originalEntityData[$oid][$property] = $value;
2507
    }
2508
2509
    /**
2510
     * Gets the identifier of an entity.
2511
     * The returned value is always an array of identifier values. If the entity
2512
     * has a composite identifier then the identifier values are in the same
2513
     * order as the identifier field names as returned by ClassMetadata#getIdentifierFieldNames().
2514
     *
2515
     * @param object $entity
2516
     *
2517
     * @return array The identifier values.
2518
     */
2519
    public function getEntityIdentifier($entity)
2520
    {
2521
        return $this->entityIdentifiers[spl_object_id($entity)];
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2521
        return $this->entityIdentifiers[/** @scrutinizer ignore-call */ spl_object_id($entity)];
Loading history...
2522
    }
2523
2524
    /**
2525
     * Processes an entity instance to extract their identifier values.
2526
     *
2527
     * @param object $entity The entity instance.
2528
     *
2529
     * @return mixed A scalar value.
2530
     *
2531
     * @throws \Doctrine\ORM\ORMInvalidArgumentException
2532
     */
2533 1
    public function getSingleIdentifierValue($entity)
2534
    {
2535 1
        $class     = $this->em->getClassMetadata(get_class($entity));
2536 1
        $persister = $this->getEntityPersister($class->getClassName());
2537
2538 1
        if ($class->isIdentifierComposite()) {
0 ignored issues
show
Bug introduced by
The method isIdentifierComposite() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. Did you maybe mean isIdentifier()? ( Ignorable by Annotation )

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

2538
        if ($class->/** @scrutinizer ignore-call */ isIdentifierComposite()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
2539
            throw ORMInvalidArgumentException::invalidCompositeIdentifier();
2540
        }
2541
2542 1
        $values = $this->isInIdentityMap($entity)
2543
            ? $this->getEntityIdentifier($entity)
2544
            : $persister->getIdentifier($entity);
2545
2546
        return $values[$class->identifier[0]] ?? null;
0 ignored issues
show
Bug introduced by
Accessing identifier on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2547
    }
2548
2549
    /**
2550
     * @param array  $id
2551
     * @param string $rootClassName
2552
     *
2553
     * @return GhostObjectInterface|object
2554
     */
2555
    private function tryGetByIdOrLoadProxy(array $id, string $rootClassName)
0 ignored issues
show
Unused Code introduced by
The method tryGetByIdOrLoadProxy() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
2556
    {
2557
        if ($fetched = $this->tryGetById($id, $rootClassName)) {
2558
            return $fetched;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $fetched also could return the type true which is incompatible with the documented return type object|ProxyManager\Proxy\GhostObjectInterface.
Loading history...
2559
        }
2560
2561
        $class = $this->em->getClassMetadata($rootClassName);
2562
2563
        if ($class->getSubClasses()) {
2564
            // can't do this for inheritance trees!
2565
            // @TODO fetching from the EntityManager feels dirty here
2566
            return $this->em->find($rootClassName, $id);
2567
        }
2568
2569
        $sortedId = [];
2570
2571
        foreach ($class->identifier as $idField) {
0 ignored issues
show
Bug introduced by
Accessing identifier on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
2572
            $sortedId[$idField] = $id[$idField];
2573
        }
2574
2575
        $proxy = $this->em->getProxyFactory()->getProxy($class, $sortedId);
0 ignored issues
show
Bug introduced by
$class of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $metadata of Doctrine\ORM\Proxy\Facto...roxyFactory::getProxy(). ( Ignorable by Annotation )

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

2575
        $proxy = $this->em->getProxyFactory()->getProxy(/** @scrutinizer ignore-type */ $class, $sortedId);
Loading history...
2576
2577
        $this->registerManaged($proxy, $sortedId, []);
2578
2579
        return $proxy;
2580
    }
2581
2582
    /**
2583
     * Tries to find an entity with the given identifier in the identity map of
2584
     * this UnitOfWork.
2585
     *
2586
     * @param mixed  $id            The entity identifier to look for.
2587
     * @param string $rootClassName The name of the root class of the mapped entity hierarchy.
2588
     *
2589
     * @return object|bool Returns the entity with the specified identifier if it exists in
2590
     *                     this UnitOfWork, FALSE otherwise.
2591
     */
2592 1
    public function tryGetById($id, $rootClassName)
2593
    {
2594 1
        $idHash = implode(' ', (array) $id);
2595
2596 1
        return $this->identityMap[$rootClassName][$idHash] ?? false;
2597
    }
2598
2599
    /**
2600
     * Schedules an entity for dirty-checking at commit-time.
2601
     *
2602
     * @param object $entity The entity to schedule for dirty-checking.
2603
     *
2604
     * @return void
2605
     */
2606
    public function scheduleForSynchronization($entity)
2607
    {
2608
        $rootClassName = $this->em->getClassMetadata(get_class($entity))->getRootClassName();
2609
2610
        $this->scheduledForSynchronization[$rootClassName][spl_object_id($entity)] = $entity;
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2610
        $this->scheduledForSynchronization[$rootClassName][/** @scrutinizer ignore-call */ spl_object_id($entity)] = $entity;
Loading history...
2611
    }
2612
2613
    /**
2614
     * Checks whether the UnitOfWork has any pending insertions.
2615
     *
2616
     * @return boolean TRUE if this UnitOfWork has pending insertions, FALSE otherwise.
2617
     */
2618
    public function hasPendingInsertions()
2619
    {
2620
        return ! empty($this->entityInsertions);
2621
    }
2622
2623
    /**
2624
     * Calculates the size of the UnitOfWork. The size of the UnitOfWork is the
2625
     * number of entities in the identity map.
2626
     *
2627
     * @return integer
2628
     */
2629
    public function size()
2630
    {
2631
        return \array_sum(\array_map('count', $this->identityMap));
2632
    }
2633
2634
    /**
2635
     * Gets the EntityPersister for an Entity.
2636
     *
2637
     * @param string $entityName The name of the Entity.
2638
     *
2639
     * @return \Doctrine\ORM\Persisters\Entity\EntityPersister
2640
     */
2641 28
    public function getEntityPersister($entityName)
2642
    {
2643 28
        if (isset($this->entityPersisters[$entityName])) {
2644 11
            return $this->entityPersisters[$entityName];
2645
        }
2646
2647 28
        $class = $this->em->getClassMetadata($entityName);
2648
2649
        switch (true) {
2650 28
            case ($class->inheritanceType === InheritanceType::NONE):
0 ignored issues
show
Bug introduced by
Accessing inheritanceType on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
2651 24
                $persister = new BasicEntityPersister($this->em, $class);
0 ignored issues
show
Bug introduced by
$class of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $class of Doctrine\ORM\Persisters\...ersister::__construct(). ( Ignorable by Annotation )

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

2651
                $persister = new BasicEntityPersister($this->em, /** @scrutinizer ignore-type */ $class);
Loading history...
2652 24
                break;
2653
2654 9
            case ($class->inheritanceType === InheritanceType::SINGLE_TABLE):
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
2655 6
                $persister = new SingleTablePersister($this->em, $class);
0 ignored issues
show
Bug introduced by
$class of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $class of Doctrine\ORM\Persisters\...ersister::__construct(). ( Ignorable by Annotation )

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

2655
                $persister = new SingleTablePersister($this->em, /** @scrutinizer ignore-type */ $class);
Loading history...
2656 6
                break;
2657
2658 8
            case ($class->inheritanceType === InheritanceType::JOINED):
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
2659 8
                $persister = new JoinedSubclassPersister($this->em, $class);
0 ignored issues
show
Bug introduced by
$class of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $class of Doctrine\ORM\Persisters\...ersister::__construct(). ( Ignorable by Annotation )

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

2659
                $persister = new JoinedSubclassPersister($this->em, /** @scrutinizer ignore-type */ $class);
Loading history...
2660 8
                break;
2661
2662
            default:
0 ignored issues
show
Coding Style introduced by
DEFAULT statements must be defined using a colon

As per the PSR-2 coding standard, default statements should not be wrapped in curly braces.

switch ($expr) {
    default: { //wrong
        doSomething();
        break;
    }
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
2663
                throw new \RuntimeException('No persister found for entity.');
2664
        }
2665
2666 28
        if ($this->hasCache && $class->getCache()) {
0 ignored issues
show
Bug introduced by
The method getCache() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

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

2666
        if ($this->hasCache && $class->/** @scrutinizer ignore-call */ getCache()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
2667 20
            $persister = $this->em->getConfiguration()
2668 20
                ->getSecondLevelCacheConfiguration()
2669 20
                ->getCacheFactory()
2670 20
                ->buildCachedEntityPersister($this->em, $persister, $class);
0 ignored issues
show
Bug introduced by
$class of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $metadata of Doctrine\ORM\Cache\Cache...CachedEntityPersister(). ( Ignorable by Annotation )

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

2670
                ->buildCachedEntityPersister($this->em, $persister, /** @scrutinizer ignore-type */ $class);
Loading history...
2671
        }
2672
2673 28
        $this->entityPersisters[$entityName] = $persister;
2674
2675 28
        return $this->entityPersisters[$entityName];
2676
    }
2677
2678
    /**
2679
     * Gets a collection persister for a collection-valued association.
2680
     *
2681
     * @param ToManyAssociationMetadata $association
2682
     *
2683
     * @return \Doctrine\ORM\Persisters\Collection\CollectionPersister
2684
     */
2685 16
    public function getCollectionPersister(ToManyAssociationMetadata $association)
2686
    {
2687 16
        $role = $association->getCache()
2688 10
            ? sprintf('%s::%s', $association->getSourceEntity(), $association->getName())
2689 16
            : get_class($association);
2690
2691 16
        if (isset($this->collectionPersisters[$role])) {
2692 6
            return $this->collectionPersisters[$role];
2693
        }
2694
2695 16
        $persister = $association instanceof OneToManyAssociationMetadata
2696 13
            ? new OneToManyPersister($this->em)
2697 16
            : new ManyToManyPersister($this->em);
2698
2699 16
        if ($this->hasCache && $association->getCache()) {
2700 10
            $persister = $this->em->getConfiguration()
2701 10
                ->getSecondLevelCacheConfiguration()
2702 10
                ->getCacheFactory()
2703 10
                ->buildCachedCollectionPersister($this->em, $persister, $association);
2704
        }
2705
2706 16
        $this->collectionPersisters[$role] = $persister;
2707
2708 16
        return $this->collectionPersisters[$role];
2709
    }
2710
2711
    /**
2712
     * INTERNAL:
2713
     * Registers an entity as managed.
2714
     *
2715
     * @param object $entity The entity.
2716
     * @param array  $id     Map containing identifier field names as key and its associated values.
2717
     * @param array  $data   The original entity data.
2718
     *
2719
     * @return void
2720
     */
2721 7
    public function registerManaged($entity, array $id, array $data)
2722
    {
2723 7
        $isProxy = $entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized();
2724 7
        $oid     = spl_object_id($entity);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2724
        $oid     = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
2725
2726
        $this->entityIdentifiers[$oid]  = $id;
2727
        $this->entityStates[$oid]       = self::STATE_MANAGED;
2728
        $this->originalEntityData[$oid] = $data;
2729
2730
        $this->addToIdentityMap($entity);
2731
2732
        if ($entity instanceof NotifyPropertyChanged && ! $isProxy) {
2733
            $entity->addPropertyChangedListener($this);
2734
        }
2735
    }
2736
2737
    /**
2738
     * INTERNAL:
2739
     * Clears the property changeset of the entity with the given OID.
2740
     *
2741
     * @param string $oid The entity's OID.
2742
     *
2743
     * @return void
2744
     */
2745
    public function clearEntityChangeSet($oid)
2746
    {
2747
        unset($this->entityChangeSets[$oid]);
2748
    }
2749
2750
    /* PropertyChangedListener implementation */
2751
2752
    /**
2753
     * Notifies this UnitOfWork of a property change in an entity.
2754
     *
2755
     * @param object $entity       The entity that owns the property.
2756
     * @param string $propertyName The name of the property that changed.
2757
     * @param mixed  $oldValue     The old value of the property.
2758
     * @param mixed  $newValue     The new value of the property.
2759
     *
2760
     * @return void
2761
     */
2762
    public function propertyChanged($entity, $propertyName, $oldValue, $newValue)
2763
    {
2764
        $class = $this->em->getClassMetadata(get_class($entity));
2765
2766
        if (! $class->getProperty($propertyName)) {
2767
            return; // ignore non-persistent fields
2768
        }
2769
2770
        $oid = spl_object_id($entity);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2770
        $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
2771
2772
        // Update changeset and mark entity for synchronization
2773
        $this->entityChangeSets[$oid][$propertyName] = [$oldValue, $newValue];
2774
2775
        if ( ! isset($this->scheduledForSynchronization[$class->getRootClassName()][$oid])) {
2776
            $this->scheduleForSynchronization($entity);
2777
        }
2778
    }
2779
2780
    /**
2781
     * Gets the currently scheduled entity insertions in this UnitOfWork.
2782
     *
2783
     * @return array
2784
     */
2785
    public function getScheduledEntityInsertions()
2786
    {
2787
        return $this->entityInsertions;
2788
    }
2789
2790
    /**
2791
     * Gets the currently scheduled entity updates in this UnitOfWork.
2792
     *
2793
     * @return array
2794
     */
2795
    public function getScheduledEntityUpdates()
2796
    {
2797
        return $this->entityUpdates;
2798
    }
2799
2800
    /**
2801
     * Gets the currently scheduled entity deletions in this UnitOfWork.
2802
     *
2803
     * @return array
2804
     */
2805
    public function getScheduledEntityDeletions()
2806
    {
2807
        return $this->entityDeletions;
2808
    }
2809
2810
    /**
2811
     * Gets the currently scheduled complete collection deletions
2812
     *
2813
     * @return array
2814
     */
2815 1
    public function getScheduledCollectionDeletions()
2816
    {
2817 1
        return $this->collectionDeletions;
2818
    }
2819
2820
    /**
2821
     * Gets the currently scheduled collection inserts, updates and deletes.
2822
     *
2823
     * @return array
2824
     */
2825
    public function getScheduledCollectionUpdates()
2826
    {
2827
        return $this->collectionUpdates;
2828
    }
2829
2830
    /**
2831
     * Helper method to initialize a lazy loading proxy or persistent collection.
2832
     *
2833
     * @param object $obj
2834
     *
2835
     * @return void
2836
     */
2837
    public function initializeObject($obj)
2838
    {
2839
        if ($obj instanceof GhostObjectInterface) {
2840
            $obj->initializeProxy();
2841
2842
            return;
2843
        }
2844
2845
        if ($obj instanceof PersistentCollection) {
2846
            $obj->initialize();
2847
        }
2848
    }
2849
2850
    /**
2851
     * Helper method to show an object as string.
2852
     *
2853
     * @param object $obj
2854
     *
2855
     * @return string
2856
     */
2857
    private static function objToStr($obj)
2858
    {
2859
        return method_exists($obj, '__toString') ? (string) $obj : get_class($obj).'@'.spl_object_id($obj);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2859
        return method_exists($obj, '__toString') ? (string) $obj : get_class($obj).'@'./** @scrutinizer ignore-call */ spl_object_id($obj);
Loading history...
2860
    }
2861
2862
    /**
2863
     * Marks an entity as read-only so that it will not be considered for updates during UnitOfWork#commit().
2864
     *
2865
     * This operation cannot be undone as some parts of the UnitOfWork now keep gathering information
2866
     * on this object that might be necessary to perform a correct update.
2867
     *
2868
     * @param object $object
2869
     *
2870
     * @return void
2871
     *
2872
     * @throws ORMInvalidArgumentException
2873
     */
2874 1
    public function markReadOnly($object)
2875
    {
2876 1
        if ( ! is_object($object) || ! $this->isInIdentityMap($object)) {
2877
            throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object);
2878
        }
2879
2880
        $this->readOnlyObjects[spl_object_id($object)] = true;
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2880
        $this->readOnlyObjects[/** @scrutinizer ignore-call */ spl_object_id($object)] = true;
Loading history...
2881
    }
2882
2883
    /**
2884
     * Is this entity read only?
2885
     *
2886
     * @param object $object
2887
     *
2888
     * @return bool
2889
     *
2890
     * @throws ORMInvalidArgumentException
2891
     */
2892
    public function isReadOnly($object)
2893
    {
2894
        if ( ! is_object($object)) {
2895
            throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object);
2896
        }
2897
2898
        return isset($this->readOnlyObjects[spl_object_id($object)]);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2898
        return isset($this->readOnlyObjects[/** @scrutinizer ignore-call */ spl_object_id($object)]);
Loading history...
2899
    }
2900
2901
    /**
2902
     * Perform whatever processing is encapsulated here after completion of the transaction.
2903
     */
2904
    private function afterTransactionComplete()
2905
    {
2906
        $this->performCallbackOnCachedPersister(function (CachedPersister $persister) {
2907
            $persister->afterTransactionComplete();
2908
        });
2909
    }
2910
2911
    /**
2912
     * Perform whatever processing is encapsulated here after completion of the rolled-back.
2913
     */
2914
    private function afterTransactionRolledBack()
2915
    {
2916
        $this->performCallbackOnCachedPersister(function (CachedPersister $persister) {
2917
            $persister->afterTransactionRolledBack();
2918
        });
2919
    }
2920
2921
    /**
2922
     * Performs an action after the transaction.
2923
     *
2924
     * @param callable $callback
2925
     */
2926
    private function performCallbackOnCachedPersister(callable $callback)
2927
    {
2928
        if ( ! $this->hasCache) {
2929
            return;
2930
        }
2931
2932
        foreach (array_merge($this->entityPersisters, $this->collectionPersisters) as $persister) {
2933
            if ($persister instanceof CachedPersister) {
2934
                $callback($persister);
2935
            }
2936
        }
2937
    }
2938
2939 5
    private function dispatchOnFlushEvent()
2940
    {
2941 5
        if ($this->eventManager->hasListeners(Events::onFlush)) {
2942 1
            $this->eventManager->dispatchEvent(Events::onFlush, new OnFlushEventArgs($this->em));
2943
        }
2944 5
    }
2945
2946 5
    private function dispatchPostFlushEvent()
2947
    {
2948 5
        if ($this->eventManager->hasListeners(Events::postFlush)) {
2949 1
            $this->eventManager->dispatchEvent(Events::postFlush, new PostFlushEventArgs($this->em));
2950
        }
2951 5
    }
2952
2953
    /**
2954
     * Verifies if two given entities actually are the same based on identifier comparison
2955
     *
2956
     * @param object $entity1
2957
     * @param object $entity2
2958
     *
2959
     * @return bool
2960
     */
2961
    private function isIdentifierEquals($entity1, $entity2)
2962
    {
2963
        if ($entity1 === $entity2) {
2964
            return true;
2965
        }
2966
2967
        $class     = $this->em->getClassMetadata(get_class($entity1));
2968
        $persister = $this->getEntityPersister($class->getClassName());
2969
2970
        if ($class !== $this->em->getClassMetadata(get_class($entity2))) {
2971
            return false;
2972
        }
2973
2974
        $identifierFlattener = $this->em->getIdentifierFlattener();
2975
2976
        $oid1 = spl_object_id($entity1);
0 ignored issues
show
Bug introduced by
The function spl_object_id was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2976
        $oid1 = /** @scrutinizer ignore-call */ spl_object_id($entity1);
Loading history...
2977
        $oid2 = spl_object_id($entity2);
2978
2979
        $id1 = $this->entityIdentifiers[$oid1]
2980
            ?? $identifierFlattener->flattenIdentifier($class, $persister->getIdentifier($entity1));
0 ignored issues
show
Bug introduced by
$class of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $class of Doctrine\ORM\Utility\Ide...er::flattenIdentifier(). ( Ignorable by Annotation )

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

2980
            ?? $identifierFlattener->flattenIdentifier(/** @scrutinizer ignore-type */ $class, $persister->getIdentifier($entity1));
Loading history...
2981
        $id2 = $this->entityIdentifiers[$oid2]
2982
            ?? $identifierFlattener->flattenIdentifier($class, $persister->getIdentifier($entity2));
2983
2984
        return $id1 === $id2 || implode(' ', $id1) === implode(' ', $id2);
2985
    }
2986
2987
    /**
2988
     * @throws ORMInvalidArgumentException
2989
     */
2990
    private function assertThatThereAreNoUnintentionallyNonPersistedAssociations() : void
2991
    {
2992
        $entitiesNeedingCascadePersist = \array_diff_key($this->nonCascadedNewDetectedEntities, $this->entityInsertions);
2993
2994
        $this->nonCascadedNewDetectedEntities = [];
2995
2996
        if ($entitiesNeedingCascadePersist) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $entitiesNeedingCascadePersist 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...
2997
            throw ORMInvalidArgumentException::newEntitiesFoundThroughRelationships(
2998
                \array_values($entitiesNeedingCascadePersist)
2999
            );
3000
        }
3001
    }
3002
3003
    /**
3004
     * This method called by hydrators, and indicates that hydrator totally completed current hydration cycle.
3005
     * Unit of work able to fire deferred events, related to loading events here.
3006
     *
3007
     * @internal should be called internally from object hydrators
3008
     */
3009 37
    public function hydrationComplete()
3010
    {
3011 37
        $this->hydrationCompleteHandler->hydrationComplete();
3012 37
    }
3013
}
3014