Failed Conditions
Pull Request — develop (#6719)
by Marco
65:21
created

UnitOfWork::setOriginalEntityData()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 2
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM;
6
7
use Doctrine\Common\Collections\Collection;
8
use Doctrine\Common\NotifyPropertyChanged;
9
use Doctrine\Common\PropertyChangedListener;
10
use Doctrine\DBAL\LockMode;
11
use Doctrine\Instantiator\Instantiator;
12
use Doctrine\ORM\Cache\Persister\CachedPersister;
13
use Doctrine\ORM\Event\LifecycleEventArgs;
14
use Doctrine\ORM\Event\ListenersInvoker;
15
use Doctrine\ORM\Event\OnFlushEventArgs;
16
use Doctrine\ORM\Event\PostFlushEventArgs;
17
use Doctrine\ORM\Event\PreFlushEventArgs;
18
use Doctrine\ORM\Event\PreUpdateEventArgs;
19
use Doctrine\ORM\Internal\HydrationCompleteHandler;
20
use Doctrine\ORM\Mapping\AssociationMetadata;
21
use Doctrine\ORM\Mapping\ChangeTrackingPolicy;
22
use Doctrine\ORM\Mapping\ClassMetadata;
23
use Doctrine\ORM\Mapping\FetchMode;
24
use Doctrine\ORM\Mapping\FieldMetadata;
25
use Doctrine\ORM\Mapping\GeneratorType;
26
use Doctrine\ORM\Mapping\InheritanceType;
27
use Doctrine\ORM\Mapping\JoinColumnMetadata;
28
use Doctrine\ORM\Mapping\ManyToManyAssociationMetadata;
29
use Doctrine\ORM\Mapping\OneToManyAssociationMetadata;
30
use Doctrine\ORM\Mapping\OneToOneAssociationMetadata;
31
use Doctrine\ORM\Mapping\ToManyAssociationMetadata;
32
use Doctrine\ORM\Mapping\ToOneAssociationMetadata;
33
use Doctrine\ORM\Mapping\VersionFieldMetadata;
34
use Doctrine\ORM\Persisters\Collection\ManyToManyPersister;
35
use Doctrine\ORM\Persisters\Collection\OneToManyPersister;
36
use Doctrine\ORM\Persisters\Entity\BasicEntityPersister;
37
use Doctrine\ORM\Persisters\Entity\JoinedSubclassPersister;
38
use Doctrine\ORM\Persisters\Entity\SingleTablePersister;
39
use Doctrine\ORM\Utility\NormalizeIdentifier;
40
use Exception;
41
use InvalidArgumentException;
42
use ProxyManager\Proxy\GhostObjectInterface;
43
use UnexpectedValueException;
44
45
/**
46
 * The UnitOfWork is responsible for tracking changes to objects during an
47
 * "object-level" transaction and for writing out changes to the database
48
 * in the correct order.
49
 *
50
 * Internal note: This class contains highly performance-sensitive code.
51
 *
52
 * @since       2.0
53
 * @author      Benjamin Eberlei <[email protected]>
54
 * @author      Guilherme Blanco <[email protected]>
55
 * @author      Jonathan Wage <[email protected]>
56
 * @author      Roman Borschel <[email protected]>
57
 * @author      Rob Caiger <[email protected]>
58
 */
59
class UnitOfWork implements PropertyChangedListener
60
{
61
    /**
62
     * An entity is in MANAGED state when its persistence is managed by an EntityManager.
63
     */
64
    const STATE_MANAGED = 1;
65
66
    /**
67
     * An entity is new if it has just been instantiated (i.e. using the "new" operator)
68
     * and is not (yet) managed by an EntityManager.
69
     */
70
    const STATE_NEW = 2;
71
72
    /**
73
     * A detached entity is an instance with persistent state and identity that is not
74
     * (or no longer) associated with an EntityManager (and a UnitOfWork).
75
     */
76
    const STATE_DETACHED = 3;
77
78
    /**
79
     * A removed entity instance is an instance with a persistent identity,
80
     * associated with an EntityManager, whose persistent state will be deleted
81
     * on commit.
82
     */
83
    const STATE_REMOVED = 4;
84
85
    /**
86
     * Hint used to collect all primary keys of associated entities during hydration
87
     * and execute it in a dedicated query afterwards
88
     * @see https://doctrine-orm.readthedocs.org/en/latest/reference/dql-doctrine-query-language.html?highlight=eager#temporarily-change-fetch-mode-in-dql
89
     */
90
    const HINT_DEFEREAGERLOAD = 'deferEagerLoad';
91
92
    /**
93
     * The identity map that holds references to all managed entities that have
94
     * an identity. The entities are grouped by their class name.
95
     * Since all classes in a hierarchy must share the same identifier set,
96
     * we always take the root class name of the hierarchy.
97
     *
98
     * @var array
99
     */
100
    private $identityMap = [];
101
102
    /**
103
     * Map of all identifiers of managed entities.
104
     * This is a 2-dimensional data structure (map of maps). Keys are object ids (spl_object_hash).
105
     * Values are maps of entity identifiers, where its key is the column name and the value is the raw value.
106
     *
107
     * @var array
108
     */
109
    private $entityIdentifiers = [];
110
111
    /**
112
     * Map of the original entity data of managed entities.
113
     * This is a 2-dimensional data structure (map of maps). Keys are object ids (spl_object_hash).
114
     * Values are maps of entity data, where its key is the field name and the value is the converted
115
     * (convertToPHPValue) value.
116
     * This structure is used for calculating changesets at commit time.
117
     *
118
     * Internal: Note that PHPs "copy-on-write" behavior helps a lot with memory usage.
119
     *           A value will only really be copied if the value in the entity is modified by the user.
120
     *
121
     * @var array
122
     */
123
    private $originalEntityData = [];
124
125
    /**
126
     * Map of entity changes. Keys are object ids (spl_object_hash).
127
     * Filled at the beginning of a commit of the UnitOfWork and cleaned at the end.
128
     *
129
     * @var array
130
     */
131
    private $entityChangeSets = [];
132
133
    /**
134
     * The (cached) states of any known entities.
135
     * Keys are object ids (spl_object_hash).
136
     *
137
     * @var array
138
     */
139
    private $entityStates = [];
140
141
    /**
142
     * Map of entities that are scheduled for dirty checking at commit time.
143
     * This is only used for entities with a change tracking policy of DEFERRED_EXPLICIT.
144
     * Keys are object ids (spl_object_hash).
145
     *
146
     * @var array
147
     */
148
    private $scheduledForSynchronization = [];
149
150
    /**
151
     * A list of all pending entity insertions.
152
     *
153
     * @var array
154
     */
155
    private $entityInsertions = [];
156
157
    /**
158
     * A list of all pending entity updates.
159
     *
160
     * @var array
161
     */
162
    private $entityUpdates = [];
163
164
    /**
165
     * Any pending extra updates that have been scheduled by persisters.
166
     *
167
     * @var array
168
     */
169
    private $extraUpdates = [];
170
171
    /**
172
     * A list of all pending entity deletions.
173
     *
174
     * @var array
175
     */
176
    private $entityDeletions = [];
177
178
    /**
179
     * All pending collection deletions.
180
     *
181
     * @var array
182
     */
183
    private $collectionDeletions = [];
184
185
    /**
186
     * All pending collection updates.
187
     *
188
     * @var array
189
     */
190
    private $collectionUpdates = [];
191
192
    /**
193
     * List of collections visited during changeset calculation on a commit-phase of a UnitOfWork.
194
     * At the end of the UnitOfWork all these collections will make new snapshots
195
     * of their data.
196
     *
197
     * @var array
198
     */
199
    private $visitedCollections = [];
200
201
    /**
202
     * The EntityManager that "owns" this UnitOfWork instance.
203
     *
204
     * @var EntityManagerInterface
205
     */
206
    private $em;
207
208
    /**
209
     * The entity persister instances used to persist entity instances.
210
     *
211
     * @var array
212
     */
213
    private $entityPersisters = [];
214
215
    /**
216
     * The collection persister instances used to persist collections.
217
     *
218
     * @var array
219
     */
220
    private $collectionPersisters = [];
221
222
    /**
223
     * The EventManager used for dispatching events.
224
     *
225
     * @var \Doctrine\Common\EventManager
226
     */
227
    private $eventManager;
228
229
    /**
230
     * The ListenersInvoker used for dispatching events.
231
     *
232
     * @var \Doctrine\ORM\Event\ListenersInvoker
233
     */
234
    private $listenersInvoker;
235
236
    /**
237
     * @var Instantiator
238
     */
239
    private $instantiator;
240
241
    /**
242
     * Orphaned entities that are scheduled for removal.
243
     *
244
     * @var array
245
     */
246
    private $orphanRemovals = [];
247
248
    /**
249
     * Read-Only objects are never evaluated
250
     *
251
     * @var array
252
     */
253
    private $readOnlyObjects = [];
254
255
    /**
256
     * Map of Entity Class-Names and corresponding IDs that should eager loaded when requested.
257
     *
258
     * @var array
259
     */
260
    private $eagerLoadingEntities = [];
261
262
    /**
263
     * @var boolean
264
     */
265
    protected $hasCache = false;
266
267
    /**
268
     * Helper for handling completion of hydration
269
     *
270
     * @var HydrationCompleteHandler
271
     */
272
    private $hydrationCompleteHandler;
273
274
    /**
275
     * @var NormalizeIdentifier
276
     */
277
    private $normalizeIdentifier;
278
279
    /**
280
     * Initializes a new UnitOfWork instance, bound to the given EntityManager.
281
     *
282
     * @param EntityManagerInterface $em
283
     */
284
    public function __construct(EntityManagerInterface $em)
285
    {
286
        $this->em                       = $em;
287
        $this->eventManager             = $em->getEventManager();
288
        $this->listenersInvoker         = new ListenersInvoker($em);
289
        $this->hasCache                 = $em->getConfiguration()->isSecondLevelCacheEnabled();
290
        $this->instantiator             = new Instantiator();
291
        $this->hydrationCompleteHandler = new HydrationCompleteHandler($this->listenersInvoker, $em);
292 2290
        $this->normalizeIdentifier      = new NormalizeIdentifier();
293
    }
294 2290
295 2290
    /**
296 2290
     * Commits the UnitOfWork, executing all operations that have been postponed
297 2290
     * up to this point. The state of all managed entities will be synchronized with
298 2290
     * the database.
299 2290
     *
300 2290
     * The operations are executed in the following order:
301 2290
     *
302
     * 1) All entity insertions
303
     * 2) All entity updates
304
     * 3) All collection deletions
305
     * 4) All collection updates
306
     * 5) All entity deletions
307
     *
308
     * @return void
309
     *
310
     * @throws \Exception
311
     */
312
    public function commit()
313
    {
314
        // Raise preFlush
315
        if ($this->eventManager->hasListeners(Events::preFlush)) {
316
            $this->eventManager->dispatchEvent(Events::preFlush, new PreFlushEventArgs($this->em));
317
        }
318
319
        $this->computeChangeSets();
320
321
        if ( ! ($this->entityInsertions ||
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->entityInsertions 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...
322 1015
                $this->entityDeletions ||
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->entityDeletions 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...
323
                $this->entityUpdates ||
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->entityUpdates of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
324
                $this->collectionUpdates ||
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->collectionUpdates 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...
325 1015
                $this->collectionDeletions ||
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->collectionDeletions 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...
326 2
                $this->orphanRemovals)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->orphanRemovals of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
327
            $this->dispatchOnFlushEvent();
328
            $this->dispatchPostFlushEvent();
329
330 1015
            return; // Nothing to do.
331 1007
        }
332 16
333 15
        if ($this->orphanRemovals) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->orphanRemovals of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
334 1
            foreach ($this->orphanRemovals as $orphan) {
335 1
                $this->remove($orphan);
336 1
            }
337
        }
338
339
        $this->dispatchOnFlushEvent();
340 1012
341 165
        // Now we need a commit order to maintain referential integrity
342 129
        $commitOrder = $this->getCommitOrder();
343 40
344 37
        $conn = $this->em->getConnection();
345 1012
        $conn->beginTransaction();
346 25
347 25
        try {
348
            // Collection deletions (deletions of complete collections)
349 25
            foreach ($this->collectionDeletions as $collectionToDelete) {
350
                $this->getCollectionPersister($collectionToDelete->getMapping())->delete($collectionToDelete);
351
            }
352 1008
353 16
            if ($this->entityInsertions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->entityInsertions 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...
354 16
                foreach ($commitOrder as $class) {
355
                    $this->executeInserts($class);
356
                }
357
            }
358 1008
359
            if ($this->entityUpdates) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->entityUpdates of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
360
                foreach ($commitOrder as $class) {
361 1008
                    $this->executeUpdates($class);
362
                }
363 1008
            }
364 1008
365
            // Extra updates that were requested by persisters.
366
            if ($this->extraUpdates) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->extraUpdates of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
367
                $this->executeExtraUpdates();
368 1008
            }
369 19
370
            // Collection updates (deleteRows, updateRows, insertRows)
371
            foreach ($this->collectionUpdates as $collectionToUpdate) {
372 1008
                $this->getCollectionPersister($collectionToUpdate->getMapping())->update($collectionToUpdate);
373 1004
            }
374 1004
375
            // Entity deletions come last and need to be in reverse commit order
376
            if ($this->entityDeletions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->entityDeletions 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...
377
                foreach (array_reverse($commitOrder) as $committedEntityName) {
378 1007
                    if (! $this->entityDeletions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->entityDeletions 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...
379 115
                        break; // just a performance optimisation
380 115
                    }
381
382
                    $this->executeDeletions($committedEntityName);
383
                }
384
            }
385 1003
386 40
            $conn->commit();
387
        } catch (Exception $e) {
388
            $this->em->close();
389
            $conn->rollBack();
390 1003
391 531
            $this->afterTransactionRolledBack();
392
393
            throw $e;
394
        }
395 1003
396 62
        $this->afterTransactionComplete();
397 62
398
        // Take new snapshots from visited collections
399
        foreach ($this->visitedCollections as $coll) {
400
            $coll->takeSnapshot();
401 1003
        }
402 11
403 11
        $this->dispatchPostFlushEvent();
404 11
405
        // Clear up
406 11
        $this->entityInsertions =
407
        $this->entityUpdates =
408 11
        $this->entityDeletions =
409
        $this->extraUpdates =
410
        $this->entityChangeSets =
411 1003
        $this->collectionUpdates =
412
        $this->collectionDeletions =
413
        $this->visitedCollections =
414 1003
        $this->scheduledForSynchronization =
415 530
        $this->orphanRemovals = [];
416
    }
417
418 1003
    /**
419
     * Computes the changesets of all entities scheduled for insertion.
420
     *
421 1002
     * @return void
422 1002
     */
423 1002
    private function computeScheduleInsertsChangeSets()
424 1002
    {
425 1002
        foreach ($this->entityInsertions as $entity) {
426 1002
            $class = $this->em->getClassMetadata(get_class($entity));
427 1002
428 1002
            $this->computeChangeSet($class, $entity);
0 ignored issues
show
Documentation introduced by
$class is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
429 1002
        }
430 1002
    }
431 1002
432
    /**
433
     * Executes any extra updates that have been scheduled.
434
     */
435
    private function executeExtraUpdates()
436
    {
437
        foreach ($this->extraUpdates as $oid => $update) {
438 1014
            list ($entity, $changeset) = $update;
439
440 1014
            $this->entityChangeSets[$oid] = $changeset;
441 1006
442
//            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...
443 1006
//            \Doctrine\Common\Util\Debug::dump($changeset, 3);
444
445 1012
            $this->getEntityPersister(get_class($entity))->update($entity);
446
        }
447
448
        $this->extraUpdates = [];
449
    }
450
451
    /**
452
     * Gets the changeset for an entity.
453
     *
454
     * @param object $entity
455
     *
456
     * @return array
457
     */
458
    public function & getEntityChangeSet($entity)
459
    {
460
        $oid  = spl_object_hash($entity);
461 16
        $data = [];
462
463 16
        if (!isset($this->entityChangeSets[$oid])) {
464
            return $data;
465 16
        }
466 1
467
        return $this->entityChangeSets[$oid];
468
    }
469 15
470
    /**
471 15
     * Computes the changes that happened to a single entity.
472 14
     *
473
     * Modifies/populates the following properties:
474
     *
475
     * {@link originalEntityData}
476 15
     * If the entity is NEW or MANAGED but not yet fully persisted (only has an id)
477
     * then it was not fetched from the database and therefore we have no original
478 15
     * entity data yet. All of the current entity data is stored as the original entity data.
479
     *
480
     * {@link entityChangeSets}
481
     * The changes detected on all properties of the entity are stored there.
482
     * A change is a tuple array where the first entry is the old value and the second
483 15
     * entry is the new value of the property. Changesets are used by persisters
484 2
     * to INSERT/UPDATE the persistent entity state.
485
     *
486
     * {@link entityUpdates}
487
     * If the entity is already fully MANAGED (has been fetched from the database before)
488 13
     * and any changes to its properties are detected, then a reference to the entity is stored
489
     * there to mark it for an update.
490 13
     *
491 6
     * {@link collectionDeletions}
492
     * If a PersistentCollection has been de-referenced in a fully MANAGED entity,
493 12
     * then this collection is marked for deletion.
494
     *
495
     * @ignore
496
     *
497
     * @internal Don't call from the outside.
498 40
     *
499
     * @param ClassMetadata $class  The class descriptor of the entity.
500 40
     * @param object        $entity The entity for which to compute the changes.
501 40
     *
502
     * @return void
503 40
     */
504
    public function computeChangeSet(ClassMetadata $class, $entity)
505
    {
506
        $oid = spl_object_hash($entity);
507
508 40
        if (isset($this->readOnlyObjects[$oid])) {
509
            return;
510
        }
511 40
512 40
        if ($class->inheritanceType !== InheritanceType::NONE) {
513
            $class = $this->em->getClassMetadata(get_class($entity));
514
        }
515
516
        $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preFlush) & ~ListenersInvoker::INVOKE_MANAGER;
0 ignored issues
show
Bug introduced by
It seems like $class defined by $this->em->getClassMetadata(get_class($entity)) on line 513 can also be of type object<Doctrine\Common\P...\Mapping\ClassMetadata>; however, Doctrine\ORM\Event\Liste...:getSubscribedSystems() does only seem to accept object<Doctrine\ORM\Mapping\ClassMetadata>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
517
518
        if ($invoke !== ListenersInvoker::INVOKE_NONE) {
519
            $this->listenersInvoker->invoke($class, Events::preFlush, $entity, new PreFlushEventArgs($this->em), $invoke);
0 ignored issues
show
Bug introduced by
It seems like $class defined by $this->em->getClassMetadata(get_class($entity)) on line 513 can also be of type object<Doctrine\Common\P...\Mapping\ClassMetadata>; however, Doctrine\ORM\Event\ListenersInvoker::invoke() does only seem to accept object<Doctrine\ORM\Mapping\ClassMetadata>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
520
        }
521 1006
522
        $actualData = [];
523 1006
524 1006
        foreach ($class->getDeclaredPropertiesIterator() as $name => $property) {
0 ignored issues
show
Bug introduced by
The method getDeclaredPropertiesIterator does only exist in Doctrine\ORM\Mapping\ClassMetadata, but not in Doctrine\Common\Persistence\Mapping\ClassMetadata.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
525
            $value = $property->getValue($entity);
526 1006
527 1
            if ($property instanceof ToManyAssociationMetadata && $value !== null) {
528
                if ($value instanceof PersistentCollection && $value->getOwner() === $entity) {
529
                    continue;
530 1006
                }
531
532
                $value = $property->wrap($entity, $value, $this->em);
533
534
                $property->setValue($entity, $value);
535
536
                $actualData[$name] = $value;
537
538
                continue;
539
            }
540
541
            if (
542
                ( ! $class->isIdentifier($name)
543
                    || ! $class->getProperty($name) instanceof FieldMetadata
0 ignored issues
show
Bug introduced by
The method getProperty does only exist in Doctrine\ORM\Mapping\ClassMetadata, but not in Doctrine\Common\Persistence\Mapping\ClassMetadata.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
544
                    || ! $class->getProperty($name)->hasValueGenerator()
545
                    || $class->getProperty($name)->getValueGenerator()->getType() !== GeneratorType::IDENTITY
546
                ) && (! $class->isVersioned() || $name !== $class->versionProperty->getName())) {
0 ignored issues
show
Bug introduced by
The method isVersioned does only exist in Doctrine\ORM\Mapping\ClassMetadata, but not in Doctrine\Common\Persistence\Mapping\ClassMetadata.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

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

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...
699 58
                continue;
700
            }
701 8
702
            // If change tracking is explicit or happens through notification, then only compute
703 8
            // changes on entities of that type that are explicitly marked for synchronization.
704
            switch (true) {
705
                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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
706
                    $entitiesToProcess = $entities;
707 8
                    break;
708 8
709
                case (isset($this->scheduledForSynchronization[$className])):
710 8
                    $entitiesToProcess = $this->scheduledForSynchronization[$className];
711
                    break;
712
713 50
                default:
714 49
                    $entitiesToProcess = [];
715 21
716
            }
717
718 49
            foreach ($entitiesToProcess as $entity) {
719 50
                // Ignore uninitialized proxy objects
720
                if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
721
                    continue;
722
                }
723
724 263
                // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION OR DELETION are processed here.
725 84
                $oid = spl_object_hash($entity);
726 84
727 84
                if ( ! isset($this->entityInsertions[$oid]) && ! isset($this->entityDeletions[$oid]) && isset($this->entityStates[$oid])) {
728
                    $this->computeChangeSet($class, $entity);
0 ignored issues
show
Documentation introduced by
$class is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
729
                }
730
            }
731
        }
732 1016
    }
733 894
734 639
    /**
735
     * Computes the changes of an association.
736
     *
737 865
     * @param AssociationMetadata $association The association mapping.
738
     * @param mixed               $value       The value of the association.
739 857
     *
740 857
     * @throws ORMInvalidArgumentException
741 857
     * @throws ORMException
742 857
     *
743 857
     * @return void
744
     */
745 35
    private function computeAssociationChanges(AssociationMetadata $association, $value)
746 35
    {
747 857
        if ($value instanceof GhostObjectInterface && ! $value->isProxyInitialized()) {
748
            return;
749
        }
750 1008
751
        if ($value instanceof PersistentCollection && $value->isDirty()) {
752
            $coid = spl_object_hash($value);
753
754
            $this->collectionUpdates[$coid] = $value;
755
            $this->visitedCollections[$coid] = $value;
756
        }
757
758
        // Look through the entities, and in any of their associations,
759 1007
        // 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
        $unwrappedValue = ($association instanceof ToOneAssociationMetadata) ? [$value] : $value->unwrap();
762 1007
        $targetEntity   = $association->getTargetEntity();
763
        $targetClass    = $this->em->getClassMetadata($targetEntity);
764
765 1005
        foreach ($unwrappedValue as $key => $entry) {
766 447
            if (! ($entry instanceof $targetEntity)) {
767
                throw ORMInvalidArgumentException::invalidAssociation($targetClass, $association, $entry);
0 ignored issues
show
Documentation introduced by
$targetClass is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
768
            }
769 447
770 1
            $state = $this->getEntityState($entry, self::STATE_NEW);
771
772
            if (! ($entry instanceof $targetEntity)) {
773
                throw ORMException::unexpectedAssociationValue(
774
                    $association->getSourceEntity(),
775
                    $association->getName(),
776 446
                    get_class($entry),
777 444
                    $targetEntity
778 444
                );
779
            }
780 3
781 3
            switch ($state) {
782 3
                case self::STATE_NEW:
783
                    if ( ! in_array('persist', $association->getCascade())) {
784
                        throw ORMInvalidArgumentException::newEntityFoundThroughRelationship($association, $entry);
785 1
                    }
786
787
                    $this->persistNew($targetClass, $entry);
0 ignored issues
show
Documentation introduced by
$targetClass is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
788
                    $this->computeChangeSet($targetClass, $entry);
0 ignored issues
show
Documentation introduced by
$targetClass is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
789 446
                    break;
790
791 426
                case self::STATE_REMOVED:
792 35
                    // Consume the $value as array (it's either an array or an ArrayAccess)
793
                    // and remove the element from Collection.
794
                    if ($association instanceof ToManyAssociationMetadata) {
795
                        unset($value[$key]);
796 425
                    }
797
                    break;
798 425
799 446
                case self::STATE_DETACHED:
800
                    // Can actually not happen right now as we assume STATE_NEW,
801
                    // so the exception will be raised from the DBAL layer (constraint violation).
802
                    throw ORMInvalidArgumentException::detachedEntityFoundThroughRelationship($association, $entry);
803 1005
                    break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
804
805
                default:
806
                    // MANAGED associated entities are already taken into account
807
                    // during changeset calculation anyway, since they are in the identity map.
808
            }
809
        }
810
    }
811
812
    /**
813
     * @param \Doctrine\ORM\Mapping\ClassMetadata $class
814
     * @param object                              $entity
815
     *
816 865
     * @return void
817
     */
818 865
    private function persistNew($class, $entity)
819 28
    {
820
        $oid    = spl_object_hash($entity);
821
        $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::prePersist);
822 864
823 533
        if ($invoke !== ListenersInvoker::INVOKE_NONE) {
824
            $this->listenersInvoker->invoke($class, Events::prePersist, $entity, new LifecycleEventArgs($entity, $this->em), $invoke);
825 533
        }
826 533
827
        $generationPlan = $class->getValueGenerationPlan();
828
        $persister = $this->getEntityPersister($class->getClassName());
829
        $generationPlan->executeImmediate($this->em, $entity);
830
831
        if (! $generationPlan->containsDeferred()) {
832 864
            $id = $this->em->getIdentifierFlattener()->flattenIdentifier($class, $persister->getIdentifier($entity));
833 864
            $this->entityIdentifiers[$oid] = $id;
834
        }
835 864
836 722
        $this->entityStates[$oid] = self::STATE_MANAGED;
837 6
838
        $this->scheduleForInsert($entity);
839
    }
840 716
841
    /**
842 716
     * INTERNAL:
843
     * Computes the changeset of an individual entity, independently of the
844
     * computeChangeSets() routine that is used at the beginning of a UnitOfWork#commit().
845
     *
846
     * The passed entity must be a managed entity. If the entity already has a change set
847 716
     * because this method is invoked during a commit cycle then the change sets are added.
848 39
     * whereby changes detected in this method prevail.
849 4
     *
850
     * @ignore
851
     *
852 35
     * @param ClassMetadata $class  The class descriptor of the entity.
853 35
     * @param object        $entity The entity for which to (re)calculate the change set.
854 35
     *
855
     * @return void
856 710
     *
857
     * @throws ORMInvalidArgumentException If the passed entity is not MANAGED.
858
     * @throws \RuntimeException
859 4
     */
860 3
    public function recomputeSingleEntityChangeSet(ClassMetadata $class, $entity) : void
861
    {
862 4
        $oid = spl_object_hash($entity);
863
864 710
        if (! isset($this->entityStates[$oid]) || $this->entityStates[$oid] !== self::STATE_MANAGED) {
865
            throw ORMInvalidArgumentException::entityNotManaged($entity);
866
        }
867
868
        // skip if change tracking is "NOTIFY"
869
        if ($class->changeTrackingPolicy === ChangeTrackingPolicy::NOTIFY) {
870 713
            return;
871
        }
872
873
        if ($class->inheritanceType !== InheritanceType::NONE) {
874
            $class = $this->em->getClassMetadata(get_class($entity));
875 856
        }
876
877
        $actualData = [];
878
879
        foreach ($class->getDeclaredPropertiesIterator() as $name => $property) {
0 ignored issues
show
Bug introduced by
The method getDeclaredPropertiesIterator does only exist in Doctrine\ORM\Mapping\ClassMetadata, but not in Doctrine\Common\Persistence\Mapping\ClassMetadata.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
880
            switch (true) {
881
                case ($property instanceof VersionFieldMetadata):
882
                    // Ignore version field
883 1031
                    break;
884
885 1031
                case ($property instanceof FieldMetadata):
886 1031
                    if (! $property->isPrimaryKey()
887
                        || ! $property->getValueGenerator()
888 1031
                        || $property->getValueGenerator()->getType() !== GeneratorType::IDENTITY) {
889 139
                        $actualData[$name] = $property->getValue($entity);
890
                    }
891
892 1031
                    break;
893
894 1031
                case ($property instanceof ToOneAssociationMetadata):
895 269
                    $actualData[$name] = $property->getValue($entity);
896
                    break;
897 269
            }
898 1
        }
899
900 1
        if ( ! isset($this->originalEntityData[$oid])) {
901
            throw new \RuntimeException('Cannot call recomputeSingleEntityChangeSet before computeChangeSet on an entity.');
902
        }
903 269
904
        $originalData = $this->originalEntityData[$oid];
905
        $changeSet = [];
906 1031
907
        foreach ($actualData as $propName => $actualValue) {
908 1031
            $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null;
909 1031
910
            if ($orgValue !== $actualValue) {
911
                $changeSet[$propName] = [$orgValue, $actualValue];
912
            }
913
        }
914
915
        if ($changeSet) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $changeSet 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...
916
            if (isset($this->entityChangeSets[$oid])) {
917
                $this->entityChangeSets[$oid] = array_merge($this->entityChangeSets[$oid], $changeSet);
918
            } else if ( ! isset($this->entityInsertions[$oid])) {
919
                $this->entityChangeSets[$oid] = $changeSet;
920
                $this->entityUpdates[$oid]    = $entity;
921
            }
922
            $this->originalEntityData[$oid] = $actualData;
923
        }
924
    }
925
926
    /**
927
     * Executes all entity insertions for entities of the specified type.
928
     *
929 16
     * @param ClassMetadata $class
930
     *
931 16
     * @return void
932
     */
933 16
    private function executeInserts(ClassMetadata $class) : void
934
    {
935
        $className      = $class->getClassName();
936
        $persister      = $this->getEntityPersister($className);
937
        $invoke         = $this->listenersInvoker->getSubscribedSystems($class, Events::postPersist);
938 16
        $generationPlan = $class->getValueGenerationPlan();
939
940
        foreach ($this->entityInsertions as $oid => $entity) {
941
            if ($this->em->getClassMetadata(get_class($entity))->getClassName() !== $className) {
0 ignored issues
show
Bug introduced by
The method getClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
942 16
                continue;
943 3
            }
944
945
            $persister->insert($entity);
946 16
947
            if ($generationPlan->containsDeferred()) {
948 16
                // Entity has post-insert IDs
949 16
                $oid = spl_object_hash($entity);
950 16
                $id  = $this->em->getIdentifierFlattener()->flattenIdentifier($class, $persister->getIdentifier($entity));
951 16
952 16
                $this->entityIdentifiers[$oid] = $id;
953
                $this->entityStates[$oid] = self::STATE_MANAGED;
954
                $this->originalEntityData[$oid] = $id + $this->originalEntityData[$oid];
955
956 16
                $this->addToIdentityMap($entity);
957
            }
958
959
            unset($this->entityInsertions[$oid]);
960 16
961 16
            if ($invoke !== ListenersInvoker::INVOKE_NONE) {
962
                $eventArgs = new LifecycleEventArgs($entity, $this->em);
963 16
964 16
                $this->listenersInvoker->invoke($class, Events::postPersist, $entity, $eventArgs, $invoke);
965
            }
966 16
        }
967 16
    }
968
969
    /**
970
     * Executes all entity updates for entities of the specified type.
971 16
     *
972 7
     * @param \Doctrine\ORM\Mapping\ClassMetadata $class
973 6
     *
974 1
     * @return void
975 1
     */
976 1
    private function executeUpdates($class)
977
    {
978 7
        $className          = $class->getClassName();
979
        $persister          = $this->getEntityPersister($className);
980 16
        $preUpdateInvoke    = $this->listenersInvoker->getSubscribedSystems($class, Events::preUpdate);
981
        $postUpdateInvoke   = $this->listenersInvoker->getSubscribedSystems($class, Events::postUpdate);
982
983
        foreach ($this->entityUpdates as $oid => $entity) {
984
            if ($this->em->getClassMetadata(get_class($entity))->getClassName() !== $className) {
0 ignored issues
show
Bug introduced by
The method getClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
985
                continue;
986
            }
987
988
            if ($preUpdateInvoke != ListenersInvoker::INVOKE_NONE) {
989 1004
                $this->listenersInvoker->invoke($class, Events::preUpdate, $entity, new PreUpdateEventArgs($entity, $this->em, $this->getEntityChangeSet($entity)), $preUpdateInvoke);
990
991 1004
                $this->recomputeSingleEntityChangeSet($class, $entity);
992 1004
            }
993 1004
994 1004
            if ( ! empty($this->entityChangeSets[$oid])) {
995
//                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...
996 1004
//                \Doctrine\Common\Util\Debug::dump($this->entityChangeSets[$oid], 3);
997
998 1004
                $persister->update($entity);
999 855
            }
1000
1001
            unset($this->entityUpdates[$oid]);
1002 1004
1003
            if ($postUpdateInvoke != ListenersInvoker::INVOKE_NONE) {
1004 1004
                $this->listenersInvoker->invoke($class, Events::postUpdate, $entity, new LifecycleEventArgs($entity, $this->em), $postUpdateInvoke);
1005
            }
1006 1004
        }
1007 1004
    }
1008
1009
    /**
1010
     * Executes all entity deletions for entities of the specified type.
1011 1004
     *
1012
     * @param \Doctrine\ORM\Mapping\ClassMetadata $class
1013 1004
     *
1014
     * @return void
1015 918
     */
1016 918
    private function executeDeletions($class)
1017 918
    {
1018 918
        $className  = $class->getClassName();
1019 918
        $persister  = $this->getEntityPersister($className);
1020
        $invoke     = $this->listenersInvoker->getSubscribedSystems($class, Events::postRemove);
1021 918
1022
        foreach ($this->entityDeletions as $oid => $entity) {
1023 918
            if ($this->em->getClassMetadata(get_class($entity))->getClassName() !== $className) {
0 ignored issues
show
Bug introduced by
The method getClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1024 918
                continue;
1025 918
            }
1026
1027 918
            $persister->delete($entity);
1028
1029
            unset(
1030
                $this->entityDeletions[$oid],
1031 1004
                $this->entityIdentifiers[$oid],
1032 135
                $this->originalEntityData[$oid],
1033
                $this->entityStates[$oid]
1034 1004
            );
1035
1036
            // Entity with this $oid after deletion treated as NEW, even if the $oid
1037
            // is obtained by a new entity because the old one went out of scope.
1038
            //$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...
1039
            if (! $class->isIdentifierComposite()) {
1040
                $property = $class->getProperty($class->getSingleIdentifierFieldName());
1041
1042
                if ($property instanceof FieldMetadata && $property->hasValueGenerator()) {
1043 115
                    $property->setValue($entity, null);
1044
                }
1045 115
            }
1046 115
1047 115
            if ($invoke !== ListenersInvoker::INVOKE_NONE) {
1048 115
                $eventArgs = new LifecycleEventArgs($entity, $this->em);
1049
1050 115
                $this->listenersInvoker->invoke($class, Events::postRemove, $entity, $eventArgs, $invoke);
1051 115
            }
1052 74
        }
1053
    }
1054
1055 115
    /**
1056 13
     * Gets the commit order.
1057
     *
1058 13
     * @return array
1059
     */
1060
    private function getCommitOrder()
1061 115
    {
1062
        $calc = new Internal\CommitOrderCalculator();
1063
1064
        // See if there are any new classes in the changeset, that are not in the
1065 81
        // commit order graph yet (don't have a node).
1066
        // We have to inspect changeSet to be able to correctly build dependencies.
1067
        // It is not possible to use IdentityMap here because post inserted ids
1068 111
        // are not yet available.
1069
        $newNodes = [];
1070 111
1071 111
        foreach (\array_merge($this->entityInsertions, $this->entityUpdates, $this->entityDeletions) as $entity) {
1072
            $class = $this->em->getClassMetadata(get_class($entity));
1073
1074 111
            if ($calc->hasNode($class->getClassName())) {
0 ignored issues
show
Bug introduced by
The method getClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1075
                continue;
1076
            }
1077
1078
            $calc->addNode($class->getClassName(), $class);
0 ignored issues
show
Bug introduced by
The method getClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1079
1080
            $newNodes[] = $class;
1081
        }
1082
1083 62
        // Calculate dependencies for new nodes
1084
        while ($class = array_pop($newNodes)) {
1085 62
            foreach ($class->getDeclaredPropertiesIterator() as $property) {
1086 62
                if (! ($property instanceof ToOneAssociationMetadata && $property->isOwningSide())) {
1087 62
                    continue;
1088
                }
1089 62
1090 62
                $targetClass = $this->em->getClassMetadata($property->getTargetEntity());
1091 26
1092
                if ( ! $calc->hasNode($targetClass->getClassName())) {
0 ignored issues
show
Bug introduced by
The method getClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1093
                    $calc->addNode($targetClass->getClassName(), $targetClass);
0 ignored issues
show
Bug introduced by
The method getClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1094 62
1095
                    $newNodes[] = $targetClass;
1096
                }
1097 62
1098 62
                $weight = ! array_filter(
1099 62
                    $property->getJoinColumns(),
1100 62
                    function (JoinColumnMetadata $joinColumn) { return $joinColumn->isNullable(); }
1101
                );
1102
1103
                $calc->addDependency($targetClass->getClassName(), $class->getClassName(), $weight);
0 ignored issues
show
Bug introduced by
The method getClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
Documentation introduced by
$weight is of type boolean, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1104
1105
                // If the target class has mapped subclasses, these share the same dependency.
1106 62
                if ( ! $targetClass->getSubClasses()) {
0 ignored issues
show
Bug introduced by
The method getSubClasses() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1107 52
                    continue;
1108
                }
1109
1110 62
                foreach ($targetClass->getSubClasses() as $subClassName) {
0 ignored issues
show
Bug introduced by
The method getSubClasses() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1111 62
                    $targetSubClass = $this->em->getClassMetadata($subClassName);
1112
1113
                    if ( ! $calc->hasNode($subClassName)) {
1114 61
                        $calc->addNode($targetSubClass->getClassName(), $targetSubClass);
0 ignored issues
show
Bug introduced by
The method getClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1115
1116
                        $newNodes[] = $targetSubClass;
1117
                    }
1118
1119
                    $calc->addDependency($targetSubClass->getClassName(), $class->getClassName(), 1);
0 ignored issues
show
Bug introduced by
The method getClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1120
                }
1121
            }
1122
        }
1123 1008
1124
        return $calc->sort();
1125 1008
    }
1126 1008
1127
    /**
1128
     * Schedules an entity for insertion into the database.
1129 1008
     * If the entity already has an identifier, it will be added to the identity map.
1130
     *
1131
     * @param object $entity The entity to schedule for insertion.
1132
     *
1133
     * @return void
1134
     *
1135
     * @throws ORMInvalidArgumentException
1136 1008
     * @throws \InvalidArgumentException
1137
     */
1138 1008
    public function scheduleForInsert($entity)
1139 1008
    {
1140
        $oid = spl_object_hash($entity);
1141 1008
1142 624
        if (isset($this->entityUpdates[$oid])) {
1143
            throw new InvalidArgumentException("Dirty entity can not be scheduled for insertion.");
1144
        }
1145 1008
1146
        if (isset($this->entityDeletions[$oid])) {
1147 1008
            throw ORMInvalidArgumentException::scheduleInsertForRemovedEntity($entity);
1148
        }
1149
        if (isset($this->originalEntityData[$oid]) && ! isset($this->entityInsertions[$oid])) {
1150
            throw ORMInvalidArgumentException::scheduleInsertForManagedEntity($entity);
1151 1008
        }
1152 1008
1153 886
        if (isset($this->entityInsertions[$oid])) {
1154 846
            throw ORMInvalidArgumentException::scheduleInsertTwice($entity);
1155
        }
1156
1157 839
        $this->entityInsertions[$oid] = $entity;
1158
1159 839
        if (isset($this->entityIdentifiers[$oid])) {
1160 649
            $this->addToIdentityMap($entity);
1161
        }
1162 649
1163
        if ($entity instanceof NotifyPropertyChanged) {
1164
            $entity->addPropertyChangedListener($this);
1165 839
        }
1166
    }
1167 839
1168
    /**
1169
     * Checks whether an entity is scheduled for insertion.
1170 839
     *
1171 832
     * @param object $entity
1172
     *
1173
     * @return boolean
1174 217
     */
1175 217
    public function isScheduledForInsert($entity)
1176
    {
1177 217
        return isset($this->entityInsertions[spl_object_hash($entity)]);
1178 189
    }
1179
1180 189
    /**
1181
     * Schedules an entity for being updated.
1182
     *
1183 217
     * @param object $entity The entity to schedule for being updated.
1184
     *
1185
     * @return void
1186
     *
1187
     * @throws ORMInvalidArgumentException
1188 1008
     */
1189
    public function scheduleForUpdate($entity) : void
1190
    {
1191
        $oid = spl_object_hash($entity);
1192
1193
        if ( ! isset($this->entityIdentifiers[$oid])) {
1194
            throw ORMInvalidArgumentException::entityHasNoIdentity($entity, "scheduling for update");
1195
        }
1196
1197
        if (isset($this->entityDeletions[$oid])) {
1198
            throw ORMInvalidArgumentException::entityIsRemoved($entity, "schedule for update");
1199
        }
1200
1201
        if ( ! isset($this->entityUpdates[$oid]) && ! isset($this->entityInsertions[$oid])) {
1202 1032
            $this->entityUpdates[$oid] = $entity;
1203
        }
1204 1032
    }
1205
1206 1032
    /**
1207
     * INTERNAL:
1208
     * Schedules an extra update that will be executed immediately after the
1209
     * regular entity updates within the currently running commit cycle.
1210 1032
     *
1211 1
     * Extra updates for entities are stored as (entity, changeset) tuples.
1212
     *
1213 1032
     * @ignore
1214 1
     *
1215
     * @param object $entity    The entity for which to schedule an extra update.
1216
     * @param array  $changeset The changeset of the entity (what to update).
1217 1032
     *
1218 1
     * @return void
1219
     */
1220
    public function scheduleExtraUpdate($entity, array $changeset) : void
1221 1032
    {
1222
        $oid         = spl_object_hash($entity);
1223 1032
        $extraUpdate = [$entity, $changeset];
1224 269
1225
        if (isset($this->extraUpdates[$oid])) {
1226
            [$unused, $changeset2] = $this->extraUpdates[$oid];
0 ignored issues
show
Bug introduced by
The variable $unused does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $changeset2 does not exist. Did you mean $changeset?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
1227 1032
1228 5
            $extraUpdate = [$entity, $changeset + $changeset2];
0 ignored issues
show
Bug introduced by
The variable $changeset2 does not exist. Did you mean $changeset?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
1229
        }
1230 1032
1231
        $this->extraUpdates[$oid] = $extraUpdate;
1232
    }
1233
1234
    /**
1235
     * Checks whether an entity is registered as dirty in the unit of work.
1236
     * Note: Is not very useful currently as dirty entities are only registered
1237
     * at commit time.
1238
     *
1239 631
     * @param object $entity
1240
     *
1241 631
     * @return boolean
1242
     */
1243
    public function isScheduledForUpdate($entity) : bool
1244
    {
1245
        return isset($this->entityUpdates[spl_object_hash($entity)]);
1246
    }
1247
1248
    /**
1249
     * Checks whether an entity is registered to be checked in the unit of work.
1250
     *
1251
     * @param object $entity
1252
     *
1253 1
     * @return boolean
1254
     */
1255 1
    public function isScheduledForDirtyCheck($entity) : bool
1256
    {
1257 1
        $rootEntityName = $this->em->getClassMetadata(get_class($entity))->getRootClassName();
0 ignored issues
show
Bug introduced by
The method getRootClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1258
1259
        return isset($this->scheduledForSynchronization[$rootEntityName][spl_object_hash($entity)]);
1260
    }
1261 1
1262
    /**
1263
     * INTERNAL:
1264
     * Schedules an entity for deletion.
1265 1
     *
1266 1
     * @param object $entity
1267
     *
1268 1
     * @return void
1269
     */
1270
    public function scheduleForDelete($entity)
1271
    {
1272
        $oid = spl_object_hash($entity);
1273
1274
        if (isset($this->entityInsertions[$oid])) {
1275
            if ($this->isInIdentityMap($entity)) {
1276
                $this->removeFromIdentityMap($entity);
1277
            }
1278
1279
            unset($this->entityInsertions[$oid], $this->entityStates[$oid]);
1280
1281
            return; // entity has not been persisted yet, so nothing more to do.
1282
        }
1283
1284 40
        if ( ! $this->isInIdentityMap($entity)) {
1285
            return;
1286 40
        }
1287 40
1288
        $this->removeFromIdentityMap($entity);
1289 40
1290 1
        unset($this->entityUpdates[$oid]);
1291
1292 1
        if ( ! isset($this->entityDeletions[$oid])) {
1293
            $this->entityDeletions[$oid] = $entity;
1294
            $this->entityStates[$oid]    = self::STATE_REMOVED;
1295 40
        }
1296 40
    }
1297
1298
    /**
1299
     * Checks whether an entity is registered as removed/deleted with the unit
1300
     * of work.
1301
     *
1302
     * @param object $entity
1303
     *
1304
     * @return boolean
1305
     */
1306
    public function isScheduledForDelete($entity)
1307
    {
1308
        return isset($this->entityDeletions[spl_object_hash($entity)]);
1309
    }
1310
1311
    /**
1312
     * Checks whether an entity is scheduled for insertion, update or deletion.
1313
     *
1314
     * @param object $entity
1315
     *
1316
     * @return boolean
1317
     */
1318
    public function isEntityScheduled($entity)
1319 1
    {
1320
        $oid = spl_object_hash($entity);
1321 1
1322
        return isset($this->entityInsertions[$oid])
1323 1
            || isset($this->entityUpdates[$oid])
1324
            || isset($this->entityDeletions[$oid]);
1325
    }
1326
1327
    /**
1328
     * INTERNAL:
1329
     * Registers an entity in the identity map.
1330
     * Note that entities in a hierarchy are registered with the class name of
1331
     * the root entity.
1332
     *
1333
     * @ignore
1334 65
     *
1335
     * @param object $entity The entity to register.
1336 65
     *
1337
     * @return boolean TRUE if the registration was successful, FALSE if the identity of
1338 65
     *                 the entity in question is already managed.
1339 1
     *
1340
     * @throws ORMInvalidArgumentException
1341
     */
1342
    public function addToIdentityMap($entity)
1343 1
    {
1344
        $classMetadata = $this->em->getClassMetadata(get_class($entity));
1345 1
        $identifier    = $this->entityIdentifiers[spl_object_hash($entity)];
1346
1347
        if (empty($identifier) || in_array(null, $identifier, true)) {
1348 65
            throw ORMInvalidArgumentException::entityWithoutIdentity($classMetadata->getClassName(), $entity);
0 ignored issues
show
Bug introduced by
The method getClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1349 1
        }
1350
1351
        $idHash    = implode(' ', $identifier);
1352 64
        $className = $classMetadata->getRootClassName();
0 ignored issues
show
Bug introduced by
The method getRootClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1353
1354 64
        if (isset($this->identityMap[$className][$idHash])) {
1355
            return false;
1356 64
        }
1357 64
1358 64
        $this->identityMap[$className][$idHash] = $entity;
1359
1360 64
        return true;
1361
    }
1362
1363
    /**
1364
     * Gets the state of an entity with regard to the current unit of work.
1365
     *
1366
     * @param object   $entity
1367
     * @param int|null $assume The state to assume if the state is not yet known (not MANAGED or REMOVED).
1368
     *                         This parameter can be set to improve performance of entity state detection
1369
     *                         by potentially avoiding a database lookup if the distinction between NEW and DETACHED
1370 17
     *                         is either known or does not matter for the caller of the method.
1371
     *
1372 17
     * @return int The entity state.
1373
     */
1374
    public function getEntityState($entity, $assume = null)
1375
    {
1376
        $oid = spl_object_hash($entity);
1377
1378
        if (isset($this->entityStates[$oid])) {
1379
            return $this->entityStates[$oid];
1380
        }
1381
1382
        if ($assume !== null) {
1383
            return $assume;
1384
        }
1385
1386
        // State can only be NEW or DETACHED, because MANAGED/REMOVED states are known.
1387
        // Note that you can not remember the NEW or DETACHED state in _entityStates since
1388
        // the UoW does not hold references to such objects and the object hash can be reused.
1389
        // More generally because the state may "change" between NEW/DETACHED without the UoW being aware of it.
1390
        $class     = $this->em->getClassMetadata(get_class($entity));
1391
        $persister = $this->getEntityPersister($class->getClassName());
0 ignored issues
show
Bug introduced by
The method getClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1392
        $id        = $persister->getIdentifier($entity);
1393
1394
        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...
1395
            return self::STATE_NEW;
1396
        }
1397
1398
        $flatId = $this->em->getIdentifierFlattener()->flattenIdentifier($class, $id);
0 ignored issues
show
Documentation introduced by
$class is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1399
1400
        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()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1401
            || ! $class->getProperty($class->getSingleIdentifierFieldName()) instanceof FieldMetadata
0 ignored issues
show
Bug introduced by
The method getSingleIdentifierFieldName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
Bug introduced by
The method getProperty() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1402
            || ! $class->getProperty($class->getSingleIdentifierFieldName())->hasValueGenerator()
0 ignored issues
show
Bug introduced by
The method getSingleIdentifierFieldName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
Bug introduced by
The method getProperty() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1403
        ) {
1404
            // Check for a version field, if available, to avoid a db lookup.
1405
            if ($class->isVersioned()) {
0 ignored issues
show
Bug introduced by
The method isVersioned() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1406 1098
                return $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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1407
                    ? self::STATE_DETACHED
1408 1098
                    : self::STATE_NEW;
1409 1098
            }
1410
1411 1098
            // Last try before db lookup: check the identity map.
1412 6
            if ($this->tryGetById($flatId, $class->getRootClassName())) {
0 ignored issues
show
Bug introduced by
The method getRootClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1413
                return self::STATE_DETACHED;
1414
            }
1415 1092
1416 1092
            // db lookup
1417
            if ($this->getEntityPersister($class->getClassName())->exists($entity)) {
0 ignored issues
show
Bug introduced by
The method getClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1418 1092
                return self::STATE_DETACHED;
1419 83
            }
1420
1421
            return self::STATE_NEW;
1422 1092
        }
1423
1424 1092
        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()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1425
            || ! $class->getProperty($class->getSingleIdentifierFieldName()) instanceof FieldMetadata
0 ignored issues
show
Bug introduced by
The method getSingleIdentifierFieldName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
Bug introduced by
The method getProperty() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1426
            || ! $class->getValueGenerationPlan()->containsDeferred()) {
0 ignored issues
show
Bug introduced by
The method getValueGenerationPlan() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1427
            // if we have a pre insert generator we can't be sure that having an id
1428
            // really means that the entity exists. We have to verify this through
1429
            // the last resort: a db lookup
1430
1431
            // Last try before db lookup: check the identity map.
1432
            if ($this->tryGetById($flatId, $class->getRootClassName())) {
0 ignored issues
show
Bug introduced by
The method getRootClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1433
                return self::STATE_DETACHED;
1434
            }
1435
1436
            // db lookup
1437
            if ($this->getEntityPersister($class->getClassName())->exists($entity)) {
0 ignored issues
show
Bug introduced by
The method getClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1438 1045
                return self::STATE_DETACHED;
1439
            }
1440 1045
1441
            return self::STATE_NEW;
1442 1045
        }
1443 783
1444
        return self::STATE_DETACHED;
1445
    }
1446 1039
1447 1035
    /**
1448
     * INTERNAL:
1449
     * Removes an entity from the identity map. This effectively detaches the
1450
     * entity from the persistence management of Doctrine.
1451
     *
1452
     * @ignore
1453
     *
1454 13
     * @param object $entity
1455 13
     *
1456
     * @return boolean
1457 13
     *
1458 5
     * @throws ORMInvalidArgumentException
1459
     */
1460
    public function removeFromIdentityMap($entity)
1461 10
    {
1462 1
        $oid           = spl_object_hash($entity);
1463
        $classMetadata = $this->em->getClassMetadata(get_class($entity));
1464
        $idHash        = implode(' ', $this->entityIdentifiers[$oid]);
1465
1466 10
        if ($idHash === '') {
1467
            throw ORMInvalidArgumentException::entityHasNoIdentity($entity, "remove from identity map");
1468 5
        }
1469 1
1470
        $className = $classMetadata->getRootClassName();
0 ignored issues
show
Bug introduced by
The method getRootClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1471 1
1472
        if (isset($this->identityMap[$className][$idHash])) {
1473
            unset($this->identityMap[$className][$idHash]);
1474
            unset($this->readOnlyObjects[$oid]);
1475 4
1476 1
            //$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...
1477
1478
            return true;
1479
        }
1480 4
1481
        return false;
1482
    }
1483
1484 4
    /**
1485
     * INTERNAL:
1486 5
     * Gets an entity in the identity map by its identifier hash.
1487
     *
1488
     * @ignore
1489
     *
1490
     * @param string $idHash
1491
     * @param string $rootClassName
1492
     *
1493
     * @return object
1494
     */
1495
    public function getByIdHash($idHash, $rootClassName)
1496
    {
1497
        return $this->identityMap[$rootClassName][$idHash];
1498
    }
1499
1500
    /**
1501
     * INTERNAL:
1502
     * Tries to get an entity by its identifier hash. If no entity is found for
1503
     * the given hash, FALSE is returned.
1504 5
     *
1505
     * @ignore
1506
     *
1507
     * @param mixed  $idHash        (must be possible to cast it to string)
1508
     * @param string $rootClassName
1509
     *
1510
     * @return object|bool The found entity or FALSE.
1511
     */
1512
    public function tryGetByIdHash($idHash, $rootClassName)
1513
    {
1514
        $stringIdHash = (string) $idHash;
1515
1516
        return isset($this->identityMap[$rootClassName][$stringIdHash])
1517
            ? $this->identityMap[$rootClassName][$stringIdHash]
1518
            : false;
1519
    }
1520
1521 76
    /**
1522
     * Checks whether an entity is registered in the identity map of this UnitOfWork.
1523 76
     *
1524 76
     * @param object $entity
1525 76
     *
1526
     * @return boolean
1527 76
     */
1528
    public function isInIdentityMap($entity)
1529
    {
1530
        $oid = spl_object_hash($entity);
1531 76
1532
        if (empty($this->entityIdentifiers[$oid])) {
1533 76
            return false;
1534 76
        }
1535 76
1536
        $classMetadata = $this->em->getClassMetadata(get_class($entity));
1537
        $idHash        = implode(' ', $this->entityIdentifiers[$oid]);
1538
1539 76
        return isset($this->identityMap[$classMetadata->getRootClassName()][$idHash]);
0 ignored issues
show
Bug introduced by
The method getRootClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1540
    }
1541
1542
    /**
1543
     * INTERNAL:
1544
     * Checks whether an identifier hash exists in the identity map.
1545
     *
1546
     * @ignore
1547
     *
1548
     * @param string $idHash
1549
     * @param string $rootClassName
1550
     *
1551
     * @return boolean
1552
     */
1553
    public function containsIdHash($idHash, $rootClassName)
1554
    {
1555
        return isset($this->identityMap[$rootClassName][$idHash]);
1556 6
    }
1557
1558 6
    /**
1559
     * Persists an entity as part of the current unit of work.
1560
     *
1561
     * @param object $entity The entity to persist.
1562
     *
1563
     * @return void
1564
     */
1565
    public function persist($entity)
1566
    {
1567
        $visited = [];
1568
1569
        $this->doPersist($entity, $visited);
1570
    }
1571
1572
    /**
1573 34
     * Persists an entity as part of the current unit of work.
1574
     *
1575 34
     * This method is internally called during persist() cascades as it tracks
1576
     * the already visited entities to prevent infinite recursions.
1577 34
     *
1578 34
     * @param object $entity  The entity to persist.
1579 34
     * @param array  $visited The already visited entities.
1580
     *
1581
     * @return void
1582
     *
1583
     * @throws ORMInvalidArgumentException
1584
     * @throws UnexpectedValueException
1585
     */
1586
    private function doPersist($entity, array &$visited)
1587
    {
1588
        $oid = spl_object_hash($entity);
1589 212
1590
        if (isset($visited[$oid])) {
1591 212
            return; // Prevent infinite recursion
1592
        }
1593 212
1594 31
        $visited[$oid] = $entity; // Mark visited
1595
1596
        $class = $this->em->getClassMetadata(get_class($entity));
1597 197
1598 197
        // We assume NEW, so DETACHED entities result in an exception on flush (constraint violation).
1599
        // If we would detect DETACHED here we would throw an exception anyway with the same
1600 197
        // consequences (not recoverable/programming error), so just assuming NEW here
1601
        // lets us avoid some database lookups for entities with natural identifiers.
1602
        $entityState = $this->getEntityState($entity, self::STATE_NEW);
1603
1604 197
        switch ($entityState) {
1605
            case self::STATE_MANAGED:
1606
                // Nothing to do, except if policy is "deferred explicit"
1607
                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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1608
                    $this->scheduleForSynchronization($entity);
1609
                }
1610
                break;
1611
1612
            case self::STATE_NEW:
1613
                $this->persistNew($class, $entity);
0 ignored issues
show
Documentation introduced by
$class is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1614
                break;
1615
1616
            case self::STATE_REMOVED:
1617
                // Entity becomes managed again
1618
                unset($this->entityDeletions[$oid]);
1619
                $this->addToIdentityMap($entity);
1620
1621
                $this->entityStates[$oid] = self::STATE_MANAGED;
1622
                break;
1623
1624
            case self::STATE_DETACHED:
1625
                // Can actually not happen right now since we assume STATE_NEW.
1626
                throw ORMInvalidArgumentException::detachedEntityCannot($entity, "persisted");
1627
1628
            default:
1629
                throw new UnexpectedValueException("Unexpected entity state: $entityState." . self::objToStr($entity));
1630 1028
        }
1631
1632 1028
        $this->cascadePersist($entity, $visited);
1633
    }
1634 1028
1635 1021
    /**
1636
     * Deletes an entity as part of the current unit of work.
1637
     *
1638
     * @param object $entity The entity to remove.
1639
     *
1640
     * @return void
1641
     */
1642
    public function remove($entity)
1643
    {
1644
        $visited = [];
1645
1646
        $this->doRemove($entity, $visited);
1647
    }
1648
1649
    /**
1650
     * Deletes an entity as part of the current unit of work.
1651 1028
     *
1652
     * This method is internally called during delete() cascades as it tracks
1653 1028
     * the already visited entities to prevent infinite recursions.
1654
     *
1655 1028
     * @param object $entity  The entity to delete.
1656 109
     * @param array  $visited The map of the already visited entities.
1657
     *
1658
     * @return void
1659 1028
     *
1660
     * @throws ORMInvalidArgumentException If the instance is a detached entity.
1661 1028
     * @throws UnexpectedValueException
1662
     */
1663
    private function doRemove($entity, array &$visited)
1664
    {
1665
        $oid = spl_object_hash($entity);
1666
1667 1028
        if (isset($visited[$oid])) {
1668
            return; // Prevent infinite recursion
1669
        }
1670 1028
1671
        $visited[$oid] = $entity; // mark visited
1672 234
1673 2
        // Cascade first, because scheduleForDelete() removes the entity from the identity map, which
1674
        // can cause problems when a lazy proxy has to be initialized for the cascade operation.
1675 234
        $this->cascadeRemove($entity, $visited);
1676
1677 1028
        $class       = $this->em->getClassMetadata(get_class($entity));
1678 1027
        $entityState = $this->getEntityState($entity);
1679 1027
1680
        switch ($entityState) {
1681 1
            case self::STATE_NEW:
1682
            case self::STATE_REMOVED:
1683 1
                // nothing to do
1684 1
                break;
1685
1686 1
            case self::STATE_MANAGED:
1687 1
                $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::preRemove);
0 ignored issues
show
Documentation introduced by
$class is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1688
1689
                if ($invoke !== ListenersInvoker::INVOKE_NONE) {
1690
                    $this->listenersInvoker->invoke($class, Events::preRemove, $entity, new LifecycleEventArgs($entity, $this->em), $invoke);
0 ignored issues
show
Documentation introduced by
$class is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1691
                }
1692
1693
                $this->scheduleForDelete($entity);
1694
                break;
1695
1696
            case self::STATE_DETACHED:
1697 1028
                throw ORMInvalidArgumentException::detachedEntityCannot($entity, "removed");
1698 1021
            default:
1699
                throw new UnexpectedValueException("Unexpected entity state: $entityState." . self::objToStr($entity));
1700
        }
1701
1702
    }
1703
1704
    /**
1705
     * Refreshes the state of the given entity from the database, overwriting
1706
     * any local, unpersisted changes.
1707 64
     *
1708
     * @param object $entity The entity to refresh.
1709 64
     *
1710
     * @return void
1711 64
     *
1712 64
     * @throws InvalidArgumentException If the entity is not MANAGED.
1713
     */
1714
    public function refresh($entity)
1715
    {
1716
        $visited = [];
1717
1718
        $this->doRefresh($entity, $visited);
1719
    }
1720
1721
    /**
1722
     * Executes a refresh operation on an entity.
1723
     *
1724
     * @param object $entity  The entity to refresh.
1725
     * @param array  $visited The already visited entities during cascades.
1726
     *
1727
     * @return void
1728 64
     *
1729
     * @throws ORMInvalidArgumentException If the entity is not MANAGED.
1730 64
     */
1731
    private function doRefresh($entity, array &$visited)
1732 64
    {
1733 1
        $oid = spl_object_hash($entity);
1734
1735
        if (isset($visited[$oid])) {
1736 64
            return; // Prevent infinite recursion
1737
        }
1738
1739
        $visited[$oid] = $entity; // mark visited
1740 64
1741
        $class = $this->em->getClassMetadata(get_class($entity));
1742 64
1743 64
        if ($this->getEntityState($entity) !== self::STATE_MANAGED) {
1744
            throw ORMInvalidArgumentException::entityNotManaged($entity);
1745
        }
1746 64
1747 64
        $this->getEntityPersister($class->getClassName())->refresh(
0 ignored issues
show
Bug introduced by
The method getClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1748
            array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
1749 2
            $entity
1750
        );
1751 64
1752 64
        $this->cascadeRefresh($entity, $visited);
1753
    }
1754 64
1755 8
    /**
1756
     * Cascades a refresh operation to associated entities.
1757
     *
1758 64
     * @param object $entity
1759 64
     * @param array  $visited
1760
     *
1761
     * @return void
1762
     */
1763
    private function cascadeRefresh($entity, array &$visited)
1764
    {
1765
        $class = $this->em->getClassMetadata(get_class($entity));
1766
1767 64
        foreach ($class->getDeclaredPropertiesIterator() as $association) {
0 ignored issues
show
Bug introduced by
The method getDeclaredPropertiesIterator() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1768
            if (! ($association instanceof AssociationMetadata && in_array('refresh', $association->getCascade()))) {
1769
                continue;
1770
            }
1771
1772
            $relatedEntities = $association->getValue($entity);
1773
1774
            switch (true) {
1775
                case ($relatedEntities instanceof PersistentCollection):
1776
                    // Unwrap so that foreach() does not initialize
1777
                    $relatedEntities = $relatedEntities->unwrap();
1778
                    // break; is commented intentionally!
1779
1780
                case ($relatedEntities instanceof Collection):
1781 40
                case (is_array($relatedEntities)):
1782
                    foreach ($relatedEntities as $relatedEntity) {
1783 40
                        $this->doRefresh($relatedEntity, $visited);
1784
                    }
1785 40
                    break;
1786
1787
                case ($relatedEntities !== null):
1788
                    $this->doRefresh($relatedEntities, $visited);
1789
                    break;
1790
1791
                default:
1792
                    // Do nothing
1793
            }
1794
        }
1795
    }
1796
1797
    /**
1798
     * Cascades the save operation to associated entities.
1799
     *
1800
     * @param object $entity
1801
     * @param array  $visited
1802
     *
1803 40
     * @return void
1804
     */
1805 40
    private function cascadePersist($entity, array &$visited)
1806
    {
1807 40
        $class = $this->em->getClassMetadata(get_class($entity));
1808 4
1809
        if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
1810 4
            // nothing to do - proxy is not initialized, therefore we don't do anything with it
1811 4
            return;
1812
        }
1813
1814 4
        foreach ($class->getDeclaredPropertiesIterator() as $association) {
0 ignored issues
show
Bug introduced by
The method getDeclaredPropertiesIterator() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1815
            if (! ($association instanceof AssociationMetadata && in_array('persist', $association->getCascade()))) {
1816
                continue;
1817 40
            }
1818
1819
            /** @var AssociationMetadata $association */
1820
            $relatedEntities = $association->getValue($entity);
1821
            $targetEntity    = $association->getTargetEntity();
1822
1823 40
            switch (true) {
1824
                case ($relatedEntities instanceof PersistentCollection):
1825 40
                    // Unwrap so that foreach() does not initialize
1826
                    $relatedEntities = $relatedEntities->unwrap();
1827 39
                    // break; is commented intentionally!
1828
1829
                case ($relatedEntities instanceof Collection):
1830 39
                case (is_array($relatedEntities)):
1831 5
                    if (! ($association instanceof ToManyAssociationMetadata)) {
1832
                        throw ORMInvalidArgumentException::invalidAssociation(
1833 5
                            $this->em->getClassMetadata($targetEntity),
0 ignored issues
show
Documentation introduced by
$this->em->getClassMetadata($targetEntity) is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1834
                            $association,
1835 35
                            $relatedEntities
1836 3
                        );
1837 35
                    }
1838
1839 35
                    foreach ($relatedEntities as $relatedEntity) {
1840
                        $this->doPersist($relatedEntity, $visited);
1841 35
                    }
1842
1843 14
                    break;
1844 14
1845
                case ($relatedEntities !== null):
1846
                    if (! $relatedEntities instanceof $targetEntity) {
1847
                        throw ORMInvalidArgumentException::invalidAssociation(
1848 24
                            $this->em->getClassMetadata($targetEntity),
0 ignored issues
show
Documentation introduced by
$this->em->getClassMetadata($targetEntity) is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1849
                            $association,
1850
                            $relatedEntities
1851 35
                        );
1852
                    }
1853
1854 2
                    $this->doPersist($relatedEntities, $visited);
1855 1
                    break;
1856 1
1857 1
                default:
1858
                    // Do nothing
1859
            }
1860
        }
1861 1
    }
1862 1
1863
    /**
1864 1
     * Cascades the delete operation to associated entities.
1865
     *
1866
     * @param object $entity
1867
     * @param array  $visited
1868 38
     *
1869 4
     * @return void
1870 4
     */
1871
    private function cascadeRemove($entity, array &$visited)
1872
    {
1873 4
        $entitiesToCascade = [];
1874 1
        $class             = $this->em->getClassMetadata(get_class($entity));
1875
1876
        foreach ($class->getDeclaredPropertiesIterator() as $association) {
0 ignored issues
show
Bug introduced by
The method getDeclaredPropertiesIterator() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1877
            if (! ($association instanceof AssociationMetadata && in_array('remove', $association->getCascade()))) {
1878 37
                continue;
1879
            }
1880 37
1881 30
            if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
1882 4
                $entity->initializeProxy();
1883
            }
1884
1885 30
            $relatedEntities = $association->getValue($entity);
1886
1887
            switch (true) {
1888 37
                case ($relatedEntities instanceof Collection):
1889
                case (is_array($relatedEntities)):
1890
                    // If its a PersistentCollection initialization is intended! No unwrap!
1891
                    foreach ($relatedEntities as $relatedEntity) {
1892
                        $entitiesToCascade[] = $relatedEntity;
1893 38
                    }
1894 6
                    break;
1895
1896
                case ($relatedEntities !== null):
1897
                    $entitiesToCascade[] = $relatedEntities;
1898 38
                    break;
1899
1900 38
                default:
1901
                    // Do nothing
1902 38
            }
1903
        }
1904
1905
        foreach ($entitiesToCascade as $relatedEntity) {
1906
            $this->doRemove($relatedEntity, $visited);
1907
        }
1908
    }
1909
1910
    /**
1911
     * Acquire a lock on the given entity.
1912 38
     *
1913
     * @param object $entity
1914 38
     * @param int    $lockMode
1915
     * @param int    $lockVersion
1916
     *
1917
     * @return void
1918
     *
1919
     * @throws ORMInvalidArgumentException
1920
     * @throws TransactionRequiredException
1921
     * @throws OptimisticLockException
1922
     */
1923
    public function lock($entity, $lockMode, $lockVersion = null)
1924
    {
1925
        if ($entity === null) {
1926
            throw new \InvalidArgumentException("No entity passed to UnitOfWork#lock().");
1927 6
        }
1928
1929 6
        if ($this->getEntityState($entity, self::STATE_DETACHED) != self::STATE_MANAGED) {
1930 6
            throw ORMInvalidArgumentException::entityNotManaged($entity);
1931
        }
1932 6
1933 6
        $class = $this->em->getClassMetadata(get_class($entity));
1934
1935 6
        switch (true) {
1936
            case LockMode::OPTIMISTIC === $lockMode:
1937
                if ( ! $class->isVersioned()) {
0 ignored issues
show
Bug introduced by
The method isVersioned() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1938
                    throw OptimisticLockException::notVersioned($class->getClassName());
0 ignored issues
show
Bug introduced by
The method getClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1939 1
                }
1940 1
1941
                if ($lockVersion === null) {
1942 1
                    return;
1943 1
                }
1944
1945 1
                if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
1946
                    $entity->initializeProxy();
1947 1
                }
1948
1949
                $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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1950
1951
                if ($entityVersion != $lockVersion) {
1952
                    throw OptimisticLockException::lockFailedVersionMismatch($entity, $lockVersion, $entityVersion);
1953
                }
1954
1955
                break;
1956
1957 12
            case LockMode::NONE === $lockMode:
1958
            case LockMode::PESSIMISTIC_READ === $lockMode:
1959 12
            case LockMode::PESSIMISTIC_WRITE === $lockMode:
1960
                if (!$this->em->getConnection()->isTransactionActive()) {
1961 12
                    throw TransactionRequiredException::transactionRequired();
1962 12
                }
1963
1964
                $oid = spl_object_hash($entity);
1965
1966
                $this->getEntityPersister($class->getClassName())->lock(
0 ignored issues
show
Bug introduced by
The method getClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
1967
                    array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
1968
                    $lockMode
1969
                );
1970
                break;
1971
1972
            default:
1973 15
                // Do nothing
1974
        }
1975 15
    }
1976
1977 15
    /**
1978
     * Clears the UnitOfWork.
1979
     *
1980
     * @return void
1981 15
     */
1982
    public function clear()
1983 15
    {
1984 15
        $this->entityPersisters =
1985 13
        $this->collectionPersisters =
1986 12
        $this->eagerLoadingEntities =
1987
        $this->identityMap =
1988
        $this->entityIdentifiers =
1989
        $this->originalEntityData =
1990 13
        $this->entityChangeSets =
1991 13
        $this->entityStates =
1992 13
        $this->scheduledForSynchronization =
1993 13
        $this->entityInsertions =
1994 13
        $this->entityUpdates =
1995 13
        $this->entityDeletions =
1996
        $this->collectionDeletions =
1997 13
        $this->collectionUpdates =
1998 3
        $this->extraUpdates =
1999 3
        $this->readOnlyObjects =
2000 3
        $this->visitedCollections =
2001
        $this->orphanRemovals = [];
2002
2003 13
        if ($this->eventManager->hasListeners(Events::onClear)) {
2004 13
            $this->eventManager->dispatchEvent(Events::onClear, new Event\OnClearEventArgs($this->em));
2005
        }
2006 13
    }
2007
2008
    /**
2009
     * INTERNAL:
2010
     * Schedules an orphaned entity for removal. The remove() operation will be
2011
     * invoked on that entity at the beginning of the next commit of this
2012
     * UnitOfWork.
2013
     *
2014
     * @ignore
2015
     *
2016
     * @param object $entity
2017
     *
2018 16
     * @return void
2019
     */
2020 16
    public function scheduleOrphanRemoval($entity)
2021
    {
2022 16
        $this->orphanRemovals[spl_object_hash($entity)] = $entity;
2023 16
    }
2024
2025
    /**
2026
     * INTERNAL:
2027
     * Cancels a previously scheduled orphan removal.
2028
     *
2029
     * @ignore
2030
     *
2031
     * @param object $entity
2032
     *
2033
     * @return void
2034
     */
2035 16
    public function cancelOrphanRemoval($entity)
2036
    {
2037 16
        unset($this->orphanRemovals[spl_object_hash($entity)]);
2038
    }
2039 16
2040
    /**
2041
     * INTERNAL:
2042
     * Schedules a complete collection for removal when this UnitOfWork commits.
2043 16
     *
2044
     * @param PersistentCollection $coll
2045 16
     *
2046
     * @return void
2047 16
     */
2048
    public function scheduleCollectionDeletion(PersistentCollection $coll)
2049
    {
2050
        $coid = spl_object_hash($coll);
2051 16
2052 16
        // TODO: if $coll is already scheduled for recreation ... what to do?
2053
        // Just remove $coll from the scheduled recreations?
2054
        unset($this->collectionUpdates[$coid]);
2055
2056 16
        $this->collectionDeletions[$coid] = $coll;
2057 16
    }
2058
2059
    /**
2060
     * @param PersistentCollection $coll
2061
     *
2062
     * @return bool
2063
     */
2064
    public function isCollectionScheduledForDeletion(PersistentCollection $coll)
2065
    {
2066
        return isset($this->collectionDeletions[spl_object_hash($coll)]);
2067 16
    }
2068
2069 16
    /**
2070
     * INTERNAL:
2071 16
     * Creates a new instance of the mapped class, without invoking the constructor.
2072 16
     * This is only meant to be used internally, and should not be consumed by end users.
2073
     *
2074
     * @ignore
2075
     *
2076 16
     * @param ClassMetadata $class
2077 5
     *
2078
     * @return EntityManagerAware|object
2079
     */
2080 5
    public function newInstance(ClassMetadata $class)
2081
    {
2082 5
        $entity = $this->instantiator->instantiate($class->getClassName());
2083
2084
        if ($entity instanceof EntityManagerAware) {
2085
            $entity->injectEntityManager($this->em, $class);
2086
        }
2087 5
2088
        return $entity;
2089
    }
2090 5
2091
    /**
2092
     * INTERNAL:
2093
     * Creates an entity. Used for reconstitution of persistent entities.
2094
     *
2095
     * Internal note: Highly performance-sensitive method.
2096 5
     *
2097
     * @ignore
2098
     *
2099
     * @param string $className The name of the entity class.
2100 16
     * @param array  $data      The data for the entity.
2101
     * @param array  $hints     Any hints to account for during reconstitution/lookup of the entity.
2102
     *
2103
     * @return object The managed entity instance.
2104
     *
2105
     * @todo Rename: getOrCreateEntity
2106
     */
2107
    public function createEntity($className, array $data, &$hints = [])
2108
    {
2109
        $class  = $this->em->getClassMetadata($className);
2110 13
        $id     = $this->em->getIdentifierFlattener()->flattenIdentifier($class, $data);
0 ignored issues
show
Documentation introduced by
$class is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2111
        $idHash = implode(' ', $id);
2112 13
2113
        if (isset($this->identityMap[$class->getRootClassName()][$idHash])) {
0 ignored issues
show
Bug introduced by
The method getRootClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
2114 13
            $entity = $this->identityMap[$class->getRootClassName()][$idHash];
0 ignored issues
show
Bug introduced by
The method getRootClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
2115 13
            $oid = spl_object_hash($entity);
2116
2117
            if (
2118
                isset($hints[Query::HINT_REFRESH])
2119 13
                && isset($hints[Query::HINT_REFRESH_ENTITY])
2120 3
                && ($unmanagedProxy = $hints[Query::HINT_REFRESH_ENTITY]) !== $entity
2121
                && $unmanagedProxy instanceof GhostObjectInterface
2122
                && $this->isIdentifierEquals($unmanagedProxy, $entity)
2123 3
            ) {
2124
                // We will hydrate the given un-managed proxy anyway:
2125 2
                // continue work, but consider it the entity from now on
2126
                $entity = $unmanagedProxy;
2127
            }
2128
2129
            if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
2130 3
                $entity->setProxyInitializer(null);
2131 1
2132
                $overrideLocalValues = true;
2133 3
            } else {
2134
                $overrideLocalValues = isset($hints[Query::HINT_REFRESH]);
2135
2136
                // If only a specific entity is set to refresh, check that it's the one
2137
                if (isset($hints[Query::HINT_REFRESH_ENTITY])) {
2138
                    $overrideLocalValues = $hints[Query::HINT_REFRESH_ENTITY] === $entity;
2139 3
                }
2140
            }
2141
2142
            if ($overrideLocalValues) {
2143 13
                // inject EntityManager upon refresh.
2144
                if ($entity instanceof EntityManagerAware) {
2145
                    $entity->injectEntityManager($this->em, $class);
0 ignored issues
show
Documentation introduced by
$class is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2146
                }
2147
2148
                $this->originalEntityData[$oid] = $data;
2149
            }
2150
        } else {
2151
            $entity = $this->newInstance($class);
0 ignored issues
show
Documentation introduced by
$class is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2152
            $oid    = spl_object_hash($entity);
2153
2154 38
            $this->entityIdentifiers[$oid]  = $id;
2155
            $this->entityStates[$oid]       = self::STATE_MANAGED;
2156 38
            $this->originalEntityData[$oid] = $data;
2157
2158 38
            $this->identityMap[$class->getRootClassName()][$idHash] = $entity;
0 ignored issues
show
Bug introduced by
The method getRootClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
2159 38
2160
            $overrideLocalValues = true;
2161
        }
2162
2163 38
        if ( ! $overrideLocalValues) {
2164 15
            return $entity;
2165
        }
2166 15
2167 9
        if ($entity instanceof NotifyPropertyChanged) {
2168 1
            $entity->addPropertyChangedListener($this);
2169
        }
2170
2171 8
        foreach ($data as $field => $value) {
2172
            $property = $class->getProperty($field);
0 ignored issues
show
Bug introduced by
The method getProperty() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
2173 5
2174
            if ($property instanceof FieldMetadata) {
2175
                $property->setValue($entity, $value);
2176 8
            }
2177 8
        }
2178
2179 7
        // Loading the entity right here, if its in the eager loading map get rid of it there.
2180 14
        unset($this->eagerLoadingEntities[$class->getRootClassName()][$idHash]);
2181
2182
        if (isset($this->eagerLoadingEntities[$class->getRootClassName()]) && ! $this->eagerLoadingEntities[$class->getRootClassName()]) {
0 ignored issues
show
Bug introduced by
The method getRootClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
2183 38
            unset($this->eagerLoadingEntities[$class->getRootClassName()]);
2184
        }
2185
2186
        // Properly initialize any unfetched associations, if partial objects are not allowed.
2187
        if (isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) {
2188
            return $entity;
2189
        }
2190
2191
        foreach ($class->getDeclaredPropertiesIterator() as $field => $association) {
0 ignored issues
show
Bug introduced by
The method getDeclaredPropertiesIterator() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
2192
            if (! ($association instanceof AssociationMetadata)) {
2193 1028
                continue;
2194
            }
2195 1028
2196
            // Check if the association is not among the fetch-joined associations already.
2197 1028
            if (isset($hints['fetchAlias']) && isset($hints['fetched'][$hints['fetchAlias']][$field])) {
2198 1028
                continue;
2199
            }
2200
2201
            $targetEntity = $association->getTargetEntity();
2202 1028
            $targetClass  = $this->em->getClassMetadata($targetEntity);
2203 650
2204
            if ($association instanceof ToManyAssociationMetadata) {
2205
                // Ignore if its a cached collection
2206 650
                if (isset($hints[Query::HINT_CACHE_ENABLED]) &&
2207
                    $association->getValue($entity) instanceof PersistentCollection) {
2208 21
                    continue;
2209
                }
2210
2211
                $hasDataField = isset($data[$field]);
2212 589
2213 554
                // use the given collection
2214 3
                if ($hasDataField && $data[$field] instanceof PersistentCollection) {
2215 3
                    $data[$field]->setOwner($entity, $association);
2216
2217
                    $association->setValue($entity, $data[$field]);
2218
2219
                    $this->originalEntityData[$oid][$field] = $data[$field];
2220
2221 551
                    continue;
2222 282
                }
2223
2224
                // Inject collection
2225 551
                $pColl = $association->wrap($entity, $hasDataField ? $data[$field] : [], $this->em);
2226
2227 579
                $pColl->setInitialized($hasDataField);
2228 246
2229 4
                $association->setValue($entity, $pColl);
2230 4
2231
                if ($association->getFetchMode() === FetchMode::EAGER) {
2232
                    $this->loadCollection($pColl);
2233
                    $pColl->takeSnapshot();
2234
                }
2235
2236 242
                $this->originalEntityData[$oid][$field] = $pColl;
2237 242
2238
                continue;
2239 644
            }
2240
2241
            if (! $association->isOwningSide()) {
2242
                // use the given entity association
2243 1021
                if (isset($data[$field]) && is_object($data[$field]) &&
2244
                    isset($this->entityStates[spl_object_hash($data[$field])])) {
2245
                    $inverseAssociation = $targetClass->getProperty($association->getMappedBy());
0 ignored issues
show
Bug introduced by
The method getProperty() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
2246
2247
                    $association->setValue($entity, $data[$field]);
2248
                    $inverseAssociation->setValue($data[$field], $entity);
2249
2250
                    $this->originalEntityData[$oid][$field] = $data[$field];
2251
2252
                    continue;
2253 64
                }
2254
2255 64
                // Inverse side of x-to-one can never be lazy
2256
                $persister = $this->getEntityPersister($targetEntity);
2257 64
2258 64
                $association->setValue($entity, $persister->loadToOneEntity($association, $entity));
0 ignored issues
show
Compatibility introduced by
$association of type object<Doctrine\ORM\Mapping\AssociationMetadata> is not a sub-type of object<Doctrine\ORM\Mapp...OneAssociationMetadata>. It seems like you assume a child class of the class Doctrine\ORM\Mapping\AssociationMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
2259
2260
                continue;
2261
            }
2262 64
2263
            // use the entity association
2264 64
            if (isset($data[$field]) && is_object($data[$field]) && isset($this->entityStates[spl_object_hash($data[$field])])) {
2265 26
                $association->setValue($entity, $data[$field]);
2266 6
2267
                $this->originalEntityData[$oid][$field] = $data[$field];
2268
2269 26
                continue;
2270
            }
2271
2272 26
            $associatedId = [];
2273 19
2274
            // TODO: Is this even computed right in all cases of composite keys?
2275 20
            foreach ($association->getJoinColumns() as $joinColumn) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Doctrine\ORM\Mapping\AssociationMetadata as the method getJoinColumns() does only exist in the following sub-classes of Doctrine\ORM\Mapping\AssociationMetadata: Doctrine\ORM\Mapping\ManyToOneAssociationMetadata, Doctrine\ORM\Mapping\OneToOneAssociationMetadata, Doctrine\ORM\Mapping\ToOneAssociationMetadata. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
2276 10
                /** @var JoinColumnMetadata $joinColumn */
2277
                $joinColumnName = $joinColumn->getColumnName();
2278 20
                $joinColumnValue = isset($data[$joinColumnName]) ? $data[$joinColumnName] : null;
2279
                $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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2280 19
2281 7
                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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2282 7
                    // the missing key is part of target's entity primary key
2283
                    $associatedId = [];
2284 26
2285
                    continue;
2286
                }
2287
2288
                $associatedId[$targetField] = $joinColumnValue;
2289 64
            }
2290 16
2291
            if (! $associatedId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $associatedId 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...
2292 64
                // Foreign key is NULL
2293
                $association->setValue($entity, null);
2294
                $this->originalEntityData[$oid][$field] = null;
2295
2296
                continue;
2297
            }
2298
2299
            // @todo guilhermeblanco Can we remove the need of this somehow?
2300
            if (!isset($hints['fetchMode'][$class->getClassName()][$field])) {
0 ignored issues
show
Bug introduced by
The method getClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
2301
                $hints['fetchMode'][$class->getClassName()][$field] = $association->getFetchMode();
0 ignored issues
show
Bug introduced by
The method getClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
2302
            }
2303
2304
            // Foreign key is set
2305
            // Check identity map first
2306
            // FIXME: Can break easily with composite keys if join column values are in
2307 11
            //        wrong order. The correct order is the one in ClassMetadata#identifier.
2308
            $relatedIdHash = implode(' ', $associatedId);
2309 11
2310 1
            switch (true) {
2311
                case (isset($this->identityMap[$targetClass->getRootClassName()][$relatedIdHash])):
0 ignored issues
show
Bug introduced by
The method getRootClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
2312
                    $newValue = $this->identityMap[$targetClass->getRootClassName()][$relatedIdHash];
0 ignored issues
show
Bug introduced by
The method getRootClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
2313 10
2314 1
                    // If this is an uninitialized proxy, we are deferring eager loads,
2315
                    // this association is marked as eager fetch, and its an uninitialized proxy (wtf!)
2316
                    // then we can append this entity for eager loading!
2317 9
                    if (!$targetClass->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()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
2318
                        $newValue instanceof GhostObjectInterface &&
2319
                        isset($hints[self::HINT_DEFEREAGERLOAD]) &&
2320 9
                        $hints['fetchMode'][$class->getClassName()][$field] === FetchMode::EAGER &&
0 ignored issues
show
Bug introduced by
The method getClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
2321 6
                        ! $newValue->isProxyInitialized()
2322 2
                    ) {
2323
2324
                        $this->eagerLoadingEntities[$targetClass->getRootClassName()][$relatedIdHash] = current($associatedId);
0 ignored issues
show
Bug introduced by
The method getRootClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
2325 4
                    }
2326
2327
                    break;
2328
2329 4
                case ($targetClass->getSubClasses()):
0 ignored issues
show
Bug introduced by
The method getSubClasses() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
2330 1
                    // If it might be a subtype, it can not be lazy. There isn't even
2331
                    // a way to solve this with deferred eager loading, which means putting
2332
                    // an entity with subclasses at a *-to-one location is really bad! (performance-wise)
2333 4
                    $persister = $this->getEntityPersister($targetEntity);
2334
                    $newValue  = $persister->loadToOneEntity($association, $entity, $associatedId);
0 ignored issues
show
Compatibility introduced by
$association of type object<Doctrine\ORM\Mapping\AssociationMetadata> is not a sub-type of object<Doctrine\ORM\Mapp...OneAssociationMetadata>. It seems like you assume a child class of the class Doctrine\ORM\Mapping\AssociationMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
2335 4
                    break;
2336 2
2337
                default:
2338
                    // Proxies do not carry any kind of original entity data until they're fully loaded/initialized
2339 2
                    $managedData = [];
2340
2341 3
                    $normalizedAssociatedId = $this->normalizeIdentifier->__invoke(
2342 3
                        $this->em,
2343 1
                        $targetClass,
0 ignored issues
show
Documentation introduced by
$targetClass is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2344 3
                        $associatedId
2345 2
                    );
2346
2347
                    switch (true) {
2348 1
                        // We are negating the condition here. Other cases will assume it is valid!
2349
                        case ($hints['fetchMode'][$class->getClassName()][$field] !== FetchMode::EAGER):
0 ignored issues
show
Bug introduced by
The method getClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
2350 1
                            $newValue = $this->em->getProxyFactory()->getProxy($targetEntity, $normalizedAssociatedId);
2351 1
                            break;
2352
2353
                        // Deferred eager load only works for single identifier classes
2354 1
                        case (isset($hints[self::HINT_DEFEREAGERLOAD]) && !$targetClass->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()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
2355
                            // TODO: Is there a faster approach?
2356
                            $this->eagerLoadingEntities[$targetClass->getRootClassName()][$relatedIdHash] = current($normalizedAssociatedId);
0 ignored issues
show
Bug introduced by
The method getRootClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
2357
2358
                            $newValue = $this->em->getProxyFactory()->getProxy($targetEntity, $normalizedAssociatedId);
2359 3
                            break;
2360
2361
                        default:
2362
                            // TODO: This is very imperformant, ignore it?
2363
                            $newValue    = $this->em->find($targetEntity, $normalizedAssociatedId);
2364
                            // Needed to re-assign original entity data for freshly loaded entity
2365
                            $managedData = $this->originalEntityData[spl_object_hash($newValue)];
2366 1008
                            break;
2367
                    }
2368 1008
2369
                    // @TODO using `$associatedId` here seems to be risky.
2370
                    $this->registerManaged($newValue, $associatedId, $managedData);
2371
2372
                    break;
2373
            }
2374
2375
            $this->originalEntityData[$oid][$field] = $newValue;
2376
            $association->setValue($entity, $newValue);
2377
2378 1218
            if (
2379
                $association->getInversedBy()
2380 1218
                && $association instanceof OneToOneAssociationMetadata
2381 1217
                // @TODO refactor this
2382 1217
                // we don't want to set any values in un-initialized proxies
2383 1217
                && ! (
2384 1217
                    $newValue instanceof GhostObjectInterface
2385 1217
                    && ! $newValue->isProxyInitialized()
2386 1217
                )
2387 1217
            ) {
2388 1217
                $inverseAssociation = $targetClass->getProperty($association->getInversedBy());
0 ignored issues
show
Bug introduced by
The method getProperty() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
2389 1217
2390 1217
                $inverseAssociation->setValue($newValue, $entity);
2391 1217
            }
2392 1217
        }
2393 1217
2394 1217
        if ($overrideLocalValues) {
2395 1217
            // defer invoking of postLoad event to hydration complete step
2396
            $this->hydrationCompleteHandler->deferPostLoadInvoking($class, $entity);
0 ignored issues
show
Documentation introduced by
$class is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2397 3
        }
2398 3
2399
        return $entity;
2400
    }
2401 1218
2402 7
    /**
2403
     * @return void
2404 1218
     */
2405
    public function triggerEagerLoads()
2406
    {
2407
        if ( ! $this->eagerLoadingEntities) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->eagerLoadingEntities of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2408
            return;
2409
        }
2410
2411
        // avoid infinite recursion
2412
        $eagerLoadingEntities       = $this->eagerLoadingEntities;
2413
        $this->eagerLoadingEntities = [];
2414
2415
        foreach ($eagerLoadingEntities as $entityName => $ids) {
2416
            if ( ! $ids) {
2417
                continue;
2418 17
            }
2419
2420 17
            $class = $this->em->getClassMetadata($entityName);
2421 17
2422
            $this->getEntityPersister($entityName)->loadAll(
2423
                array_combine($class->identifier, [array_values($ids)])
0 ignored issues
show
Bug introduced by
Accessing identifier on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2424
            );
2425
        }
2426
    }
2427
2428
    /**
2429
     * Initializes (loads) an uninitialized persistent collection of an entity.
2430
     *
2431
     * @param \Doctrine\ORM\PersistentCollection $collection The collection to initialize.
2432
     *
2433 112
     * @return void
2434
     *
2435 112
     * @todo Maybe later move to EntityManager#initialize($proxyOrCollection). See DDC-733.
2436 112
     */
2437
    public function loadCollection(PersistentCollection $collection)
2438
    {
2439
        $association = $collection->getMapping();
2440
        $persister   = $this->getEntityPersister($association->getTargetEntity());
2441
2442
        if ($association instanceof OneToManyAssociationMetadata) {
2443
            $persister->loadOneToManyCollection($association, $collection->getOwner(), $collection);
2444
        } else {
2445
            $persister->loadManyToManyCollection($association, $collection->getOwner(), $collection);
0 ignored issues
show
Compatibility introduced by
$association of type object<Doctrine\ORM\Mapp...anyAssociationMetadata> is not a sub-type of object<Doctrine\ORM\Mapp...anyAssociationMetadata>. It seems like you assume a child class of the class Doctrine\ORM\Mapping\ToManyAssociationMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
2446 13
        }
2447
2448 13
        $collection->setInitialized(true);
2449
    }
2450
2451
    /**
2452 13
     * Gets the identity map of the UnitOfWork.
2453
     *
2454 13
     * @return array
2455 13
     */
2456
    public function getIdentityMap()
2457
    {
2458
        return $this->identityMap;
2459
    }
2460
2461
    /**
2462
     * Gets the original data of an entity. The original data is the data that was
2463
     * present at the time the entity was reconstituted from the database.
2464
     *
2465
     * @param object $entity
2466
     *
2467
     * @return array
2468
     */
2469
    public function getOriginalEntityData($entity)
2470
    {
2471
        $oid = spl_object_hash($entity);
2472 668
2473
        return isset($this->originalEntityData[$oid])
2474 668
            ? $this->originalEntityData[$oid]
2475
            : [];
2476 668
    }
2477 4
2478
    /**
2479
     * @ignore
2480 668
     *
2481
     * @param object $entity
2482
     * @param array  $data
2483
     *
2484
     * @return void
2485
     */
2486
    public function setOriginalEntityData($entity, array $data)
2487
    {
2488
        $this->originalEntityData[spl_object_hash($entity)] = $data;
2489
    }
2490
2491
    /**
2492
     * INTERNAL:
2493
     * Sets a property value of the original data array of an entity.
2494
     *
2495
     * @ignore
2496
     *
2497
     * @param string $oid
2498
     * @param string $property
2499 806
     * @param mixed  $value
2500
     *
2501 806
     * @return void
2502
     */
2503
    public function setOriginalEntityProperty($oid, $property, $value)
2504 806
    {
2505 806
        $this->originalEntityData[$oid][$property] = $value;
2506
    }
2507 806
2508 310
    /**
2509 310
     * Gets the identifier of an entity.
2510
     * The returned value is always an array of identifier values. If the entity
2511
     * has a composite identifier then the identifier values are in the same
2512 310
     * order as the identifier field names as returned by ClassMetadata#getIdentifierFieldNames().
2513 310
     *
2514 310
     * @param object $entity
2515 310
     *
2516 310
     * @return array The identifier values.
2517
     */
2518
    public function getEntityIdentifier($entity)
2519
    {
2520
        return $this->entityIdentifiers[spl_object_hash($entity)];
2521
    }
2522 2
2523 2
    /**
2524
     * Processes an entity instance to extract their identifier values.
2525
     *
2526 2
     * @param object $entity The entity instance.
2527
     *
2528
     * @return mixed A scalar value.
2529 308
     *
2530 21
     * @throws \Doctrine\ORM\ORMInvalidArgumentException
2531
     */
2532 21
    public function getSingleIdentifierValue($entity)
2533
    {
2534 21
        $class     = $this->em->getClassMetadata(get_class($entity));
2535 21
        $persister = $this->getEntityPersister($class->getClassName());
0 ignored issues
show
Bug introduced by
The method getClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
2536
2537
        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()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
2538 289
            throw ORMInvalidArgumentException::invalidCompositeIdentifier();
2539
        }
2540
2541 289
        $values = $this->isInIdentityMap($entity)
2542 71
            ? $this->getEntityIdentifier($entity)
2543
            : $persister->getIdentifier($entity);
2544
2545
        return isset($values[$class->identifier[0]]) ? $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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2546 308
    }
2547
2548 111
    /**
2549 3
     * @param array  $id
2550
     * @param string $rootClassName
2551
     *
2552 308
     * @return GhostObjectInterface|object
2553
     */
2554
    private function tryGetByIdOrLoadProxy(array $id, string $rootClassName)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
2555 665
    {
2556 665
        if ($fetched = $this->tryGetById($id, $rootClassName)) {
2557
            return $fetched;
2558 665
        }
2559 665
2560 665
        $class = $this->em->getClassMetadata($rootClassName);
2561
2562 665
        if ($class->getSubClasses()) {
0 ignored issues
show
Bug introduced by
The method getSubClasses() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
2563
            // can't do this for inheritance trees!
2564 665
            // @TODO fetching from the EntityManager feels dirty here
2565 2
            return $this->em->find($rootClassName, $id);
2566
        }
2567
2568 665
        $sortedId = [];
2569
2570
        foreach ($class->identifier as $idField) {
0 ignored issues
show
Bug introduced by
Accessing identifier on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2571 805
            $sortedId[$idField] = $id[$idField];
2572 219
        }
2573
2574
        $proxy = $this->em->getProxyFactory()->getProxy($rootClassName, $sortedId);
2575 702
2576 702
        $this->registerManaged($proxy, $sortedId, []);
2577 702
2578
        return $proxy;
2579
    }
2580
2581
    /**
2582 702
     * Tries to find an entity with the given identifier in the identity map of
2583
     * this UnitOfWork.
2584 702
     *
2585
     * @param mixed  $id            The entity identifier to look for.
2586
     * @param string $rootClassName The name of the root class of the mapped entity hierarchy.
2587
     *
2588
     * @return object|bool Returns the entity with the specified identifier if it exists in
2589 702
     *                     this UnitOfWork, FALSE otherwise.
2590 33
     */
2591
    public function tryGetById($id, $rootClassName)
2592
    {
2593 669
        $idHash = implode(' ', (array) $id);
2594
2595 586
        return isset($this->identityMap[$rootClassName][$idHash])
2596 260
            ? $this->identityMap[$rootClassName][$idHash]
2597
            : false;
2598
    }
2599 564
2600
    /**
2601
     * Schedules an entity for dirty-checking at commit-time.
2602 564
     *
2603 485
     * @param object $entity The entity to schedule for dirty-checking.
2604
     *
2605
     * @return void
2606 64
     */
2607
    public function scheduleForSynchronization($entity)
2608 2
    {
2609
        $rootClassName = $this->em->getClassMetadata(get_class($entity))->getRootClassName();
0 ignored issues
show
Bug introduced by
The method getRootClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
2610 2
2611 2
        $this->scheduledForSynchronization[$rootClassName][spl_object_hash($entity)] = $entity;
2612
    }
2613 2
2614
    /**
2615
     * Checks whether the UnitOfWork has any pending insertions.
2616
     *
2617 62
     * @return boolean TRUE if this UnitOfWork has pending insertions, FALSE otherwise.
2618
     */
2619 62
    public function hasPendingInsertions()
2620
    {
2621
        return ! empty($this->entityInsertions);
2622
    }
2623 485
2624 38
    /**
2625 38
     * Calculates the size of the UnitOfWork. The size of the UnitOfWork is the
2626
     * number of entities in the identity map.
2627 38
     *
2628
     * @return integer
2629
     */
2630 478
    public function size()
2631
    {
2632
        return \array_sum(\array_map('count', $this->identityMap));
2633 478
    }
2634 478
2635 478
    /**
2636
     * Gets the EntityPersister for an Entity.
2637 478
     *
2638
     * @param string $entityName The name of the Entity.
2639 287
     *
2640
     * @return \Doctrine\ORM\Persisters\Entity\EntityPersister
2641 287
     */
2642
    public function getEntityPersister($entityName)
2643
    {
2644 284
        if (isset($this->entityPersisters[$entityName])) {
2645
            return $this->entityPersisters[$entityName];
2646
        }
2647 478
2648
        $class = $this->em->getClassMetadata($entityName);
2649 287
2650 287
        switch (true) {
2651
            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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2652 287
                $persister = new BasicEntityPersister($this->em, $class);
0 ignored issues
show
Documentation introduced by
$class is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2653
                break;
2654
2655 284
            case ($class->inheritanceType === InheritanceType::SINGLE_TABLE):
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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2656 281
                $persister = new SingleTablePersister($this->em, $class);
0 ignored issues
show
Documentation introduced by
$class is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2657
                break;
2658
2659
            case ($class->inheritanceType === InheritanceType::JOINED):
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?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2660
                $persister = new JoinedSubclassPersister($this->em, $class);
0 ignored issues
show
Documentation introduced by
$class is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2661
                break;
2662
2663 284
            default:
2664
                throw new \RuntimeException('No persister found for entity.');
2665
        }
2666 284
2667 166
        if ($this->hasCache && $class->getCache()) {
0 ignored issues
show
Bug introduced by
The method getCache() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
2668
            $persister = $this->em->getConfiguration()
2669
                ->getSecondLevelCacheConfiguration()
2670
                ->getCacheFactory()
2671
                ->buildCachedEntityPersister($this->em, $persister, $class);
2672 166
        }
2673 166
2674 166
        $this->entityPersisters[$entityName] = $persister;
2675 166
2676 166
        return $this->entityPersisters[$entityName];
2677
    }
2678
2679
    /**
2680
     * Gets a collection persister for a collection-valued association.
2681 166
     *
2682
     * @param ToManyAssociationMetadata $association
2683 192
     *
2684
     * @return \Doctrine\ORM\Persisters\Collection\CollectionPersister
2685
     */
2686
    public function getCollectionPersister(ToManyAssociationMetadata $association)
2687 30
    {
2688 30
        $role = $association->getCache()
2689
            ? sprintf('%s::%s', $association->getSourceEntity(), $association->getName())
2690
            : get_class($association);
2691
2692
        if (isset($this->collectionPersisters[$role])) {
2693 163
            return $this->collectionPersisters[$role];
2694 157
        }
2695 157
2696
        $persister = $association instanceof OneToManyAssociationMetadata
2697
            ? new OneToManyPersister($this->em)
2698 6
            : new ManyToManyPersister($this->em);
2699
2700 6
        if ($this->hasCache && $association->getCache()) {
2701
            $persister = $this->em->getConfiguration()
2702 6
                ->getSecondLevelCacheConfiguration()
2703 6
                ->getCacheFactory()
2704
                ->buildCachedCollectionPersister($this->em, $persister, $association);
2705
        }
2706
2707
        $this->collectionPersisters[$role] = $persister;
2708
2709
        return $this->collectionPersisters[$role];
2710
    }
2711
2712 163
    /**
2713 163
     * INTERNAL:
2714 163
     * Registers an entity as managed.
2715
     *
2716
     * @param object $entity The entity.
2717 163
     * @param array  $id     Map containing identifier field names as key and its associated values.
2718 163
     * @param array  $data   The original entity data.
2719
     *
2720
     * @return void
2721
     */
2722 163
    public function registerManaged($entity, array $id, array $data)
2723
    {
2724 163
        $isProxy = $entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized();
2725
        $oid     = spl_object_hash($entity);
2726
2727 284
        $this->entityIdentifiers[$oid]  = $id;
2728 284
        $this->entityStates[$oid]       = self::STATE_MANAGED;
2729
        $this->originalEntityData[$oid] = $data;
2730 284
2731 49
        $this->addToIdentityMap($entity);
2732 49
2733
        if ($entity instanceof NotifyPropertyChanged && ! $isProxy) {
2734
            $entity->addPropertyChangedListener($this);
2735 284
        }
2736
    }
2737
2738
    /**
2739 486
     * INTERNAL:
2740
     * Clears the property changeset of the entity with the given OID.
2741
     *
2742
     * @param string $oid The entity's OID.
2743
     *
2744 486
     * @return void
2745
     */
2746 3
    public function clearEntityChangeSet($oid)
2747
    {
2748 3
        $this->entityChangeSets[$oid] = [];
2749 3
    }
2750
2751 3
    /* PropertyChangedListener implementation */
2752
2753
    /**
2754
     * Notifies this UnitOfWork of a property change in an entity.
2755 486
     *
2756 486
     * @param object $entity       The entity that owns the property.
2757 486
     * @param string $propertyName The name of the property that changed.
2758
     * @param mixed  $oldValue     The old value of the property.
2759 486
     * @param mixed  $newValue     The new value of the property.
2760 486
     *
2761
     * @return void
2762 486
     */
2763 4
    public function propertyChanged($entity, $propertyName, $oldValue, $newValue)
2764 4
    {
2765
        $class = $this->em->getClassMetadata(get_class($entity));
2766
2767 486
        if (! $class->getProperty($propertyName)) {
0 ignored issues
show
Bug introduced by
The method getProperty() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
2768 564
            return; // ignore non-persistent fields
2769
        }
2770
2771
        $oid = spl_object_hash($entity);
2772 669
2773
        // Update changeset and mark entity for synchronization
2774 669
        $this->entityChangeSets[$oid][$propertyName] = [$oldValue, $newValue];
2775
2776
        if ( ! isset($this->scheduledForSynchronization[$class->getRootClassName()][$oid])) {
0 ignored issues
show
Bug introduced by
The method getRootClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
2777 669
            $this->scheduleForSynchronization($entity);
2778
        }
2779
    }
2780
2781
    /**
2782
     * Gets the currently scheduled entity insertions in this UnitOfWork.
2783 861
     *
2784
     * @return array
2785 861
     */
2786 861
    public function getScheduledEntityInsertions()
2787
    {
2788
        return $this->entityInsertions;
2789
    }
2790 6
2791 6
    /**
2792
     * Gets the currently scheduled entity updates in this UnitOfWork.
2793 6
     *
2794 6
     * @return array
2795
     */
2796
    public function getScheduledEntityUpdates()
2797
    {
2798 6
        return $this->entityUpdates;
2799
    }
2800 6
2801 6
    /**
2802
     * Gets the currently scheduled entity deletions in this UnitOfWork.
2803
     *
2804 6
     * @return array
2805
     */
2806
    public function getScheduledEntityDeletions()
2807
    {
2808
        return $this->entityDeletions;
2809
    }
2810
2811
    /**
2812
     * Gets the currently scheduled complete collection deletions
2813
     *
2814
     * @return array
2815 142
     */
2816
    public function getScheduledCollectionDeletions()
2817 142
    {
2818 142
        return $this->collectionDeletions;
2819
    }
2820 142
2821 142
    /**
2822 76
     * Gets the currently scheduled collection inserts, updates and deletes.
2823 76
     *
2824
     * @return array
2825 80
     */
2826 80
    public function getScheduledCollectionUpdates()
2827 80
    {
2828
        return $this->collectionUpdates;
2829
    }
2830 142
2831 142
    /**
2832
     * Helper method to initialize a lazy loading proxy or persistent collection.
2833
     *
2834
     * @param object $obj
2835
     *
2836
     * @return void
2837
     */
2838 2
    public function initializeObject($obj)
2839
    {
2840 2
        if ($obj instanceof GhostObjectInterface) {
2841
            $obj->initializeProxy();
2842
2843
            return;
2844
        }
2845
2846
        if ($obj instanceof PersistentCollection) {
2847
            $obj->initialize();
2848
        }
2849
    }
2850
2851 115
    /**
2852
     * Helper method to show an object as string.
2853 115
     *
2854
     * @param object $obj
2855 115
     *
2856 112
     * @return string
2857 115
     */
2858
    private static function objToStr($obj)
2859
    {
2860
        return method_exists($obj, '__toString') ? (string) $obj : get_class($obj).'@'.spl_object_hash($obj);
2861
    }
2862
2863
    /**
2864
     * Marks an entity as read-only so that it will not be considered for updates during UnitOfWork#commit().
2865
     *
2866
     * This operation cannot be undone as some parts of the UnitOfWork now keep gathering information
2867
     * on this object that might be necessary to perform a correct update.
2868
     *
2869
     * @param object $object
2870
     *
2871
     * @return void
2872
     *
2873
     * @throws ORMInvalidArgumentException
2874
     */
2875
    public function markReadOnly($object)
2876
    {
2877
        if ( ! is_object($object) || ! $this->isInIdentityMap($object)) {
2878
            throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object);
2879
        }
2880
2881
        $this->readOnlyObjects[spl_object_hash($object)] = true;
2882
    }
2883
2884
    /**
2885 313
     * Is this entity read only?
2886
     *
2887 313
     * @param object $object
2888 313
     *
2889
     * @return bool
2890
     *
2891
     * @throws ORMInvalidArgumentException
2892
     */
2893
    public function isReadOnly($object)
2894
    {
2895
        if ( ! is_object($object)) {
2896
            throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object);
2897
        }
2898
2899
        return isset($this->readOnlyObjects[spl_object_hash($object)]);
2900 842
    }
2901
2902 842
    /**
2903
     * Perform whatever processing is encapsulated here after completion of the transaction.
2904
     */
2905
    private function afterTransactionComplete()
2906
    {
2907
        $this->performCallbackOnCachedPersister(function (CachedPersister $persister) {
2908
            $persister->afterTransactionComplete();
2909
        });
2910
    }
2911
2912
    /**
2913
     * Perform whatever processing is encapsulated here after completion of the rolled-back.
2914 126
     */
2915
    private function afterTransactionRolledBack()
2916 126
    {
2917
        $this->performCallbackOnCachedPersister(function (CachedPersister $persister) {
2918 126
            $persister->afterTransactionRolledBack();
2919
        });
2920
    }
2921
2922 126
    /**
2923 113
     * Performs an action after the transaction.
2924 126
     *
2925
     * @param callable $callback
2926 126
     */
2927
    private function performCallbackOnCachedPersister(callable $callback)
2928
    {
2929
        if ( ! $this->hasCache) {
2930
            return;
2931
        }
2932
2933
        foreach (array_merge($this->entityPersisters, $this->collectionPersisters) as $persister) {
2934
            if ($persister instanceof CachedPersister) {
2935
                $callback($persister);
2936
            }
2937
        }
2938
    }
2939 522
2940
    private function dispatchOnFlushEvent()
2941 522
    {
2942
        if ($this->eventManager->hasListeners(Events::onFlush)) {
2943 522
            $this->eventManager->dispatchEvent(Events::onFlush, new OnFlushEventArgs($this->em));
2944 79
        }
2945 522
    }
2946
2947
    private function dispatchPostFlushEvent()
2948
    {
2949
        if ($this->eventManager->hasListeners(Events::postFlush)) {
2950
            $this->eventManager->dispatchEvent(Events::postFlush, new PostFlushEventArgs($this->em));
2951
        }
2952
    }
2953
2954
    /**
2955
     * Verifies if two given entities actually are the same based on identifier comparison
2956
     *
2957 5
     * @param object $entity1
2958
     * @param object $entity2
2959 5
     *
2960
     * @return bool
2961 5
     */
2962 5
    private function isIdentifierEquals($entity1, $entity2)
2963
    {
2964
        if ($entity1 === $entity2) {
2965
            return true;
2966
        }
2967
2968
        $class     = $this->em->getClassMetadata(get_class($entity1));
2969
        $persister = $this->getEntityPersister($class->getClassName());
0 ignored issues
show
Bug introduced by
The method getClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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...
2970
2971
        if ($class !== $this->em->getClassMetadata(get_class($entity2))) {
2972
            return false;
2973
        }
2974
2975
        $identifierFlattener = $this->em->getIdentifierFlattener();
2976
2977
        $oid1 = spl_object_hash($entity1);
2978
        $oid2 = spl_object_hash($entity2);
2979
2980 1
        $id1 = isset($this->entityIdentifiers[$oid1])
2981
            ? $this->entityIdentifiers[$oid1]
2982 1
            : $identifierFlattener->flattenIdentifier($class, $persister->getIdentifier($entity1));
0 ignored issues
show
Documentation introduced by
$class is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2983
        $id2 = isset($this->entityIdentifiers[$oid2])
2984 1
            ? $this->entityIdentifiers[$oid2]
2985
            : $identifierFlattener->flattenIdentifier($class, $persister->getIdentifier($entity2));
0 ignored issues
show
Documentation introduced by
$class is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2986
2987
        return $id1 === $id2 || implode(' ', $id1) === implode(' ', $id2);
2988
    }
2989
2990
    /**
2991
     * This method called by hydrators, and indicates that hydrator totally completed current hydration cycle.
2992
     * Unit of work able to fire deferred events, related to loading events here.
2993
     *
2994 1063
     * @internal should be called internally from object hydrators
2995
     */
2996 1063
    public function hydrationComplete()
2997 845
    {
2998
        $this->hydrationCompleteHandler->hydrationComplete();
2999
    }
3000
}
3001