Failed Conditions
Pull Request — develop (#1577)
by Marco
66:01
created

UnitOfWork::cascadeRemove()   C

Complexity

Conditions 11
Paths 28

Size

Total Lines 38
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 11.0245

Importance

Changes 0
Metric Value
dl 0
loc 38
ccs 16
cts 17
cp 0.9412
rs 5.2653
c 0
b 0
f 0
cc 11
eloc 21
nc 28
nop 2
crap 11.0245

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM;
6
7
use Doctrine\Common\Collections\Collection;
8
use Doctrine\Common\NotifyPropertyChanged;
9
use Doctrine\Common\PropertyChangedListener;
10
use Doctrine\DBAL\LockMode;
11
use Doctrine\Instantiator\Instantiator;
12
use Doctrine\ORM\Cache\Persister\CachedPersister;
13
use Doctrine\ORM\Event\LifecycleEventArgs;
14
use Doctrine\ORM\Event\ListenersInvoker;
15
use Doctrine\ORM\Event\OnFlushEventArgs;
16
use Doctrine\ORM\Event\PostFlushEventArgs;
17
use Doctrine\ORM\Event\PreFlushEventArgs;
18
use Doctrine\ORM\Event\PreUpdateEventArgs;
19
use Doctrine\ORM\Internal\HydrationCompleteHandler;
20
use Doctrine\ORM\Mapping\AssociationMetadata;
21
use Doctrine\ORM\Mapping\ChangeTrackingPolicy;
22
use Doctrine\ORM\Mapping\ClassMetadata;
23
use Doctrine\ORM\Mapping\FetchMode;
24
use Doctrine\ORM\Mapping\FieldMetadata;
25
use Doctrine\ORM\Mapping\GeneratorType;
26
use Doctrine\ORM\Mapping\InheritanceType;
27
use Doctrine\ORM\Mapping\JoinColumnMetadata;
28
use Doctrine\ORM\Mapping\ManyToManyAssociationMetadata;
29
use Doctrine\ORM\Mapping\OneToManyAssociationMetadata;
30
use Doctrine\ORM\Mapping\OneToOneAssociationMetadata;
31
use Doctrine\ORM\Mapping\Property;
32
use Doctrine\ORM\Mapping\ToManyAssociationMetadata;
33
use Doctrine\ORM\Mapping\ToOneAssociationMetadata;
34
use Doctrine\ORM\Mapping\VersionFieldMetadata;
35
use Doctrine\ORM\Persisters\Collection\ManyToManyPersister;
36
use Doctrine\ORM\Persisters\Collection\OneToManyPersister;
37
use Doctrine\ORM\Persisters\Entity\BasicEntityPersister;
38
use Doctrine\ORM\Persisters\Entity\JoinedSubclassPersister;
39
use Doctrine\ORM\Persisters\Entity\SingleTablePersister;
40
use Doctrine\ORM\Proxy\Proxy;
41
use Exception;
42
use InvalidArgumentException;
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
     * Initializes a new UnitOfWork instance, bound to the given EntityManager.
276
     *
277
     * @param EntityManagerInterface $em
278
     */
279
    public function __construct(EntityManagerInterface $em)
280
    {
281
        $this->em                       = $em;
282
        $this->eventManager             = $em->getEventManager();
283
        $this->listenersInvoker         = new ListenersInvoker($em);
284
        $this->hasCache                 = $em->getConfiguration()->isSecondLevelCacheEnabled();
285
        $this->instantiator             = new Instantiator();
286
        $this->hydrationCompleteHandler = new HydrationCompleteHandler($this->listenersInvoker, $em);
287
    }
288
289
    /**
290
     * Commits the UnitOfWork, executing all operations that have been postponed
291
     * up to this point. The state of all managed entities will be synchronized with
292 2290
     * the database.
293
     *
294 2290
     * The operations are executed in the following order:
295 2290
     *
296 2290
     * 1) All entity insertions
297 2290
     * 2) All entity updates
298 2290
     * 3) All collection deletions
299 2290
     * 4) All collection updates
300 2290
     * 5) All entity deletions
301 2290
     *
302
     * @return void
303
     *
304
     * @throws \Exception
305
     */
306
    public function commit()
307
    {
308
        // Raise preFlush
309
        if ($this->eventManager->hasListeners(Events::preFlush)) {
310
            $this->eventManager->dispatchEvent(Events::preFlush, new PreFlushEventArgs($this->em));
311
        }
312
313
        $this->computeChangeSets();
314
315
        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...
316
                $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...
317
                $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...
318
                $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...
319
                $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...
320
                $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...
321
            $this->dispatchOnFlushEvent();
322 1015
            $this->dispatchPostFlushEvent();
323
324
            return; // Nothing to do.
325 1015
        }
326 2
327
        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...
328
            foreach ($this->orphanRemovals as $orphan) {
329
                $this->remove($orphan);
330 1015
            }
331 1007
        }
332 16
333 15
        $this->dispatchOnFlushEvent();
334 1
335 1
        // Now we need a commit order to maintain referential integrity
336 1
        $commitOrder = $this->getCommitOrder();
337
338
        $conn = $this->em->getConnection();
339
        $conn->beginTransaction();
340 1012
341 165
        try {
342 129
            // Collection deletions (deletions of complete collections)
343 40
            foreach ($this->collectionDeletions as $collectionToDelete) {
344 37
                $this->getCollectionPersister($collectionToDelete->getMapping())->delete($collectionToDelete);
345 1012
            }
346 25
347 25
            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...
348
                foreach ($commitOrder as $class) {
349 25
                    $this->executeInserts($class);
350
                }
351
            }
352 1008
353 16
            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...
354 16
                foreach ($commitOrder as $class) {
355
                    $this->executeUpdates($class);
356
                }
357
            }
358 1008
359
            // Extra updates that were requested by persisters.
360
            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...
361 1008
                $this->executeExtraUpdates();
362
            }
363 1008
364 1008
            // Collection updates (deleteRows, updateRows, insertRows)
365
            foreach ($this->collectionUpdates as $collectionToUpdate) {
366
                $this->getCollectionPersister($collectionToUpdate->getMapping())->update($collectionToUpdate);
367
            }
368 1008
369 19
            // Entity deletions come last and need to be in reverse commit order
370
            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...
371
                foreach (array_reverse($commitOrder) as $committedEntityName) {
372 1008
                    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...
373 1004
                        break; // just a performance optimisation
374 1004
                    }
375
376
                    $this->executeDeletions($committedEntityName);
377
                }
378 1007
            }
379 115
380 115
            $conn->commit();
381
        } catch (Exception $e) {
382
            $this->em->close();
383
            $conn->rollBack();
384
385 1003
            $this->afterTransactionRolledBack();
386 40
387
            throw $e;
388
        }
389
390 1003
        $this->afterTransactionComplete();
391 531
392
        // Take new snapshots from visited collections
393
        foreach ($this->visitedCollections as $coll) {
394
            $coll->takeSnapshot();
395 1003
        }
396 62
397 62
        $this->dispatchPostFlushEvent();
398
399
        // Clear up
400
        $this->entityInsertions =
401 1003
        $this->entityUpdates =
402 11
        $this->entityDeletions =
403 11
        $this->extraUpdates =
404 11
        $this->entityChangeSets =
405
        $this->collectionUpdates =
406 11
        $this->collectionDeletions =
407
        $this->visitedCollections =
408 11
        $this->scheduledForSynchronization =
409
        $this->orphanRemovals = [];
410
    }
411 1003
412
    /**
413
     * Computes the changesets of all entities scheduled for insertion.
414 1003
     *
415 530
     * @return void
416
     */
417
    private function computeScheduleInsertsChangeSets()
418 1003
    {
419
        foreach ($this->entityInsertions as $entity) {
420
            $class = $this->em->getClassMetadata(get_class($entity));
421 1002
422 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...
423 1002
        }
424 1002
    }
425 1002
426 1002
    /**
427 1002
     * Executes any extra updates that have been scheduled.
428 1002
     */
429 1002
    private function executeExtraUpdates()
430 1002
    {
431 1002
        foreach ($this->extraUpdates as $oid => $update) {
432
            list ($entity, $changeset) = $update;
433
434
            $this->entityChangeSets[$oid] = $changeset;
435
436
//            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...
437
//            \Doctrine\Common\Util\Debug::dump($changeset, 3);
438 1014
439
            $this->getEntityPersister(get_class($entity))->update($entity);
440 1014
        }
441 1006
442
        $this->extraUpdates = [];
443 1006
    }
444
445 1012
    /**
446
     * Gets the changeset for an entity.
447
     *
448
     * @param object $entity
449
     *
450
     * @return array
451
     */
452
    public function & getEntityChangeSet($entity)
453
    {
454
        $oid  = spl_object_hash($entity);
455
        $data = [];
456
457
        if (!isset($this->entityChangeSets[$oid])) {
458
            return $data;
459
        }
460
461 16
        return $this->entityChangeSets[$oid];
462
    }
463 16
464
    /**
465 16
     * Computes the changes that happened to a single entity.
466 1
     *
467
     * Modifies/populates the following properties:
468
     *
469 15
     * {@link originalEntityData}
470
     * If the entity is NEW or MANAGED but not yet fully persisted (only has an id)
471 15
     * then it was not fetched from the database and therefore we have no original
472 14
     * entity data yet. All of the current entity data is stored as the original entity data.
473
     *
474
     * {@link entityChangeSets}
475
     * The changes detected on all properties of the entity are stored there.
476 15
     * A change is a tuple array where the first entry is the old value and the second
477
     * entry is the new value of the property. Changesets are used by persisters
478 15
     * to INSERT/UPDATE the persistent entity state.
479
     *
480
     * {@link entityUpdates}
481
     * If the entity is already fully MANAGED (has been fetched from the database before)
482
     * and any changes to its properties are detected, then a reference to the entity is stored
483 15
     * there to mark it for an update.
484 2
     *
485
     * {@link collectionDeletions}
486
     * If a PersistentCollection has been de-referenced in a fully MANAGED entity,
487
     * then this collection is marked for deletion.
488 13
     *
489
     * @ignore
490 13
     *
491 6
     * @internal Don't call from the outside.
492
     *
493 12
     * @param ClassMetadata $class  The class descriptor of the entity.
494
     * @param object        $entity The entity for which to compute the changes.
495
     *
496
     * @return void
497
     */
498 40
    public function computeChangeSet(ClassMetadata $class, $entity)
499
    {
500 40
        $oid = spl_object_hash($entity);
501 40
502
        if (isset($this->readOnlyObjects[$oid])) {
503 40
            return;
504
        }
505
506
        if ($class->inheritanceType !== InheritanceType::NONE) {
507
            $class = $this->em->getClassMetadata(get_class($entity));
508 40
        }
509
510
        $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 507 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...
511 40
512 40
        if ($invoke !== ListenersInvoker::INVOKE_NONE) {
513
            $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 507 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...
514
        }
515
516
        $actualData = [];
517
518
        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...
519
            $value = $property->getValue($entity);
520
521 1006
            if ($property instanceof ToManyAssociationMetadata && $value !== null) {
522
                if ($value instanceof PersistentCollection && $value->getOwner() === $entity) {
523 1006
                    continue;
524 1006
                }
525
526 1006
                $value = $property->wrap($entity, $value, $this->em);
527 1
528
                $property->setValue($entity, $value);
529
530 1006
                $actualData[$name] = $value;
531
532
                continue;
533
            }
534
535
            if (
536
                ( ! $class->isIdentifier($name)
537
                    || ! $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...
538
                    || ! $class->getProperty($name)->hasValueGenerator()
539
                    || $class->getProperty($name)->getValueGenerator()->getType() !== GeneratorType::IDENTITY
540
                ) && (! $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...
541
                $actualData[$name] = $value;
542
            }
543
        }
544
545
        if ( ! isset($this->originalEntityData[$oid])) {
546
            // Entity is either NEW or MANAGED but not yet fully persisted (only has an id).
547
            // These result in an INSERT.
548
            $this->originalEntityData[$oid] = $actualData;
549
            $changeSet = [];
550
551
            foreach ($actualData as $propName => $actualValue) {
552
                $property = $class->getProperty($propName);
553
554
                if (($property instanceof FieldMetadata) ||
555
                    ($property instanceof ToOneAssociationMetadata && $property->isOwningSide())) {
556
                    $changeSet[$propName] = [null, $actualValue];
557
                }
558
            }
559
560
            $this->entityChangeSets[$oid] = $changeSet;
561
        } else {
562
            // Entity is "fully" MANAGED: it was already fully persisted before
563
            // and we have a copy of the original data
564
            $originalData           = $this->originalEntityData[$oid];
565
            $isChangeTrackingNotify = $class->changeTrackingPolicy === ChangeTrackingPolicy::NOTIFY;
566
            $changeSet              = ($isChangeTrackingNotify && isset($this->entityChangeSets[$oid]))
567 1016
                ? $this->entityChangeSets[$oid]
568
                : [];
569 1016
570
            foreach ($actualData as $propName => $actualValue) {
571 1016
                // skip field, its a partially omitted one!
572 2
                if ( ! (isset($originalData[$propName]) || array_key_exists($propName, $originalData))) {
573
                    continue;
574
                }
575 1016
576 307
                $orgValue = $originalData[$propName];
577
578
                // skip if value haven't changed
579 1016
                if ($orgValue === $actualValue) {
580
                    continue;
581 1016
                }
582 137
583
                $property = $class->getProperty($propName);
584
585 1016
                // Persistent collection was exchanged with the "originally"
586
                // created one. This can only mean it was cloned and replaced
587 1016
                // on another entity.
588 1016
                if ($actualValue instanceof PersistentCollection) {
589
                    $owner = $actualValue->getOwner();
590 1016
591 777
                    if ($owner === null) { // cloned
592 200
                        $actualValue->setOwner($entity, $property);
593 200
                    } else if ($owner !== $entity) { // no clone, we have to fix
594
                        if (! $actualValue->isInitialized()) {
595
                            $actualValue->initialize(); // we have to do this otherwise the cols share state
596 5
                        }
597
598
                        $newValue = clone $actualValue;
599
600 772
                        $newValue->setOwner($entity, $property);
601 242
602
                        $property->setValue($entity, $newValue);
603
                    }
604 772
                }
605
606
                switch (true) {
607 772
                    case ($property instanceof FieldMetadata):
608 772
                        if ($isChangeTrackingNotify) {
609
                            // Continue inside switch behaves as break.
610 772
                            // We are required to use continue 2, since we need to continue to next $actualData item
611 772
                            continue 2;
612
                        }
613 772
614
                        $changeSet[$propName] = [$orgValue, $actualValue];
615 772
                        break;
616
617 772
                    case ($property instanceof ToOneAssociationMetadata):
618
                        if ($property->isOwningSide()) {
619
                            $changeSet[$propName] = [$orgValue, $actualValue];
620 1016
                        }
621 1016
622 1016
                        if ($orgValue !== null && $property->isOrphanRemoval()) {
623
                            $this->scheduleOrphanRemoval($orgValue);
624
                        }
625
626 1016
                        break;
627
628
                    case ($property instanceof ToManyAssociationMetadata):
629 1012
                        // Check if original value exists
630 1012
                        if ($orgValue instanceof PersistentCollection) {
631
                            // A PersistentCollection was de-referenced, so delete it.
632 1012
                            $coid = spl_object_hash($orgValue);
633 996
634 945
                            if (!isset($this->collectionDeletions[$coid])) {
635
                                $this->collectionDeletions[$coid] = $orgValue;
636 945
                                $changeSet[$propName] = $orgValue; // Signal changeset, to-many associations will be ignored
637
                            }
638
                        }
639 894
640
                        break;
641 894
642 894
                    default:
643
                        // Do nothing
644
                }
645
            }
646 1012
647
            if ($changeSet) {
648
                $this->entityChangeSets[$oid]   = $changeSet;
649
                $this->originalEntityData[$oid] = $actualData;
650 263
                $this->entityUpdates[$oid]      = $entity;
651 263
            }
652 263
        }
653
654 263
        // Look for changes in associations of the entity
655
        foreach ($class->getDeclaredPropertiesIterator() as $property) {
656 263
            if (! ($property instanceof AssociationMetadata) || ($value = $property->getValue($entity)) === null) {
657
                continue;
658 248
            }
659 7
660
            $this->computeAssociationChanges($property, $value);
661
662 248
            if ($property instanceof ManyToManyAssociationMetadata &&
663
                $value instanceof PersistentCollection &&
664
                ! isset($this->entityChangeSets[$oid]) &&
665 248
                $property->isOwningSide() &&
666 232
                $value->isDirty()) {
667
668
                $this->entityChangeSets[$oid]   = [];
669
                $this->originalEntityData[$oid] = $actualData;
670 111
                $this->entityUpdates[$oid]      = $entity;
671 57
            }
672
        }
673
    }
674
675 57
    /**
676
     * Computes all the changes that have been done to entities and collections
677 57
     * since the last commit and stores these changes in the _entityChangeSet map
678
     * temporarily for access by the persisters, until the UoW commit is finished.
679
     *
680 58
     * @return void
681
     */
682
    public function computeChangeSets()
683
    {
684
        // Compute changes for INSERTed entities first. This must always happen.
685 58
        $this->computeScheduleInsertsChangeSets();
686 8
687 8
        // Compute changes for other MANAGED entities. Change tracking policies take effect here.
688
        foreach ($this->identityMap as $className => $entities) {
689 8
            $class = $this->em->getClassMetadata($className);
690
691
            // Skip class if instances are read-only
692
            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...
693
                continue;
694
            }
695
696
            // If change tracking is explicit or happens through notification, then only compute
697
            // changes on entities of that type that are explicitly marked for synchronization.
698
            switch (true) {
699 58
                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...
700
                    $entitiesToProcess = $entities;
701 8
                    break;
702
703 8
                case (isset($this->scheduledForSynchronization[$className])):
704
                    $entitiesToProcess = $this->scheduledForSynchronization[$className];
705
                    break;
706
707 8
                default:
708 8
                    $entitiesToProcess = [];
709
710 8
            }
711
712
            foreach ($entitiesToProcess as $entity) {
713 50
                // Ignore uninitialized proxy objects
714 49
                if ($entity instanceof Proxy && ! $entity->__isInitialized()) {
715 21
                    continue;
716
                }
717
718 49
                // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION OR DELETION are processed here.
719 50
                $oid = spl_object_hash($entity);
720
721
                if ( ! isset($this->entityInsertions[$oid]) && ! isset($this->entityDeletions[$oid]) && isset($this->entityStates[$oid])) {
722
                    $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...
723
                }
724 263
            }
725 84
        }
726 84
    }
727 84
728
    /**
729
     * Computes the changes of an association.
730
     *
731
     * @param AssociationMetadata $association The association mapping.
732 1016
     * @param mixed               $value       The value of the association.
733 894
     *
734 639
     * @throws ORMInvalidArgumentException
735
     * @throws ORMException
736
     *
737 865
     * @return void
738
     */
739 857
    private function computeAssociationChanges(AssociationMetadata $association, $value)
740 857
    {
741 857
        if ($value instanceof Proxy && ! $value->__isInitialized()) {
742 857
            return;
743 857
        }
744
745 35
        if ($value instanceof PersistentCollection && $value->isDirty()) {
746 35
            $coid = spl_object_hash($value);
747 857
748
            $this->collectionUpdates[$coid] = $value;
749
            $this->visitedCollections[$coid] = $value;
750 1008
        }
751
752
        // Look through the entities, and in any of their associations,
753
        // 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...
754
        // Unwrap. Uninitialized collections will simply be empty.
755
        $unwrappedValue = ($association instanceof ToOneAssociationMetadata) ? [$value] : $value->unwrap();
756
        $targetEntity   = $association->getTargetEntity();
757
        $targetClass    = $this->em->getClassMetadata($targetEntity);
758
759 1007
        foreach ($unwrappedValue as $key => $entry) {
760
            if (! ($entry instanceof $targetEntity)) {
761
                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...
762 1007
            }
763
764
            $state = $this->getEntityState($entry, self::STATE_NEW);
765 1005
766 447
            if (! ($entry instanceof $targetEntity)) {
767
                throw ORMException::unexpectedAssociationValue(
768
                    $association->getSourceEntity(),
769 447
                    $association->getName(),
770 1
                    get_class($entry),
771
                    $targetEntity
772
                );
773
            }
774
775
            switch ($state) {
776 446
                case self::STATE_NEW:
777 444
                    if ( ! in_array('persist', $association->getCascade())) {
778 444
                        throw ORMInvalidArgumentException::newEntityFoundThroughRelationship($association, $entry);
779
                    }
780 3
781 3
                    $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...
782 3
                    $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...
783
                    break;
784
785 1
                case self::STATE_REMOVED:
786
                    // Consume the $value as array (it's either an array or an ArrayAccess)
787
                    // and remove the element from Collection.
788
                    if ($association instanceof ToManyAssociationMetadata) {
789 446
                        unset($value[$key]);
790
                    }
791 426
                    break;
792 35
793
                case self::STATE_DETACHED:
794
                    // Can actually not happen right now as we assume STATE_NEW,
795
                    // so the exception will be raised from the DBAL layer (constraint violation).
796 425
                    throw ORMInvalidArgumentException::detachedEntityFoundThroughRelationship($association, $entry);
797
                    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...
798 425
799 446
                default:
800
                    // MANAGED associated entities are already taken into account
801
                    // during changeset calculation anyway, since they are in the identity map.
802
            }
803 1005
        }
804
    }
805
806
    /**
807
     * @param \Doctrine\ORM\Mapping\ClassMetadata $class
808
     * @param object                              $entity
809
     *
810
     * @return void
811
     */
812
    private function persistNew($class, $entity)
813
    {
814
        $oid    = spl_object_hash($entity);
815
        $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::prePersist);
816 865
817
        if ($invoke !== ListenersInvoker::INVOKE_NONE) {
818 865
            $this->listenersInvoker->invoke($class, Events::prePersist, $entity, new LifecycleEventArgs($entity, $this->em), $invoke);
819 28
        }
820
821
        $generationPlan = $class->getValueGenerationPlan();
822 864
        $persister = $this->getEntityPersister($class->getClassName());
823 533
        $generationPlan->executeImmediate($this->em, $entity);
824
825 533
        if (! $generationPlan->containsDeferred()) {
826 533
            $id = $this->em->getIdentifierFlattener()->flattenIdentifier($class, $persister->getIdentifier($entity));
827
            $this->entityIdentifiers[$oid] = $id;
828
        }
829
830
        $this->entityStates[$oid] = self::STATE_MANAGED;
831
832 864
        $this->scheduleForInsert($entity);
833 864
    }
834
835 864
    /**
836 722
     * INTERNAL:
837 6
     * Computes the changeset of an individual entity, independently of the
838
     * computeChangeSets() routine that is used at the beginning of a UnitOfWork#commit().
839
     *
840 716
     * The passed entity must be a managed entity. If the entity already has a change set
841
     * because this method is invoked during a commit cycle then the change sets are added.
842 716
     * whereby changes detected in this method prevail.
843
     *
844
     * @ignore
845
     *
846
     * @param ClassMetadata $class  The class descriptor of the entity.
847 716
     * @param object        $entity The entity for which to (re)calculate the change set.
848 39
     *
849 4
     * @return void
850
     *
851
     * @throws ORMInvalidArgumentException If the passed entity is not MANAGED.
852 35
     * @throws \RuntimeException
853 35
     */
854 35
    public function recomputeSingleEntityChangeSet(ClassMetadata $class, $entity) : void
855
    {
856 710
        $oid = spl_object_hash($entity);
857
858
        if (! isset($this->entityStates[$oid]) || $this->entityStates[$oid] !== self::STATE_MANAGED) {
859 4
            throw ORMInvalidArgumentException::entityNotManaged($entity);
860 3
        }
861
862 4
        // skip if change tracking is "NOTIFY"
863
        if ($class->changeTrackingPolicy === ChangeTrackingPolicy::NOTIFY) {
864 710
            return;
865
        }
866
867
        if ($class->inheritanceType !== InheritanceType::NONE) {
868
            $class = $this->em->getClassMetadata(get_class($entity));
869
        }
870 713
871
        $actualData = [];
872
873
        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...
874
            switch (true) {
875 856
                case ($property instanceof VersionFieldMetadata):
876
                    // Ignore version field
877
                    break;
878
879
                case ($property instanceof FieldMetadata):
880
                    if (! $property->isPrimaryKey()
881
                        || ! $property->getValueGenerator()
882
                        || $property->getValueGenerator()->getType() !== GeneratorType::IDENTITY) {
883 1031
                        $actualData[$name] = $property->getValue($entity);
884
                    }
885 1031
886 1031
                    break;
887
888 1031
                case ($property instanceof ToOneAssociationMetadata):
889 139
                    $actualData[$name] = $property->getValue($entity);
890
                    break;
891
            }
892 1031
        }
893
894 1031
        if ( ! isset($this->originalEntityData[$oid])) {
895 269
            throw new \RuntimeException('Cannot call recomputeSingleEntityChangeSet before computeChangeSet on an entity.');
896
        }
897 269
898 1
        $originalData = $this->originalEntityData[$oid];
899
        $changeSet = [];
900 1
901
        foreach ($actualData as $propName => $actualValue) {
902
            $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null;
903 269
904
            if ($orgValue !== $actualValue) {
905
                $changeSet[$propName] = [$orgValue, $actualValue];
906 1031
            }
907
        }
908 1031
909 1031
        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...
910
            if (isset($this->entityChangeSets[$oid])) {
911
                $this->entityChangeSets[$oid] = array_merge($this->entityChangeSets[$oid], $changeSet);
912
            } else if ( ! isset($this->entityInsertions[$oid])) {
913
                $this->entityChangeSets[$oid] = $changeSet;
914
                $this->entityUpdates[$oid]    = $entity;
915
            }
916
            $this->originalEntityData[$oid] = $actualData;
917
        }
918
    }
919
920
    /**
921
     * Executes all entity insertions for entities of the specified type.
922
     *
923
     * @param ClassMetadata $class
924
     *
925
     * @return void
926
     */
927
    private function executeInserts(ClassMetadata $class) : void
928
    {
929 16
        $className      = $class->getClassName();
930
        $persister      = $this->getEntityPersister($className);
931 16
        $invoke         = $this->listenersInvoker->getSubscribedSystems($class, Events::postPersist);
932
        $generationPlan = $class->getValueGenerationPlan();
933 16
934
        foreach ($this->entityInsertions as $oid => $entity) {
935
            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...
936
                continue;
937
            }
938 16
939
            $persister->insert($entity);
940
941
            if ($generationPlan->containsDeferred()) {
942 16
                // Entity has post-insert IDs
943 3
                $oid = spl_object_hash($entity);
944
                $id  = $this->em->getIdentifierFlattener()->flattenIdentifier($class, $persister->getIdentifier($entity));
945
946 16
                $this->entityIdentifiers[$oid] = $id;
947
                $this->entityStates[$oid] = self::STATE_MANAGED;
948 16
                $this->originalEntityData[$oid] = $id + $this->originalEntityData[$oid];
949 16
950 16
                $this->addToIdentityMap($entity);
951 16
            }
952 16
953
            unset($this->entityInsertions[$oid]);
954
955
            if ($invoke !== ListenersInvoker::INVOKE_NONE) {
956 16
                $eventArgs = new LifecycleEventArgs($entity, $this->em);
957
958
                $this->listenersInvoker->invoke($class, Events::postPersist, $entity, $eventArgs, $invoke);
959
            }
960 16
        }
961 16
    }
962
963 16
    /**
964 16
     * Executes all entity updates for entities of the specified type.
965
     *
966 16
     * @param \Doctrine\ORM\Mapping\ClassMetadata $class
967 16
     *
968
     * @return void
969
     */
970
    private function executeUpdates($class)
971 16
    {
972 7
        $className          = $class->getClassName();
973 6
        $persister          = $this->getEntityPersister($className);
974 1
        $preUpdateInvoke    = $this->listenersInvoker->getSubscribedSystems($class, Events::preUpdate);
975 1
        $postUpdateInvoke   = $this->listenersInvoker->getSubscribedSystems($class, Events::postUpdate);
976 1
977
        foreach ($this->entityUpdates as $oid => $entity) {
978 7
            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...
979
                continue;
980 16
            }
981
982
            if ($preUpdateInvoke != ListenersInvoker::INVOKE_NONE) {
983
                $this->listenersInvoker->invoke($class, Events::preUpdate, $entity, new PreUpdateEventArgs($entity, $this->em, $this->getEntityChangeSet($entity)), $preUpdateInvoke);
984
985
                $this->recomputeSingleEntityChangeSet($class, $entity);
986
            }
987
988
            if ( ! empty($this->entityChangeSets[$oid])) {
989 1004
//                echo 'Update: ';
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
990
//                \Doctrine\Common\Util\Debug::dump($this->entityChangeSets[$oid], 3);
991 1004
992 1004
                $persister->update($entity);
993 1004
            }
994 1004
995
            unset($this->entityUpdates[$oid]);
996 1004
997
            if ($postUpdateInvoke != ListenersInvoker::INVOKE_NONE) {
998 1004
                $this->listenersInvoker->invoke($class, Events::postUpdate, $entity, new LifecycleEventArgs($entity, $this->em), $postUpdateInvoke);
999 855
            }
1000
        }
1001
    }
1002 1004
1003
    /**
1004 1004
     * Executes all entity deletions for entities of the specified type.
1005
     *
1006 1004
     * @param \Doctrine\ORM\Mapping\ClassMetadata $class
1007 1004
     *
1008
     * @return void
1009
     */
1010
    private function executeDeletions($class)
1011 1004
    {
1012
        $className  = $class->getClassName();
1013 1004
        $persister  = $this->getEntityPersister($className);
1014
        $invoke     = $this->listenersInvoker->getSubscribedSystems($class, Events::postRemove);
1015 918
1016 918
        foreach ($this->entityDeletions as $oid => $entity) {
1017 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...
1018 918
                continue;
1019 918
            }
1020
1021 918
            $persister->delete($entity);
1022
1023 918
            unset(
1024 918
                $this->entityDeletions[$oid],
1025 918
                $this->entityIdentifiers[$oid],
1026
                $this->originalEntityData[$oid],
1027 918
                $this->entityStates[$oid]
1028
            );
1029
1030
            // Entity with this $oid after deletion treated as NEW, even if the $oid
1031 1004
            // is obtained by a new entity because the old one went out of scope.
1032 135
            //$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...
1033
            if (! $class->isIdentifierComposite()) {
1034 1004
                $property = $class->getProperty($class->getSingleIdentifierFieldName());
1035
1036
                if ($property instanceof FieldMetadata && $property->hasValueGenerator()) {
1037
                    $property->setValue($entity, null);
1038
                }
1039
            }
1040
1041
            if ($invoke !== ListenersInvoker::INVOKE_NONE) {
1042
                $eventArgs = new LifecycleEventArgs($entity, $this->em);
1043 115
1044
                $this->listenersInvoker->invoke($class, Events::postRemove, $entity, $eventArgs, $invoke);
1045 115
            }
1046 115
        }
1047 115
    }
1048 115
1049
    /**
1050 115
     * Gets the commit order.
1051 115
     *
1052 74
     * @return array
1053
     */
1054
    private function getCommitOrder()
1055 115
    {
1056 13
        $calc = new Internal\CommitOrderCalculator();
1057
1058 13
        // See if there are any new classes in the changeset, that are not in the
1059
        // commit order graph yet (don't have a node).
1060
        // We have to inspect changeSet to be able to correctly build dependencies.
1061 115
        // It is not possible to use IdentityMap here because post inserted ids
1062
        // are not yet available.
1063
        $newNodes = [];
1064
1065 81
        foreach (\array_merge($this->entityInsertions, $this->entityUpdates, $this->entityDeletions) as $entity) {
1066
            $class = $this->em->getClassMetadata(get_class($entity));
1067
1068 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...
1069
                continue;
1070 111
            }
1071 111
1072
            $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...
1073
1074 111
            $newNodes[] = $class;
1075
        }
1076
1077
        // Calculate dependencies for new nodes
1078
        while ($class = array_pop($newNodes)) {
1079
            foreach ($class->getDeclaredPropertiesIterator() as $property) {
1080
                if (! ($property instanceof ToOneAssociationMetadata && $property->isOwningSide())) {
1081
                    continue;
1082
                }
1083 62
1084
                $targetClass = $this->em->getClassMetadata($property->getTargetEntity());
1085 62
1086 62
                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...
1087 62
                    $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...
1088
1089 62
                    $newNodes[] = $targetClass;
1090 62
                }
1091 26
1092
                $weight = ! array_filter(
1093
                    $property->getJoinColumns(),
1094 62
                    function (JoinColumnMetadata $joinColumn) { return $joinColumn->isNullable(); }
1095
                );
1096
1097 62
                $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...
1098 62
1099 62
                // If the target class has mapped subclasses, these share the same dependency.
1100 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...
1101
                    continue;
1102
                }
1103
1104
                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...
1105
                    $targetSubClass = $this->em->getClassMetadata($subClassName);
1106 62
1107 52
                    if ( ! $calc->hasNode($subClassName)) {
1108
                        $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...
1109
1110 62
                        $newNodes[] = $targetSubClass;
1111 62
                    }
1112
1113
                    $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...
1114 61
                }
1115
            }
1116
        }
1117
1118
        return $calc->sort();
1119
    }
1120
1121
    /**
1122
     * Schedules an entity for insertion into the database.
1123 1008
     * If the entity already has an identifier, it will be added to the identity map.
1124
     *
1125 1008
     * @param object $entity The entity to schedule for insertion.
1126 1008
     *
1127
     * @return void
1128
     *
1129 1008
     * @throws ORMInvalidArgumentException
1130
     * @throws \InvalidArgumentException
1131
     */
1132
    public function scheduleForInsert($entity)
1133
    {
1134
        $oid = spl_object_hash($entity);
1135
1136 1008
        if (isset($this->entityUpdates[$oid])) {
1137
            throw new InvalidArgumentException("Dirty entity can not be scheduled for insertion.");
1138 1008
        }
1139 1008
1140
        if (isset($this->entityDeletions[$oid])) {
1141 1008
            throw ORMInvalidArgumentException::scheduleInsertForRemovedEntity($entity);
1142 624
        }
1143
        if (isset($this->originalEntityData[$oid]) && ! isset($this->entityInsertions[$oid])) {
1144
            throw ORMInvalidArgumentException::scheduleInsertForManagedEntity($entity);
1145 1008
        }
1146
1147 1008
        if (isset($this->entityInsertions[$oid])) {
1148
            throw ORMInvalidArgumentException::scheduleInsertTwice($entity);
1149
        }
1150
1151 1008
        $this->entityInsertions[$oid] = $entity;
1152 1008
1153 886
        if (isset($this->entityIdentifiers[$oid])) {
1154 846
            $this->addToIdentityMap($entity);
1155
        }
1156
1157 839
        if ($entity instanceof NotifyPropertyChanged) {
1158
            $entity->addPropertyChangedListener($this);
1159 839
        }
1160 649
    }
1161
1162 649
    /**
1163
     * Checks whether an entity is scheduled for insertion.
1164
     *
1165 839
     * @param object $entity
1166
     *
1167 839
     * @return boolean
1168
     */
1169
    public function isScheduledForInsert($entity)
1170 839
    {
1171 832
        return isset($this->entityInsertions[spl_object_hash($entity)]);
1172
    }
1173
1174 217
    /**
1175 217
     * Schedules an entity for being updated.
1176
     *
1177 217
     * @param object $entity The entity to schedule for being updated.
1178 189
     *
1179
     * @return void
1180 189
     *
1181
     * @throws ORMInvalidArgumentException
1182
     */
1183 217
    public function scheduleForUpdate($entity) : void
1184
    {
1185
        $oid = spl_object_hash($entity);
1186
1187
        if ( ! isset($this->entityIdentifiers[$oid])) {
1188 1008
            throw ORMInvalidArgumentException::entityHasNoIdentity($entity, "scheduling for update");
1189
        }
1190
1191
        if (isset($this->entityDeletions[$oid])) {
1192
            throw ORMInvalidArgumentException::entityIsRemoved($entity, "schedule for update");
1193
        }
1194
1195
        if ( ! isset($this->entityUpdates[$oid]) && ! isset($this->entityInsertions[$oid])) {
1196
            $this->entityUpdates[$oid] = $entity;
1197
        }
1198
    }
1199
1200
    /**
1201
     * INTERNAL:
1202 1032
     * Schedules an extra update that will be executed immediately after the
1203
     * regular entity updates within the currently running commit cycle.
1204 1032
     *
1205
     * Extra updates for entities are stored as (entity, changeset) tuples.
1206 1032
     *
1207
     * @ignore
1208
     *
1209
     * @param object $entity    The entity for which to schedule an extra update.
1210 1032
     * @param array  $changeset The changeset of the entity (what to update).
1211 1
     *
1212
     * @return void
1213 1032
     */
1214 1
    public function scheduleExtraUpdate($entity, array $changeset) : void
1215
    {
1216
        $oid         = spl_object_hash($entity);
1217 1032
        $extraUpdate = [$entity, $changeset];
1218 1
1219
        if (isset($this->extraUpdates[$oid])) {
1220
            [$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...
1221 1032
1222
            $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...
1223 1032
        }
1224 269
1225
        $this->extraUpdates[$oid] = $extraUpdate;
1226
    }
1227 1032
1228 5
    /**
1229
     * Checks whether an entity is registered as dirty in the unit of work.
1230 1032
     * Note: Is not very useful currently as dirty entities are only registered
1231
     * at commit time.
1232
     *
1233
     * @param object $entity
1234
     *
1235
     * @return boolean
1236
     */
1237
    public function isScheduledForUpdate($entity) : bool
1238
    {
1239 631
        return isset($this->entityUpdates[spl_object_hash($entity)]);
1240
    }
1241 631
1242
    /**
1243
     * Checks whether an entity is registered to be checked in the unit of work.
1244
     *
1245
     * @param object $entity
1246
     *
1247
     * @return boolean
1248
     */
1249
    public function isScheduledForDirtyCheck($entity) : bool
1250
    {
1251
        $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...
1252
1253 1
        return isset($this->scheduledForSynchronization[$rootEntityName][spl_object_hash($entity)]);
1254
    }
1255 1
1256
    /**
1257 1
     * INTERNAL:
1258
     * Schedules an entity for deletion.
1259
     *
1260
     * @param object $entity
1261 1
     *
1262
     * @return void
1263
     */
1264
    public function scheduleForDelete($entity)
1265 1
    {
1266 1
        $oid = spl_object_hash($entity);
1267
1268 1
        if (isset($this->entityInsertions[$oid])) {
1269
            if ($this->isInIdentityMap($entity)) {
1270
                $this->removeFromIdentityMap($entity);
1271
            }
1272
1273
            unset($this->entityInsertions[$oid], $this->entityStates[$oid]);
1274
1275
            return; // entity has not been persisted yet, so nothing more to do.
1276
        }
1277
1278
        if ( ! $this->isInIdentityMap($entity)) {
1279
            return;
1280
        }
1281
1282
        $this->removeFromIdentityMap($entity);
1283
1284 40
        unset($this->entityUpdates[$oid]);
1285
1286 40
        if ( ! isset($this->entityDeletions[$oid])) {
1287 40
            $this->entityDeletions[$oid] = $entity;
1288
            $this->entityStates[$oid]    = self::STATE_REMOVED;
1289 40
        }
1290 1
    }
1291
1292 1
    /**
1293
     * Checks whether an entity is registered as removed/deleted with the unit
1294
     * of work.
1295 40
     *
1296 40
     * @param object $entity
1297
     *
1298
     * @return boolean
1299
     */
1300
    public function isScheduledForDelete($entity)
1301
    {
1302
        return isset($this->entityDeletions[spl_object_hash($entity)]);
1303
    }
1304
1305
    /**
1306
     * Checks whether an entity is scheduled for insertion, update or deletion.
1307
     *
1308
     * @param object $entity
1309
     *
1310
     * @return boolean
1311
     */
1312
    public function isEntityScheduled($entity)
1313
    {
1314
        $oid = spl_object_hash($entity);
1315
1316
        return isset($this->entityInsertions[$oid])
1317
            || isset($this->entityUpdates[$oid])
1318
            || isset($this->entityDeletions[$oid]);
1319 1
    }
1320
1321 1
    /**
1322
     * INTERNAL:
1323 1
     * Registers an entity in the identity map.
1324
     * Note that entities in a hierarchy are registered with the class name of
1325
     * the root entity.
1326
     *
1327
     * @ignore
1328
     *
1329
     * @param object $entity The entity to register.
1330
     *
1331
     * @return boolean TRUE if the registration was successful, FALSE if the identity of
1332
     *                 the entity in question is already managed.
1333
     *
1334 65
     * @throws ORMInvalidArgumentException
1335
     */
1336 65
    public function addToIdentityMap($entity)
1337
    {
1338 65
        $classMetadata = $this->em->getClassMetadata(get_class($entity));
1339 1
        $identifier    = $this->entityIdentifiers[spl_object_hash($entity)];
1340
1341
        if (empty($identifier) || in_array(null, $identifier, true)) {
1342
            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...
1343 1
        }
1344
1345 1
        $idHash    = implode(' ', $identifier);
1346
        $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...
1347
1348 65
        if (isset($this->identityMap[$className][$idHash])) {
1349 1
            return false;
1350
        }
1351
1352 64
        $this->identityMap[$className][$idHash] = $entity;
1353
1354 64
        return true;
1355
    }
1356 64
1357 64
    /**
1358 64
     * Gets the state of an entity with regard to the current unit of work.
1359
     *
1360 64
     * @param object   $entity
1361
     * @param int|null $assume The state to assume if the state is not yet known (not MANAGED or REMOVED).
1362
     *                         This parameter can be set to improve performance of entity state detection
1363
     *                         by potentially avoiding a database lookup if the distinction between NEW and DETACHED
1364
     *                         is either known or does not matter for the caller of the method.
1365
     *
1366
     * @return int The entity state.
1367
     */
1368
    public function getEntityState($entity, $assume = null)
1369
    {
1370 17
        $oid = spl_object_hash($entity);
1371
1372 17
        if (isset($this->entityStates[$oid])) {
1373
            return $this->entityStates[$oid];
1374
        }
1375
1376
        if ($assume !== null) {
1377
            return $assume;
1378
        }
1379
1380
        // State can only be NEW or DETACHED, because MANAGED/REMOVED states are known.
1381
        // Note that you can not remember the NEW or DETACHED state in _entityStates since
1382
        // the UoW does not hold references to such objects and the object hash can be reused.
1383
        // More generally because the state may "change" between NEW/DETACHED without the UoW being aware of it.
1384
        $class     = $this->em->getClassMetadata(get_class($entity));
1385
        $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...
1386
        $id        = $persister->getIdentifier($entity);
1387
1388
        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...
1389
            return self::STATE_NEW;
1390
        }
1391
1392
        $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...
1393
1394
        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...
1395
            || ! $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...
1396
            || ! $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...
1397
        ) {
1398
            // Check for a version field, if available, to avoid a db lookup.
1399
            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...
1400
                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...
1401
                    ? self::STATE_DETACHED
1402
                    : self::STATE_NEW;
1403
            }
1404
1405
            // Last try before db lookup: check the identity map.
1406 1098
            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...
1407
                return self::STATE_DETACHED;
1408 1098
            }
1409 1098
1410
            // db lookup
1411 1098
            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...
1412 6
                return self::STATE_DETACHED;
1413
            }
1414
1415 1092
            return self::STATE_NEW;
1416 1092
        }
1417
1418 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...
1419 83
            || ! $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...
1420
            || ! $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...
1421
            // if we have a pre insert generator we can't be sure that having an id
1422 1092
            // really means that the entity exists. We have to verify this through
1423
            // the last resort: a db lookup
1424 1092
1425
            // Last try before db lookup: check the identity map.
1426
            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...
1427
                return self::STATE_DETACHED;
1428
            }
1429
1430
            // db lookup
1431
            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...
1432
                return self::STATE_DETACHED;
1433
            }
1434
1435
            return self::STATE_NEW;
1436
        }
1437
1438 1045
        return self::STATE_DETACHED;
1439
    }
1440 1045
1441
    /**
1442 1045
     * INTERNAL:
1443 783
     * Removes an entity from the identity map. This effectively detaches the
1444
     * entity from the persistence management of Doctrine.
1445
     *
1446 1039
     * @ignore
1447 1035
     *
1448
     * @param object $entity
1449
     *
1450
     * @return boolean
1451
     *
1452
     * @throws ORMInvalidArgumentException
1453
     */
1454 13
    public function removeFromIdentityMap($entity)
1455 13
    {
1456
        $oid           = spl_object_hash($entity);
1457 13
        $classMetadata = $this->em->getClassMetadata(get_class($entity));
1458 5
        $idHash        = implode(' ', $this->entityIdentifiers[$oid]);
1459
1460
        if ($idHash === '') {
1461 10
            throw ORMInvalidArgumentException::entityHasNoIdentity($entity, "remove from identity map");
1462 1
        }
1463
1464
        $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...
1465
1466 10
        if (isset($this->identityMap[$className][$idHash])) {
1467
            unset($this->identityMap[$className][$idHash]);
1468 5
            unset($this->readOnlyObjects[$oid]);
1469 1
1470
            //$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...
1471 1
1472
            return true;
1473
        }
1474
1475 4
        return false;
1476 1
    }
1477
1478
    /**
1479
     * INTERNAL:
1480 4
     * Gets an entity in the identity map by its identifier hash.
1481
     *
1482
     * @ignore
1483
     *
1484 4
     * @param string $idHash
1485
     * @param string $rootClassName
1486 5
     *
1487
     * @return object
1488
     */
1489
    public function getByIdHash($idHash, $rootClassName)
1490
    {
1491
        return $this->identityMap[$rootClassName][$idHash];
1492
    }
1493
1494
    /**
1495
     * INTERNAL:
1496
     * Tries to get an entity by its identifier hash. If no entity is found for
1497
     * the given hash, FALSE is returned.
1498
     *
1499
     * @ignore
1500
     *
1501
     * @param mixed  $idHash        (must be possible to cast it to string)
1502
     * @param string $rootClassName
1503
     *
1504 5
     * @return object|bool The found entity or FALSE.
1505
     */
1506
    public function tryGetByIdHash($idHash, $rootClassName)
1507
    {
1508
        $stringIdHash = (string) $idHash;
1509
1510
        return isset($this->identityMap[$rootClassName][$stringIdHash])
1511
            ? $this->identityMap[$rootClassName][$stringIdHash]
1512
            : false;
1513
    }
1514
1515
    /**
1516
     * Checks whether an entity is registered in the identity map of this UnitOfWork.
1517
     *
1518
     * @param object $entity
1519
     *
1520
     * @return boolean
1521 76
     */
1522
    public function isInIdentityMap($entity)
1523 76
    {
1524 76
        $oid = spl_object_hash($entity);
1525 76
1526
        if (empty($this->entityIdentifiers[$oid])) {
1527 76
            return false;
1528
        }
1529
1530
        $classMetadata = $this->em->getClassMetadata(get_class($entity));
1531 76
        $idHash        = implode(' ', $this->entityIdentifiers[$oid]);
1532
1533 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...
1534 76
    }
1535 76
1536
    /**
1537
     * INTERNAL:
1538
     * Checks whether an identifier hash exists in the identity map.
1539 76
     *
1540
     * @ignore
1541
     *
1542
     * @param string $idHash
1543
     * @param string $rootClassName
1544
     *
1545
     * @return boolean
1546
     */
1547
    public function containsIdHash($idHash, $rootClassName)
1548
    {
1549
        return isset($this->identityMap[$rootClassName][$idHash]);
1550
    }
1551
1552
    /**
1553
     * Persists an entity as part of the current unit of work.
1554
     *
1555
     * @param object $entity The entity to persist.
1556 6
     *
1557
     * @return void
1558 6
     */
1559
    public function persist($entity)
1560
    {
1561
        $visited = [];
1562
1563
        $this->doPersist($entity, $visited);
1564
    }
1565
1566
    /**
1567
     * Persists an entity as part of the current unit of work.
1568
     *
1569
     * This method is internally called during persist() cascades as it tracks
1570
     * the already visited entities to prevent infinite recursions.
1571
     *
1572
     * @param object $entity  The entity to persist.
1573 34
     * @param array  $visited The already visited entities.
1574
     *
1575 34
     * @return void
1576
     *
1577 34
     * @throws ORMInvalidArgumentException
1578 34
     * @throws UnexpectedValueException
1579 34
     */
1580
    private function doPersist($entity, array &$visited)
1581
    {
1582
        $oid = spl_object_hash($entity);
1583
1584
        if (isset($visited[$oid])) {
1585
            return; // Prevent infinite recursion
1586
        }
1587
1588
        $visited[$oid] = $entity; // Mark visited
1589 212
1590
        $class = $this->em->getClassMetadata(get_class($entity));
1591 212
1592
        // We assume NEW, so DETACHED entities result in an exception on flush (constraint violation).
1593 212
        // If we would detect DETACHED here we would throw an exception anyway with the same
1594 31
        // consequences (not recoverable/programming error), so just assuming NEW here
1595
        // lets us avoid some database lookups for entities with natural identifiers.
1596
        $entityState = $this->getEntityState($entity, self::STATE_NEW);
1597 197
1598 197
        switch ($entityState) {
1599
            case self::STATE_MANAGED:
1600 197
                // Nothing to do, except if policy is "deferred explicit"
1601
                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...
1602
                    $this->scheduleForSynchronization($entity);
1603
                }
1604 197
                break;
1605
1606
            case self::STATE_NEW:
1607
                $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...
1608
                break;
1609
1610
            case self::STATE_REMOVED:
1611
                // Entity becomes managed again
1612
                unset($this->entityDeletions[$oid]);
1613
                $this->addToIdentityMap($entity);
1614
1615
                $this->entityStates[$oid] = self::STATE_MANAGED;
1616
                break;
1617
1618
            case self::STATE_DETACHED:
1619
                // Can actually not happen right now since we assume STATE_NEW.
1620
                throw ORMInvalidArgumentException::detachedEntityCannot($entity, "persisted");
1621
1622
            default:
1623
                throw new UnexpectedValueException("Unexpected entity state: $entityState." . self::objToStr($entity));
1624
        }
1625
1626
        $this->cascadePersist($entity, $visited);
1627
    }
1628
1629
    /**
1630 1028
     * Deletes an entity as part of the current unit of work.
1631
     *
1632 1028
     * @param object $entity The entity to remove.
1633
     *
1634 1028
     * @return void
1635 1021
     */
1636
    public function remove($entity)
1637
    {
1638
        $visited = [];
1639
1640
        $this->doRemove($entity, $visited);
1641
    }
1642
1643
    /**
1644
     * Deletes an entity as part of the current unit of work.
1645
     *
1646
     * This method is internally called during delete() cascades as it tracks
1647
     * the already visited entities to prevent infinite recursions.
1648
     *
1649
     * @param object $entity  The entity to delete.
1650
     * @param array  $visited The map of the already visited entities.
1651 1028
     *
1652
     * @return void
1653 1028
     *
1654
     * @throws ORMInvalidArgumentException If the instance is a detached entity.
1655 1028
     * @throws UnexpectedValueException
1656 109
     */
1657
    private function doRemove($entity, array &$visited)
1658
    {
1659 1028
        $oid = spl_object_hash($entity);
1660
1661 1028
        if (isset($visited[$oid])) {
1662
            return; // Prevent infinite recursion
1663
        }
1664
1665
        $visited[$oid] = $entity; // mark visited
1666
1667 1028
        // Cascade first, because scheduleForDelete() removes the entity from the identity map, which
1668
        // can cause problems when a lazy proxy has to be initialized for the cascade operation.
1669
        $this->cascadeRemove($entity, $visited);
1670 1028
1671
        $class       = $this->em->getClassMetadata(get_class($entity));
1672 234
        $entityState = $this->getEntityState($entity);
1673 2
1674
        switch ($entityState) {
1675 234
            case self::STATE_NEW:
1676
            case self::STATE_REMOVED:
1677 1028
                // nothing to do
1678 1027
                break;
1679 1027
1680
            case self::STATE_MANAGED:
1681 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...
1682
1683 1
                if ($invoke !== ListenersInvoker::INVOKE_NONE) {
1684 1
                    $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...
1685
                }
1686 1
1687 1
                $this->scheduleForDelete($entity);
1688
                break;
1689
1690
            case self::STATE_DETACHED:
1691
                throw ORMInvalidArgumentException::detachedEntityCannot($entity, "removed");
1692
            default:
1693
                throw new UnexpectedValueException("Unexpected entity state: $entityState." . self::objToStr($entity));
1694
        }
1695
1696
    }
1697 1028
1698 1021
    /**
1699
     * Refreshes the state of the given entity from the database, overwriting
1700
     * any local, unpersisted changes.
1701
     *
1702
     * @param object $entity The entity to refresh.
1703
     *
1704
     * @return void
1705
     *
1706
     * @throws InvalidArgumentException If the entity is not MANAGED.
1707 64
     */
1708
    public function refresh($entity)
1709 64
    {
1710
        $visited = [];
1711 64
1712 64
        $this->doRefresh($entity, $visited);
1713
    }
1714
1715
    /**
1716
     * Executes a refresh operation on an entity.
1717
     *
1718
     * @param object $entity  The entity to refresh.
1719
     * @param array  $visited The already visited entities during cascades.
1720
     *
1721
     * @return void
1722
     *
1723
     * @throws ORMInvalidArgumentException If the entity is not MANAGED.
1724
     */
1725
    private function doRefresh($entity, array &$visited)
1726
    {
1727
        $oid = spl_object_hash($entity);
1728 64
1729
        if (isset($visited[$oid])) {
1730 64
            return; // Prevent infinite recursion
1731
        }
1732 64
1733 1
        $visited[$oid] = $entity; // mark visited
1734
1735
        $class = $this->em->getClassMetadata(get_class($entity));
1736 64
1737
        if ($this->getEntityState($entity) !== self::STATE_MANAGED) {
1738
            throw ORMInvalidArgumentException::entityNotManaged($entity);
1739
        }
1740 64
1741
        $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...
1742 64
            array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
1743 64
            $entity
1744
        );
1745
1746 64
        $this->cascadeRefresh($entity, $visited);
1747 64
    }
1748
1749 2
    /**
1750
     * Cascades a refresh operation to associated entities.
1751 64
     *
1752 64
     * @param object $entity
1753
     * @param array  $visited
1754 64
     *
1755 8
     * @return void
1756
     */
1757
    private function cascadeRefresh($entity, array &$visited)
1758 64
    {
1759 64
        $class = $this->em->getClassMetadata(get_class($entity));
1760
1761
        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...
1762
            if (! ($association instanceof AssociationMetadata && in_array('refresh', $association->getCascade()))) {
1763
                continue;
1764
            }
1765
1766
            $relatedEntities = $association->getValue($entity);
1767 64
1768
            switch (true) {
1769
                case ($relatedEntities instanceof PersistentCollection):
1770
                    // Unwrap so that foreach() does not initialize
1771
                    $relatedEntities = $relatedEntities->unwrap();
1772
                    // break; is commented intentionally!
1773
1774
                case ($relatedEntities instanceof Collection):
1775
                case (is_array($relatedEntities)):
1776
                    foreach ($relatedEntities as $relatedEntity) {
1777
                        $this->doRefresh($relatedEntity, $visited);
1778
                    }
1779
                    break;
1780
1781 40
                case ($relatedEntities !== null):
1782
                    $this->doRefresh($relatedEntities, $visited);
1783 40
                    break;
1784
1785 40
                default:
1786
                    // Do nothing
1787
            }
1788
        }
1789
    }
1790
1791
    /**
1792
     * Cascades the save operation to associated entities.
1793
     *
1794
     * @param object $entity
1795
     * @param array  $visited
1796
     *
1797
     * @return void
1798
     */
1799
    private function cascadePersist($entity, array &$visited)
1800
    {
1801
        $class = $this->em->getClassMetadata(get_class($entity));
1802
1803 40
        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...
1804
            if (! ($association instanceof AssociationMetadata && in_array('persist', $association->getCascade()))) {
1805 40
                continue;
1806
            }
1807 40
1808 4
            /** @var AssociationMetadata $association */
1809
            $relatedEntities = $association->getValue($entity);
1810 4
            $targetEntity    = $association->getTargetEntity();
1811 4
1812
            switch (true) {
1813
                case ($relatedEntities instanceof PersistentCollection):
1814 4
                    // Unwrap so that foreach() does not initialize
1815
                    $relatedEntities = $relatedEntities->unwrap();
1816
                    // break; is commented intentionally!
1817 40
1818
                case ($relatedEntities instanceof Collection):
1819
                case (is_array($relatedEntities)):
1820
                    if (! ($association instanceof ToManyAssociationMetadata)) {
1821
                        throw ORMInvalidArgumentException::invalidAssociation(
1822
                            $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...
1823 40
                            $association,
1824
                            $relatedEntities
1825 40
                        );
1826
                    }
1827 39
1828
                    foreach ($relatedEntities as $relatedEntity) {
1829
                        $this->doPersist($relatedEntity, $visited);
1830 39
                    }
1831 5
1832
                    break;
1833 5
1834
                case ($relatedEntities !== null):
1835 35
                    if (! $relatedEntities instanceof $targetEntity) {
1836 3
                        throw ORMInvalidArgumentException::invalidAssociation(
1837 35
                            $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...
1838
                            $association,
1839 35
                            $relatedEntities
1840
                        );
1841 35
                    }
1842
1843 14
                    $this->doPersist($relatedEntities, $visited);
1844 14
                    break;
1845
1846
                default:
1847
                    // Do nothing
1848 24
            }
1849
        }
1850
    }
1851 35
1852
    /**
1853
     * Cascades the delete operation to associated entities.
1854 2
     *
1855 1
     * @param object $entity
1856 1
     * @param array  $visited
1857 1
     *
1858
     * @return void
1859
     */
1860
    private function cascadeRemove($entity, array &$visited)
1861 1
    {
1862 1
        $entitiesToCascade = [];
1863
        $class             = $this->em->getClassMetadata(get_class($entity));
1864 1
1865
        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...
1866
            if (! ($association instanceof AssociationMetadata && in_array('remove', $association->getCascade()))) {
1867
                continue;
1868 38
            }
1869 4
1870 4
            if ($entity instanceof Proxy && ! $entity->__isInitialized()) {
1871
                $entity->__load();
1872
            }
1873 4
1874 1
            $relatedEntities = $association->getValue($entity);
1875
1876
            switch (true) {
1877
                case ($relatedEntities instanceof Collection):
1878 37
                case (is_array($relatedEntities)):
1879
                    // If its a PersistentCollection initialization is intended! No unwrap!
1880 37
                    foreach ($relatedEntities as $relatedEntity) {
1881 30
                        $entitiesToCascade[] = $relatedEntity;
1882 4
                    }
1883
                    break;
1884
1885 30
                case ($relatedEntities !== null):
1886
                    $entitiesToCascade[] = $relatedEntities;
1887
                    break;
1888 37
1889
                default:
1890
                    // Do nothing
1891
            }
1892
        }
1893 38
1894 6
        foreach ($entitiesToCascade as $relatedEntity) {
1895
            $this->doRemove($relatedEntity, $visited);
1896
        }
1897
    }
1898 38
1899
    /**
1900 38
     * Acquire a lock on the given entity.
1901
     *
1902 38
     * @param object $entity
1903
     * @param int    $lockMode
1904
     * @param int    $lockVersion
1905
     *
1906
     * @return void
1907
     *
1908
     * @throws ORMInvalidArgumentException
1909
     * @throws TransactionRequiredException
1910
     * @throws OptimisticLockException
1911
     */
1912 38
    public function lock($entity, $lockMode, $lockVersion = null)
1913
    {
1914 38
        if ($entity === null) {
1915
            throw new \InvalidArgumentException("No entity passed to UnitOfWork#lock().");
1916
        }
1917
1918
        if ($this->getEntityState($entity, self::STATE_DETACHED) != self::STATE_MANAGED) {
1919
            throw ORMInvalidArgumentException::entityNotManaged($entity);
1920
        }
1921
1922
        $class = $this->em->getClassMetadata(get_class($entity));
1923
1924
        switch (true) {
1925
            case LockMode::OPTIMISTIC === $lockMode:
1926
                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...
1927 6
                    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...
1928
                }
1929 6
1930 6
                if ($lockVersion === null) {
1931
                    return;
1932 6
                }
1933 6
1934
                if ($entity instanceof Proxy && ! $entity->__isInitialized()) {
1935 6
                    $entity->__load();
1936
                }
1937
1938
                $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...
1939 1
1940 1
                if ($entityVersion != $lockVersion) {
1941
                    throw OptimisticLockException::lockFailedVersionMismatch($entity, $lockVersion, $entityVersion);
1942 1
                }
1943 1
1944
                break;
1945 1
1946
            case LockMode::NONE === $lockMode:
1947 1
            case LockMode::PESSIMISTIC_READ === $lockMode:
1948
            case LockMode::PESSIMISTIC_WRITE === $lockMode:
1949
                if (!$this->em->getConnection()->isTransactionActive()) {
1950
                    throw TransactionRequiredException::transactionRequired();
1951
                }
1952
1953
                $oid = spl_object_hash($entity);
1954
1955
                $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...
1956
                    array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
1957 12
                    $lockMode
1958
                );
1959 12
                break;
1960
1961 12
            default:
1962 12
                // Do nothing
1963
        }
1964
    }
1965
1966
    /**
1967
     * Clears the UnitOfWork.
1968
     *
1969
     * @return void
1970
     */
1971
    public function clear()
1972
    {
1973 15
        $this->entityPersisters =
1974
        $this->collectionPersisters =
1975 15
        $this->eagerLoadingEntities =
1976
        $this->identityMap =
1977 15
        $this->entityIdentifiers =
1978
        $this->originalEntityData =
1979
        $this->entityChangeSets =
1980
        $this->entityStates =
1981 15
        $this->scheduledForSynchronization =
1982
        $this->entityInsertions =
1983 15
        $this->entityUpdates =
1984 15
        $this->entityDeletions =
1985 13
        $this->collectionDeletions =
1986 12
        $this->collectionUpdates =
1987
        $this->extraUpdates =
1988
        $this->readOnlyObjects =
1989
        $this->visitedCollections =
1990 13
        $this->orphanRemovals = [];
1991 13
1992 13
        if ($this->eventManager->hasListeners(Events::onClear)) {
1993 13
            $this->eventManager->dispatchEvent(Events::onClear, new Event\OnClearEventArgs($this->em));
1994 13
        }
1995 13
    }
1996
1997 13
    /**
1998 3
     * INTERNAL:
1999 3
     * Schedules an orphaned entity for removal. The remove() operation will be
2000 3
     * invoked on that entity at the beginning of the next commit of this
2001
     * UnitOfWork.
2002
     *
2003 13
     * @ignore
2004 13
     *
2005
     * @param object $entity
2006 13
     *
2007
     * @return void
2008
     */
2009
    public function scheduleOrphanRemoval($entity)
2010
    {
2011
        $this->orphanRemovals[spl_object_hash($entity)] = $entity;
2012
    }
2013
2014
    /**
2015
     * INTERNAL:
2016
     * Cancels a previously scheduled orphan removal.
2017
     *
2018 16
     * @ignore
2019
     *
2020 16
     * @param object $entity
2021
     *
2022 16
     * @return void
2023 16
     */
2024
    public function cancelOrphanRemoval($entity)
2025
    {
2026
        unset($this->orphanRemovals[spl_object_hash($entity)]);
2027
    }
2028
2029
    /**
2030
     * INTERNAL:
2031
     * Schedules a complete collection for removal when this UnitOfWork commits.
2032
     *
2033
     * @param PersistentCollection $coll
2034
     *
2035 16
     * @return void
2036
     */
2037 16
    public function scheduleCollectionDeletion(PersistentCollection $coll)
2038
    {
2039 16
        $coid = spl_object_hash($coll);
2040
2041
        // TODO: if $coll is already scheduled for recreation ... what to do?
2042
        // Just remove $coll from the scheduled recreations?
2043 16
        unset($this->collectionUpdates[$coid]);
2044
2045 16
        $this->collectionDeletions[$coid] = $coll;
2046
    }
2047 16
2048
    /**
2049
     * @param PersistentCollection $coll
2050
     *
2051 16
     * @return bool
2052 16
     */
2053
    public function isCollectionScheduledForDeletion(PersistentCollection $coll)
2054
    {
2055
        return isset($this->collectionDeletions[spl_object_hash($coll)]);
2056 16
    }
2057 16
2058
    /**
2059
     * INTERNAL:
2060
     * Creates a new instance of the mapped class, without invoking the constructor.
2061
     * This is only meant to be used internally, and should not be consumed by end users.
2062
     *
2063
     * @ignore
2064
     *
2065
     * @param ClassMetadata $class
2066
     *
2067 16
     * @return EntityManagerAware|object
2068
     */
2069 16
    public function newInstance(ClassMetadata $class)
2070
    {
2071 16
        $entity = $this->instantiator->instantiate($class->getClassName());
2072 16
2073
        if ($entity instanceof EntityManagerAware) {
2074
            $entity->injectEntityManager($this->em, $class);
2075
        }
2076 16
2077 5
        return $entity;
2078
    }
2079
2080 5
    /**
2081
     * INTERNAL:
2082 5
     * Creates an entity. Used for reconstitution of persistent entities.
2083
     *
2084
     * Internal note: Highly performance-sensitive method.
2085
     *
2086
     * @ignore
2087 5
     *
2088
     * @param string $className The name of the entity class.
2089
     * @param array  $data      The data for the entity.
2090 5
     * @param array  $hints     Any hints to account for during reconstitution/lookup of the entity.
2091
     *
2092
     * @return object The managed entity instance.
2093
     *
2094
     * @todo Rename: getOrCreateEntity
2095
     */
2096 5
    public function createEntity($className, array $data, &$hints = [])
2097
    {
2098
        $class  = $this->em->getClassMetadata($className);
2099
        $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...
2100 16
        $idHash = implode(' ', $id);
2101
        //$isReadOnly = isset($hints[Query::HINT_READ_ONLY]);
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% 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...
2102
2103
        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...
2104
            $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...
2105
            $oid = spl_object_hash($entity);
2106
2107
            if (
2108
                isset($hints[Query::HINT_REFRESH])
2109
                && isset($hints[Query::HINT_REFRESH_ENTITY])
2110 13
                && ($unmanagedProxy = $hints[Query::HINT_REFRESH_ENTITY]) !== $entity
2111
                && $unmanagedProxy instanceof Proxy
2112 13
                && $this->isIdentifierEquals($unmanagedProxy, $entity)
2113
            ) {
2114 13
                // DDC-1238 - we have a managed instance, but it isn't the provided one.
2115 13
                // Therefore we clear its identifier. Also, we must re-fetch metadata since the
2116
                // refreshed object may be anything
2117
                foreach ($class->identifier as $fieldName) {
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...
2118
                    $property = $class->getProperty($fieldName);
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...
2119 13
2120 3
                    $property->setValue($unmanagedProxy, null);
2121
                }
2122
2123 3
                return $unmanagedProxy;
2124
            }
2125 2
2126
            if ($entity instanceof Proxy && ! $entity->__isInitialized()) {
2127
                $entity->__setInitialized(true);
2128
2129
                $overrideLocalValues = true;
2130 3
2131 1
                if ($entity instanceof NotifyPropertyChanged) {
2132
                    $entity->addPropertyChangedListener($this);
2133 3
                }
2134
            } else {
2135
                $overrideLocalValues = isset($hints[Query::HINT_REFRESH]);
2136
2137
                // If only a specific entity is set to refresh, check that it's the one
2138
                if (isset($hints[Query::HINT_REFRESH_ENTITY])) {
2139 3
                    $overrideLocalValues = $hints[Query::HINT_REFRESH_ENTITY] === $entity;
2140
                }
2141
            }
2142
2143 13
            if ($overrideLocalValues) {
2144
                // inject EntityManager upon refresh.
2145
                if ($entity instanceof EntityManagerAware) {
2146
                    $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...
2147
                }
2148
2149
                $this->originalEntityData[$oid] = $data;
2150
            }
2151
        } else {
2152
            $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...
2153
            $oid    = spl_object_hash($entity);
2154 38
2155
            $this->entityIdentifiers[$oid]  = $id;
2156 38
            $this->entityStates[$oid]       = self::STATE_MANAGED;
2157
            $this->originalEntityData[$oid] = $data;
2158 38
2159 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...
2160
2161
            if ($entity instanceof NotifyPropertyChanged) {
2162
                $entity->addPropertyChangedListener($this);
2163 38
            }
2164 15
2165
            $overrideLocalValues = true;
2166 15
        }
2167 9
2168 1
        if ( ! $overrideLocalValues) {
2169
            return $entity;
2170
        }
2171 8
2172
        foreach ($data as $field => $value) {
2173 5
            $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...
2174
2175
            if ($property instanceof FieldMetadata) {
2176 8
                $property->setValue($entity, $value);
2177 8
            }
2178
        }
2179 7
2180 14
        // Loading the entity right here, if its in the eager loading map get rid of it there.
2181
        unset($this->eagerLoadingEntities[$class->getRootClassName()][$idHash]);
2182
2183 38
        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...
2184
            unset($this->eagerLoadingEntities[$class->getRootClassName()]);
2185
        }
2186
2187
        // Properly initialize any unfetched associations, if partial objects are not allowed.
2188
        if (isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) {
2189
            return $entity;
2190
        }
2191
2192
        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...
2193 1028
            if (! ($association instanceof AssociationMetadata)) {
2194
                continue;
2195 1028
            }
2196
2197 1028
            // Check if the association is not among the fetch-joined associations already.
2198 1028
            if (isset($hints['fetchAlias']) && isset($hints['fetched'][$hints['fetchAlias']][$field])) {
2199
                continue;
2200
            }
2201
2202 1028
            $targetEntity = $association->getTargetEntity();
2203 650
            $targetClass  = $this->em->getClassMetadata($targetEntity);
2204
2205
            if ($association instanceof ToManyAssociationMetadata) {
2206 650
                // Ignore if its a cached collection
2207
                if (isset($hints[Query::HINT_CACHE_ENABLED]) &&
2208 21
                    $association->getValue($entity) instanceof PersistentCollection) {
2209
                    continue;
2210
                }
2211
2212 589
                $hasDataField = isset($data[$field]);
2213 554
2214 3
                // use the given collection
2215 3
                if ($hasDataField && $data[$field] instanceof PersistentCollection) {
2216
                    $data[$field]->setOwner($entity, $association);
2217
2218
                    $association->setValue($entity, $data[$field]);
2219
2220
                    $this->originalEntityData[$oid][$field] = $data[$field];
2221 551
2222 282
                    continue;
2223
                }
2224
2225 551
                // Inject collection
2226
                $pColl = $association->wrap($entity, $hasDataField ? $data[$field] : [], $this->em);
2227 579
2228 246
                $pColl->setInitialized($hasDataField);
2229 4
2230 4
                $association->setValue($entity, $pColl);
2231
2232
                if ($association->getFetchMode() === FetchMode::EAGER) {
2233
                    $this->loadCollection($pColl);
2234
                    $pColl->takeSnapshot();
2235
                }
2236 242
2237 242
                $this->originalEntityData[$oid][$field] = $pColl;
2238
2239 644
                continue;
2240
            }
2241
2242
            if (! $association->isOwningSide()) {
2243 1021
                // use the given entity association
2244
                if (isset($data[$field]) && is_object($data[$field]) &&
2245
                    isset($this->entityStates[spl_object_hash($data[$field])])) {
2246
                    $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...
2247
2248
                    $association->setValue($entity, $data[$field]);
2249
                    $inverseAssociation->setValue($data[$field], $entity);
2250
2251
                    $this->originalEntityData[$oid][$field] = $data[$field];
2252
2253 64
                    continue;
2254
                }
2255 64
2256
                // Inverse side of x-to-one can never be lazy
2257 64
                $persister = $this->getEntityPersister($targetEntity);
2258 64
2259
                $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...
2260
2261
                continue;
2262 64
            }
2263
2264 64
            // use the entity association
2265 26
            if (isset($data[$field]) && is_object($data[$field]) && isset($this->entityStates[spl_object_hash($data[$field])])) {
2266 6
                $association->setValue($entity, $data[$field]);
2267
2268
                $this->originalEntityData[$oid][$field] = $data[$field];
2269 26
2270
                continue;
2271
            }
2272 26
2273 19
            $associatedId = [];
2274
2275 20
            // TODO: Is this even computed right in all cases of composite keys?
2276 10
            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...
2277
                /** @var JoinColumnMetadata $joinColumn */
2278 20
                $joinColumnName = $joinColumn->getColumnName();
2279
                $joinColumnValue = isset($data[$joinColumnName]) ? $data[$joinColumnName] : null;
2280 19
                $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...
2281 7
2282 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...
2283
                    // the missing key is part of target's entity primary key
2284 26
                    $associatedId = [];
2285
2286
                    continue;
2287
                }
2288
2289 64
                $associatedId[$targetField] = $joinColumnValue;
2290 16
            }
2291
2292 64
            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...
2293
                // Foreign key is NULL
2294
                $association->setValue($entity, null);
2295
                $this->originalEntityData[$oid][$field] = null;
2296
2297
                continue;
2298
            }
2299
2300
            // @todo guilhermeblanco Can we remove the need of this somehow?
2301
            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...
2302
                $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...
2303
            }
2304
2305
            // Foreign key is set
2306
            // Check identity map first
2307 11
            // FIXME: Can break easily with composite keys if join column values are in
2308
            //        wrong order. The correct order is the one in ClassMetadata#identifier.
2309 11
            $relatedIdHash = implode(' ', $associatedId);
2310 1
2311
            switch (true) {
2312
                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...
2313 10
                    $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...
2314 1
2315
                    // If this is an uninitialized proxy, we are deferring eager loads,
2316
                    // this association is marked as eager fetch, and its an uninitialized proxy (wtf!)
2317 9
                    // then we can append this entity for eager loading!
2318
                    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...
2319
                        $newValue instanceof Proxy &&
2320 9
                        isset($hints[self::HINT_DEFEREAGERLOAD]) &&
2321 6
                        $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...
2322 2
                        ! $newValue->__isInitialized()
2323
                    ) {
2324
2325 4
                        $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...
2326
                    }
2327
2328
                    break;
2329 4
2330 1
                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...
2331
                    // If it might be a subtype, it can not be lazy. There isn't even
2332
                    // a way to solve this with deferred eager loading, which means putting
2333 4
                    // an entity with subclasses at a *-to-one location is really bad! (performance-wise)
2334
                    $persister = $this->getEntityPersister($targetEntity);
2335 4
                    $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...
2336 2
                    break;
2337
2338
                default:
2339 2
                    // Proxies do not carry any kind of original entity data until they're fully loaded/initialized
2340
                    $managedData = [];
2341 3
2342 3
                    switch (true) {
2343 1
                        // We are negating the condition here. Other cases will assume it is valid!
2344 3
                        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...
2345 2
                            $newValue = $this->em->getProxyFactory()->getProxy($targetEntity, $associatedId);
2346
                            break;
2347
2348 1
                        // Deferred eager load only works for single identifier classes
2349
                        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...
2350 1
                            // TODO: Is there a faster approach?
2351 1
                            $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...
2352
2353
                            $newValue = $this->em->getProxyFactory()->getProxy($targetEntity, $associatedId);
2354 1
                            break;
2355
2356
                        default:
2357
                            // TODO: This is very imperformant, ignore it?
2358
                            $newValue    = $this->em->find($targetEntity, $associatedId);
2359 3
                            // Needed to re-assign original entity data for freshly loaded entity
2360
                            $managedData = $this->originalEntityData[spl_object_hash($newValue)];
2361
                            break;
2362
                    }
2363
2364
                    $this->registerManaged($newValue, $associatedId, $managedData);
2365
2366 1008
                    break;
2367
            }
2368 1008
2369
            $this->originalEntityData[$oid][$field] = $newValue;
2370
            $association->setValue($entity, $newValue);
2371
2372
            if ($association->getInversedBy() && $association instanceof OneToOneAssociationMetadata) {
2373
                $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...
2374
2375
                $inverseAssociation->setValue($newValue, $entity);
2376
            }
2377
        }
2378 1218
2379
        if ($overrideLocalValues) {
2380 1218
            // defer invoking of postLoad event to hydration complete step
2381 1217
            $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...
2382 1217
        }
2383 1217
2384 1217
        return $entity;
2385 1217
    }
2386 1217
2387 1217
    /**
2388 1217
     * @return void
2389 1217
     */
2390 1217
    public function triggerEagerLoads()
2391 1217
    {
2392 1217
        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...
2393 1217
            return;
2394 1217
        }
2395 1217
2396
        // avoid infinite recursion
2397 3
        $eagerLoadingEntities       = $this->eagerLoadingEntities;
2398 3
        $this->eagerLoadingEntities = [];
2399
2400
        foreach ($eagerLoadingEntities as $entityName => $ids) {
2401 1218
            if ( ! $ids) {
2402 7
                continue;
2403
            }
2404 1218
2405
            $class = $this->em->getClassMetadata($entityName);
2406
2407
            $this->getEntityPersister($entityName)->loadAll(
2408
                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...
2409
            );
2410
        }
2411
    }
2412
2413
    /**
2414
     * Initializes (loads) an uninitialized persistent collection of an entity.
2415
     *
2416
     * @param \Doctrine\ORM\PersistentCollection $collection The collection to initialize.
2417
     *
2418 17
     * @return void
2419
     *
2420 17
     * @todo Maybe later move to EntityManager#initialize($proxyOrCollection). See DDC-733.
2421 17
     */
2422
    public function loadCollection(PersistentCollection $collection)
2423
    {
2424
        $association = $collection->getMapping();
2425
        $persister   = $this->getEntityPersister($association->getTargetEntity());
2426
2427
        if ($association instanceof OneToManyAssociationMetadata) {
2428
            $persister->loadOneToManyCollection($association, $collection->getOwner(), $collection);
2429
        } else {
2430
            $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...
2431
        }
2432
2433 112
        $collection->setInitialized(true);
2434
    }
2435 112
2436 112
    /**
2437
     * Gets the identity map of the UnitOfWork.
2438
     *
2439
     * @return array
2440
     */
2441
    public function getIdentityMap()
2442
    {
2443
        return $this->identityMap;
2444
    }
2445
2446 13
    /**
2447
     * Gets the original data of an entity. The original data is the data that was
2448 13
     * present at the time the entity was reconstituted from the database.
2449
     *
2450
     * @param object $entity
2451
     *
2452 13
     * @return array
2453
     */
2454 13
    public function getOriginalEntityData($entity)
2455 13
    {
2456
        $oid = spl_object_hash($entity);
2457
2458
        return isset($this->originalEntityData[$oid])
2459
            ? $this->originalEntityData[$oid]
2460
            : [];
2461
    }
2462
2463
    /**
2464
     * @ignore
2465
     *
2466
     * @param object $entity
2467
     * @param array  $data
2468
     *
2469
     * @return void
2470
     */
2471
    public function setOriginalEntityData($entity, array $data)
2472 668
    {
2473
        $this->originalEntityData[spl_object_hash($entity)] = $data;
2474 668
    }
2475
2476 668
    /**
2477 4
     * INTERNAL:
2478
     * Sets a property value of the original data array of an entity.
2479
     *
2480 668
     * @ignore
2481
     *
2482
     * @param string $oid
2483
     * @param string $property
2484
     * @param mixed  $value
2485
     *
2486
     * @return void
2487
     */
2488
    public function setOriginalEntityProperty($oid, $property, $value)
2489
    {
2490
        $this->originalEntityData[$oid][$property] = $value;
2491
    }
2492
2493
    /**
2494
     * Gets the identifier of an entity.
2495
     * The returned value is always an array of identifier values. If the entity
2496
     * has a composite identifier then the identifier values are in the same
2497
     * order as the identifier field names as returned by ClassMetadata#getIdentifierFieldNames().
2498
     *
2499 806
     * @param object $entity
2500
     *
2501 806
     * @return array The identifier values.
2502
     */
2503
    public function getEntityIdentifier($entity)
2504 806
    {
2505 806
        return $this->entityIdentifiers[spl_object_hash($entity)];
2506
    }
2507 806
2508 310
    /**
2509 310
     * Processes an entity instance to extract their identifier values.
2510
     *
2511
     * @param object $entity The entity instance.
2512 310
     *
2513 310
     * @return mixed A scalar value.
2514 310
     *
2515 310
     * @throws \Doctrine\ORM\ORMInvalidArgumentException
2516 310
     */
2517
    public function getSingleIdentifierValue($entity)
2518
    {
2519
        $class     = $this->em->getClassMetadata(get_class($entity));
2520
        $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...
2521
2522 2
        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...
2523 2
            throw ORMInvalidArgumentException::invalidCompositeIdentifier();
2524
        }
2525
2526 2
        $values = $this->isInIdentityMap($entity)
2527
            ? $this->getEntityIdentifier($entity)
2528
            : $persister->getIdentifier($entity);
2529 308
2530 21
        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...
2531
    }
2532 21
2533
    /**
2534 21
     * Tries to find an entity with the given identifier in the identity map of
2535 21
     * this UnitOfWork.
2536
     *
2537
     * @param mixed  $id            The entity identifier to look for.
2538 289
     * @param string $rootClassName The name of the root class of the mapped entity hierarchy.
2539
     *
2540
     * @return object|bool Returns the entity with the specified identifier if it exists in
2541 289
     *                     this UnitOfWork, FALSE otherwise.
2542 71
     */
2543
    public function tryGetById($id, $rootClassName)
2544
    {
2545
        $idHash = implode(' ', (array) $id);
2546 308
2547
        return isset($this->identityMap[$rootClassName][$idHash])
2548 111
            ? $this->identityMap[$rootClassName][$idHash]
2549 3
            : false;
2550
    }
2551
2552 308
    /**
2553
     * Schedules an entity for dirty-checking at commit-time.
2554
     *
2555 665
     * @param object $entity The entity to schedule for dirty-checking.
2556 665
     *
2557
     * @return void
2558 665
     */
2559 665
    public function scheduleForSynchronization($entity)
2560 665
    {
2561
        $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...
2562 665
2563
        $this->scheduledForSynchronization[$rootClassName][spl_object_hash($entity)] = $entity;
2564 665
    }
2565 2
2566
    /**
2567
     * Checks whether the UnitOfWork has any pending insertions.
2568 665
     *
2569
     * @return boolean TRUE if this UnitOfWork has pending insertions, FALSE otherwise.
2570
     */
2571 805
    public function hasPendingInsertions()
2572 219
    {
2573
        return ! empty($this->entityInsertions);
2574
    }
2575 702
2576 702
    /**
2577 702
     * Calculates the size of the UnitOfWork. The size of the UnitOfWork is the
2578
     * number of entities in the identity map.
2579
     *
2580
     * @return integer
2581
     */
2582 702
    public function size()
2583
    {
2584 702
        return \array_sum(\array_map('count', $this->identityMap));
2585
    }
2586
2587
    /**
2588
     * Gets the EntityPersister for an Entity.
2589 702
     *
2590 33
     * @param string $entityName The name of the Entity.
2591
     *
2592
     * @return \Doctrine\ORM\Persisters\Entity\EntityPersister
2593 669
     */
2594
    public function getEntityPersister($entityName)
2595 586
    {
2596 260
        if (isset($this->entityPersisters[$entityName])) {
2597
            return $this->entityPersisters[$entityName];
2598
        }
2599 564
2600
        $class = $this->em->getClassMetadata($entityName);
2601
2602 564
        switch (true) {
2603 485
            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...
2604
                $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...
2605
                break;
2606 64
2607
            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...
2608 2
                $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...
2609
                break;
2610 2
2611 2
            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...
2612
                $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...
2613 2
                break;
2614
2615
            default:
2616
                throw new \RuntimeException('No persister found for entity.');
2617 62
        }
2618
2619 62
        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...
2620
            $persister = $this->em->getConfiguration()
2621
                ->getSecondLevelCacheConfiguration()
2622
                ->getCacheFactory()
2623 485
                ->buildCachedEntityPersister($this->em, $persister, $class);
2624 38
        }
2625 38
2626
        $this->entityPersisters[$entityName] = $persister;
2627 38
2628
        return $this->entityPersisters[$entityName];
2629
    }
2630 478
2631
    /**
2632
     * Gets a collection persister for a collection-valued association.
2633 478
     *
2634 478
     * @param ToManyAssociationMetadata $association
2635 478
     *
2636
     * @return \Doctrine\ORM\Persisters\Collection\CollectionPersister
2637 478
     */
2638
    public function getCollectionPersister(ToManyAssociationMetadata $association)
2639 287
    {
2640
        $role = $association->getCache()
2641 287
            ? sprintf('%s::%s', $association->getSourceEntity(), $association->getName())
2642
            : get_class($association);
2643
2644 284
        if (isset($this->collectionPersisters[$role])) {
2645
            return $this->collectionPersisters[$role];
2646
        }
2647 478
2648
        $persister = $association instanceof OneToManyAssociationMetadata
2649 287
            ? new OneToManyPersister($this->em)
2650 287
            : new ManyToManyPersister($this->em);
2651
2652 287
        if ($this->hasCache && $association->getCache()) {
2653
            $persister = $this->em->getConfiguration()
2654
                ->getSecondLevelCacheConfiguration()
2655 284
                ->getCacheFactory()
2656 281
                ->buildCachedCollectionPersister($this->em, $persister, $association);
2657
        }
2658
2659
        $this->collectionPersisters[$role] = $persister;
2660
2661
        return $this->collectionPersisters[$role];
2662
    }
2663 284
2664
    /**
2665
     * INTERNAL:
2666 284
     * Registers an entity as managed.
2667 166
     *
2668
     * @param object $entity The entity.
2669
     * @param array  $id     Map containing identifier field names as key and its associated values.
2670
     * @param array  $data   The original entity data.
2671
     *
2672 166
     * @return void
2673 166
     */
2674 166
    public function registerManaged($entity, array $id, array $data)
2675 166
    {
2676 166
        $isProxy = $entity instanceof Proxy && ! $entity->__isInitialized();
2677
        $oid     = spl_object_hash($entity);
2678
2679
        $this->entityIdentifiers[$oid]  = $id;
2680
        $this->entityStates[$oid]       = self::STATE_MANAGED;
2681 166
        $this->originalEntityData[$oid] = $data;
2682
2683 192
        $this->addToIdentityMap($entity);
2684
2685
        if ($entity instanceof NotifyPropertyChanged && ! $isProxy) {
2686
            $entity->addPropertyChangedListener($this);
2687 30
        }
2688 30
    }
2689
2690
    /**
2691
     * INTERNAL:
2692
     * Clears the property changeset of the entity with the given OID.
2693 163
     *
2694 157
     * @param string $oid The entity's OID.
2695 157
     *
2696
     * @return void
2697
     */
2698 6
    public function clearEntityChangeSet($oid)
2699
    {
2700 6
        $this->entityChangeSets[$oid] = [];
2701
    }
2702 6
2703 6
    /* PropertyChangedListener implementation */
2704
2705
    /**
2706
     * Notifies this UnitOfWork of a property change in an entity.
2707
     *
2708
     * @param object $entity       The entity that owns the property.
2709
     * @param string $propertyName The name of the property that changed.
2710
     * @param mixed  $oldValue     The old value of the property.
2711
     * @param mixed  $newValue     The new value of the property.
2712 163
     *
2713 163
     * @return void
2714 163
     */
2715
    public function propertyChanged($entity, $propertyName, $oldValue, $newValue)
2716
    {
2717 163
        $class = $this->em->getClassMetadata(get_class($entity));
2718 163
2719
        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...
2720
            return; // ignore non-persistent fields
2721
        }
2722 163
2723
        $oid = spl_object_hash($entity);
2724 163
2725
        // Update changeset and mark entity for synchronization
2726
        $this->entityChangeSets[$oid][$propertyName] = [$oldValue, $newValue];
2727 284
2728 284
        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...
2729
            $this->scheduleForSynchronization($entity);
2730 284
        }
2731 49
    }
2732 49
2733
    /**
2734
     * Gets the currently scheduled entity insertions in this UnitOfWork.
2735 284
     *
2736
     * @return array
2737
     */
2738
    public function getScheduledEntityInsertions()
2739 486
    {
2740
        return $this->entityInsertions;
2741
    }
2742
2743
    /**
2744 486
     * Gets the currently scheduled entity updates in this UnitOfWork.
2745
     *
2746 3
     * @return array
2747
     */
2748 3
    public function getScheduledEntityUpdates()
2749 3
    {
2750
        return $this->entityUpdates;
2751 3
    }
2752
2753
    /**
2754
     * Gets the currently scheduled entity deletions in this UnitOfWork.
2755 486
     *
2756 486
     * @return array
2757 486
     */
2758
    public function getScheduledEntityDeletions()
2759 486
    {
2760 486
        return $this->entityDeletions;
2761
    }
2762 486
2763 4
    /**
2764 4
     * Gets the currently scheduled complete collection deletions
2765
     *
2766
     * @return array
2767 486
     */
2768 564
    public function getScheduledCollectionDeletions()
2769
    {
2770
        return $this->collectionDeletions;
2771
    }
2772 669
2773
    /**
2774 669
     * Gets the currently scheduled collection inserts, updates and deletes.
2775
     *
2776
     * @return array
2777 669
     */
2778
    public function getScheduledCollectionUpdates()
2779
    {
2780
        return $this->collectionUpdates;
2781
    }
2782
2783 861
    /**
2784
     * Helper method to initialize a lazy loading proxy or persistent collection.
2785 861
     *
2786 861
     * @param object $obj
2787
     *
2788
     * @return void
2789
     */
2790 6
    public function initializeObject($obj)
2791 6
    {
2792
        if ($obj instanceof Proxy) {
2793 6
            $obj->__load();
2794 6
2795
            return;
2796
        }
2797
2798 6
        if ($obj instanceof PersistentCollection) {
2799
            $obj->initialize();
2800 6
        }
2801 6
    }
2802
2803
    /**
2804 6
     * Helper method to show an object as string.
2805
     *
2806
     * @param object $obj
2807
     *
2808
     * @return string
2809
     */
2810
    private static function objToStr($obj)
2811
    {
2812
        return method_exists($obj, '__toString') ? (string) $obj : get_class($obj).'@'.spl_object_hash($obj);
2813
    }
2814
2815 142
    /**
2816
     * Marks an entity as read-only so that it will not be considered for updates during UnitOfWork#commit().
2817 142
     *
2818 142
     * This operation cannot be undone as some parts of the UnitOfWork now keep gathering information
2819
     * on this object that might be necessary to perform a correct update.
2820 142
     *
2821 142
     * @param object $object
2822 76
     *
2823 76
     * @return void
2824
     *
2825 80
     * @throws ORMInvalidArgumentException
2826 80
     */
2827 80
    public function markReadOnly($object)
2828
    {
2829
        if ( ! is_object($object) || ! $this->isInIdentityMap($object)) {
2830 142
            throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object);
2831 142
        }
2832
2833
        $this->readOnlyObjects[spl_object_hash($object)] = true;
2834
    }
2835
2836
    /**
2837
     * Is this entity read only?
2838 2
     *
2839
     * @param object $object
2840 2
     *
2841
     * @return bool
2842
     *
2843
     * @throws ORMInvalidArgumentException
2844
     */
2845
    public function isReadOnly($object)
2846
    {
2847
        if ( ! is_object($object)) {
2848
            throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object);
2849
        }
2850
2851 115
        return isset($this->readOnlyObjects[spl_object_hash($object)]);
2852
    }
2853 115
2854
    /**
2855 115
     * Perform whatever processing is encapsulated here after completion of the transaction.
2856 112
     */
2857 115
    private function afterTransactionComplete()
2858
    {
2859
        $this->performCallbackOnCachedPersister(function (CachedPersister $persister) {
2860
            $persister->afterTransactionComplete();
2861
        });
2862
    }
2863
2864
    /**
2865
     * Perform whatever processing is encapsulated here after completion of the rolled-back.
2866
     */
2867
    private function afterTransactionRolledBack()
2868
    {
2869
        $this->performCallbackOnCachedPersister(function (CachedPersister $persister) {
2870
            $persister->afterTransactionRolledBack();
2871
        });
2872
    }
2873
2874
    /**
2875
     * Performs an action after the transaction.
2876
     *
2877
     * @param callable $callback
2878
     */
2879
    private function performCallbackOnCachedPersister(callable $callback)
2880
    {
2881
        if ( ! $this->hasCache) {
2882
            return;
2883
        }
2884
2885 313
        foreach (array_merge($this->entityPersisters, $this->collectionPersisters) as $persister) {
2886
            if ($persister instanceof CachedPersister) {
2887 313
                $callback($persister);
2888 313
            }
2889
        }
2890
    }
2891
2892
    private function dispatchOnFlushEvent()
2893
    {
2894
        if ($this->eventManager->hasListeners(Events::onFlush)) {
2895
            $this->eventManager->dispatchEvent(Events::onFlush, new OnFlushEventArgs($this->em));
2896
        }
2897
    }
2898
2899
    private function dispatchPostFlushEvent()
2900 842
    {
2901
        if ($this->eventManager->hasListeners(Events::postFlush)) {
2902 842
            $this->eventManager->dispatchEvent(Events::postFlush, new PostFlushEventArgs($this->em));
2903
        }
2904
    }
2905
2906
    /**
2907
     * Verifies if two given entities actually are the same based on identifier comparison
2908
     *
2909
     * @param object $entity1
2910
     * @param object $entity2
2911
     *
2912
     * @return bool
2913
     */
2914 126
    private function isIdentifierEquals($entity1, $entity2)
2915
    {
2916 126
        if ($entity1 === $entity2) {
2917
            return true;
2918 126
        }
2919
2920
        $class     = $this->em->getClassMetadata(get_class($entity1));
2921
        $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...
2922 126
2923 113
        if ($class !== $this->em->getClassMetadata(get_class($entity2))) {
2924 126
            return false;
2925
        }
2926 126
2927
        $identifierFlattener = $this->em->getIdentifierFlattener();
2928
2929
        $oid1 = spl_object_hash($entity1);
2930
        $oid2 = spl_object_hash($entity2);
2931
2932
        $id1 = isset($this->entityIdentifiers[$oid1])
2933
            ? $this->entityIdentifiers[$oid1]
2934
            : $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...
2935
        $id2 = isset($this->entityIdentifiers[$oid2])
2936
            ? $this->entityIdentifiers[$oid2]
2937
            : $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...
2938
2939 522
        return $id1 === $id2 || implode(' ', $id1) === implode(' ', $id2);
2940
    }
2941 522
2942
    /**
2943 522
     * This method called by hydrators, and indicates that hydrator totally completed current hydration cycle.
2944 79
     * Unit of work able to fire deferred events, related to loading events here.
2945 522
     *
2946
     * @internal should be called internally from object hydrators
2947
     */
2948
    public function hydrationComplete()
2949
    {
2950
        $this->hydrationCompleteHandler->hydrationComplete();
2951
    }
2952
}
2953