Failed Conditions
Push — master ( 8be1e3...e3936d )
by Marco
14s
created

UnitOfWork::tryGetByIdOrLoadProxy()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 25
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 11
nc 4
nop 2
dl 0
loc 25
ccs 0
cts 12
cp 0
crap 20
rs 8.5806
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 2273
    public function __construct(EntityManagerInterface $em)
290
    {
291 2273
        $this->em                       = $em;
292 2273
        $this->eventManager             = $em->getEventManager();
293 2273
        $this->listenersInvoker         = new ListenersInvoker($em);
294 2273
        $this->hasCache                 = $em->getConfiguration()->isSecondLevelCacheEnabled();
295 2273
        $this->instantiator             = new Instantiator();
296 2273
        $this->hydrationCompleteHandler = new HydrationCompleteHandler($this->listenersInvoker, $em);
297 2273
        $this->normalizeIdentifier      = new NormalizeIdentifier();
298 2273
    }
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 1016
    public function commit()
316
    {
317
        // Raise preFlush
318 1016
        if ($this->eventManager->hasListeners(Events::preFlush)) {
319 2
            $this->eventManager->dispatchEvent(Events::preFlush, new PreFlushEventArgs($this->em));
320
        }
321
322 1016
        $this->computeChangeSets();
323
324 1014
        if (! ($this->entityInsertions ||
325 154
                $this->entityDeletions ||
326 119
                $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 36
                $this->collectionUpdates ||
328 33
                $this->collectionDeletions ||
329 1014
                $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 21
            $this->dispatchOnFlushEvent();
331 21
            $this->dispatchPostFlushEvent();
332
333 21
            return; // Nothing to do.
334
        }
335
336 1009
        $this->assertThatThereAreNoUnintentionallyNonPersistedAssociations();
337
338 1007
        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 1007
        $this->dispatchOnFlushEvent();
345
346
        // Now we need a commit order to maintain referential integrity
347 1007
        $commitOrder = $this->getCommitOrder();
348
349 1007
        $conn = $this->em->getConnection();
350 1007
        $conn->beginTransaction();
351
352
        try {
353
            // Collection deletions (deletions of complete collections)
354 1007
            foreach ($this->collectionDeletions as $collectionToDelete) {
355 19
                $this->getCollectionPersister($collectionToDelete->getMapping())->delete($collectionToDelete);
356
            }
357
358 1007
            if ($this->entityInsertions) {
359 1003
                foreach ($commitOrder as $class) {
360 1003
                    $this->executeInserts($class);
361
                }
362
            }
363
364 1006
            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 108
                foreach ($commitOrder as $class) {
366 108
                    $this->executeUpdates($class);
367
                }
368
            }
369
370
            // Extra updates that were requested by persisters.
371 1002
            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 33
                $this->executeExtraUpdates();
373
            }
374
375
            // Collection updates (deleteRows, updateRows, insertRows)
376 1002
            foreach ($this->collectionUpdates as $collectionToUpdate) {
377 528
                $this->getCollectionPersister($collectionToUpdate->getMapping())->update($collectionToUpdate);
378
            }
379
380
            // Entity deletions come last and need to be in reverse commit order
381 1002
            if ($this->entityDeletions) {
382 60
                foreach (array_reverse($commitOrder) as $committedEntityName) {
383 60
                    if (! $this->entityDeletions) {
384 34
                        break; // just a performance optimisation
385
                    }
386
387 60
                    $this->executeDeletions($committedEntityName);
388
                }
389
            }
390
391 1002
            $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 1002
        $this->afterTransactionComplete();
402
403
        // Take new snapshots from visited collections
404 1002
        foreach ($this->visitedCollections as $coll) {
405 527
            $coll->takeSnapshot();
406
        }
407
408 1002
        $this->dispatchPostFlushEvent();
409
410
        // Clean up
411 1001
        $this->entityInsertions            =
412 1001
        $this->entityUpdates               =
413 1001
        $this->entityDeletions             =
414 1001
        $this->extraUpdates                =
415 1001
        $this->entityChangeSets            =
416 1001
        $this->collectionUpdates           =
417 1001
        $this->collectionDeletions         =
418 1001
        $this->visitedCollections          =
419 1001
        $this->scheduledForSynchronization =
420 1001
        $this->orphanRemovals              = [];
421 1001
    }
422
423
    /**
424
     * Computes the changesets of all entities scheduled for insertion.
425
     */
426 1016
    private function computeScheduleInsertsChangeSets()
427
    {
428 1016
        foreach ($this->entityInsertions as $entity) {
429 1007
            $class = $this->em->getClassMetadata(get_class($entity));
430
431 1007
            $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 1014
    }
434
435
    /**
436
     * Executes any extra updates that have been scheduled.
437
     */
438 33
    private function executeExtraUpdates()
439
    {
440 33
        foreach ($this->extraUpdates as $oid => $update) {
441 33
            list ($entity, $changeset) = $update;
442
443 33
            $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 33
            $this->getEntityPersister(get_class($entity))->update($entity);
449
        }
450
451 33
        $this->extraUpdates = [];
452 33
    }
453
454
    /**
455
     * Gets the changeset for an entity.
456
     *
457
     * @param object $entity
458
     *
459
     * @return mixed[]
460
     */
461 1002
    public function & getEntityChangeSet($entity)
462
    {
463 1002
        $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 1002
        $data = [];
465
466 1002
        if (! isset($this->entityChangeSets[$oid])) {
467 2
            return $data;
468
        }
469
470 1002
        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 1017
    public function computeChangeSet(ClassMetadata $class, $entity)
507
    {
508 1017
        $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 1017
        if (isset($this->readOnlyObjects[$oid])) {
511 2
            return;
512
        }
513
514 1017
        if ($class->inheritanceType !== InheritanceType::NONE) {
515 330
            $class = $this->em->getClassMetadata(get_class($entity));
516
        }
517
518 1017
        $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 1017
        if ($invoke !== ListenersInvoker::INVOKE_NONE) {
521 135
            $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 1017
        $actualData = [];
525
526 1017
        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 1017
            $value = $property->getValue($entity);
528
529 1017
            if ($property instanceof ToManyAssociationMetadata && $value !== null) {
530 778
                if ($value instanceof PersistentCollection && $value->getOwner() === $entity) {
531 184
                    continue;
532
                }
533
534 775
                $value = $property->wrap($entity, $value, $this->em);
535
536 775
                $property->setValue($entity, $value);
537
538 775
                $actualData[$name] = $value;
539
540 775
                continue;
541
            }
542
543 1017
            if (( ! $class->isIdentifier($name)
544 1017
                    || ! $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 1017
                    || ! $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 1017
                    || $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 1017
                ) && (! $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 1017
                $actualData[$name] = $value;
549
            }
550
        }
551
552 1017
        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 1013
            $this->originalEntityData[$oid] = $actualData;
556 1013
            $changeSet                      = [];
557
558 1013
            foreach ($actualData as $propName => $actualValue) {
559 993
                $property = $class->getProperty($propName);
560
561 993
                if (($property instanceof FieldMetadata) ||
562 993
                    ($property instanceof ToOneAssociationMetadata && $property->isOwningSide())) {
563 993
                    $changeSet[$propName] = [null, $actualValue];
564
                }
565
            }
566
567 1013
            $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 250
            $originalData           = $this->originalEntityData[$oid];
572 250
            $isChangeTrackingNotify = $class->changeTrackingPolicy === ChangeTrackingPolicy::NOTIFY;
573 250
            $changeSet              = ($isChangeTrackingNotify && isset($this->entityChangeSets[$oid]))
574
                ? $this->entityChangeSets[$oid]
575 250
                : [];
576
577 250
            foreach ($actualData as $propName => $actualValue) {
578
                // skip field, its a partially omitted one!
579 240
                if (! (isset($originalData[$propName]) || array_key_exists($propName, $originalData))) {
580 36
                    continue;
581
                }
582
583 240
                $orgValue = $originalData[$propName];
584
585
                // skip if value haven't changed
586 240
                if ($orgValue === $actualValue) {
587 224
                    continue;
588
                }
589
590 106
                $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 106
                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 106
                    case ($property instanceof FieldMetadata):
615 55
                        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 55
                        $changeSet[$propName] = [$orgValue, $actualValue];
622 55
                        break;
623
624 57
                    case ($property instanceof ToOneAssociationMetadata):
625 46
                        if ($property->isOwningSide()) {
626 20
                            $changeSet[$propName] = [$orgValue, $actualValue];
627
                        }
628
629 46
                        if ($orgValue !== null && $property->isOrphanRemoval()) {
630 4
                            $this->scheduleOrphanRemoval($orgValue);
631
                        }
632
633 46
                        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 106
                    default:
649
                        // Do nothing
650
                }
651
            }
652
653 250
            if ($changeSet) {
654 81
                $this->entityChangeSets[$oid]   = $changeSet;
655 81
                $this->originalEntityData[$oid] = $actualData;
656 81
                $this->entityUpdates[$oid]      = $entity;
657
            }
658
        }
659
660
        // Look for changes in associations of the entity
661 1017
        foreach ($class->getDeclaredPropertiesIterator() as $property) {
662 1017
            if (! $property instanceof AssociationMetadata) {
663 1017
                continue;
664
            }
665
666 889
            $value = $property->getValue($entity);
667
668 889
            if ($value === null) {
669 628
                continue;
670
            }
671
672 865
            $this->computeAssociationChanges($property, $value);
673
674 857
            if ($property instanceof ManyToManyAssociationMetadata &&
675 857
                $value instanceof PersistentCollection &&
676 857
                ! isset($this->entityChangeSets[$oid]) &&
677 857
                $property->isOwningSide() &&
678 857
                $value->isDirty()) {
679 31
                $this->entityChangeSets[$oid]   = [];
680 31
                $this->originalEntityData[$oid] = $actualData;
681 857
                $this->entityUpdates[$oid]      = $entity;
682
            }
683
        }
684 1009
    }
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 1016
    public function computeChangeSets()
692
    {
693
        // Compute changes for INSERTed entities first. This must always happen.
694 1016
        $this->computeScheduleInsertsChangeSets();
695
696
        // Compute changes for other MANAGED entities. Change tracking policies take effect here.
697 1014
        foreach ($this->identityMap as $className => $entities) {
698 447
            $class = $this->em->getClassMetadata($className);
699
700
            // Skip class if instances are read-only
701 447
            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 446
                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 444
                    $entitiesToProcess = $entities;
710 444
                    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 446
            foreach ($entitiesToProcess as $entity) {
721
                // Ignore uninitialized proxy objects
722 426
                if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
723 37
                    continue;
724
                }
725
726
                // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION OR DELETION are processed here.
727 424
                $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 424
                if (! isset($this->entityInsertions[$oid]) && ! isset($this->entityDeletions[$oid]) && isset($this->entityStates[$oid])) {
730 446
                    $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 1014
    }
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 865
    private function computeAssociationChanges(AssociationMetadata $association, $value)
746
    {
747 865
        if ($value instanceof GhostObjectInterface && ! $value->isProxyInitialized()) {
748 30
            return;
749
        }
750
751 864
        if ($value instanceof PersistentCollection && $value->isDirty()) {
752 531
            $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 531
            $this->collectionUpdates[$coid]  = $value;
755 531
            $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 864
        $unwrappedValue = ($association instanceof ToOneAssociationMetadata) ? [$value] : $value->unwrap();
762 864
        $targetEntity   = $association->getTargetEntity();
763 864
        $targetClass    = $this->em->getClassMetadata($targetEntity);
764
765 864
        foreach ($unwrappedValue as $key => $entry) {
766 721
            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 713
            $state = $this->getEntityState($entry, self::STATE_NEW);
771
772 713
            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 713
                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 706
                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 706
                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 713
                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 856
    }
814
815
    /**
816
     * @param \Doctrine\ORM\Mapping\ClassMetadata $class
817
     * @param object                              $entity
818
     */
819 1028
    private function persistNew($class, $entity)
820
    {
821 1028
        $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 1028
        $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::prePersist);
823
824 1028
        if ($invoke !== ListenersInvoker::INVOKE_NONE) {
825 137
            $this->listenersInvoker->invoke($class, Events::prePersist, $entity, new LifecycleEventArgs($entity, $this->em), $invoke);
826
        }
827
828 1028
        $generationPlan = $class->getValueGenerationPlan();
829 1028
        $persister      = $this->getEntityPersister($class->getClassName());
830 1028
        $generationPlan->executeImmediate($this->em, $entity);
831
832 1028
        if (! $generationPlan->containsDeferred()) {
833 269
            $id                            = $this->em->getIdentifierFlattener()->flattenIdentifier($class, $persister->getIdentifier($entity));
834 269
            $this->entityIdentifiers[$oid] = $id;
835
        }
836
837 1028
        $this->entityStates[$oid] = self::STATE_MANAGED;
838
839 1028
        $this->scheduleForInsert($entity);
840 1028
    }
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 15
    public function recomputeSingleEntityChangeSet(ClassMetadata $class, $entity) : void
860
    {
861 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

861
        $oid = /** @scrutinizer ignore-call */ spl_object_id($entity);
Loading history...
862
863 15
        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 15
        if ($class->changeTrackingPolicy === ChangeTrackingPolicy::NOTIFY) {
869
            return;
870
        }
871
872 15
        if ($class->inheritanceType !== InheritanceType::NONE) {
873 3
            $class = $this->em->getClassMetadata(get_class($entity));
874
        }
875
876 15
        $actualData = [];
877
878 15
        foreach ($class->getDeclaredPropertiesIterator() as $name => $property) {
879
            switch (true) {
880 15
                case ($property instanceof VersionFieldMetadata):
881
                    // Ignore version field
882
                    break;
883
884 15
                case ($property instanceof FieldMetadata):
885 15
                    if (! $property->isPrimaryKey()
886 15
                        || ! $property->getValueGenerator()
887 15
                        || $property->getValueGenerator()->getType() !== GeneratorType::IDENTITY) {
888 15
                        $actualData[$name] = $property->getValue($entity);
889
                    }
890
891 15
                    break;
892
893 11
                case ($property instanceof ToOneAssociationMetadata):
894 9
                    $actualData[$name] = $property->getValue($entity);
895 15
                    break;
896
            }
897
        }
898
899 15
        if (! isset($this->originalEntityData[$oid])) {
900
            throw new \RuntimeException('Cannot call recomputeSingleEntityChangeSet before computeChangeSet on an entity.');
901
        }
902
903 15
        $originalData = $this->originalEntityData[$oid];
904 15
        $changeSet    = [];
905
906 15
        foreach ($actualData as $propName => $actualValue) {
907 15
            $orgValue = $originalData[$propName] ?? null;
908
909 15
            if ($orgValue !== $actualValue) {
910 15
                $changeSet[$propName] = [$orgValue, $actualValue];
911
            }
912
        }
913
914 15
        if ($changeSet) {
915 7
            if (isset($this->entityChangeSets[$oid])) {
916 6
                $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 7
            $this->originalEntityData[$oid] = $actualData;
922
        }
923 15
    }
924
925
    /**
926
     * Executes all entity insertions for entities of the specified type.
927
     */
928 1003
    private function executeInserts(ClassMetadata $class) : void
929
    {
930 1003
        $className      = $class->getClassName();
931 1003
        $persister      = $this->getEntityPersister($className);
932 1003
        $invoke         = $this->listenersInvoker->getSubscribedSystems($class, Events::postPersist);
933 1003
        $generationPlan = $class->getValueGenerationPlan();
934
935 1003
        foreach ($this->entityInsertions as $oid => $entity) {
936 1003
            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 857
                continue;
938
            }
939
940 1003
            $persister->insert($entity);
941
942 1002
            if ($generationPlan->containsDeferred()) {
943
                // Entity has post-insert IDs
944 910
                $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 910
                $id  = $this->em->getIdentifierFlattener()->flattenIdentifier($class, $persister->getIdentifier($entity));
946
947 910
                $this->entityIdentifiers[$oid]  = $id;
948 910
                $this->entityStates[$oid]       = self::STATE_MANAGED;
949 910
                $this->originalEntityData[$oid] = $id + $this->originalEntityData[$oid];
950
951 910
                $this->addToIdentityMap($entity);
952
            }
953
954 1002
            unset($this->entityInsertions[$oid]);
955
956 1002
            if ($invoke !== ListenersInvoker::INVOKE_NONE) {
957 133
                $eventArgs = new LifecycleEventArgs($entity, $this->em);
958
959 1002
                $this->listenersInvoker->invoke($class, Events::postPersist, $entity, $eventArgs, $invoke);
960
            }
961
        }
962 1003
    }
963
964
    /**
965
     * Executes all entity updates for entities of the specified type.
966
     *
967
     * @param \Doctrine\ORM\Mapping\ClassMetadata $class
968
     */
969 108
    private function executeUpdates($class)
970
    {
971 108
        $className        = $class->getClassName();
972 108
        $persister        = $this->getEntityPersister($className);
973 108
        $preUpdateInvoke  = $this->listenersInvoker->getSubscribedSystems($class, Events::preUpdate);
974 108
        $postUpdateInvoke = $this->listenersInvoker->getSubscribedSystems($class, Events::postUpdate);
975
976 108
        foreach ($this->entityUpdates as $oid => $entity) {
977 108
            if ($this->em->getClassMetadata(get_class($entity))->getClassName() !== $className) {
978 70
                continue;
979
            }
980
981 108
            if ($preUpdateInvoke !== ListenersInvoker::INVOKE_NONE) {
982 12
                $this->listenersInvoker->invoke($class, Events::preUpdate, $entity, new PreUpdateEventArgs($entity, $this->em, $this->getEntityChangeSet($entity)), $preUpdateInvoke);
983
984 12
                $this->recomputeSingleEntityChangeSet($class, $entity);
985
            }
986
987 108
            if (! empty($this->entityChangeSets[$oid])) {
988
//                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...
989
//                \Doctrine\Common\Util\Debug::dump($this->entityChangeSets[$oid], 3);
990
991 78
                $persister->update($entity);
992
            }
993
994 104
            unset($this->entityUpdates[$oid]);
995
996 104
            if ($postUpdateInvoke !== ListenersInvoker::INVOKE_NONE) {
997 104
                $this->listenersInvoker->invoke($class, Events::postUpdate, $entity, new LifecycleEventArgs($entity, $this->em), $postUpdateInvoke);
998
            }
999
        }
1000 104
    }
1001
1002
    /**
1003
     * Executes all entity deletions for entities of the specified type.
1004
     *
1005
     * @param \Doctrine\ORM\Mapping\ClassMetadata $class
1006
     */
1007 60
    private function executeDeletions($class)
1008
    {
1009 60
        $className = $class->getClassName();
1010 60
        $persister = $this->getEntityPersister($className);
1011 60
        $invoke    = $this->listenersInvoker->getSubscribedSystems($class, Events::postRemove);
1012
1013 60
        foreach ($this->entityDeletions as $oid => $entity) {
1014 60
            if ($this->em->getClassMetadata(get_class($entity))->getClassName() !== $className) {
1015 24
                continue;
1016
            }
1017
1018 60
            $persister->delete($entity);
1019
1020
            unset(
1021 60
                $this->entityDeletions[$oid],
1022 60
                $this->entityIdentifiers[$oid],
1023 60
                $this->originalEntityData[$oid],
1024 60
                $this->entityStates[$oid]
1025
            );
1026
1027
            // Entity with this $oid after deletion treated as NEW, even if the $oid
1028
            // is obtained by a new entity because the old one went out of scope.
1029
            //$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...
1030 60
            if (! $class->isIdentifierComposite()) {
1031 57
                $property = $class->getProperty($class->getSingleIdentifierFieldName());
1032
1033 57
                if ($property instanceof FieldMetadata && $property->hasValueGenerator()) {
1034 50
                    $property->setValue($entity, null);
1035
                }
1036
            }
1037
1038 60
            if ($invoke !== ListenersInvoker::INVOKE_NONE) {
1039 9
                $eventArgs = new LifecycleEventArgs($entity, $this->em);
1040
1041 60
                $this->listenersInvoker->invoke($class, Events::postRemove, $entity, $eventArgs, $invoke);
1042
            }
1043
        }
1044 59
    }
1045
1046
    /**
1047
     * Gets the commit order.
1048
     *
1049
     * @return ClassMetadata[]
1050
     */
1051 1007
    private function getCommitOrder()
1052
    {
1053 1007
        $calc = new Internal\CommitOrderCalculator();
1054
1055
        // See if there are any new classes in the changeset, that are not in the
1056
        // commit order graph yet (don't have a node).
1057
        // We have to inspect changeSet to be able to correctly build dependencies.
1058
        // It is not possible to use IdentityMap here because post inserted ids
1059
        // are not yet available.
1060 1007
        $newNodes = [];
1061
1062 1007
        foreach (\array_merge($this->entityInsertions, $this->entityUpdates, $this->entityDeletions) as $entity) {
1063 1007
            $class = $this->em->getClassMetadata(get_class($entity));
1064
1065 1007
            if ($calc->hasNode($class->getClassName())) {
1066 636
                continue;
1067
            }
1068
1069 1007
            $calc->addNode($class->getClassName(), $class);
1070
1071 1007
            $newNodes[] = $class;
1072
        }
1073
1074
        // Calculate dependencies for new nodes
1075 1007
        while ($class = array_pop($newNodes)) {
1076 1007
            foreach ($class->getDeclaredPropertiesIterator() as $property) {
1077 1007
                if (! ($property instanceof ToOneAssociationMetadata && $property->isOwningSide())) {
1078 1007
                    continue;
1079
                }
1080
1081 837
                $targetClass = $this->em->getClassMetadata($property->getTargetEntity());
1082
1083 837
                if (! $calc->hasNode($targetClass->getClassName())) {
1084 645
                    $calc->addNode($targetClass->getClassName(), $targetClass);
1085
1086 645
                    $newNodes[] = $targetClass;
1087
                }
1088
1089 837
                $weight = ! array_filter(
1090 837
                    $property->getJoinColumns(),
1091 837
                    function (JoinColumnMetadata $joinColumn) {
1092 837
                        return $joinColumn->isNullable();
1093 837
                    }
1094
                );
1095
1096 837
                $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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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