Failed Conditions
Pull Request — master (#6959)
by Matthew
19:32
created

UnitOfWork::scheduleCollectionDeletion()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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

431
            $this->computeChangeSet(/** @scrutinizer ignore-type */ $class, $entity);
Loading history...
432
        }
433 998
    }
434
435
    /**
436
     * Executes any extra updates that have been scheduled.
437
     */
438 31
    private function executeExtraUpdates()
439
    {
440 31
        foreach ($this->extraUpdates as $oid => $update) {
441 31
            list ($entity, $changeset) = $update;
442
443 31
            $this->entityChangeSets[$oid] = $changeset;
444
445
//            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...
446
//            \Doctrine\Common\Util\Debug::dump($changeset, 3);
447
448 31
            $this->getEntityPersister(get_class($entity))->update($entity);
449
        }
450
451 31
        $this->extraUpdates = [];
452 31
    }
453
454
    /**
455
     * Gets the changeset for an entity.
456
     *
457
     * @param object $entity
458
     *
459
     * @return mixed[]
460
     */
461 986
    public function & getEntityChangeSet($entity)
462
    {
463 986
        $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

463
        $oid  = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
464 986
        $data = [];
465
466 986
        if (! isset($this->entityChangeSets[$oid])) {
467 2
            return $data;
468
        }
469
470 986
        return $this->entityChangeSets[$oid];
471
    }
472
473
    /**
474
     * Computes the changes that happened to a single entity.
475
     *
476
     * Modifies/populates the following properties:
477
     *
478
     * {@link originalEntityData}
479
     * If the entity is NEW or MANAGED but not yet fully persisted (only has an id)
480
     * then it was not fetched from the database and therefore we have no original
481
     * entity data yet. All of the current entity data is stored as the original entity data.
482
     *
483
     * {@link entityChangeSets}
484
     * The changes detected on all properties of the entity are stored there.
485
     * A change is a tuple array where the first entry is the old value and the second
486
     * entry is the new value of the property. Changesets are used by persisters
487
     * to INSERT/UPDATE the persistent entity state.
488
     *
489
     * {@link entityUpdates}
490
     * If the entity is already fully MANAGED (has been fetched from the database before)
491
     * and any changes to its properties are detected, then a reference to the entity is stored
492
     * there to mark it for an update.
493
     *
494
     * {@link collectionDeletions}
495
     * If a PersistentCollection has been de-referenced in a fully MANAGED entity,
496
     * then this collection is marked for deletion.
497
     *
498
     * @ignore
499
     *
500
     * @internal Don't call from the outside.
501
     *
502
     * @param ClassMetadata $class  The class descriptor of the entity.
503
     * @param object        $entity The entity for which to compute the changes.
504
     *
505
     */
506 1001
    public function computeChangeSet(ClassMetadata $class, $entity)
507
    {
508 1001
        $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

508
        $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
509
510 1001
        if (isset($this->readOnlyObjects[$oid])) {
511 2
            return;
512
        }
513
514 1001
        if ($class->inheritanceType !== InheritanceType::NONE) {
515 323
            $class = $this->em->getClassMetadata(get_class($entity));
516
        }
517
518 1001
        $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

518
        $invoke = $this->listenersInvoker->getSubscribedSystems(/** @scrutinizer ignore-type */ $class, Events::preFlush) & ~ListenersInvoker::INVOKE_MANAGER;
Loading history...
519
520 1001
        if ($invoke !== ListenersInvoker::INVOKE_NONE) {
521 131
            $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

521
            $this->listenersInvoker->invoke(/** @scrutinizer ignore-type */ $class, Events::preFlush, $entity, new PreFlushEventArgs($this->em), $invoke);
Loading history...
522
        }
523
524 1001
        $actualData = [];
525
526 1001
        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

526
        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...
527 1001
            $value = $property->getValue($entity);
528
529 1001
            if ($property instanceof ToManyAssociationMetadata && $value !== null) {
530 763
                if ($value instanceof PersistentCollection && $value->getOwner() === $entity) {
531 179
                    continue;
532
                }
533
534 760
                $value = $property->wrap($entity, $value, $this->em);
535
536 760
                $property->setValue($entity, $value);
537
538 760
                $actualData[$name] = $value;
539
540 760
                continue;
541
            }
542
543 1001
            if (( ! $class->isIdentifier($name)
544 1001
                    || ! $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

544
                    || ! $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...
545 1001
                    || ! $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

545
                    || ! $class->getProperty($name)->/** @scrutinizer ignore-call */ hasValueGenerator()
Loading history...
546 1001
                    || $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

546
                    || $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...
547 1001
                ) && (! $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

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

701
            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...
702 1
                continue;
703
            }
704
705
            // If change tracking is explicit or happens through notification, then only compute
706
            // changes on entities of that type that are explicitly marked for synchronization.
707
            switch (true) {
708 438
                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...
709 436
                    $entitiesToProcess = $entities;
710 436
                    break;
711
712 3
                case (isset($this->scheduledForSynchronization[$className])):
713 3
                    $entitiesToProcess = $this->scheduledForSynchronization[$className];
714 3
                    break;
715
716
                default:
717 1
                    $entitiesToProcess = [];
718
            }
719
720 438
            foreach ($entitiesToProcess as $entity) {
721
                // Ignore uninitialized proxy objects
722 419
                if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
723 36
                    continue;
724
                }
725
726
                // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION OR DELETION are processed here.
727 418
                $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

727
                $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
728
729 418
                if (! isset($this->entityInsertions[$oid]) && ! isset($this->entityDeletions[$oid]) && isset($this->entityStates[$oid])) {
730 438
                    $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

730
                    $this->computeChangeSet(/** @scrutinizer ignore-type */ $class, $entity);
Loading history...
731
                }
732
            }
733
        }
734 998
    }
735
736
    /**
737
     * Computes the changes of an association.
738
     *
739
     * @param AssociationMetadata $association The association mapping.
740
     * @param mixed               $value       The value of the association.
741
     *
742
     * @throws ORMInvalidArgumentException
743
     * @throws ORMException
744
     */
745 850
    private function computeAssociationChanges(AssociationMetadata $association, $value)
746
    {
747 850
        if ($value instanceof GhostObjectInterface && ! $value->isProxyInitialized()) {
748 30
            return;
749
        }
750
751 849
        if ($value instanceof PersistentCollection && $value->isDirty()) {
752 524
            $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

752
            $coid = /** @scrutinizer ignore-call */ spl_object_id($value);
Loading history...
753
754 524
            $this->collectionUpdates[$coid]  = $value;
755 524
            $this->visitedCollections[$coid] = $value;
756
        }
757
758
        // Look through the entities, and in any of their associations,
759
        // 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...
760
        // Unwrap. Uninitialized collections will simply be empty.
761 849
        $unwrappedValue = ($association instanceof ToOneAssociationMetadata) ? [$value] : $value->unwrap();
762 849
        $targetEntity   = $association->getTargetEntity();
763 849
        $targetClass    = $this->em->getClassMetadata($targetEntity);
764
765 849
        foreach ($unwrappedValue as $key => $entry) {
766 710
            if (! ($entry instanceof $targetEntity)) {
767 8
                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

767
                throw ORMInvalidArgumentException::invalidAssociation(/** @scrutinizer ignore-type */ $targetClass, $association, $entry);
Loading history...
768
            }
769
770 702
            $state = $this->getEntityState($entry, self::STATE_NEW);
771
772 702
            if (! ($entry instanceof $targetEntity)) {
773
                throw ORMException::unexpectedAssociationValue(
774
                    $association->getSourceEntity(),
775
                    $association->getName(),
776
                    get_class($entry),
777
                    $targetEntity
778
                );
779
            }
780
781
            switch ($state) {
782 702
                case self::STATE_NEW:
783 41
                    if (! in_array('persist', $association->getCascade())) {
784 5
                        $this->nonCascadedNewDetectedEntities[\spl_object_id($entry)] = [$association, $entry];
785
786 5
                        break;
787
                    }
788
789 37
                    $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

789
                    $this->persistNew(/** @scrutinizer ignore-type */ $targetClass, $entry);
Loading history...
790 37
                    $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

790
                    $this->computeChangeSet(/** @scrutinizer ignore-type */ $targetClass, $entry);
Loading history...
791
792 37
                    break;
793
794 695
                case self::STATE_REMOVED:
795
                    // Consume the $value as array (it's either an array or an ArrayAccess)
796
                    // and remove the element from Collection.
797 4
                    if ($association instanceof ToManyAssociationMetadata) {
798 3
                        unset($value[$key]);
799
                    }
800 4
                    break;
801
802 695
                case self::STATE_DETACHED:
803
                    // Can actually not happen right now as we assume STATE_NEW,
804
                    // so the exception will be raised from the DBAL layer (constraint violation).
805
                    throw ORMInvalidArgumentException::detachedEntityFoundThroughRelationship($association, $entry);
806
                    break;
807
808 702
                default:
809
                    // MANAGED associated entities are already taken into account
810
                    // during changeset calculation anyway, since they are in the identity map.
811
            }
812
        }
813 841
    }
814
815
    /**
816
     * @param \Doctrine\ORM\Mapping\ClassMetadata $class
817
     * @param object                              $entity
818
     */
819 1012
    private function persistNew($class, $entity)
820
    {
821 1012
        $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

821
        $oid    = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
822 1012
        $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::prePersist);
823
824 1012
        if ($invoke !== ListenersInvoker::INVOKE_NONE) {
825 133
            $this->listenersInvoker->invoke($class, Events::prePersist, $entity, new LifecycleEventArgs($entity, $this->em), $invoke);
826
        }
827
828 1012
        $generationPlan = $class->getValueGenerationPlan();
829 1012
        $persister      = $this->getEntityPersister($class->getClassName());
830 1012
        $generationPlan->executeImmediate($this->em, $entity);
831
832 1012
        if (! $generationPlan->containsDeferred()) {
833 264
            $id                            = $this->em->getIdentifierFlattener()->flattenIdentifier($class, $persister->getIdentifier($entity));
834 264
            $this->entityIdentifiers[$oid] = $id;
835
        }
836
837 1012
        $this->entityStates[$oid] = self::STATE_MANAGED;
838
839 1012
        $this->scheduleForInsert($entity);
840 1012
    }
841
842
    /**
843
     * INTERNAL:
844
     * Computes the changeset of an individual entity, independently of the
845
     * computeChangeSets() routine that is used at the beginning of a UnitOfWork#commit().
846
     *
847
     * The passed entity must be a managed entity. If the entity already has a change set
848
     * because this method is invoked during a commit cycle then the change sets are added.
849
     * whereby changes detected in this method prevail.
850
     *
851
     * @ignore
852
     *
853
     * @param ClassMetadata $class  The class descriptor of the entity.
854
     * @param object        $entity The entity for which to (re)calculate the change set.
855
     *
856
     * @throws ORMInvalidArgumentException If the passed entity is not MANAGED.
857
     * @throws \RuntimeException
858
     */
859 14
    public function recomputeSingleEntityChangeSet(ClassMetadata $class, $entity) : void
860
    {
861 14
        $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

861
        $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
862
863 14
        if (! isset($this->entityStates[$oid]) || $this->entityStates[$oid] !== self::STATE_MANAGED) {
864
            throw ORMInvalidArgumentException::entityNotManaged($entity);
865
        }
866
867
        // skip if change tracking is "NOTIFY"
868 14
        if ($class->changeTrackingPolicy === ChangeTrackingPolicy::NOTIFY) {
869
            return;
870
        }
871
872 14
        if ($class->inheritanceType !== InheritanceType::NONE) {
873 3
            $class = $this->em->getClassMetadata(get_class($entity));
874
        }
875
876 14
        $actualData = [];
877
878 14
        foreach ($class->getDeclaredPropertiesIterator() as $name => $property) {
879
            switch (true) {
880 14
                case ($property instanceof VersionFieldMetadata):
881
                    // Ignore version field
882
                    break;
883
884 14
                case ($property instanceof FieldMetadata):
885 14
                    if (! $property->isPrimaryKey()
886 14
                        || ! $property->getValueGenerator()
887 14
                        || $property->getValueGenerator()->getType() !== GeneratorType::IDENTITY) {
888 14
                        $actualData[$name] = $property->getValue($entity);
889
                    }
890
891 14
                    break;
892
893 11
                case ($property instanceof ToOneAssociationMetadata):
894 9
                    $actualData[$name] = $property->getValue($entity);
895 14
                    break;
896
            }
897
        }
898
899 14
        if (! isset($this->originalEntityData[$oid])) {
900
            throw new \RuntimeException('Cannot call recomputeSingleEntityChangeSet before computeChangeSet on an entity.');
901
        }
902
903 14
        $originalData = $this->originalEntityData[$oid];
904 14
        $changeSet    = [];
905
906 14
        foreach ($actualData as $propName => $actualValue) {
907 14
            $orgValue = $originalData[$propName] ?? null;
908
909 14
            if ($orgValue !== $actualValue) {
910 14
                $changeSet[$propName] = [$orgValue, $actualValue];
911
            }
912
        }
913
914 14
        if ($changeSet) {
915 6
            if (isset($this->entityChangeSets[$oid])) {
916 5
                $this->entityChangeSets[$oid] = array_merge($this->entityChangeSets[$oid], $changeSet);
917 1
            } elseif (! isset($this->entityInsertions[$oid])) {
918 1
                $this->entityChangeSets[$oid] = $changeSet;
919 1
                $this->entityUpdates[$oid]    = $entity;
920
            }
921 6
            $this->originalEntityData[$oid] = $actualData;
922
        }
923 14
    }
924
925
    /**
926
     * Executes all entity insertions for entities of the specified type.
927
     */
928 987
    private function executeInserts(ClassMetadata $class) : void
929
    {
930 987
        $className      = $class->getClassName();
931 987
        $persister      = $this->getEntityPersister($className);
932 987
        $invoke         = $this->listenersInvoker->getSubscribedSystems($class, Events::postPersist);
933 987
        $generationPlan = $class->getValueGenerationPlan();
934
935 987
        foreach ($this->entityInsertions as $oid => $entity) {
936 987
            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

936
            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...
937 842
                continue;
938
            }
939
940 987
            $persister->insert($entity);
941
942 986
            if ($generationPlan->containsDeferred()) {
943
                // Entity has post-insert IDs
944 894
                $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

944
                $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
945 894
                $id  = $this->em->getIdentifierFlattener()->flattenIdentifier($class, $persister->getIdentifier($entity));
946
947 894
                $this->entityIdentifiers[$oid]  = $id;
948 894
                $this->entityStates[$oid]       = self::STATE_MANAGED;
949 894
                $this->originalEntityData[$oid] = $id + $this->originalEntityData[$oid];
950
951 894
                $this->addToIdentityMap($entity);
952
            }
953
954 986
            unset($this->entityInsertions[$oid]);
955
956 986
            if ($invoke !== ListenersInvoker::INVOKE_NONE) {
957 129
                $eventArgs = new LifecycleEventArgs($entity, $this->em);
958
959 986
                $this->listenersInvoker->invoke($class, Events::postPersist, $entity, $eventArgs, $invoke);
960
            }
961
        }
962 987
    }
963
964
    /**
965
     * Executes all entity updates for entities of the specified type.
966
     *
967
     * @param \Doctrine\ORM\Mapping\ClassMetadata $class
968
     */
969 107
    private function executeUpdates($class)
970
    {
971 107
        $className        = $class->getClassName();
972 107
        $persister        = $this->getEntityPersister($className);
973 107
        $preUpdateInvoke  = $this->listenersInvoker->getSubscribedSystems($class, Events::preUpdate);
974 107
        $postUpdateInvoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postUpdate);
975
976 107
        foreach ($this->entityUpdates as $oid => $entity) {
977 107
            if ($this->em->getClassMetadata(get_class($entity))->getClassName() !== $className) {
978 70
                continue;
979
            }
980
981 107
            if ($preUpdateInvoke != ListenersInvoker::INVOKE_NONE) {
982 11
                $changeSet = $this->getEntityChangeSet($entity);
983 11
                $this->listenersInvoker->invoke($class, Events::preUpdate, $entity, new PreUpdateEventArgs($entity, $this->em, $changeSet), $preUpdateInvoke);
984
985 11
                $this->recomputeSingleEntityChangeSet($class, $entity);
986
            }
987
988 107
            if (! empty($this->entityChangeSets[$oid])) {
989
//                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...
990
//                \Doctrine\Common\Util\Debug::dump($this->entityChangeSets[$oid], 3);
991
992 77
                $persister->update($entity);
993
            }
994
995 103
            unset($this->entityUpdates[$oid]);
996
997 103
            if ($postUpdateInvoke !== ListenersInvoker::INVOKE_NONE) {
998 103
                $this->listenersInvoker->invoke($class, Events::postUpdate, $entity, new LifecycleEventArgs($entity, $this->em), $postUpdateInvoke);
999
            }
1000
        }
1001 103
    }
1002
1003
    /**
1004
     * Executes all entity deletions for entities of the specified type.
1005
     *
1006
     * @param \Doctrine\ORM\Mapping\ClassMetadata $class
1007
     */
1008 58
    private function executeDeletions($class)
1009
    {
1010 58
        $className = $class->getClassName();
1011 58
        $persister = $this->getEntityPersister($className);
1012 58
        $invoke    = $this->listenersInvoker->getSubscribedSystems($class, Events::postRemove);
1013
1014 58
        foreach ($this->entityDeletions as $oid => $entity) {
1015 58
            if ($this->em->getClassMetadata(get_class($entity))->getClassName() !== $className) {
1016 24
                continue;
1017
            }
1018
1019 58
            $persister->delete($entity);
1020
1021
            unset(
1022 58
                $this->entityDeletions[$oid],
1023 58
                $this->entityIdentifiers[$oid],
1024 58
                $this->originalEntityData[$oid],
1025 58
                $this->entityStates[$oid]
1026
            );
1027
1028
            // Entity with this $oid after deletion treated as NEW, even if the $oid
1029
            // is obtained by a new entity because the old one went out of scope.
1030
            //$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...
1031 58
            if (! $class->isIdentifierComposite()) {
1032 55
                $property = $class->getProperty($class->getSingleIdentifierFieldName());
1033
1034 55
                if ($property instanceof FieldMetadata && $property->hasValueGenerator()) {
1035 48
                    $property->setValue($entity, null);
1036
                }
1037
            }
1038
1039 58
            if ($invoke !== ListenersInvoker::INVOKE_NONE) {
1040 9
                $eventArgs = new LifecycleEventArgs($entity, $this->em);
1041
1042 58
                $this->listenersInvoker->invoke($class, Events::postRemove, $entity, $eventArgs, $invoke);
1043
            }
1044
        }
1045 57
    }
1046
1047
    /**
1048
     * Gets the commit order.
1049
     *
1050
     * @return ClassMetadata[]
1051
     */
1052 991
    private function getCommitOrder()
1053
    {
1054 991
        $calc = new Internal\CommitOrderCalculator();
1055
1056
        // See if there are any new classes in the changeset, that are not in the
1057
        // commit order graph yet (don't have a node).
1058
        // We have to inspect changeSet to be able to correctly build dependencies.
1059
        // It is not possible to use IdentityMap here because post inserted ids
1060
        // are not yet available.
1061 991
        $newNodes = [];
1062
1063 991
        foreach (\array_merge($this->entityInsertions, $this->entityUpdates, $this->entityDeletions) as $entity) {
1064 991
            $class = $this->em->getClassMetadata(get_class($entity));
1065
1066 991
            if ($calc->hasNode($class->getClassName())) {
1067 628
                continue;
1068
            }
1069
1070 991
            $calc->addNode($class->getClassName(), $class);
1071
1072 991
            $newNodes[] = $class;
1073
        }
1074
1075
        // Calculate dependencies for new nodes
1076 991
        while ($class = array_pop($newNodes)) {
1077 991
            foreach ($class->getDeclaredPropertiesIterator() as $property) {
1078 991
                if (! ($property instanceof ToOneAssociationMetadata && $property->isOwningSide())) {
1079 991
                    continue;
1080
                }
1081
1082 822
                $targetClass = $this->em->getClassMetadata($property->getTargetEntity());
1083
1084 822
                if (! $calc->hasNode($targetClass->getClassName())) {
1085 630
                    $calc->addNode($targetClass->getClassName(), $targetClass);
1086
1087 630
                    $newNodes[] = $targetClass;
1088
                }
1089
1090 822
                $weight = ! array_filter(
1091 822
                    $property->getJoinColumns(),
1092 822
                    function (JoinColumnMetadata $joinColumn) {
1093 822
                        return $joinColumn->isNullable();
1094 822
                    }
1095
                );
1096
1097 822
                $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

1097
                $calc->addDependency($targetClass->getClassName(), $class->getClassName(), /** @scrutinizer ignore-type */ $weight);
Loading history...
1098
1099
                // If the target class has mapped subclasses, these share the same dependency.
1100 822
                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

1100
                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...
1101 817
                    continue;
1102
                }
1103
1104 226
                foreach ($targetClass->getSubClasses() as $subClassName) {
1105 226
                    $targetSubClass = $this->em->getClassMetadata($subClassName);
1106
1107 226
                    if (! $calc->hasNode($subClassName)) {
1108 200
                        $calc->addNode($targetSubClass->getClassName(), $targetSubClass);
1109
1110 200
                        $newNodes[] = $targetSubClass;
1111
                    }
1112
1113 226
                    $calc->addDependency($targetSubClass->getClassName(), $class->getClassName(), 1);
1114
                }
1115
            }
1116
        }
1117
1118 991
        return $calc->sort();
1119
    }
1120
1121
    /**
1122
     * Schedules an entity for insertion into the database.
1123
     * If the entity already has an identifier, it will be added to the identity map.
1124
     *
1125
     * @param object $entity The entity to schedule for insertion.
1126
     *
1127
     * @throws ORMInvalidArgumentException
1128
     * @throws \InvalidArgumentException
1129
     */
1130 1013
    public function scheduleForInsert($entity)
1131
    {
1132 1013
        $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

1132
        $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
1133
1134 1013
        if (isset($this->entityUpdates[$oid])) {
1135
            throw new InvalidArgumentException('Dirty entity can not be scheduled for insertion.');
1136
        }
1137
1138 1013
        if (isset($this->entityDeletions[$oid])) {
1139 1
            throw ORMInvalidArgumentException::scheduleInsertForRemovedEntity($entity);
1140
        }
1141 1013
        if (isset($this->originalEntityData[$oid]) && ! isset($this->entityInsertions[$oid])) {
1142 1
            throw ORMInvalidArgumentException::scheduleInsertForManagedEntity($entity);
1143
        }
1144
1145 1013
        if (isset($this->entityInsertions[$oid])) {
1146 1
            throw ORMInvalidArgumentException::scheduleInsertTwice($entity);
1147
        }
1148
1149 1013
        $this->entityInsertions[$oid] = $entity;
1150
1151 1013
        if (isset($this->entityIdentifiers[$oid])) {
1152 264
            $this->addToIdentityMap($entity);
1153
        }
1154
1155 1013
        if ($entity instanceof NotifyPropertyChanged) {
1156 5
            $entity->addPropertyChangedListener($this);
1157
        }
1158 1013
    }
1159
1160
    /**
1161
     * Checks whether an entity is scheduled for insertion.
1162
     *
1163
     * @param object $entity
1164
     *
1165
     * @return bool
1166
     */
1167 613
    public function isScheduledForInsert($entity)
1168
    {
1169 613
        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

1169
        return isset($this->entityInsertions[/** @scrutinizer ignore-call */ spl_object_id($entity)]);
Loading history...
1170
    }
1171
1172
    /**
1173
     * Schedules an entity for being updated.
1174
     *
1175
     * @param object $entity The entity to schedule for being updated.
1176
     *
1177
     * @throws ORMInvalidArgumentException
1178
     */
1179 1
    public function scheduleForUpdate($entity) : void
1180
    {
1181 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

1181
        $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
1182
1183 1
        if (! isset($this->entityIdentifiers[$oid])) {
1184
            throw ORMInvalidArgumentException::entityHasNoIdentity($entity, 'scheduling for update');
1185
        }
1186
1187 1
        if (isset($this->entityDeletions[$oid])) {
1188
            throw ORMInvalidArgumentException::entityIsRemoved($entity, 'schedule for update');
1189
        }
1190
1191 1
        if (! isset($this->entityUpdates[$oid]) && ! isset($this->entityInsertions[$oid])) {
1192 1
            $this->entityUpdates[$oid] = $entity;
1193
        }
1194 1
    }
1195
1196
    /**
1197
     * INTERNAL:
1198
     * Schedules an extra update that will be executed immediately after the
1199
     * regular entity updates within the currently running commit cycle.
1200
     *
1201
     * Extra updates for entities are stored as (entity, changeset) tuples.
1202
     *
1203
     * @ignore
1204
     *
1205
     * @param object  $entity    The entity for which to schedule an extra update.
1206
     * @param mixed[] $changeset The changeset of the entity (what to update).
1207
     *
1208
     */
1209 31
    public function scheduleExtraUpdate($entity, array $changeset) : void
1210
    {
1211 31
        $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

1211
        $oid         = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
1212 31
        $extraUpdate = [$entity, $changeset];
1213
1214 31
        if (isset($this->extraUpdates[$oid])) {
1215 1
            [$unused, $changeset2] = $this->extraUpdates[$oid];
1216
1217 1
            $extraUpdate = [$entity, $changeset + $changeset2];
1218
        }
1219
1220 31
        $this->extraUpdates[$oid] = $extraUpdate;
1221 31
    }
1222
1223
    /**
1224
     * Checks whether an entity is registered as dirty in the unit of work.
1225
     * Note: Is not very useful currently as dirty entities are only registered
1226
     * at commit time.
1227
     *
1228
     * @param object $entity
1229
     */
1230
    public function isScheduledForUpdate($entity) : bool
1231
    {
1232
        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

1232
        return isset($this->entityUpdates[/** @scrutinizer ignore-call */ spl_object_id($entity)]);
Loading history...
1233
    }
1234
1235
    /**
1236
     * Checks whether an entity is registered to be checked in the unit of work.
1237
     *
1238
     * @param object $entity
1239
     */
1240 1
    public function isScheduledForDirtyCheck($entity) : bool
1241
    {
1242 1
        $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

1242
        $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...
1243
1244 1
        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

1244
        return isset($this->scheduledForSynchronization[$rootEntityName][/** @scrutinizer ignore-call */ spl_object_id($entity)]);
Loading history...
1245
    }
1246
1247
    /**
1248
     * INTERNAL:
1249
     * Schedules an entity for deletion.
1250
     *
1251
     * @param object $entity
1252
     */
1253 61
    public function scheduleForDelete($entity)
1254
    {
1255 61
        $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

1255
        $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
1256
1257 61
        if (isset($this->entityInsertions[$oid])) {
1258 1
            if ($this->isInIdentityMap($entity)) {
1259
                $this->removeFromIdentityMap($entity);
1260
            }
1261
1262 1
            unset($this->entityInsertions[$oid], $this->entityStates[$oid]);
1263
1264 1
            return; // entity has not been persisted yet, so nothing more to do.
1265
        }
1266
1267 61
        if (! $this->isInIdentityMap($entity)) {
1268 1
            return;
1269
        }
1270
1271 60
        $this->removeFromIdentityMap($entity);
1272
1273 60
        unset($this->entityUpdates[$oid]);
1274
1275 60
        if (! isset($this->entityDeletions[$oid])) {
1276 60
            $this->entityDeletions[$oid] = $entity;
1277 60
            $this->entityStates[$oid]    = self::STATE_REMOVED;
1278
        }
1279 60
    }
1280
1281
    /**
1282
     * Checks whether an entity is registered as removed/deleted with the unit
1283
     * of work.
1284
     *
1285
     * @param object $entity
1286
     *
1287
     * @return bool
1288
     */
1289 13
    public function isScheduledForDelete($entity)
1290
    {
1291 13
        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

1291
        return isset($this->entityDeletions[/** @scrutinizer ignore-call */ spl_object_id($entity)]);
Loading history...
1292
    }
1293
1294
    /**
1295
     * Checks whether an entity is scheduled for insertion, update or deletion.
1296
     *
1297
     * @param object $entity
1298
     *
1299
     * @return bool
1300
     */
1301
    public function isEntityScheduled($entity)
1302
    {
1303
        $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

1303
        $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
1304
1305
        return isset($this->entityInsertions[$oid])
1306
            || isset($this->entityUpdates[$oid])
1307
            || isset($this->entityDeletions[$oid]);
1308
    }
1309
1310
    /**
1311
     * INTERNAL:
1312
     * Registers an entity in the identity map.
1313
     * Note that entities in a hierarchy are registered with the class name of
1314
     * the root entity.
1315
     *
1316
     * @ignore
1317
     *
1318
     * @param object $entity The entity to register.
1319
     *
1320
     * @return bool  TRUE if the registration was successful, FALSE if the identity of
1321
     *               the entity in question is already managed.
1322
     *
1323
     * @throws ORMInvalidArgumentException
1324
     */
1325 1081
    public function addToIdentityMap($entity)
1326
    {
1327 1081
        $classMetadata = $this->em->getClassMetadata(get_class($entity));
1328 1081
        $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

1328
        $identifier    = $this->entityIdentifiers[/** @scrutinizer ignore-call */ spl_object_id($entity)];
Loading history...
1329
1330 1081
        if (empty($identifier) || in_array(null, $identifier, true)) {
1331 6
            throw ORMInvalidArgumentException::entityWithoutIdentity($classMetadata->getClassName(), $entity);
1332
        }
1333
1334 1075
        $idHash    = implode(' ', $identifier);
1335 1075
        $className = $classMetadata->getRootClassName();
1336
1337 1075
        if (isset($this->identityMap[$className][$idHash])) {
1338 30
            return false;
1339
        }
1340
1341 1075
        $this->identityMap[$className][$idHash] = $entity;
1342
1343 1075
        return true;
1344
    }
1345
1346
    /**
1347
     * Gets the state of an entity with regard to the current unit of work.
1348
     *
1349
     * @param object   $entity
1350
     * @param int|null $assume The state to assume if the state is not yet known (not MANAGED or REMOVED).
1351
     *                         This parameter can be set to improve performance of entity state detection
1352
     *                         by potentially avoiding a database lookup if the distinction between NEW and DETACHED
1353
     *                         is either known or does not matter for the caller of the method.
1354
     *
1355
     * @return int The entity state.
1356
     */
1357 1021
    public function getEntityState($entity, $assume = null)
1358
    {
1359 1021
        $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

1359
        $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
1360
1361 1021
        if (isset($this->entityStates[$oid])) {
1362 740
            return $this->entityStates[$oid];
1363
        }
1364
1365 1016
        if ($assume !== null) {
1366 1013
            return $assume;
1367
        }
1368
1369
        // State can only be NEW or DETACHED, because MANAGED/REMOVED states are known.
1370
        // Note that you can not remember the NEW or DETACHED state in _entityStates since
1371
        // the UoW does not hold references to such objects and the object hash can be reused.
1372
        // More generally because the state may "change" between NEW/DETACHED without the UoW being aware of it.
1373 8
        $class     = $this->em->getClassMetadata(get_class($entity));
1374 8
        $persister = $this->getEntityPersister($class->getClassName());
1375 8
        $id        = $persister->getIdentifier($entity);
1376
1377 8
        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...
1378 3
            return self::STATE_NEW;
1379
        }
1380
1381 6
        $flatId = $this->em->getIdentifierFlattener()->flattenIdentifier($class, $id);
1382
1383 6
        if ($class->isIdentifierComposite()
1384 5
            || ! $class->getProperty($class->getSingleIdentifierFieldName()) instanceof FieldMetadata
1385 6
            || ! $class->getProperty($class->getSingleIdentifierFieldName())->hasValueGenerator()
1386
        ) {
1387
            // Check for a version field, if available, to avoid a db lookup.
1388 5
            if ($class->isVersioned()) {
1389 1
                return $class->versionProperty->getValue($entity)
1390
                    ? self::STATE_DETACHED
1391 1
                    : self::STATE_NEW;
1392
            }
1393
1394
            // Last try before db lookup: check the identity map.
1395 4
            if ($this->tryGetById($flatId, $class->getRootClassName())) {
1396 1
                return self::STATE_DETACHED;
1397
            }
1398
1399
            // db lookup
1400 4
            if ($this->getEntityPersister($class->getClassName())->exists($entity)) {
1401
                return self::STATE_DETACHED;
1402
            }
1403
1404 4
            return self::STATE_NEW;
1405
        }
1406
1407 1
        if ($class->isIdentifierComposite()
1408 1
            || ! $class->getProperty($class->getSingleIdentifierFieldName()) instanceof FieldMetadata
1409 1
            || ! $class->getValueGenerationPlan()->containsDeferred()) {
1410
            // if we have a pre insert generator we can't be sure that having an id
1411
            // really means that the entity exists. We have to verify this through
1412
            // the last resort: a db lookup
1413
1414
            // Last try before db lookup: check the identity map.
1415
            if ($this->tryGetById($flatId, $class->getRootClassName())) {
1416
                return self::STATE_DETACHED;
1417
            }
1418
1419
            // db lookup
1420
            if ($this->getEntityPersister($class->getClassName())->exists($entity)) {
1421
                return self::STATE_DETACHED;
1422
            }
1423
1424
            return self::STATE_NEW;
1425
        }
1426
1427 1
        return self::STATE_DETACHED;
1428
    }
1429
1430
    /**
1431
     * INTERNAL:
1432
     * Removes an entity from the identity map. This effectively detaches the
1433
     * entity from the persistence management of Doctrine.
1434
     *
1435
     * @ignore
1436
     *
1437
     * @param object $entity
1438
     *
1439
     * @return bool
1440
     *
1441
     * @throws ORMInvalidArgumentException
1442
     */
1443 60
    public function removeFromIdentityMap($entity)
1444
    {
1445 60
        $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

1445
        $oid           = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
1446 60
        $classMetadata = $this->em->getClassMetadata(get_class($entity));
1447 60
        $idHash        = implode(' ', $this->entityIdentifiers[$oid]);
1448
1449 60
        if ($idHash === '') {
1450
            throw ORMInvalidArgumentException::entityHasNoIdentity($entity, 'remove from identity map');
1451
        }
1452
1453 60
        $className = $classMetadata->getRootClassName();
1454
1455 60
        if (isset($this->identityMap[$className][$idHash])) {
1456 60
            unset($this->identityMap[$className][$idHash], $this->readOnlyObjects[$oid]);
1457
1458
            //$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...
1459
1460 60
            return true;
1461
        }
1462
1463
        return false;
1464
    }
1465
1466
    /**
1467
     * INTERNAL:
1468
     * Gets an entity in the identity map by its identifier hash.
1469
     *
1470
     * @ignore
1471
     *
1472
     * @param string $idHash
1473
     * @param string $rootClassName
1474
     *
1475
     * @return object
1476
     */
1477 6
    public function getByIdHash($idHash, $rootClassName)
1478
    {
1479 6
        return $this->identityMap[$rootClassName][$idHash];
1480
    }
1481
1482
    /**
1483
     * INTERNAL:
1484
     * Tries to get an entity by its identifier hash. If no entity is found for
1485
     * the given hash, FALSE is returned.
1486
     *
1487
     * @ignore
1488
     *
1489
     * @param mixed  $idHash        (must be possible to cast it to string)
1490
     * @param string $rootClassName
1491
     *
1492
     * @return object|bool The found entity or FALSE.
1493
     */
1494
    public function tryGetByIdHash($idHash, $rootClassName)
1495
    {
1496
        $stringIdHash = (string) $idHash;
1497
1498
        return $this->identityMap[$rootClassName][$stringIdHash] ?? false;
1499
    }
1500
1501
    /**
1502
     * Checks whether an entity is registered in the identity map of this UnitOfWork.
1503
     *
1504
     * @param object $entity
1505
     *
1506
     * @return bool
1507
     */
1508 141
    public function isInIdentityMap($entity)
1509
    {
1510 141
        $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

1510
        $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
1511
1512 141
        if (empty($this->entityIdentifiers[$oid])) {
1513 21
            return false;
1514
        }
1515
1516 128
        $classMetadata = $this->em->getClassMetadata(get_class($entity));
1517 128
        $idHash        = implode(' ', $this->entityIdentifiers[$oid]);
1518
1519 128
        return isset($this->identityMap[$classMetadata->getRootClassName()][$idHash]);
1520
    }
1521
1522
    /**
1523
     * INTERNAL:
1524
     * Checks whether an identifier hash exists in the identity map.
1525
     *
1526
     * @ignore
1527
     *
1528
     * @param string $idHash
1529
     * @param string $rootClassName
1530
     *
1531
     * @return bool
1532
     */
1533
    public function containsIdHash($idHash, $rootClassName)
1534
    {
1535
        return isset($this->identityMap[$rootClassName][$idHash]);
1536
    }
1537
1538
    /**
1539
     * Persists an entity as part of the current unit of work.
1540
     *
1541
     * @param object $entity The entity to persist.
1542
     */
1543 1013
    public function persist($entity)
1544
    {
1545 1013
        $visited = [];
1546
1547 1013
        $this->doPersist($entity, $visited);
1548 1006
    }
1549
1550
    /**
1551
     * Persists an entity as part of the current unit of work.
1552
     *
1553
     * This method is internally called during persist() cascades as it tracks
1554
     * the already visited entities to prevent infinite recursions.
1555
     *
1556
     * @param object   $entity  The entity to persist.
1557
     * @param object[] $visited The already visited entities.
1558
     *
1559
     * @throws ORMInvalidArgumentException
1560
     * @throws UnexpectedValueException
1561
     */
1562 1013
    private function doPersist($entity, array &$visited)
1563
    {
1564 1013
        $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

1564
        $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
1565
1566 1013
        if (isset($visited[$oid])) {
1567 109
            return; // Prevent infinite recursion
1568
        }
1569
1570 1013
        $visited[$oid] = $entity; // Mark visited
1571
1572 1013
        $class = $this->em->getClassMetadata(get_class($entity));
1573
1574
        // We assume NEW, so DETACHED entities result in an exception on flush (constraint violation).
1575
        // If we would detect DETACHED here we would throw an exception anyway with the same
1576
        // consequences (not recoverable/programming error), so just assuming NEW here
1577
        // lets us avoid some database lookups for entities with natural identifiers.
1578 1013
        $entityState = $this->getEntityState($entity, self::STATE_NEW);
1579
1580
        switch ($entityState) {
1581 1013
            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...
1582
                // Nothing to do, except if policy is "deferred explicit"
1583 217
                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...
1584 2
                    $this->scheduleForSynchronization($entity);
1585
                }
1586 217
                break;
1587
1588 1013
            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...
1589 1012
                $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

1589
                $this->persistNew(/** @scrutinizer ignore-type */ $class, $entity);
Loading history...
1590 1012
                break;
1591
1592 1
            case self::STATE_REMOVED:
1593
                // Entity becomes managed again
1594 1
                unset($this->entityDeletions[$oid]);
1595 1
                $this->addToIdentityMap($entity);
1596
1597 1
                $this->entityStates[$oid] = self::STATE_MANAGED;
1598 1
                break;
1599
1600
            case self::STATE_DETACHED:
1601
                // Can actually not happen right now since we assume STATE_NEW.
1602
                throw ORMInvalidArgumentException::detachedEntityCannot($entity, 'persisted');
1603
1604
            default:
1605
                throw new UnexpectedValueException(
1606
                    sprintf('Unexpected entity state: %d.%s', $entityState, self::objToStr($entity))
1607
                );
1608
        }
1609
1610 1013
        $this->cascadePersist($entity, $visited);
1611 1006
    }
1612
1613
    /**
1614
     * Deletes an entity as part of the current unit of work.
1615
     *
1616
     * @param object $entity The entity to remove.
1617
     */
1618 60
    public function remove($entity)
1619
    {
1620 60
        $visited = [];
1621
1622 60
        $this->doRemove($entity, $visited);
1623 60
    }
1624
1625
    /**
1626
     * Deletes an entity as part of the current unit of work.
1627
     *
1628
     * This method is internally called during delete() cascades as it tracks
1629
     * the already visited entities to prevent infinite recursions.
1630
     *
1631
     * @param object   $entity  The entity to delete.
1632
     * @param object[] $visited The map of the already visited entities.
1633
     *
1634
     * @throws ORMInvalidArgumentException If the instance is a detached entity.
1635
     * @throws UnexpectedValueException
1636
     */
1637 60
    private function doRemove($entity, array &$visited)
1638
    {
1639 60
        $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

1639
        $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
1640
1641 60
        if (isset($visited[$oid])) {
1642 1
            return; // Prevent infinite recursion
1643
        }
1644
1645 60
        $visited[$oid] = $entity; // mark visited
1646
1647
        // Cascade first, because scheduleForDelete() removes the entity from the identity map, which
1648
        // can cause problems when a lazy proxy has to be initialized for the cascade operation.
1649 60
        $this->cascadeRemove($entity, $visited);
1650
1651 60
        $class       = $this->em->getClassMetadata(get_class($entity));
1652 60
        $entityState = $this->getEntityState($entity);
1653
1654
        switch ($entityState) {
1655 60
            case self::STATE_NEW:
1656 60
            case self::STATE_REMOVED:
1657
                // nothing to do
1658 2
                break;
1659
1660 60
            case self::STATE_MANAGED:
1661 60
                $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

1661
                $invoke = $this->listenersInvoker->getSubscribedSystems(/** @scrutinizer ignore-type */ $class, Events::preRemove);
Loading history...
1662
1663 60
                if ($invoke !== ListenersInvoker::INVOKE_NONE) {
1664 8
                    $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

1664
                    $this->listenersInvoker->invoke(/** @scrutinizer ignore-type */ $class, Events::preRemove, $entity, new LifecycleEventArgs($entity, $this->em), $invoke);
Loading history...
1665
                }
1666
1667 60
                $this->scheduleForDelete($entity);
1668 60
                break;
1669
1670
            case self::STATE_DETACHED:
1671
                throw ORMInvalidArgumentException::detachedEntityCannot($entity, 'removed');
1672
            default:
1673
                throw new UnexpectedValueException(
1674
                    sprintf('Unexpected entity state: %d.%s', $entityState, self::objToStr($entity))
1675
                );
1676
        }
1677 60
    }
1678
1679
    /**
1680
     * Refreshes the state of the given entity from the database, overwriting
1681
     * any local, unpersisted changes.
1682
     *
1683
     * @param object $entity The entity to refresh.
1684
     *
1685
     * @throws InvalidArgumentException If the entity is not MANAGED.
1686
     */
1687 15
    public function refresh($entity)
1688
    {
1689 15
        $visited = [];
1690
1691 15
        $this->doRefresh($entity, $visited);
1692 15
    }
1693
1694
    /**
1695
     * Executes a refresh operation on an entity.
1696
     *
1697
     * @param object   $entity  The entity to refresh.
1698
     * @param object[] $visited The already visited entities during cascades.
1699
     *
1700
     * @throws ORMInvalidArgumentException If the entity is not MANAGED.
1701
     */
1702 15
    private function doRefresh($entity, array &$visited)
1703
    {
1704 15
        $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

1704
        $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
1705
1706 15
        if (isset($visited[$oid])) {
1707
            return; // Prevent infinite recursion
1708
        }
1709
1710 15
        $visited[$oid] = $entity; // mark visited
1711
1712 15
        $class = $this->em->getClassMetadata(get_class($entity));
1713
1714 15
        if ($this->getEntityState($entity) !== self::STATE_MANAGED) {
1715
            throw ORMInvalidArgumentException::entityNotManaged($entity);
1716
        }
1717
1718 15
        $this->getEntityPersister($class->getClassName())->refresh(
1719 15
            array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
1720 15
            $entity
1721
        );
1722
1723 15
        $this->cascadeRefresh($entity, $visited);
1724 15
    }
1725
1726
    /**
1727
     * Cascades a refresh operation to associated entities.
1728
     *
1729
     * @param object   $entity
1730
     * @param object[] $visited
1731
     */
1732 15
    private function cascadeRefresh($entity, array &$visited)
1733
    {
1734 15
        $class = $this->em->getClassMetadata(get_class($entity));
1735
1736 15
        foreach ($class->getDeclaredPropertiesIterator() as $association) {
1737 15
            if (! ($association instanceof AssociationMetadata && in_array('refresh', $association->getCascade()))) {
1738 15
                continue;
1739
            }
1740
1741 4
            $relatedEntities = $association->getValue($entity);
1742
1743
            switch (true) {
1744 4
                case ($relatedEntities instanceof PersistentCollection):
1745
                    // Unwrap so that foreach() does not initialize
1746 4
                    $relatedEntities = $relatedEntities->unwrap();
1747
                    // break; is commented intentionally!
1748
1749
                case ($relatedEntities instanceof Collection):
1750
                case (is_array($relatedEntities)):
1751 4
                    foreach ($relatedEntities as $relatedEntity) {
1752
                        $this->doRefresh($relatedEntity, $visited);
1753
                    }
1754 4
                    break;
1755
1756
                case ($relatedEntities !== null):
1757
                    $this->doRefresh($relatedEntities, $visited);
1758
                    break;
1759
1760 4
                default:
1761
                    // Do nothing
1762
            }
1763
        }
1764 15
    }
1765
1766
    /**
1767
     * Cascades the save operation to associated entities.
1768
     *
1769
     * @param object   $entity
1770
     * @param object[] $visited
1771
     *
1772
     * @throws ORMInvalidArgumentException
1773
     */
1774 1013
    private function cascadePersist($entity, array &$visited)
1775
    {
1776 1013
        $class = $this->em->getClassMetadata(get_class($entity));
1777
1778 1013
        if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
1779
            // nothing to do - proxy is not initialized, therefore we don't do anything with it
1780
            return;
1781
        }
1782
1783 1013
        foreach ($class->getDeclaredPropertiesIterator() as $association) {
1784 1013
            if (! ($association instanceof AssociationMetadata && in_array('persist', $association->getCascade()))) {
1785 1013
                continue;
1786
            }
1787
1788
            /** @var AssociationMetadata $association */
1789 641
            $relatedEntities = $association->getValue($entity);
1790 641
            $targetEntity    = $association->getTargetEntity();
1791
1792
            switch (true) {
1793 641
                case ($relatedEntities instanceof PersistentCollection):
1794
                    // Unwrap so that foreach() does not initialize
1795 13
                    $relatedEntities = $relatedEntities->unwrap();
1796
                    // break; is commented intentionally!
1797
1798 641
                case ($relatedEntities instanceof Collection):
1799 579
                case (is_array($relatedEntities)):
1800 539
                    if (! ($association instanceof ToManyAssociationMetadata)) {
1801 3
                        throw ORMInvalidArgumentException::invalidAssociation(
1802 3
                            $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

1802
                            /** @scrutinizer ignore-type */ $this->em->getClassMetadata($targetEntity),
Loading history...
1803 3
                            $association,
1804 3
                            $relatedEntities
1805
                        );
1806
                    }
1807
1808 536
                    foreach ($relatedEntities as $relatedEntity) {
1809 285
                        $this->doPersist($relatedEntity, $visited);
1810
                    }
1811
1812 536
                    break;
1813
1814 569
                case ($relatedEntities !== null):
1815 245
                    if (! $relatedEntities instanceof $targetEntity) {
1816 4
                        throw ORMInvalidArgumentException::invalidAssociation(
1817 4
                            $this->em->getClassMetadata($targetEntity),
1818 4
                            $association,
1819 4
                            $relatedEntities
1820
                        );
1821
                    }
1822
1823 241
                    $this->doPersist($relatedEntities, $visited);
1824 241
                    break;
1825
1826 635
                default:
1827
                    // Do nothing
1828
            }
1829
        }
1830 1006
    }
1831
1832
    /**
1833
     * Cascades the delete operation to associated entities.
1834
     *
1835
     * @param object   $entity
1836
     * @param object[] $visited
1837
     */
1838 60
    private function cascadeRemove($entity, array &$visited)
1839
    {
1840 60
        $entitiesToCascade = [];
1841 60
        $class             = $this->em->getClassMetadata(get_class($entity));
1842
1843 60
        foreach ($class->getDeclaredPropertiesIterator() as $association) {
1844 60
            if (! ($association instanceof AssociationMetadata && in_array('remove', $association->getCascade()))) {
1845 60
                continue;
1846
            }
1847
1848 23
            if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
1849 4
                $entity->initializeProxy();
1850
            }
1851
1852 23
            $relatedEntities = $association->getValue($entity);
1853
1854
            switch (true) {
1855 23
                case ($relatedEntities instanceof Collection):
1856 16
                case (\is_array($relatedEntities)):
1857
                    // If its a PersistentCollection initialization is intended! No unwrap!
1858 18
                    foreach ($relatedEntities as $relatedEntity) {
1859 10
                        $entitiesToCascade[] = $relatedEntity;
1860
                    }
1861 18
                    break;
1862
1863 16
                case ($relatedEntities !== null):
1864 7
                    $entitiesToCascade[] = $relatedEntities;
1865 7
                    break;
1866
1867 23
                default:
1868
                    // Do nothing
1869
            }
1870
        }
1871
1872 60
        foreach ($entitiesToCascade as $relatedEntity) {
1873 16
            $this->doRemove($relatedEntity, $visited);
1874
        }
1875 60
    }
1876
1877
    /**
1878
     * Acquire a lock on the given entity.
1879
     *
1880
     * @param object $entity
1881
     * @param int    $lockMode
1882
     * @param int    $lockVersion
1883
     *
1884
     * @throws ORMInvalidArgumentException
1885
     * @throws TransactionRequiredException
1886
     * @throws OptimisticLockException
1887
     * @throws \InvalidArgumentException
1888
     */
1889 10
    public function lock($entity, $lockMode, $lockVersion = null)
1890
    {
1891 10
        if ($entity === null) {
1892 1
            throw new \InvalidArgumentException('No entity passed to UnitOfWork#lock().');
1893
        }
1894
1895 9
        if ($this->getEntityState($entity, self::STATE_DETACHED) !== self::STATE_MANAGED) {
1896 1
            throw ORMInvalidArgumentException::entityNotManaged($entity);
1897
        }
1898
1899 8
        $class = $this->em->getClassMetadata(get_class($entity));
1900
1901
        switch (true) {
1902 8
            case $lockMode === LockMode::OPTIMISTIC:
1903 6
                if (! $class->isVersioned()) {
1904 2
                    throw OptimisticLockException::notVersioned($class->getClassName());
1905
                }
1906
1907 4
                if ($lockVersion === null) {
1908
                    return;
1909
                }
1910
1911 4
                if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
1912 1
                    $entity->initializeProxy();
1913
                }
1914
1915 4
                $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...
1916
1917 4
                if ($entityVersion !== $lockVersion) {
1918 2
                    throw OptimisticLockException::lockFailedVersionMismatch($entity, $lockVersion, $entityVersion);
1919
                }
1920
1921 2
                break;
1922
1923 2
            case $lockMode === LockMode::NONE:
1924 2
            case $lockMode === LockMode::PESSIMISTIC_READ:
1925 1
            case $lockMode === LockMode::PESSIMISTIC_WRITE:
1926 2
                if (! $this->em->getConnection()->isTransactionActive()) {
1927 2
                    throw TransactionRequiredException::transactionRequired();
1928
                }
1929
1930
                $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

1930
                $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
1931
1932
                $this->getEntityPersister($class->getClassName())->lock(
1933
                    array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
1934
                    $lockMode
1935
                );
1936
                break;
1937
1938
            default:
1939
                // Do nothing
1940
        }
1941 2
    }
1942
1943
    /**
1944
     * Clears the UnitOfWork.
1945
     */
1946 1192
    public function clear()
1947
    {
1948 1192
        $this->entityPersisters               =
1949 1192
        $this->collectionPersisters           =
1950 1192
        $this->eagerLoadingEntities           =
1951 1192
        $this->identityMap                    =
1952 1192
        $this->entityIdentifiers              =
1953 1192
        $this->originalEntityData             =
1954 1192
        $this->entityChangeSets               =
1955 1192
        $this->entityStates                   =
1956 1192
        $this->scheduledForSynchronization    =
1957 1192
        $this->entityInsertions               =
1958 1192
        $this->entityUpdates                  =
1959 1192
        $this->entityDeletions                =
1960 1192
        $this->collectionDeletions            =
1961 1192
        $this->collectionUpdates              =
1962 1192
        $this->extraUpdates                   =
1963 1192
        $this->readOnlyObjects                =
1964 1192
        $this->visitedCollections             =
1965 1192
        $this->nonCascadedNewDetectedEntities =
1966 1192
        $this->orphanRemovals                 = [];
1967 1192
    }
1968
1969
    /**
1970
     * INTERNAL:
1971
     * Schedules an orphaned entity for removal. The remove() operation will be
1972
     * invoked on that entity at the beginning of the next commit of this
1973
     * UnitOfWork.
1974
     *
1975
     * @ignore
1976
     *
1977
     * @param object $entity
1978
     */
1979 16
    public function scheduleOrphanRemoval($entity)
1980
    {
1981 16
        $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

1981
        $this->orphanRemovals[/** @scrutinizer ignore-call */ spl_object_id($entity)] = $entity;
Loading history...
1982 16
    }
1983
1984
    /**
1985
     * INTERNAL:
1986
     * Cancels a previously scheduled orphan removal.
1987
     *
1988
     * @ignore
1989
     *
1990
     * @param object $entity
1991
     */
1992 110
    public function cancelOrphanRemoval($entity)
1993
    {
1994 110
        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

1994
        unset($this->orphanRemovals[/** @scrutinizer ignore-call */ spl_object_id($entity)]);
Loading history...
1995 110
    }
1996
1997
    /**
1998
     * INTERNAL:
1999
     * Schedules a complete collection for removal when this UnitOfWork commits.
2000
     */
2001 22
    public function scheduleCollectionDeletion(PersistentCollection $coll)
2002
    {
2003 22
        $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

2003
        $coid = /** @scrutinizer ignore-call */ spl_object_id($coll);
Loading history...
2004
2005
        // TODO: if $coll is already scheduled for recreation ... what to do?
2006
        // Just remove $coll from the scheduled recreations?
2007 22
        unset($this->collectionUpdates[$coid]);
2008
2009 22
        $this->collectionDeletions[$coid] = $coll;
2010 22
    }
2011
2012
    /**
2013
     * @return bool
2014
     */
2015 8
    public function isCollectionScheduledForDeletion(PersistentCollection $coll)
2016
    {
2017 8
        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

2017
        return isset($this->collectionDeletions[/** @scrutinizer ignore-call */ spl_object_id($coll)]);
Loading history...
2018
    }
2019
2020
    /**
2021
     * INTERNAL:
2022
     * Creates a new instance of the mapped class, without invoking the constructor.
2023
     * This is only meant to be used internally, and should not be consumed by end users.
2024
     *
2025
     * @ignore
2026
     *
2027
     * @return EntityManagerAware|object
2028
     */
2029 664
    public function newInstance(ClassMetadata $class)
2030
    {
2031 664
        $entity = $this->instantiator->instantiate($class->getClassName());
2032
2033 664
        if ($entity instanceof EntityManagerAware) {
2034 5
            $entity->injectEntityManager($this->em, $class);
2035
        }
2036
2037 664
        return $entity;
2038
    }
2039
2040
    /**
2041
     * INTERNAL:
2042
     * Creates an entity. Used for reconstitution of persistent entities.
2043
     *
2044
     * Internal note: Highly performance-sensitive method.
2045
     *
2046
     * @ignore
2047
     *
2048
     * @param string  $className The name of the entity class.
2049
     * @param mixed[] $data      The data for the entity.
2050
     * @param mixed[] $hints     Any hints to account for during reconstitution/lookup of the entity.
2051
     *
2052
     * @return object The managed entity instance.
2053
     *
2054
     * @todo Rename: getOrCreateEntity
2055
     */
2056 800
    public function createEntity($className, array $data, &$hints = [])
2057
    {
2058 800
        $class  = $this->em->getClassMetadata($className);
2059 800
        $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

2059
        $id     = $this->em->getIdentifierFlattener()->flattenIdentifier(/** @scrutinizer ignore-type */ $class, $data);
Loading history...
2060 800
        $idHash = implode(' ', $id);
2061
2062 800
        if (isset($this->identityMap[$class->getRootClassName()][$idHash])) {
2063 304
            $entity = $this->identityMap[$class->getRootClassName()][$idHash];
2064 304
            $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

2064
            $oid    = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
2065
2066 304
            if (isset($hints[Query::HINT_REFRESH], $hints[Query::HINT_REFRESH_ENTITY])) {
2067 64
                $unmanagedProxy = $hints[Query::HINT_REFRESH_ENTITY];
2068 64
                if ($unmanagedProxy !== $entity
2069 64
                    && $unmanagedProxy instanceof GhostObjectInterface
2070 64
                    && $this->isIdentifierEquals($unmanagedProxy, $entity)
2071
                ) {
2072
                    // We will hydrate the given un-managed proxy anyway:
2073
                    // continue work, but consider it the entity from now on
2074 4
                    $entity = $unmanagedProxy;
2075
                }
2076
            }
2077
2078 304
            if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
2079 21
                $entity->setProxyInitializer(null);
2080
2081 21
                if ($entity instanceof NotifyPropertyChanged) {
2082 21
                    $entity->addPropertyChangedListener($this);
2083
                }
2084
            } else {
2085 290
                if (! isset($hints[Query::HINT_REFRESH])
2086 290
                    || (isset($hints[Query::HINT_REFRESH_ENTITY]) && $hints[Query::HINT_REFRESH_ENTITY] !== $entity)) {
2087 229
                    return $entity;
2088
                }
2089
            }
2090
2091
            // inject EntityManager upon refresh.
2092 102
            if ($entity instanceof EntityManagerAware) {
2093 3
                $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

2093
                $entity->injectEntityManager($this->em, /** @scrutinizer ignore-type */ $class);
Loading history...
2094
            }
2095
2096 102
            $this->originalEntityData[$oid] = $data;
2097
        } else {
2098 661
            $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

2098
            $entity = $this->newInstance(/** @scrutinizer ignore-type */ $class);
Loading history...
2099 661
            $oid    = spl_object_id($entity);
2100
2101 661
            $this->entityIdentifiers[$oid]  = $id;
2102 661
            $this->entityStates[$oid]       = self::STATE_MANAGED;
2103 661
            $this->originalEntityData[$oid] = $data;
2104
2105 661
            $this->identityMap[$class->getRootClassName()][$idHash] = $entity;
2106
        }
2107
2108 693
        if ($entity instanceof NotifyPropertyChanged) {
2109 3
            $entity->addPropertyChangedListener($this);
2110
        }
2111
2112 693
        foreach ($data as $field => $value) {
2113 693
            $property = $class->getProperty($field);
2114
2115 693
            if ($property instanceof FieldMetadata) {
2116 693
                $property->setValue($entity, $value);
2117
            }
2118
        }
2119
2120
        // Loading the entity right here, if its in the eager loading map get rid of it there.
2121 693
        unset($this->eagerLoadingEntities[$class->getRootClassName()][$idHash]);
2122
2123 693
        if (isset($this->eagerLoadingEntities[$class->getRootClassName()]) && ! $this->eagerLoadingEntities[$class->getRootClassName()]) {
2124
            unset($this->eagerLoadingEntities[$class->getRootClassName()]);
2125
        }
2126
2127
        // Properly initialize any unfetched associations, if partial objects are not allowed.
2128 693
        if (isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) {
2129 34
            return $entity;
2130
        }
2131
2132 659
        foreach ($class->getDeclaredPropertiesIterator() as $field => $association) {
2133 659
            if (! ($association instanceof AssociationMetadata)) {
2134 659
                continue;
2135
            }
2136
2137
            // Check if the association is not among the fetch-joined associations already.
2138 569
            if (isset($hints['fetchAlias'], $hints['fetched'][$hints['fetchAlias']][$field])) {
2139 244
                continue;
2140
            }
2141
2142 546
            $targetEntity = $association->getTargetEntity();
2143 546
            $targetClass  = $this->em->getClassMetadata($targetEntity);
2144
2145 546
            if ($association instanceof ToManyAssociationMetadata) {
2146
                // Ignore if its a cached collection
2147 466
                if (isset($hints[Query::HINT_CACHE_ENABLED]) &&
2148 466
                    $association->getValue($entity) instanceof PersistentCollection) {
2149
                    continue;
2150
                }
2151
2152 466
                $hasDataField = isset($data[$field]);
2153
2154
                // use the given collection
2155 466
                if ($hasDataField && $data[$field] instanceof PersistentCollection) {
2156
                    $data[$field]->setOwner($entity, $association);
2157
2158
                    $association->setValue($entity, $data[$field]);
2159
2160
                    $this->originalEntityData[$oid][$field] = $data[$field];
2161
2162
                    continue;
2163
                }
2164
2165
                // Inject collection
2166 466
                $pColl = $association->wrap($entity, $hasDataField ? $data[$field] : [], $this->em);
2167
2168 466
                $pColl->setInitialized($hasDataField);
2169
2170 466
                $association->setValue($entity, $pColl);
2171
2172 466
                if ($association->getFetchMode() === FetchMode::EAGER) {
2173 4
                    $this->loadCollection($pColl);
2174 4
                    $pColl->takeSnapshot();
2175
                }
2176
2177 466
                $this->originalEntityData[$oid][$field] = $pColl;
2178
2179 466
                continue;
2180
            }
2181
2182 472
            if (! $association->isOwningSide()) {
2183
                // use the given entity association
2184 64
                if (isset($data[$field]) && is_object($data[$field]) &&
2185 64
                    isset($this->entityStates[spl_object_id($data[$field])])) {
2186 3
                    $inverseAssociation = $targetClass->getProperty($association->getMappedBy());
2187
2188 3
                    $association->setValue($entity, $data[$field]);
2189 3
                    $inverseAssociation->setValue($data[$field], $entity);
2190
2191 3
                    $this->originalEntityData[$oid][$field] = $data[$field];
2192
2193 3
                    continue;
2194
                }
2195
2196
                // Inverse side of x-to-one can never be lazy
2197 61
                $persister = $this->getEntityPersister($targetEntity);
2198
2199 61
                $association->setValue($entity, $persister->loadToOneEntity($association, $entity));
2200
2201 61
                continue;
2202
            }
2203
2204
            // use the entity association
2205 472
            if (isset($data[$field]) && is_object($data[$field]) && isset($this->entityStates[spl_object_id($data[$field])])) {
2206 38
                $association->setValue($entity, $data[$field]);
2207
2208 38
                $this->originalEntityData[$oid][$field] = $data[$field];
2209
2210 38
                continue;
2211
            }
2212
2213 465
            $associatedId = [];
2214
2215
            // TODO: Is this even computed right in all cases of composite keys?
2216 465
            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

2216
            foreach ($association->/** @scrutinizer ignore-call */ getJoinColumns() as $joinColumn) {
Loading history...
2217
                /** @var JoinColumnMetadata $joinColumn */
2218 465
                $joinColumnName  = $joinColumn->getColumnName();
2219 465
                $joinColumnValue = $data[$joinColumnName] ?? null;
2220 465
                $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...
2221
2222 465
                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...
2223
                    // the missing key is part of target's entity primary key
2224 270
                    $associatedId = [];
2225
2226 270
                    continue;
2227
                }
2228
2229 282
                $associatedId[$targetField] = $joinColumnValue;
2230
            }
2231
2232 465
            if (! $associatedId) {
2233
                // Foreign key is NULL
2234 270
                $association->setValue($entity, null);
2235 270
                $this->originalEntityData[$oid][$field] = null;
2236
2237 270
                continue;
2238
            }
2239
2240
            // @todo guilhermeblanco Can we remove the need of this somehow?
2241 282
            if (! isset($hints['fetchMode'][$class->getClassName()][$field])) {
2242 279
                $hints['fetchMode'][$class->getClassName()][$field] = $association->getFetchMode();
2243
            }
2244
2245
            // Foreign key is set
2246
            // Check identity map first
2247
            // FIXME: Can break easily with composite keys if join column values are in
2248
            //        wrong order. The correct order is the one in ClassMetadata#identifier.
2249 282
            $relatedIdHash = implode(' ', $associatedId);
2250
2251
            switch (true) {
2252 282
                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...
2253 165
                    $newValue = $this->identityMap[$targetClass->getRootClassName()][$relatedIdHash];
2254
2255
                    // If this is an uninitialized proxy, we are deferring eager loads,
2256
                    // this association is marked as eager fetch, and its an uninitialized proxy (wtf!)
2257
                    // then we can append this entity for eager loading!
2258 165
                    if (! $targetClass->isIdentifierComposite() &&
2259 165
                        $newValue instanceof GhostObjectInterface &&
2260 165
                        isset($hints[self::HINT_DEFEREAGERLOAD]) &&
2261 165
                        $hints['fetchMode'][$class->getClassName()][$field] === FetchMode::EAGER &&
2262 165
                        ! $newValue->isProxyInitialized()
2263
                    ) {
2264
                        $this->eagerLoadingEntities[$targetClass->getRootClassName()][$relatedIdHash] = current($associatedId);
2265
                    }
2266
2267 165
                    break;
2268
2269 190
                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...
2270
                    // If it might be a subtype, it can not be lazy. There isn't even
2271
                    // a way to solve this with deferred eager loading, which means putting
2272
                    // an entity with subclasses at a *-to-one location is really bad! (performance-wise)
2273 28
                    $persister = $this->getEntityPersister($targetEntity);
2274 28
                    $newValue  = $persister->loadToOneEntity($association, $entity, $associatedId);
2275 28
                    break;
2276
2277
                default:
2278
                    // Proxies do not carry any kind of original entity data until they're fully loaded/initialized
2279 164
                    $managedData = [];
2280
2281 164
                    $normalizedAssociatedId = $this->normalizeIdentifier->__invoke(
2282 164
                        $this->em,
2283 164
                        $targetClass,
2284 164
                        $associatedId
2285
                    );
2286
2287
                    switch (true) {
2288
                        // We are negating the condition here. Other cases will assume it is valid!
2289 164
                        case ($hints['fetchMode'][$class->getClassName()][$field] !== FetchMode::EAGER):
2290 157
                            $newValue = $this->em->getProxyFactory()->getProxy($targetClass, $normalizedAssociatedId);
2291 157
                            break;
2292
2293
                        // Deferred eager load only works for single identifier classes
2294 7
                        case (isset($hints[self::HINT_DEFEREAGERLOAD]) && ! $targetClass->isIdentifierComposite()):
2295
                            // TODO: Is there a faster approach?
2296 7
                            $this->eagerLoadingEntities[$targetClass->getRootClassName()][$relatedIdHash] = current($normalizedAssociatedId);
2297
2298 7
                            $newValue = $this->em->getProxyFactory()->getProxy($targetClass, $normalizedAssociatedId);
2299 7
                            break;
2300
2301
                        default:
2302
                            // TODO: This is very imperformant, ignore it?
2303
                            $newValue = $this->em->find($targetEntity, $normalizedAssociatedId);
2304
                            // Needed to re-assign original entity data for freshly loaded entity
2305
                            $managedData = $this->originalEntityData[spl_object_id($newValue)];
2306
                            break;
2307
                    }
2308
2309
                    // @TODO using `$associatedId` here seems to be risky.
2310 164
                    $this->registerManaged($newValue, $associatedId, $managedData);
2311
2312 164
                    break;
2313
            }
2314
2315 282
            $this->originalEntityData[$oid][$field] = $newValue;
2316 282
            $association->setValue($entity, $newValue);
2317
2318 282
            if ($association->getInversedBy()
2319 282
                && $association instanceof OneToOneAssociationMetadata
2320
                // @TODO refactor this
2321
                // we don't want to set any values in un-initialized proxies
2322
                && ! (
2323 56
                    $newValue instanceof GhostObjectInterface
2324 282
                    && ! $newValue->isProxyInitialized()
2325
                )
2326
            ) {
2327 19
                $inverseAssociation = $targetClass->getProperty($association->getInversedBy());
2328
2329 282
                $inverseAssociation->setValue($newValue, $entity);
2330
            }
2331
        }
2332
2333
        // defer invoking of postLoad event to hydration complete step
2334 659
        $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

2334
        $this->hydrationCompleteHandler->deferPostLoadInvoking(/** @scrutinizer ignore-type */ $class, $entity);
Loading history...
2335
2336 659
        return $entity;
2337
    }
2338
2339 857
    public function triggerEagerLoads()
2340
    {
2341 857
        if (! $this->eagerLoadingEntities) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->eagerLoadingEntities of type array<mixed,array<mixed,array<mixed,mixed>>> 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...
2342 857
            return;
2343
        }
2344
2345
        // avoid infinite recursion
2346 7
        $eagerLoadingEntities       = $this->eagerLoadingEntities;
2347 7
        $this->eagerLoadingEntities = [];
2348
2349 7
        foreach ($eagerLoadingEntities as $entityName => $ids) {
2350 7
            if (! $ids) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $ids of type array<mixed,array<mixed,mixed>> 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...
2351
                continue;
2352
            }
2353
2354 7
            $class = $this->em->getClassMetadata($entityName);
2355
2356 7
            $this->getEntityPersister($entityName)->loadAll(
2357 7
                array_combine($class->identifier, [array_values($ids)])
2358
            );
2359
        }
2360 7
    }
2361
2362
    /**
2363
     * Initializes (loads) an uninitialized persistent collection of an entity.
2364
     *
2365
     * @param \Doctrine\ORM\PersistentCollection $collection The collection to initialize.
2366
     *
2367
     * @todo Maybe later move to EntityManager#initialize($proxyOrCollection). See DDC-733.
2368
     */
2369 136
    public function loadCollection(PersistentCollection $collection)
2370
    {
2371 136
        $association = $collection->getMapping();
2372 136
        $persister   = $this->getEntityPersister($association->getTargetEntity());
2373
2374 136
        if ($association instanceof OneToManyAssociationMetadata) {
2375 71
            $persister->loadOneToManyCollection($association, $collection->getOwner(), $collection);
2376
        } else {
2377 73
            $persister->loadManyToManyCollection($association, $collection->getOwner(), $collection);
2378
        }
2379
2380 136
        $collection->setInitialized(true);
2381 136
    }
2382
2383
    /**
2384
     * Gets the identity map of the UnitOfWork.
2385
     *
2386
     * @return object[]
2387
     */
2388 1
    public function getIdentityMap()
2389
    {
2390 1
        return $this->identityMap;
2391
    }
2392
2393
    /**
2394
     * Gets the original data of an entity. The original data is the data that was
2395
     * present at the time the entity was reconstituted from the database.
2396
     *
2397
     * @param object $entity
2398
     *
2399
     * @return mixed[]
2400
     */
2401 120
    public function getOriginalEntityData($entity)
2402
    {
2403 120
        $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

2403
        $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
2404
2405 120
        return $this->originalEntityData[$oid] ?? [];
2406
    }
2407
2408
    /**
2409
     * @ignore
2410
     *
2411
     * @param object  $entity
2412
     * @param mixed[] $data
2413
     */
2414
    public function setOriginalEntityData($entity, array $data)
2415
    {
2416
        $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

2416
        $this->originalEntityData[/** @scrutinizer ignore-call */ spl_object_id($entity)] = $data;
Loading history...
2417
    }
2418
2419
    /**
2420
     * INTERNAL:
2421
     * Sets a property value of the original data array of an entity.
2422
     *
2423
     * @ignore
2424
     *
2425
     * @param string $oid
2426
     * @param string $property
2427
     * @param mixed  $value
2428
     */
2429 302
    public function setOriginalEntityProperty($oid, $property, $value)
2430
    {
2431 302
        $this->originalEntityData[$oid][$property] = $value;
2432 302
    }
2433
2434
    /**
2435
     * Gets the identifier of an entity.
2436
     * The returned value is always an array of identifier values. If the entity
2437
     * has a composite identifier then the identifier values are in the same
2438
     * order as the identifier field names as returned by ClassMetadata#getIdentifierFieldNames().
2439
     *
2440
     * @param object $entity
2441
     *
2442
     * @return mixed[] The identifier values.
2443
     */
2444 555
    public function getEntityIdentifier($entity)
2445
    {
2446 555
        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

2446
        return $this->entityIdentifiers[/** @scrutinizer ignore-call */ spl_object_id($entity)];
Loading history...
2447
    }
2448
2449
    /**
2450
     * Processes an entity instance to extract their identifier values.
2451
     *
2452
     * @param object $entity The entity instance.
2453
     *
2454
     * @return mixed A scalar value.
2455
     *
2456
     * @throws \Doctrine\ORM\ORMInvalidArgumentException
2457
     */
2458 65
    public function getSingleIdentifierValue($entity)
2459
    {
2460 65
        $class     = $this->em->getClassMetadata(get_class($entity));
2461 65
        $persister = $this->getEntityPersister($class->getClassName());
2462
2463 65
        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

2463
        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...
2464
            throw ORMInvalidArgumentException::invalidCompositeIdentifier();
2465
        }
2466
2467 65
        $values = $this->isInIdentityMap($entity)
2468 55
            ? $this->getEntityIdentifier($entity)
2469 65
            : $persister->getIdentifier($entity);
2470
2471 65
        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...
2472
    }
2473
2474
    /**
2475
     * Tries to find an entity with the given identifier in the identity map of
2476
     * this UnitOfWork.
2477
     *
2478
     * @param mixed|mixed[] $id            The entity identifier to look for.
2479
     * @param string        $rootClassName The name of the root class of the mapped entity hierarchy.
2480
     *
2481
     * @return object|bool Returns the entity with the specified identifier if it exists in
2482
     *                     this UnitOfWork, FALSE otherwise.
2483
     */
2484 531
    public function tryGetById($id, $rootClassName)
2485
    {
2486 531
        $idHash = implode(' ', (array) $id);
2487
2488 531
        return $this->identityMap[$rootClassName][$idHash] ?? false;
2489
    }
2490
2491
    /**
2492
     * Schedules an entity for dirty-checking at commit-time.
2493
     *
2494
     * @param object $entity The entity to schedule for dirty-checking.
2495
     */
2496 5
    public function scheduleForSynchronization($entity)
2497
    {
2498 5
        $rootClassName = $this->em->getClassMetadata(get_class($entity))->getRootClassName();
2499
2500 5
        $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

2500
        $this->scheduledForSynchronization[$rootClassName][/** @scrutinizer ignore-call */ spl_object_id($entity)] = $entity;
Loading history...
2501 5
    }
2502
2503
    /**
2504
     * Checks whether the UnitOfWork has any pending insertions.
2505
     *
2506
     * @return bool TRUE if this UnitOfWork has pending insertions, FALSE otherwise.
2507
     */
2508
    public function hasPendingInsertions()
2509
    {
2510
        return ! empty($this->entityInsertions);
2511
    }
2512
2513
    /**
2514
     * Calculates the size of the UnitOfWork. The size of the UnitOfWork is the
2515
     * number of entities in the identity map.
2516
     *
2517
     * @return int
2518
     */
2519 1
    public function size()
2520
    {
2521 1
        return \array_sum(\array_map('count', $this->identityMap));
2522
    }
2523
2524
    /**
2525
     * Gets the EntityPersister for an Entity.
2526
     *
2527
     * @param string $entityName The name of the Entity.
2528
     *
2529
     * @return \Doctrine\ORM\Persisters\Entity\EntityPersister
2530
     */
2531 1071
    public function getEntityPersister($entityName)
2532
    {
2533 1071
        if (isset($this->entityPersisters[$entityName])) {
2534 1013
            return $this->entityPersisters[$entityName];
2535
        }
2536
2537 1071
        $class = $this->em->getClassMetadata($entityName);
2538
2539
        switch (true) {
2540 1071
            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...
2541 1030
                $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

2541
                $persister = new BasicEntityPersister($this->em, /** @scrutinizer ignore-type */ $class);
Loading history...
2542 1030
                break;
2543
2544 375
            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...
2545 219
                $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

2545
                $persister = new SingleTablePersister($this->em, /** @scrutinizer ignore-type */ $class);
Loading history...
2546 219
                break;
2547
2548 345
            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...
2549 345
                $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

2549
                $persister = new JoinedSubclassPersister($this->em, /** @scrutinizer ignore-type */ $class);
Loading history...
2550 345
                break;
2551
2552
            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...
2553
                throw new \RuntimeException('No persister found for entity.');
2554
        }
2555
2556 1071
        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

2556
        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...
2557 130
            $persister = $this->em->getConfiguration()
2558 130
                ->getSecondLevelCacheConfiguration()
2559 130
                ->getCacheFactory()
2560 130
                ->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

2560
                ->buildCachedEntityPersister($this->em, $persister, /** @scrutinizer ignore-type */ $class);
Loading history...
2561
        }
2562
2563 1071
        $this->entityPersisters[$entityName] = $persister;
2564
2565 1071
        return $this->entityPersisters[$entityName];
2566
    }
2567
2568
    /**
2569
     * Gets a collection persister for a collection-valued association.
2570
     *
2571
     * @return \Doctrine\ORM\Persisters\Collection\CollectionPersister
2572
     */
2573 560
    public function getCollectionPersister(ToManyAssociationMetadata $association)
2574
    {
2575 560
        $role = $association->getCache()
2576 78
            ? sprintf('%s::%s', $association->getSourceEntity(), $association->getName())
2577 560
            : get_class($association);
2578
2579 560
        if (isset($this->collectionPersisters[$role])) {
2580 427
            return $this->collectionPersisters[$role];
2581
        }
2582
2583 560
        $persister = $association instanceof OneToManyAssociationMetadata
2584 403
            ? new OneToManyPersister($this->em)
2585 560
            : new ManyToManyPersister($this->em);
2586
2587 560
        if ($this->hasCache && $association->getCache()) {
2588 77
            $persister = $this->em->getConfiguration()
2589 77
                ->getSecondLevelCacheConfiguration()
2590 77
                ->getCacheFactory()
2591 77
                ->buildCachedCollectionPersister($this->em, $persister, $association);
2592
        }
2593
2594 560
        $this->collectionPersisters[$role] = $persister;
2595
2596 560
        return $this->collectionPersisters[$role];
2597
    }
2598
2599
    /**
2600
     * INTERNAL:
2601
     * Registers an entity as managed.
2602
     *
2603
     * @param object  $entity The entity.
2604
     * @param mixed[] $id     Map containing identifier field names as key and its associated values.
2605
     * @param mixed[] $data   The original entity data.
2606
     */
2607 289
    public function registerManaged($entity, array $id, array $data)
2608
    {
2609 289
        $isProxy = $entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized();
2610 289
        $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

2610
        $oid     = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
2611
2612 289
        $this->entityIdentifiers[$oid]  = $id;
2613 289
        $this->entityStates[$oid]       = self::STATE_MANAGED;
2614 289
        $this->originalEntityData[$oid] = $data;
2615
2616 289
        $this->addToIdentityMap($entity);
2617
2618 283
        if ($entity instanceof NotifyPropertyChanged && ! $isProxy) {
2619 1
            $entity->addPropertyChangedListener($this);
2620
        }
2621 283
    }
2622
2623
    /**
2624
     * INTERNAL:
2625
     * Clears the property changeset of the entity with the given OID.
2626
     *
2627
     * @param string $oid The entity's OID.
2628
     */
2629
    public function clearEntityChangeSet($oid)
2630
    {
2631
        unset($this->entityChangeSets[$oid]);
2632
    }
2633
2634
    /* PropertyChangedListener implementation */
2635
2636
    /**
2637
     * Notifies this UnitOfWork of a property change in an entity.
2638
     *
2639
     * @param object $entity       The entity that owns the property.
2640
     * @param string $propertyName The name of the property that changed.
2641
     * @param mixed  $oldValue     The old value of the property.
2642
     * @param mixed  $newValue     The new value of the property.
2643
     */
2644 3
    public function propertyChanged($entity, $propertyName, $oldValue, $newValue)
2645
    {
2646 3
        $class = $this->em->getClassMetadata(get_class($entity));
2647
2648 3
        if (! $class->getProperty($propertyName)) {
2649
            return; // ignore non-persistent fields
2650
        }
2651
2652 3
        $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

2652
        $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
2653
2654
        // Update changeset and mark entity for synchronization
2655 3
        $this->entityChangeSets[$oid][$propertyName] = [$oldValue, $newValue];
2656
2657 3
        if (! isset($this->scheduledForSynchronization[$class->getRootClassName()][$oid])) {
2658 3
            $this->scheduleForSynchronization($entity);
2659
        }
2660 3
    }
2661
2662
    /**
2663
     * Gets the currently scheduled entity insertions in this UnitOfWork.
2664
     *
2665
     * @return object[]
2666
     */
2667 2
    public function getScheduledEntityInsertions()
2668
    {
2669 2
        return $this->entityInsertions;
2670
    }
2671
2672
    /**
2673
     * Gets the currently scheduled entity updates in this UnitOfWork.
2674
     *
2675
     * @return object[]
2676
     */
2677 3
    public function getScheduledEntityUpdates()
2678
    {
2679 3
        return $this->entityUpdates;
2680
    }
2681
2682
    /**
2683
     * Gets the currently scheduled entity deletions in this UnitOfWork.
2684
     *
2685
     * @return object[]
2686
     */
2687 1
    public function getScheduledEntityDeletions()
2688
    {
2689 1
        return $this->entityDeletions;
2690
    }
2691
2692
    /**
2693
     * Gets the currently scheduled complete collection deletions
2694
     *
2695
     * @return Collection[]|object[][]
2696
     */
2697 1
    public function getScheduledCollectionDeletions()
2698
    {
2699 1
        return $this->collectionDeletions;
2700
    }
2701
2702
    /**
2703
     * Gets the currently scheduled collection inserts, updates and deletes.
2704
     *
2705
     * @return Collection[]|object[][]
2706
     */
2707
    public function getScheduledCollectionUpdates()
2708
    {
2709
        return $this->collectionUpdates;
2710
    }
2711
2712
    /**
2713
     * Helper method to initialize a lazy loading proxy or persistent collection.
2714
     *
2715
     * @param object $obj
2716
     */
2717 2
    public function initializeObject($obj)
2718
    {
2719 2
        if ($obj instanceof GhostObjectInterface) {
2720 1
            $obj->initializeProxy();
2721
2722 1
            return;
2723
        }
2724
2725 1
        if ($obj instanceof PersistentCollection) {
2726 1
            $obj->initialize();
2727
        }
2728 1
    }
2729
2730
    /**
2731
     * Helper method to show an object as string.
2732
     *
2733
     * @param object $obj
2734
     *
2735
     * @return string
2736
     */
2737
    private static function objToStr($obj)
2738
    {
2739
        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

2739
        return method_exists($obj, '__toString') ? (string) $obj : get_class($obj) . '@' . /** @scrutinizer ignore-call */ spl_object_id($obj);
Loading history...
2740
    }
2741
2742
    /**
2743
     * Marks an entity as read-only so that it will not be considered for updates during UnitOfWork#commit().
2744
     *
2745
     * This operation cannot be undone as some parts of the UnitOfWork now keep gathering information
2746
     * on this object that might be necessary to perform a correct update.
2747
     *
2748
     * @param object $object
2749
     *
2750
     * @throws ORMInvalidArgumentException
2751
     */
2752 6
    public function markReadOnly($object)
2753
    {
2754 6
        if (! is_object($object) || ! $this->isInIdentityMap($object)) {
2755 1
            throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object);
2756
        }
2757
2758 5
        $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

2758
        $this->readOnlyObjects[/** @scrutinizer ignore-call */ spl_object_id($object)] = true;
Loading history...
2759 5
    }
2760
2761
    /**
2762
     * Is this entity read only?
2763
     *
2764
     * @param object $object
2765
     *
2766
     * @return bool
2767
     *
2768
     * @throws ORMInvalidArgumentException
2769
     */
2770 3
    public function isReadOnly($object)
2771
    {
2772 3
        if (! is_object($object)) {
2773
            throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object);
2774
        }
2775
2776 3
        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

2776
        return isset($this->readOnlyObjects[/** @scrutinizer ignore-call */ spl_object_id($object)]);
Loading history...
2777
    }
2778
2779
    /**
2780
     * Perform whatever processing is encapsulated here after completion of the transaction.
2781
     */
2782
    private function afterTransactionComplete()
2783
    {
2784 986
        $this->performCallbackOnCachedPersister(function (CachedPersister $persister) {
2785 94
            $persister->afterTransactionComplete();
2786 986
        });
2787 986
    }
2788
2789
    /**
2790
     * Perform whatever processing is encapsulated here after completion of the rolled-back.
2791
     */
2792
    private function afterTransactionRolledBack()
2793
    {
2794 10
        $this->performCallbackOnCachedPersister(function (CachedPersister $persister) {
2795
            $persister->afterTransactionRolledBack();
2796 10
        });
2797 10
    }
2798
2799
    /**
2800
     * Performs an action after the transaction.
2801
     */
2802 991
    private function performCallbackOnCachedPersister(callable $callback)
2803
    {
2804 991
        if (! $this->hasCache) {
2805 897
            return;
2806
        }
2807
2808 94
        foreach (array_merge($this->entityPersisters, $this->collectionPersisters) as $persister) {
2809 94
            if ($persister instanceof CachedPersister) {
2810 94
                $callback($persister);
2811
            }
2812
        }
2813 94
    }
2814
2815 996
    private function dispatchOnFlushEvent()
2816
    {
2817 996
        if ($this->eventManager->hasListeners(Events::onFlush)) {
2818 4
            $this->eventManager->dispatchEvent(Events::onFlush, new OnFlushEventArgs($this->em));
2819
        }
2820 996
    }
2821
2822 991
    private function dispatchPostFlushEvent()
2823
    {
2824 991
        if ($this->eventManager->hasListeners(Events::postFlush)) {
2825 5
            $this->eventManager->dispatchEvent(Events::postFlush, new PostFlushEventArgs($this->em));
2826
        }
2827 990
    }
2828
2829
    /**
2830
     * Verifies if two given entities actually are the same based on identifier comparison
2831
     *
2832
     * @param object $entity1
2833
     * @param object $entity2
2834
     *
2835
     * @return bool
2836
     */
2837 16
    private function isIdentifierEquals($entity1, $entity2)
2838
    {
2839 16
        if ($entity1 === $entity2) {
2840
            return true;
2841
        }
2842
2843 16
        $class     = $this->em->getClassMetadata(get_class($entity1));
2844 16
        $persister = $this->getEntityPersister($class->getClassName());
2845
2846 16
        if ($class !== $this->em->getClassMetadata(get_class($entity2))) {
2847 11
            return false;
2848
        }
2849
2850 5
        $identifierFlattener = $this->em->getIdentifierFlattener();
2851
2852 5
        $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

2852
        $oid1 = /** @scrutinizer ignore-call */ spl_object_id($entity1);
Loading history...
2853 5
        $oid2 = spl_object_id($entity2);
2854
2855 5
        $id1 = $this->entityIdentifiers[$oid1]
2856 5
            ?? $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

2856
            ?? $identifierFlattener->flattenIdentifier(/** @scrutinizer ignore-type */ $class, $persister->getIdentifier($entity1));
Loading history...
2857 5
        $id2 = $this->entityIdentifiers[$oid2]
2858 5
            ?? $identifierFlattener->flattenIdentifier($class, $persister->getIdentifier($entity2));
2859
2860 5
        return $id1 === $id2 || implode(' ', $id1) === implode(' ', $id2);
2861
    }
2862
2863
    /**
2864
     * @throws ORMInvalidArgumentException
2865
     */
2866 993
    private function assertThatThereAreNoUnintentionallyNonPersistedAssociations() : void
2867
    {
2868 993
        $entitiesNeedingCascadePersist = \array_diff_key($this->nonCascadedNewDetectedEntities, $this->entityInsertions);
2869
2870 993
        $this->nonCascadedNewDetectedEntities = [];
2871
2872 993
        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...
2873 4
            throw ORMInvalidArgumentException::newEntitiesFoundThroughRelationships(
2874 4
                \array_values($entitiesNeedingCascadePersist)
2875
            );
2876
        }
2877 991
    }
2878
2879
    /**
2880
     * This method called by hydrators, and indicates that hydrator totally completed current hydration cycle.
2881
     * Unit of work able to fire deferred events, related to loading events here.
2882
     *
2883
     * @internal should be called internally from object hydrators
2884
     */
2885 872
    public function hydrationComplete()
2886
    {
2887 872
        $this->hydrationCompleteHandler->hydrationComplete();
2888 872
    }
2889
}
2890