Failed Conditions
Pull Request — develop (#6724)
by Marco
122:37 queued 57:31
created

UnitOfWork::tryGetByIdHash()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

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

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

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

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

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

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

Loading history...
340 1012
            $this->dispatchOnFlushEvent();
341 165
            $this->dispatchPostFlushEvent();
342 129
343 40
            return; // Nothing to do.
344 37
        }
345 1012
346 25
        $this->assertThatThereAreNoUnintentionallyNonPersistedAssociations();
347 25
348
        if ($this->orphanRemovals) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->orphanRemovals of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
349 25
            foreach ($this->orphanRemovals as $orphan) {
350
                $this->remove($orphan);
351
            }
352 1008
        }
353 16
354 16
        $this->dispatchOnFlushEvent();
355
356
        // Now we need a commit order to maintain referential integrity
357
        $commitOrder = $this->getCommitOrder();
358 1008
359
        $conn = $this->em->getConnection();
360
        $conn->beginTransaction();
361 1008
362
        try {
363 1008
            // Collection deletions (deletions of complete collections)
364 1008
            foreach ($this->collectionDeletions as $collectionToDelete) {
365
                $this->getCollectionPersister($collectionToDelete->getMapping())->delete($collectionToDelete);
366
            }
367
368 1008
            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...
369 19
                foreach ($commitOrder as $class) {
370
                    $this->executeInserts($class);
371
                }
372 1008
            }
373 1004
374 1004
            if ($this->entityUpdates) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->entityUpdates of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

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

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

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

Loading history...
382
                $this->executeExtraUpdates();
383
            }
384
385 1003
            // Collection updates (deleteRows, updateRows, insertRows)
386 40
            foreach ($this->collectionUpdates as $collectionToUpdate) {
387
                $this->getCollectionPersister($collectionToUpdate->getMapping())->update($collectionToUpdate);
388
            }
389
390 1003
            // Entity deletions come last and need to be in reverse commit order
391 531
            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...
392
                foreach (array_reverse($commitOrder) as $committedEntityName) {
393
                    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...
394
                        break; // just a performance optimisation
395 1003
                    }
396 62
397 62
                    $this->executeDeletions($committedEntityName);
398
                }
399
            }
400
401 1003
            $conn->commit();
402 11
        } catch (\Throwable $e) {
403 11
            $this->em->close();
404 11
            $conn->rollBack();
405
406 11
            $this->afterTransactionRolledBack();
407
408 11
            throw $e;
409
        }
410
411 1003
        $this->afterTransactionComplete();
412
413
        // Take new snapshots from visited collections
414 1003
        foreach ($this->visitedCollections as $coll) {
415 530
            $coll->takeSnapshot();
416
        }
417
418 1003
        $this->dispatchPostFlushEvent();
419
420
        // Clean up
421 1002
        $this->entityInsertions =
422 1002
        $this->entityUpdates =
423 1002
        $this->entityDeletions =
424 1002
        $this->extraUpdates =
425 1002
        $this->entityChangeSets =
426 1002
        $this->collectionUpdates =
427 1002
        $this->collectionDeletions =
428 1002
        $this->visitedCollections =
429 1002
        $this->scheduledForSynchronization =
430 1002
        $this->orphanRemovals = [];
431 1002
    }
432
433
    /**
434
     * Computes the changesets of all entities scheduled for insertion.
435
     *
436
     * @return void
437
     */
438 1014
    private function computeScheduleInsertsChangeSets()
439
    {
440 1014
        foreach ($this->entityInsertions as $entity) {
441 1006
            $class = $this->em->getClassMetadata(get_class($entity));
442
443 1006
            $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...
444
        }
445 1012
    }
446
447
    /**
448
     * Executes any extra updates that have been scheduled.
449
     */
450
    private function executeExtraUpdates()
451
    {
452
        foreach ($this->extraUpdates as $oid => $update) {
453
            list ($entity, $changeset) = $update;
454
455
            $this->entityChangeSets[$oid] = $changeset;
456
457
//            echo 'Extra update: ';
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
458
//            \Doctrine\Common\Util\Debug::dump($changeset, 3);
459
460
            $this->getEntityPersister(get_class($entity))->update($entity);
461 16
        }
462
463 16
        $this->extraUpdates = [];
464
    }
465 16
466 1
    /**
467
     * Gets the changeset for an entity.
468
     *
469 15
     * @param object $entity
470
     *
471 15
     * @return array
472 14
     */
473
    public function & getEntityChangeSet($entity)
474
    {
475
        $oid  = spl_object_hash($entity);
476 15
        $data = [];
477
478 15
        if (!isset($this->entityChangeSets[$oid])) {
479
            return $data;
480
        }
481
482
        return $this->entityChangeSets[$oid];
483 15
    }
484 2
485
    /**
486
     * Computes the changes that happened to a single entity.
487
     *
488 13
     * Modifies/populates the following properties:
489
     *
490 13
     * {@link originalEntityData}
491 6
     * If the entity is NEW or MANAGED but not yet fully persisted (only has an id)
492
     * then it was not fetched from the database and therefore we have no original
493 12
     * entity data yet. All of the current entity data is stored as the original entity data.
494
     *
495
     * {@link entityChangeSets}
496
     * The changes detected on all properties of the entity are stored there.
497
     * A change is a tuple array where the first entry is the old value and the second
498 40
     * entry is the new value of the property. Changesets are used by persisters
499
     * to INSERT/UPDATE the persistent entity state.
500 40
     *
501 40
     * {@link entityUpdates}
502
     * If the entity is already fully MANAGED (has been fetched from the database before)
503 40
     * and any changes to its properties are detected, then a reference to the entity is stored
504
     * there to mark it for an update.
505
     *
506
     * {@link collectionDeletions}
507
     * If a PersistentCollection has been de-referenced in a fully MANAGED entity,
508 40
     * then this collection is marked for deletion.
509
     *
510
     * @ignore
511 40
     *
512 40
     * @internal Don't call from the outside.
513
     *
514
     * @param ClassMetadata $class  The class descriptor of the entity.
515
     * @param object        $entity The entity for which to compute the changes.
516
     *
517
     * @return void
518
     */
519
    public function computeChangeSet(ClassMetadata $class, $entity)
520
    {
521 1006
        $oid = spl_object_hash($entity);
522
523 1006
        if (isset($this->readOnlyObjects[$oid])) {
524 1006
            return;
525
        }
526 1006
527 1
        if ($class->inheritanceType !== InheritanceType::NONE) {
528
            $class = $this->em->getClassMetadata(get_class($entity));
529
        }
530 1006
531
        $isUnInitializedProxy = $entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized();
532
533
        $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 528 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...
534
535
        if ($invoke !== ListenersInvoker::INVOKE_NONE) {
536
            $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 528 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...
537
        }
538
539
        $actualData = [];
540
541
        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...
542
            if ($property instanceof ToManyAssociationMetadata) {
543
                $collectionFieldValue = $property->getValue($entity);
544
545
                if (null !== $collectionFieldValue) {
546
                    if ($collectionFieldValue instanceof PersistentCollection
547
                        && $collectionFieldValue->getOwner() === $entity
548
                    ) {
549
                        continue;
550
                    }
551
552
                    $wrappedValue = $property->wrap($entity, $collectionFieldValue, $this->em);
553
554
                    $property->setValue($entity, $wrappedValue);
555
556
                    $actualData[$name] = $wrappedValue;
557
558
                    continue;
559
                }
560
            }
561
562
            if ($isUnInitializedProxy) {
563
                // we only track to-many associations when a proxy is un-initialized: all other properties are lazy.
564
                continue;
565
            }
566
567 1016
            // Only attempt reading field values for initialized objects
568
            $value = $property->getValue($entity);
569 1016
570
            if (
571 1016
                ( ! $class->isIdentifier($name)
572 2
                    || ! $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...
573
                    || ! $class->getProperty($name)->hasValueGenerator()
574
                    || $class->getProperty($name)->getValueGenerator()->getType() !== GeneratorType::IDENTITY
575 1016
                ) && (! $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...
576 307
                $actualData[$name] = $value;
577
            }
578
        }
579 1016
580
        if ( ! isset($this->originalEntityData[$oid])) {
581 1016
            // Entity is either NEW or MANAGED but not yet fully persisted (only has an id).
582 137
            // These result in an INSERT.
583
            $this->originalEntityData[$oid] = $actualData;
584
            $changeSet = [];
585 1016
586
            foreach ($actualData as $propName => $actualValue) {
587 1016
                $property = $class->getProperty($propName);
588 1016
589
                if (($property instanceof FieldMetadata) ||
590 1016
                    ($property instanceof ToOneAssociationMetadata && $property->isOwningSide())) {
591 777
                    $changeSet[$propName] = [null, $actualValue];
592 200
                }
593 200
            }
594
595
            $this->entityChangeSets[$oid] = $changeSet;
596 5
        } else {
597
            // Entity is "fully" MANAGED: it was already fully persisted before
598
            // and we have a copy of the original data
599
            $originalData           = $this->originalEntityData[$oid];
600 772
            $isChangeTrackingNotify = $class->changeTrackingPolicy === ChangeTrackingPolicy::NOTIFY;
601 242
            $changeSet              = ($isChangeTrackingNotify && isset($this->entityChangeSets[$oid]))
602
                ? $this->entityChangeSets[$oid]
603
                : [];
604 772
605
            foreach ($actualData as $propName => $actualValue) {
606
                // skip field, its a partially omitted one!
607 772
                if ( ! (isset($originalData[$propName]) || array_key_exists($propName, $originalData))) {
608 772
                    continue;
609
                }
610 772
611 772
                $orgValue = $originalData[$propName];
612
613 772
                // skip if value haven't changed
614
                if ($orgValue === $actualValue) {
615 772
                    continue;
616
                }
617 772
618
                $property = $class->getProperty($propName);
619
620 1016
                // Persistent collection was exchanged with the "originally"
621 1016
                // created one. This can only mean it was cloned and replaced
622 1016
                // on another entity.
623
                if ($actualValue instanceof PersistentCollection) {
624
                    $owner = $actualValue->getOwner();
625
626 1016
                    if ($owner === null) { // cloned
627
                        $actualValue->setOwner($entity, $property);
628
                    } else if ($owner !== $entity) { // no clone, we have to fix
629 1012
                        if (! $actualValue->isInitialized()) {
630 1012
                            $actualValue->initialize(); // we have to do this otherwise the cols share state
631
                        }
632 1012
633 996
                        $newValue = clone $actualValue;
634 945
635
                        $newValue->setOwner($entity, $property);
636 945
637
                        $property->setValue($entity, $newValue);
638
                    }
639 894
                }
640
641 894
                switch (true) {
642 894
                    case ($property instanceof FieldMetadata):
643
                        if ($isChangeTrackingNotify) {
644
                            // Continue inside switch behaves as break.
645
                            // We are required to use continue 2, since we need to continue to next $actualData item
646 1012
                            continue 2;
647
                        }
648
649
                        $changeSet[$propName] = [$orgValue, $actualValue];
650 263
                        break;
651 263
652 263
                    case ($property instanceof ToOneAssociationMetadata):
653
                        if ($property->isOwningSide()) {
654 263
                            $changeSet[$propName] = [$orgValue, $actualValue];
655
                        }
656 263
657
                        if ($orgValue !== null && $property->isOrphanRemoval()) {
658 248
                            $this->scheduleOrphanRemoval($orgValue);
659 7
                        }
660
661
                        break;
662 248
663
                    case ($property instanceof ToManyAssociationMetadata):
664
                        // Check if original value exists
665 248
                        if ($orgValue instanceof PersistentCollection) {
666 232
                            // A PersistentCollection was de-referenced, so delete it.
667
                            if (! $this->isCollectionScheduledForDeletion($orgValue)) {
668
                                $this->scheduleCollectionDeletion($orgValue);
669
670 111
                                $changeSet[$propName] = $orgValue; // Signal changeset, to-many associations will be ignored
671 57
                            }
672
                        }
673
674
                        break;
675 57
676
                    default:
677 57
                        // Do nothing
678
                }
679
            }
680 58
681
            if ($changeSet) {
682
                $this->entityChangeSets[$oid]   = $changeSet;
683
                $this->originalEntityData[$oid] = $actualData;
684
                $this->entityUpdates[$oid]      = $entity;
685 58
            }
686 8
        }
687 8
688
        // Look for changes in associations of the entity
689 8
        foreach ($class->getDeclaredPropertiesIterator() as $property) {
690
            if ($property instanceof ToManyAssociationMetadata) {
691
                $value = $property->getValue($entity);
692
693
                if (null === $value) {
694
                    continue;
695
                }
696
697
                $this->computeToManyAssociationChanges($property, $value);
698
            } else {
699 58
                if ($isUnInitializedProxy) {
700
                    continue;
701 8
                }
702
703 8
                if (! $property instanceof AssociationMetadata) {
704
                    continue;
705
                }
706
707 8
                $value = $property->getValue($entity);
708 8
709
                if (null === $value) {
710 8
                    continue;
711
                }
712
713 50
                $this->computeAssociationChanges($property, $value);
714 49
            }
715 21
716
            if ($property instanceof ManyToManyAssociationMetadata &&
717
                $value instanceof PersistentCollection &&
718 49
                ! isset($this->entityChangeSets[$oid]) &&
719 50
                $property->isOwningSide() &&
720
                $value->isDirty()) {
721
722
                $this->entityChangeSets[$oid]   = [];
723
                $this->originalEntityData[$oid] = $actualData;
724 263
                $this->entityUpdates[$oid]      = $entity;
725 84
            }
726 84
        }
727 84
    }
728
729
    /**
730
     * Computes all the changes that have been done to entities and collections
731
     * since the last commit and stores these changes in the _entityChangeSet map
732 1016
     * temporarily for access by the persisters, until the UoW commit is finished.
733 894
     *
734 639
     * @return void
735
     */
736
    public function computeChangeSets()
737 865
    {
738
        // Compute changes for INSERTed entities first. This must always happen.
739 857
        $this->computeScheduleInsertsChangeSets();
740 857
741 857
        // Compute changes for other MANAGED entities. Change tracking policies take effect here.
742 857
        foreach ($this->identityMap as $className => $entities) {
743 857
            $class = $this->em->getClassMetadata($className);
744
745 35
            // Skip class if instances are read-only
746 35
            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...
747 857
                continue;
748
            }
749
750 1008
            // If change tracking is explicit or happens through notification, then only compute
751
            // changes on entities of that type that are explicitly marked for synchronization.
752
            switch (true) {
753
                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...
754
                    $entitiesToProcess = $entities;
755
                    break;
756
757
                case (isset($this->scheduledForSynchronization[$className])):
758
                    $entitiesToProcess = $this->scheduledForSynchronization[$className];
759 1007
                    break;
760
761
                default:
762 1007
                    $entitiesToProcess = [];
763
764
            }
765 1005
766 447
            foreach ($entitiesToProcess as $entity) {
767
                // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION OR DELETION are processed here.
768
                $oid = spl_object_hash($entity);
769 447
770 1
                if ( ! isset($this->entityInsertions[$oid]) && ! isset($this->entityDeletions[$oid]) && isset($this->entityStates[$oid])) {
771
                    $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...
772
                }
773
            }
774
        }
775
    }
776 446
777 444
    private function computeToManyAssociationChanges(ToManyAssociationMetadata $association, $value)
778 444
    {
779
        if ($value instanceof PersistentCollection && $value->isDirty()) {
780 3
            $coid = spl_object_hash($value);
781 3
782 3
            $this->collectionUpdates[$coid] = $value;
783
            $this->visitedCollections[$coid] = $value;
784
        }
785 1
786
        // Look through the entities, and in any of their associations,
787
        // 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...
788
        // Unwrap. Uninitialized collections will simply be empty.
789 446
        $unwrappedValue = $value->unwrap();
790
        $targetEntity   = $association->getTargetEntity();
791 426
        $targetClass    = $this->em->getClassMetadata($targetEntity);
792 35
793
        // @TODO massive amount of duplication here - to be fixed.
794
        foreach ($unwrappedValue as $key => $entry) {
795
            if (! ($entry instanceof $targetEntity)) {
796 425
                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...
797
            }
798 425
799 446
            $state = $this->getEntityState($entry, self::STATE_NEW);
800
801
            if (! ($entry instanceof $targetEntity)) {
802
                throw ORMException::unexpectedAssociationValue(
803 1005
                    $association->getSourceEntity(),
804
                    $association->getName(),
805
                    get_class($entry),
806
                    $targetEntity
807
                );
808
            }
809
810
            switch ($state) {
811
                case self::STATE_NEW:
812
                    if ( ! in_array('persist', $association->getCascade())) {
813
                        $this->nonCascadedNewDetectedEntities[\spl_object_hash($entry)] = [$association, $entry];
814
815
                        break;
816 865
                    }
817
818 865
                    $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...
819 28
                    $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...
820
821
                    break;
822 864
823 533
                case self::STATE_REMOVED:
824
                    // Consume the $value as array (it's either an array or an ArrayAccess)
825 533
                    // and remove the element from Collection.
826 533
                    if ($association instanceof ToManyAssociationMetadata) {
827
                        unset($value[$key]);
828
                    }
829
                    break;
830
831
                case self::STATE_DETACHED:
832 864
                    // Can actually not happen right now as we assume STATE_NEW,
833 864
                    // so the exception will be raised from the DBAL layer (constraint violation).
834
                    throw ORMInvalidArgumentException::detachedEntityFoundThroughRelationship($association, $entry);
835 864
                    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...
836 722
837 6
                default:
838
                    // MANAGED associated entities are already taken into account
839
                    // during changeset calculation anyway, since they are in the identity map.
840 716
            }
841
        }
842 716
    }
843
844
    /**
845
     * Computes the changes of an association.
846
     *
847 716
     * @param AssociationMetadata $association The association mapping.
848 39
     * @param mixed               $value       The value of the association.
849 4
     *
850
     * @throws ORMInvalidArgumentException
851
     * @throws ORMException
852 35
     *
853 35
     * @return void
854 35
     */
855
    private function computeAssociationChanges(AssociationMetadata $association, $value)
856 710
    {
857
        if ($value instanceof GhostObjectInterface && ! $value->isProxyInitialized()) {
858
            return;
859 4
        }
860 3
861
        if ($value instanceof PersistentCollection && $value->isDirty()) {
862 4
            $coid = spl_object_hash($value);
863
864 710
            $this->collectionUpdates[$coid] = $value;
865
            $this->visitedCollections[$coid] = $value;
866
        }
867
868
        // Look through the entities, and in any of their associations,
869
        // 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...
870 713
        // Unwrap. Uninitialized collections will simply be empty.
871
        $unwrappedValue = ($association instanceof ToOneAssociationMetadata) ? [$value] : $value->unwrap();
872
        $targetEntity   = $association->getTargetEntity();
873
        $targetClass    = $this->em->getClassMetadata($targetEntity);
874
875 856
        foreach ($unwrappedValue as $key => $entry) {
876
            if (! ($entry instanceof $targetEntity)) {
877
                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...
878
            }
879
880
            $state = $this->getEntityState($entry, self::STATE_NEW);
881
882
            if (! ($entry instanceof $targetEntity)) {
883 1031
                throw ORMException::unexpectedAssociationValue(
884
                    $association->getSourceEntity(),
885 1031
                    $association->getName(),
886 1031
                    get_class($entry),
887
                    $targetEntity
888 1031
                );
889 139
            }
890
891
            switch ($state) {
892 1031
                case self::STATE_NEW:
893
                    if ( ! in_array('persist', $association->getCascade())) {
894 1031
                        $this->nonCascadedNewDetectedEntities[\spl_object_hash($entry)] = [$association, $entry];
895 269
896
                        break;
897 269
                    }
898 1
899
                    $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...
900 1
                    $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...
901
902
                    break;
903 269
904
                case self::STATE_REMOVED:
905
                    // Consume the $value as array (it's either an array or an ArrayAccess)
906 1031
                    // and remove the element from Collection.
907
                    if ($association instanceof ToManyAssociationMetadata) {
908 1031
                        unset($value[$key]);
909 1031
                    }
910
                    break;
911
912
                case self::STATE_DETACHED:
913
                    // Can actually not happen right now as we assume STATE_NEW,
914
                    // so the exception will be raised from the DBAL layer (constraint violation).
915
                    throw ORMInvalidArgumentException::detachedEntityFoundThroughRelationship($association, $entry);
916
                    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...
917
918
                default:
919
                    // MANAGED associated entities are already taken into account
920
                    // during changeset calculation anyway, since they are in the identity map.
921
            }
922
        }
923
    }
924
925
    /**
926
     * @param \Doctrine\ORM\Mapping\ClassMetadata $class
927
     * @param object                              $entity
928
     *
929 16
     * @return void
930
     */
931 16
    private function persistNew($class, $entity)
932
    {
933 16
        $oid    = spl_object_hash($entity);
934
        $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::prePersist);
935
936
        if ($invoke !== ListenersInvoker::INVOKE_NONE) {
937
            $this->listenersInvoker->invoke($class, Events::prePersist, $entity, new LifecycleEventArgs($entity, $this->em), $invoke);
938 16
        }
939
940
        $generationPlan = $class->getValueGenerationPlan();
941
        $persister = $this->getEntityPersister($class->getClassName());
942 16
        $generationPlan->executeImmediate($this->em, $entity);
943 3
944
        if (! $generationPlan->containsDeferred()) {
945
            $id = $this->em->getIdentifierFlattener()->flattenIdentifier($class, $persister->getIdentifier($entity));
946 16
            $this->entityIdentifiers[$oid] = $id;
947
        }
948 16
949 16
        $this->entityStates[$oid] = self::STATE_MANAGED;
950 16
951 16
        $this->scheduleForInsert($entity);
952 16
    }
953
954
    /**
955
     * INTERNAL:
956 16
     * Computes the changeset of an individual entity, independently of the
957
     * computeChangeSets() routine that is used at the beginning of a UnitOfWork#commit().
958
     *
959
     * The passed entity must be a managed entity. If the entity already has a change set
960 16
     * because this method is invoked during a commit cycle then the change sets are added.
961 16
     * whereby changes detected in this method prevail.
962
     *
963 16
     * @ignore
964 16
     *
965
     * @param ClassMetadata $class  The class descriptor of the entity.
966 16
     * @param object        $entity The entity for which to (re)calculate the change set.
967 16
     *
968
     * @return void
969
     *
970
     * @throws ORMInvalidArgumentException If the passed entity is not MANAGED.
971 16
     * @throws \RuntimeException
972 7
     */
973 6
    public function recomputeSingleEntityChangeSet(ClassMetadata $class, $entity) : void
974 1
    {
975 1
        $oid = spl_object_hash($entity);
976 1
977
        if (! isset($this->entityStates[$oid]) || $this->entityStates[$oid] !== self::STATE_MANAGED) {
978 7
            throw ORMInvalidArgumentException::entityNotManaged($entity);
979
        }
980 16
981
        // skip if change tracking is "NOTIFY"
982
        if ($class->changeTrackingPolicy === ChangeTrackingPolicy::NOTIFY) {
983
            return;
984
        }
985
986
        if ($class->inheritanceType !== InheritanceType::NONE) {
987
            $class = $this->em->getClassMetadata(get_class($entity));
988
        }
989 1004
990
        $actualData = [];
991 1004
992 1004
        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...
993 1004
            switch (true) {
994 1004
                case ($property instanceof VersionFieldMetadata):
995
                    // Ignore version field
996 1004
                    break;
997
998 1004
                case ($property instanceof FieldMetadata):
999 855
                    if (! $property->isPrimaryKey()
1000
                        || ! $property->getValueGenerator()
1001
                        || $property->getValueGenerator()->getType() !== GeneratorType::IDENTITY) {
1002 1004
                        $actualData[$name] = $property->getValue($entity);
1003
                    }
1004 1004
1005
                    break;
1006 1004
1007 1004
                case ($property instanceof ToOneAssociationMetadata):
1008
                    $actualData[$name] = $property->getValue($entity);
1009
                    break;
1010
            }
1011 1004
        }
1012
1013 1004
        if ( ! isset($this->originalEntityData[$oid])) {
1014
            throw new \RuntimeException('Cannot call recomputeSingleEntityChangeSet before computeChangeSet on an entity.');
1015 918
        }
1016 918
1017 918
        $originalData = $this->originalEntityData[$oid];
1018 918
        $changeSet = [];
1019 918
1020
        foreach ($actualData as $propName => $actualValue) {
1021 918
            $orgValue = $originalData[$propName] ?? null;
1022
1023 918
            if ($orgValue !== $actualValue) {
1024 918
                $changeSet[$propName] = [$orgValue, $actualValue];
1025 918
            }
1026
        }
1027 918
1028
        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...
1029
            if (isset($this->entityChangeSets[$oid])) {
1030
                $this->entityChangeSets[$oid] = array_merge($this->entityChangeSets[$oid], $changeSet);
1031 1004
            } else if ( ! isset($this->entityInsertions[$oid])) {
1032 135
                $this->entityChangeSets[$oid] = $changeSet;
1033
                $this->entityUpdates[$oid]    = $entity;
1034 1004
            }
1035
            $this->originalEntityData[$oid] = $actualData;
1036
        }
1037
    }
1038
1039
    /**
1040
     * Executes all entity insertions for entities of the specified type.
1041
     *
1042
     * @param ClassMetadata $class
1043 115
     *
1044
     * @return void
1045 115
     */
1046 115
    private function executeInserts(ClassMetadata $class) : void
1047 115
    {
1048 115
        $className      = $class->getClassName();
1049
        $persister      = $this->getEntityPersister($className);
1050 115
        $invoke         = $this->listenersInvoker->getSubscribedSystems($class, Events::postPersist);
1051 115
        $generationPlan = $class->getValueGenerationPlan();
1052 74
1053
        foreach ($this->entityInsertions as $oid => $entity) {
1054
            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...
1055 115
                continue;
1056 13
            }
1057
1058 13
            $persister->insert($entity);
1059
1060
            if ($generationPlan->containsDeferred()) {
1061 115
                // Entity has post-insert IDs
1062
                $oid = spl_object_hash($entity);
1063
                $id  = $this->em->getIdentifierFlattener()->flattenIdentifier($class, $persister->getIdentifier($entity));
1064
1065 81
                $this->entityIdentifiers[$oid] = $id;
1066
                $this->entityStates[$oid] = self::STATE_MANAGED;
1067
                $this->originalEntityData[$oid] = $id + $this->originalEntityData[$oid];
1068 111
1069
                $this->addToIdentityMap($entity);
1070 111
            }
1071 111
1072
            unset($this->entityInsertions[$oid]);
1073
1074 111
            if ($invoke !== ListenersInvoker::INVOKE_NONE) {
1075
                $eventArgs = new LifecycleEventArgs($entity, $this->em);
1076
1077
                $this->listenersInvoker->invoke($class, Events::postPersist, $entity, $eventArgs, $invoke);
1078
            }
1079
        }
1080
    }
1081
1082
    /**
1083 62
     * Executes all entity updates for entities of the specified type.
1084
     *
1085 62
     * @param \Doctrine\ORM\Mapping\ClassMetadata $class
1086 62
     *
1087 62
     * @return void
1088
     */
1089 62
    private function executeUpdates($class)
1090 62
    {
1091 26
        $className          = $class->getClassName();
1092
        $persister          = $this->getEntityPersister($className);
1093
        $preUpdateInvoke    = $this->listenersInvoker->getSubscribedSystems($class, Events::preUpdate);
1094 62
        $postUpdateInvoke   = $this->listenersInvoker->getSubscribedSystems($class, Events::postUpdate);
1095
1096
        foreach ($this->entityUpdates as $oid => $entity) {
1097 62
            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...
1098 62
                continue;
1099 62
            }
1100 62
1101
            if ($preUpdateInvoke != ListenersInvoker::INVOKE_NONE) {
1102
                $this->listenersInvoker->invoke($class, Events::preUpdate, $entity, new PreUpdateEventArgs($entity, $this->em, $this->getEntityChangeSet($entity)), $preUpdateInvoke);
1103
1104
                $this->recomputeSingleEntityChangeSet($class, $entity);
1105
            }
1106 62
1107 52
            if ( ! empty($this->entityChangeSets[$oid])) {
1108
//                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...
1109
//                \Doctrine\Common\Util\Debug::dump($this->entityChangeSets[$oid], 3);
1110 62
1111 62
                $persister->update($entity);
1112
            }
1113
1114 61
            unset($this->entityUpdates[$oid]);
1115
1116
            if ($postUpdateInvoke != ListenersInvoker::INVOKE_NONE) {
1117
                $this->listenersInvoker->invoke($class, Events::postUpdate, $entity, new LifecycleEventArgs($entity, $this->em), $postUpdateInvoke);
1118
            }
1119
        }
1120
    }
1121
1122
    /**
1123 1008
     * Executes all entity deletions for entities of the specified type.
1124
     *
1125 1008
     * @param \Doctrine\ORM\Mapping\ClassMetadata $class
1126 1008
     *
1127
     * @return void
1128
     */
1129 1008
    private function executeDeletions($class)
1130
    {
1131
        $className  = $class->getClassName();
1132
        $persister  = $this->getEntityPersister($className);
1133
        $invoke     = $this->listenersInvoker->getSubscribedSystems($class, Events::postRemove);
1134
1135
        foreach ($this->entityDeletions as $oid => $entity) {
1136 1008
            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...
1137
                continue;
1138 1008
            }
1139 1008
1140
            $persister->delete($entity);
1141 1008
1142 624
            unset(
1143
                $this->entityDeletions[$oid],
1144
                $this->entityIdentifiers[$oid],
1145 1008
                $this->originalEntityData[$oid],
1146
                $this->entityStates[$oid]
1147 1008
            );
1148
1149
            // Entity with this $oid after deletion treated as NEW, even if the $oid
1150
            // is obtained by a new entity because the old one went out of scope.
1151 1008
            //$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...
1152 1008
            if (! $class->isIdentifierComposite()) {
1153 886
                $property = $class->getProperty($class->getSingleIdentifierFieldName());
1154 846
1155
                if ($property instanceof FieldMetadata && $property->hasValueGenerator()) {
1156
                    $property->setValue($entity, null);
1157 839
                }
1158
            }
1159 839
1160 649
            if ($invoke !== ListenersInvoker::INVOKE_NONE) {
1161
                $eventArgs = new LifecycleEventArgs($entity, $this->em);
1162 649
1163
                $this->listenersInvoker->invoke($class, Events::postRemove, $entity, $eventArgs, $invoke);
1164
            }
1165 839
        }
1166
    }
1167 839
1168
    /**
1169
     * Gets the commit order.
1170 839
     *
1171 832
     * @return array
1172
     */
1173
    private function getCommitOrder()
1174 217
    {
1175 217
        $calc = new Internal\CommitOrderCalculator();
1176
1177 217
        // See if there are any new classes in the changeset, that are not in the
1178 189
        // commit order graph yet (don't have a node).
1179
        // We have to inspect changeSet to be able to correctly build dependencies.
1180 189
        // It is not possible to use IdentityMap here because post inserted ids
1181
        // are not yet available.
1182
        $newNodes = [];
1183 217
1184
        foreach (\array_merge($this->entityInsertions, $this->entityUpdates, $this->entityDeletions) as $entity) {
1185
            $class = $this->em->getClassMetadata(get_class($entity));
1186
1187
            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...
1188 1008
                continue;
1189
            }
1190
1191
            $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...
1192
1193
            $newNodes[] = $class;
1194
        }
1195
1196
        // Calculate dependencies for new nodes
1197
        while ($class = array_pop($newNodes)) {
1198
            foreach ($class->getDeclaredPropertiesIterator() as $property) {
1199
                if (! ($property instanceof ToOneAssociationMetadata && $property->isOwningSide())) {
1200
                    continue;
1201
                }
1202 1032
1203
                $targetClass = $this->em->getClassMetadata($property->getTargetEntity());
1204 1032
1205
                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...
1206 1032
                    $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...
1207
1208
                    $newNodes[] = $targetClass;
1209
                }
1210 1032
1211 1
                $weight = ! array_filter(
1212
                    $property->getJoinColumns(),
1213 1032
                    function (JoinColumnMetadata $joinColumn) { return $joinColumn->isNullable(); }
1214 1
                );
1215
1216
                $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...
1217 1032
1218 1
                // If the target class has mapped subclasses, these share the same dependency.
1219
                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...
1220
                    continue;
1221 1032
                }
1222
1223 1032
                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...
1224 269
                    $targetSubClass = $this->em->getClassMetadata($subClassName);
1225
1226
                    if ( ! $calc->hasNode($subClassName)) {
1227 1032
                        $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...
1228 5
1229
                        $newNodes[] = $targetSubClass;
1230 1032
                    }
1231
1232
                    $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...
1233
                }
1234
            }
1235
        }
1236
1237
        return $calc->sort();
1238
    }
1239 631
1240
    /**
1241 631
     * Schedules an entity for insertion into the database.
1242
     * If the entity already has an identifier, it will be added to the identity map.
1243
     *
1244
     * @param object $entity The entity to schedule for insertion.
1245
     *
1246
     * @return void
1247
     *
1248
     * @throws ORMInvalidArgumentException
1249
     * @throws \InvalidArgumentException
1250
     */
1251
    public function scheduleForInsert($entity)
1252
    {
1253 1
        $oid = spl_object_hash($entity);
1254
1255 1
        if (isset($this->entityUpdates[$oid])) {
1256
            throw new InvalidArgumentException("Dirty entity can not be scheduled for insertion.");
1257 1
        }
1258
1259
        if (isset($this->entityDeletions[$oid])) {
1260
            throw ORMInvalidArgumentException::scheduleInsertForRemovedEntity($entity);
1261 1
        }
1262
        if (isset($this->originalEntityData[$oid]) && ! isset($this->entityInsertions[$oid])) {
1263
            throw ORMInvalidArgumentException::scheduleInsertForManagedEntity($entity);
1264
        }
1265 1
1266 1
        if (isset($this->entityInsertions[$oid])) {
1267
            throw ORMInvalidArgumentException::scheduleInsertTwice($entity);
1268 1
        }
1269
1270
        $this->entityInsertions[$oid] = $entity;
1271
1272
        if (isset($this->entityIdentifiers[$oid])) {
1273
            $this->addToIdentityMap($entity);
1274
        }
1275
1276
        if ($entity instanceof NotifyPropertyChanged) {
1277
            $entity->addPropertyChangedListener($this);
1278
        }
1279
    }
1280
1281
    /**
1282
     * Checks whether an entity is scheduled for insertion.
1283
     *
1284 40
     * @param object $entity
1285
     *
1286 40
     * @return boolean
1287 40
     */
1288
    public function isScheduledForInsert($entity)
1289 40
    {
1290 1
        return isset($this->entityInsertions[spl_object_hash($entity)]);
1291
    }
1292 1
1293
    /**
1294
     * Schedules an entity for being updated.
1295 40
     *
1296 40
     * @param object $entity The entity to schedule for being updated.
1297
     *
1298
     * @return void
1299
     *
1300
     * @throws ORMInvalidArgumentException
1301
     */
1302
    public function scheduleForUpdate($entity) : void
1303
    {
1304
        $oid = spl_object_hash($entity);
1305
1306
        if ( ! isset($this->entityIdentifiers[$oid])) {
1307
            throw ORMInvalidArgumentException::entityHasNoIdentity($entity, "scheduling for update");
1308
        }
1309
1310
        if (isset($this->entityDeletions[$oid])) {
1311
            throw ORMInvalidArgumentException::entityIsRemoved($entity, "schedule for update");
1312
        }
1313
1314
        if ( ! isset($this->entityUpdates[$oid]) && ! isset($this->entityInsertions[$oid])) {
1315
            $this->entityUpdates[$oid] = $entity;
1316
        }
1317
    }
1318
1319 1
    /**
1320
     * INTERNAL:
1321 1
     * Schedules an extra update that will be executed immediately after the
1322
     * regular entity updates within the currently running commit cycle.
1323 1
     *
1324
     * Extra updates for entities are stored as (entity, changeset) tuples.
1325
     *
1326
     * @ignore
1327
     *
1328
     * @param object $entity    The entity for which to schedule an extra update.
1329
     * @param array  $changeset The changeset of the entity (what to update).
1330
     *
1331
     * @return void
1332
     */
1333
    public function scheduleExtraUpdate($entity, array $changeset) : void
1334 65
    {
1335
        $oid         = spl_object_hash($entity);
1336 65
        $extraUpdate = [$entity, $changeset];
1337
1338 65
        if (isset($this->extraUpdates[$oid])) {
1339 1
            [$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...
1340
1341
            $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...
1342
        }
1343 1
1344
        $this->extraUpdates[$oid] = $extraUpdate;
1345 1
    }
1346
1347
    /**
1348 65
     * Checks whether an entity is registered as dirty in the unit of work.
1349 1
     * Note: Is not very useful currently as dirty entities are only registered
1350
     * at commit time.
1351
     *
1352 64
     * @param object $entity
1353
     *
1354 64
     * @return boolean
1355
     */
1356 64
    public function isScheduledForUpdate($entity) : bool
1357 64
    {
1358 64
        return isset($this->entityUpdates[spl_object_hash($entity)]);
1359
    }
1360 64
1361
    /**
1362
     * Checks whether an entity is registered to be checked in the unit of work.
1363
     *
1364
     * @param object $entity
1365
     *
1366
     * @return boolean
1367
     */
1368
    public function isScheduledForDirtyCheck($entity) : bool
1369
    {
1370 17
        $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...
1371
1372 17
        return isset($this->scheduledForSynchronization[$rootEntityName][spl_object_hash($entity)]);
1373
    }
1374
1375
    /**
1376
     * INTERNAL:
1377
     * Schedules an entity for deletion.
1378
     *
1379
     * @param object $entity
1380
     *
1381
     * @return void
1382
     */
1383
    public function scheduleForDelete($entity)
1384
    {
1385
        $oid = spl_object_hash($entity);
1386
1387
        if (isset($this->entityInsertions[$oid])) {
1388
            if ($this->isInIdentityMap($entity)) {
1389
                $this->removeFromIdentityMap($entity);
1390
            }
1391
1392
            unset($this->entityInsertions[$oid], $this->entityStates[$oid]);
1393
1394
            return; // entity has not been persisted yet, so nothing more to do.
1395
        }
1396
1397
        if ( ! $this->isInIdentityMap($entity)) {
1398
            return;
1399
        }
1400
1401
        $this->removeFromIdentityMap($entity);
1402
1403
        unset($this->entityUpdates[$oid]);
1404
1405
        if ( ! isset($this->entityDeletions[$oid])) {
1406 1098
            $this->entityDeletions[$oid] = $entity;
1407
            $this->entityStates[$oid]    = self::STATE_REMOVED;
1408 1098
        }
1409 1098
    }
1410
1411 1098
    /**
1412 6
     * Checks whether an entity is registered as removed/deleted with the unit
1413
     * of work.
1414
     *
1415 1092
     * @param object $entity
1416 1092
     *
1417
     * @return boolean
1418 1092
     */
1419 83
    public function isScheduledForDelete($entity)
1420
    {
1421
        return isset($this->entityDeletions[spl_object_hash($entity)]);
1422 1092
    }
1423
1424 1092
    /**
1425
     * Checks whether an entity is scheduled for insertion, update or deletion.
1426
     *
1427
     * @param object $entity
1428
     *
1429
     * @return boolean
1430
     */
1431
    public function isEntityScheduled($entity)
1432
    {
1433
        $oid = spl_object_hash($entity);
1434
1435
        return isset($this->entityInsertions[$oid])
1436
            || isset($this->entityUpdates[$oid])
1437
            || isset($this->entityDeletions[$oid]);
1438 1045
    }
1439
1440 1045
    /**
1441
     * INTERNAL:
1442 1045
     * Registers an entity in the identity map.
1443 783
     * Note that entities in a hierarchy are registered with the class name of
1444
     * the root entity.
1445
     *
1446 1039
     * @ignore
1447 1035
     *
1448
     * @param object $entity The entity to register.
1449
     *
1450
     * @return boolean TRUE if the registration was successful, FALSE if the identity of
1451
     *                 the entity in question is already managed.
1452
     *
1453
     * @throws ORMInvalidArgumentException
1454 13
     */
1455 13
    public function addToIdentityMap($entity)
1456
    {
1457 13
        $classMetadata = $this->em->getClassMetadata(get_class($entity));
1458 5
        $identifier    = $this->entityIdentifiers[spl_object_hash($entity)];
1459
1460
        if (empty($identifier) || in_array(null, $identifier, true)) {
1461 10
            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...
1462 1
        }
1463
1464
        $idHash    = implode(' ', $identifier);
1465
        $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...
1466 10
1467
        if (isset($this->identityMap[$className][$idHash])) {
1468 5
            return false;
1469 1
        }
1470
1471 1
        $this->identityMap[$className][$idHash] = $entity;
1472
1473
        return true;
1474
    }
1475 4
1476 1
    /**
1477
     * Gets the state of an entity with regard to the current unit of work.
1478
     *
1479
     * @param object   $entity
1480 4
     * @param int|null $assume The state to assume if the state is not yet known (not MANAGED or REMOVED).
1481
     *                         This parameter can be set to improve performance of entity state detection
1482
     *                         by potentially avoiding a database lookup if the distinction between NEW and DETACHED
1483
     *                         is either known or does not matter for the caller of the method.
1484 4
     *
1485
     * @return int The entity state.
1486 5
     */
1487
    public function getEntityState($entity, $assume = null)
1488
    {
1489
        $oid = spl_object_hash($entity);
1490
1491
        if (isset($this->entityStates[$oid])) {
1492
            return $this->entityStates[$oid];
1493
        }
1494
1495
        if ($assume !== null) {
1496
            return $assume;
1497
        }
1498
1499
        // State can only be NEW or DETACHED, because MANAGED/REMOVED states are known.
1500
        // Note that you can not remember the NEW or DETACHED state in _entityStates since
1501
        // the UoW does not hold references to such objects and the object hash can be reused.
1502
        // More generally because the state may "change" between NEW/DETACHED without the UoW being aware of it.
1503
        $class     = $this->em->getClassMetadata(get_class($entity));
1504 5
        $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...
1505
        $id        = $persister->getIdentifier($entity);
1506
1507
        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...
1508
            return self::STATE_NEW;
1509
        }
1510
1511
        $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...
1512
1513
        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...
1514
            || ! $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...
1515
            || ! $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...
1516
        ) {
1517
            // Check for a version field, if available, to avoid a db lookup.
1518
            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...
1519
                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...
1520
                    ? self::STATE_DETACHED
1521 76
                    : self::STATE_NEW;
1522
            }
1523 76
1524 76
            // Last try before db lookup: check the identity map.
1525 76
            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...
1526
                return self::STATE_DETACHED;
1527 76
            }
1528
1529
            // db lookup
1530
            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...
1531 76
                return self::STATE_DETACHED;
1532
            }
1533 76
1534 76
            return self::STATE_NEW;
1535 76
        }
1536
1537
        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...
1538
            || ! $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...
1539 76
            || ! $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...
1540
            // if we have a pre insert generator we can't be sure that having an id
1541
            // really means that the entity exists. We have to verify this through
1542
            // the last resort: a db lookup
1543
1544
            // Last try before db lookup: check the identity map.
1545
            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...
1546
                return self::STATE_DETACHED;
1547
            }
1548
1549
            // db lookup
1550
            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...
1551
                return self::STATE_DETACHED;
1552
            }
1553
1554
            return self::STATE_NEW;
1555
        }
1556 6
1557
        return self::STATE_DETACHED;
1558 6
    }
1559
1560
    /**
1561
     * INTERNAL:
1562
     * Removes an entity from the identity map. This effectively detaches the
1563
     * entity from the persistence management of Doctrine.
1564
     *
1565
     * @ignore
1566
     *
1567
     * @param object $entity
1568
     *
1569
     * @return boolean
1570
     *
1571
     * @throws ORMInvalidArgumentException
1572
     */
1573 34
    public function removeFromIdentityMap($entity)
1574
    {
1575 34
        $oid           = spl_object_hash($entity);
1576
        $classMetadata = $this->em->getClassMetadata(get_class($entity));
1577 34
        $idHash        = implode(' ', $this->entityIdentifiers[$oid]);
1578 34
1579 34
        if ($idHash === '') {
1580
            throw ORMInvalidArgumentException::entityHasNoIdentity($entity, "remove from identity map");
1581
        }
1582
1583
        $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...
1584
1585
        if (isset($this->identityMap[$className][$idHash])) {
1586
            unset($this->identityMap[$className][$idHash]);
1587
            unset($this->readOnlyObjects[$oid]);
1588
1589 212
            //$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...
1590
1591 212
            return true;
1592
        }
1593 212
1594 31
        return false;
1595
    }
1596
1597 197
    /**
1598 197
     * INTERNAL:
1599
     * Gets an entity in the identity map by its identifier hash.
1600 197
     *
1601
     * @ignore
1602
     *
1603
     * @param string $idHash
1604 197
     * @param string $rootClassName
1605
     *
1606
     * @return object
1607
     */
1608
    public function getByIdHash($idHash, $rootClassName)
1609
    {
1610
        return $this->identityMap[$rootClassName][$idHash];
1611
    }
1612
1613
    /**
1614
     * INTERNAL:
1615
     * Tries to get an entity by its identifier hash. If no entity is found for
1616
     * the given hash, FALSE is returned.
1617
     *
1618
     * @ignore
1619
     *
1620
     * @param mixed  $idHash        (must be possible to cast it to string)
1621
     * @param string $rootClassName
1622
     *
1623
     * @return object|bool The found entity or FALSE.
1624
     */
1625
    public function tryGetByIdHash($idHash, $rootClassName)
1626
    {
1627
        $stringIdHash = (string) $idHash;
1628
1629
        return $this->identityMap[$rootClassName][$stringIdHash] ?? false;
1630 1028
    }
1631
1632 1028
    /**
1633
     * Checks whether an entity is registered in the identity map of this UnitOfWork.
1634 1028
     *
1635 1021
     * @param object $entity
1636
     *
1637
     * @return boolean
1638
     */
1639
    public function isInIdentityMap($entity)
1640
    {
1641
        $oid = spl_object_hash($entity);
1642
1643
        if (empty($this->entityIdentifiers[$oid])) {
1644
            return false;
1645
        }
1646
1647
        $classMetadata = $this->em->getClassMetadata(get_class($entity));
1648
        $idHash        = implode(' ', $this->entityIdentifiers[$oid]);
1649
1650
        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...
1651 1028
    }
1652
1653 1028
    /**
1654
     * INTERNAL:
1655 1028
     * Checks whether an identifier hash exists in the identity map.
1656 109
     *
1657
     * @ignore
1658
     *
1659 1028
     * @param string $idHash
1660
     * @param string $rootClassName
1661 1028
     *
1662
     * @return boolean
1663
     */
1664
    public function containsIdHash($idHash, $rootClassName)
1665
    {
1666
        return isset($this->identityMap[$rootClassName][$idHash]);
1667 1028
    }
1668
1669
    /**
1670 1028
     * Persists an entity as part of the current unit of work.
1671
     *
1672 234
     * @param object $entity The entity to persist.
1673 2
     *
1674
     * @return void
1675 234
     */
1676
    public function persist($entity)
1677 1028
    {
1678 1027
        $visited = [];
1679 1027
1680
        $this->doPersist($entity, $visited);
1681 1
    }
1682
1683 1
    /**
1684 1
     * Persists an entity as part of the current unit of work.
1685
     *
1686 1
     * This method is internally called during persist() cascades as it tracks
1687 1
     * the already visited entities to prevent infinite recursions.
1688
     *
1689
     * @param object $entity  The entity to persist.
1690
     * @param array  $visited The already visited entities.
1691
     *
1692
     * @return void
1693
     *
1694
     * @throws ORMInvalidArgumentException
1695
     * @throws UnexpectedValueException
1696
     */
1697 1028
    private function doPersist($entity, array &$visited)
1698 1021
    {
1699
        $oid = spl_object_hash($entity);
1700
1701
        if (isset($visited[$oid])) {
1702
            return; // Prevent infinite recursion
1703
        }
1704
1705
        $visited[$oid] = $entity; // Mark visited
1706
1707 64
        $class = $this->em->getClassMetadata(get_class($entity));
1708
1709 64
        // We assume NEW, so DETACHED entities result in an exception on flush (constraint violation).
1710
        // If we would detect DETACHED here we would throw an exception anyway with the same
1711 64
        // consequences (not recoverable/programming error), so just assuming NEW here
1712 64
        // lets us avoid some database lookups for entities with natural identifiers.
1713
        $entityState = $this->getEntityState($entity, self::STATE_NEW);
1714
1715
        switch ($entityState) {
1716
            case self::STATE_MANAGED:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

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

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

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

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

Loading history...
1717
                // Nothing to do, except if policy is "deferred explicit"
1718
                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...
1719
                    $this->scheduleForSynchronization($entity);
1720
                }
1721
                break;
1722
1723
            case self::STATE_NEW:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

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

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

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

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

Loading history...
1724
                $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...
1725
                break;
1726
1727
            case self::STATE_REMOVED:
1728 64
                // Entity becomes managed again
1729
                unset($this->entityDeletions[$oid]);
1730 64
                $this->addToIdentityMap($entity);
1731
1732 64
                $this->entityStates[$oid] = self::STATE_MANAGED;
1733 1
                break;
1734
1735
            case self::STATE_DETACHED:
1736 64
                // Can actually not happen right now since we assume STATE_NEW.
1737
                throw ORMInvalidArgumentException::detachedEntityCannot($entity, "persisted");
1738
1739
            default:
1740 64
                throw new UnexpectedValueException("Unexpected entity state: $entityState." . self::objToStr($entity));
1741
        }
1742 64
1743 64
        $this->cascadePersist($entity, $visited);
1744
    }
1745
1746 64
    /**
1747 64
     * Deletes an entity as part of the current unit of work.
1748
     *
1749 2
     * @param object $entity The entity to remove.
1750
     *
1751 64
     * @return void
1752 64
     */
1753
    public function remove($entity)
1754 64
    {
1755 8
        $visited = [];
1756
1757
        $this->doRemove($entity, $visited);
1758 64
    }
1759 64
1760
    /**
1761
     * Deletes an entity as part of the current unit of work.
1762
     *
1763
     * This method is internally called during delete() cascades as it tracks
1764
     * the already visited entities to prevent infinite recursions.
1765
     *
1766
     * @param object $entity  The entity to delete.
1767 64
     * @param array  $visited The map of the already visited entities.
1768
     *
1769
     * @return void
1770
     *
1771
     * @throws ORMInvalidArgumentException If the instance is a detached entity.
1772
     * @throws UnexpectedValueException
1773
     */
1774
    private function doRemove($entity, array &$visited)
1775
    {
1776
        $oid = spl_object_hash($entity);
1777
1778
        if (isset($visited[$oid])) {
1779
            return; // Prevent infinite recursion
1780
        }
1781 40
1782
        $visited[$oid] = $entity; // mark visited
1783 40
1784
        // Cascade first, because scheduleForDelete() removes the entity from the identity map, which
1785 40
        // can cause problems when a lazy proxy has to be initialized for the cascade operation.
1786
        $this->cascadeRemove($entity, $visited);
1787
1788
        $class       = $this->em->getClassMetadata(get_class($entity));
1789
        $entityState = $this->getEntityState($entity);
1790
1791
        switch ($entityState) {
1792
            case self::STATE_NEW:
1793
            case self::STATE_REMOVED:
1794
                // nothing to do
1795
                break;
1796
1797
            case self::STATE_MANAGED:
1798
                $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...
1799
1800
                if ($invoke !== ListenersInvoker::INVOKE_NONE) {
1801
                    $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...
1802
                }
1803 40
1804
                $this->scheduleForDelete($entity);
1805 40
                break;
1806
1807 40
            case self::STATE_DETACHED:
1808 4
                throw ORMInvalidArgumentException::detachedEntityCannot($entity, "removed");
1809
            default:
1810 4
                throw new UnexpectedValueException("Unexpected entity state: $entityState." . self::objToStr($entity));
1811 4
        }
1812
1813
    }
1814 4
1815
    /**
1816
     * Refreshes the state of the given entity from the database, overwriting
1817 40
     * any local, unpersisted changes.
1818
     *
1819
     * @param object $entity The entity to refresh.
1820
     *
1821
     * @return void
1822
     *
1823 40
     * @throws InvalidArgumentException If the entity is not MANAGED.
1824
     */
1825 40
    public function refresh($entity)
1826
    {
1827 39
        $visited = [];
1828
1829
        $this->doRefresh($entity, $visited);
1830 39
    }
1831 5
1832
    /**
1833 5
     * Executes a refresh operation on an entity.
1834
     *
1835 35
     * @param object $entity  The entity to refresh.
1836 3
     * @param array  $visited The already visited entities during cascades.
1837 35
     *
1838
     * @return void
1839 35
     *
1840
     * @throws ORMInvalidArgumentException If the entity is not MANAGED.
1841 35
     */
1842
    private function doRefresh($entity, array &$visited)
1843 14
    {
1844 14
        $oid = spl_object_hash($entity);
1845
1846
        if (isset($visited[$oid])) {
1847
            return; // Prevent infinite recursion
1848 24
        }
1849
1850
        $visited[$oid] = $entity; // mark visited
1851 35
1852
        $class = $this->em->getClassMetadata(get_class($entity));
1853
1854 2
        if ($this->getEntityState($entity) !== self::STATE_MANAGED) {
1855 1
            throw ORMInvalidArgumentException::entityNotManaged($entity);
1856 1
        }
1857 1
1858
        $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...
1859
            array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
1860
            $entity
1861 1
        );
1862 1
1863
        $this->cascadeRefresh($entity, $visited);
1864 1
    }
1865
1866
    /**
1867
     * Cascades a refresh operation to associated entities.
1868 38
     *
1869 4
     * @param object $entity
1870 4
     * @param array  $visited
1871
     *
1872
     * @return void
1873 4
     */
1874 1
    private function cascadeRefresh($entity, array &$visited)
1875
    {
1876
        $class = $this->em->getClassMetadata(get_class($entity));
1877
1878 37
        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...
1879
            if (! ($association instanceof AssociationMetadata && in_array('refresh', $association->getCascade()))) {
1880 37
                continue;
1881 30
            }
1882 4
1883
            $relatedEntities = $association->getValue($entity);
1884
1885 30
            switch (true) {
1886
                case ($relatedEntities instanceof PersistentCollection):
1887
                    // Unwrap so that foreach() does not initialize
1888 37
                    $relatedEntities = $relatedEntities->unwrap();
1889
                    // break; is commented intentionally!
1890
1891
                case ($relatedEntities instanceof Collection):
1892
                case (is_array($relatedEntities)):
1893 38
                    foreach ($relatedEntities as $relatedEntity) {
1894 6
                        $this->doRefresh($relatedEntity, $visited);
1895
                    }
1896
                    break;
1897
1898 38
                case ($relatedEntities !== null):
1899
                    $this->doRefresh($relatedEntities, $visited);
1900 38
                    break;
1901
1902 38
                default:
1903
                    // Do nothing
1904
            }
1905
        }
1906
    }
1907
1908
    /**
1909
     * Cascades the save operation to associated entities.
1910
     *
1911
     * @param object $entity
1912 38
     * @param array  $visited
1913
     *
1914 38
     * @throws ORMInvalidArgumentException
1915
     *
1916
     * @return void
1917
     */
1918
    private function cascadePersist($entity, array &$visited)
1919
    {
1920
        $class = $this->em->getClassMetadata(get_class($entity));
1921
1922
        if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
1923
            // nothing to do - proxy is not initialized, therefore we don't do anything with it
1924
            return;
1925
        }
1926
1927 6
        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...
1928
            if (! ($association instanceof AssociationMetadata && in_array('persist', $association->getCascade()))) {
1929 6
                continue;
1930 6
            }
1931
1932 6
            /** @var AssociationMetadata $association */
1933 6
            $relatedEntities = $association->getValue($entity);
1934
            $targetEntity    = $association->getTargetEntity();
1935 6
1936
            switch (true) {
1937
                case ($relatedEntities instanceof PersistentCollection):
1938
                    // Unwrap so that foreach() does not initialize
1939 1
                    $relatedEntities = $relatedEntities->unwrap();
1940 1
                    // break; is commented intentionally!
1941
1942 1
                case ($relatedEntities instanceof Collection):
1943 1
                case (is_array($relatedEntities)):
1944
                    if (! ($association instanceof ToManyAssociationMetadata)) {
1945 1
                        throw ORMInvalidArgumentException::invalidAssociation(
1946
                            $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...
1947 1
                            $association,
1948
                            $relatedEntities
1949
                        );
1950
                    }
1951
1952
                    foreach ($relatedEntities as $relatedEntity) {
1953
                        $this->doPersist($relatedEntity, $visited);
1954
                    }
1955
1956
                    break;
1957 12
1958
                case ($relatedEntities !== null):
1959 12
                    if (! $relatedEntities instanceof $targetEntity) {
1960
                        throw ORMInvalidArgumentException::invalidAssociation(
1961 12
                            $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...
1962 12
                            $association,
1963
                            $relatedEntities
1964
                        );
1965
                    }
1966
1967
                    $this->doPersist($relatedEntities, $visited);
1968
                    break;
1969
1970
                default:
1971
                    // Do nothing
1972
            }
1973 15
        }
1974
    }
1975 15
1976
    /**
1977 15
     * Cascades the delete operation to associated entities.
1978
     *
1979
     * @param object $entity
1980
     * @param array  $visited
1981 15
     *
1982
     * @return void
1983 15
     */
1984 15
    private function cascadeRemove($entity, array &$visited)
1985 13
    {
1986 12
        $entitiesToCascade = [];
1987
        $class             = $this->em->getClassMetadata(get_class($entity));
1988
1989
        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...
1990 13
            if (! ($association instanceof AssociationMetadata && in_array('remove', $association->getCascade()))) {
1991 13
                continue;
1992 13
            }
1993 13
1994 13
            if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
1995 13
                $entity->initializeProxy();
1996
            }
1997 13
1998 3
            $relatedEntities = $association->getValue($entity);
1999 3
2000 3
            switch (true) {
2001
                case ($relatedEntities instanceof Collection):
2002
                case (\is_array($relatedEntities)):
2003 13
                    // If its a PersistentCollection initialization is intended! No unwrap!
2004 13
                    foreach ($relatedEntities as $relatedEntity) {
2005
                        $entitiesToCascade[] = $relatedEntity;
2006 13
                    }
2007
                    break;
2008
2009
                case ($relatedEntities !== null):
2010
                    $entitiesToCascade[] = $relatedEntities;
2011
                    break;
2012
2013
                default:
2014
                    // Do nothing
2015
            }
2016
        }
2017
2018 16
        foreach ($entitiesToCascade as $relatedEntity) {
2019
            $this->doRemove($relatedEntity, $visited);
2020 16
        }
2021
    }
2022 16
2023 16
    /**
2024
     * Acquire a lock on the given entity.
2025
     *
2026
     * @param object $entity
2027
     * @param int    $lockMode
2028
     * @param int    $lockVersion
2029
     *
2030
     * @return void
2031
     *
2032
     * @throws ORMInvalidArgumentException
2033
     * @throws TransactionRequiredException
2034
     * @throws OptimisticLockException
2035 16
     * @throws \InvalidArgumentException
2036
     */
2037 16
    public function lock($entity, $lockMode, $lockVersion = null)
2038
    {
2039 16
        if ($entity === null) {
2040
            throw new \InvalidArgumentException("No entity passed to UnitOfWork#lock().");
2041
        }
2042
2043 16
        if ($this->getEntityState($entity, self::STATE_DETACHED) !== self::STATE_MANAGED) {
2044
            throw ORMInvalidArgumentException::entityNotManaged($entity);
2045 16
        }
2046
2047 16
        $class = $this->em->getClassMetadata(get_class($entity));
2048
2049
        switch (true) {
2050
            case LockMode::OPTIMISTIC === $lockMode:
2051 16
                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...
2052 16
                    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...
2053
                }
2054
2055
                if ($lockVersion === null) {
2056 16
                    return;
2057 16
                }
2058
2059
                if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
2060
                    $entity->initializeProxy();
2061
                }
2062
2063
                $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...
2064
2065
                if ($entityVersion !== $lockVersion) {
2066
                    throw OptimisticLockException::lockFailedVersionMismatch($entity, $lockVersion, $entityVersion);
2067 16
                }
2068
2069 16
                break;
2070
2071 16
            case LockMode::NONE === $lockMode:
2072 16
            case LockMode::PESSIMISTIC_READ === $lockMode:
2073
            case LockMode::PESSIMISTIC_WRITE === $lockMode:
2074
                if (!$this->em->getConnection()->isTransactionActive()) {
2075
                    throw TransactionRequiredException::transactionRequired();
2076 16
                }
2077 5
2078
                $oid = spl_object_hash($entity);
2079
2080 5
                $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...
2081
                    array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
2082 5
                    $lockMode
2083
                );
2084
                break;
2085
2086
            default:
2087 5
                // Do nothing
2088
        }
2089
    }
2090 5
2091
    /**
2092
     * Clears the UnitOfWork.
2093
     *
2094
     * @return void
2095
     */
2096 5
    public function clear()
2097
    {
2098
        $this->entityPersisters =
2099
        $this->collectionPersisters =
2100 16
        $this->eagerLoadingEntities =
2101
        $this->identityMap =
2102
        $this->entityIdentifiers =
2103
        $this->originalEntityData =
2104
        $this->entityChangeSets =
2105
        $this->entityStates =
2106
        $this->scheduledForSynchronization =
2107
        $this->entityInsertions =
2108
        $this->entityUpdates =
2109
        $this->entityDeletions =
2110 13
        $this->collectionDeletions =
2111
        $this->collectionUpdates =
2112 13
        $this->extraUpdates =
2113
        $this->readOnlyObjects =
2114 13
        $this->visitedCollections =
2115 13
        $this->nonCascadedNewDetectedEntities =
2116
        $this->orphanRemovals = [];
2117
    }
2118
2119 13
    /**
2120 3
     * INTERNAL:
2121
     * Schedules an orphaned entity for removal. The remove() operation will be
2122
     * invoked on that entity at the beginning of the next commit of this
2123 3
     * UnitOfWork.
2124
     *
2125 2
     * @ignore
2126
     *
2127
     * @param object $entity
2128
     *
2129
     * @return void
2130 3
     */
2131 1
    public function scheduleOrphanRemoval($entity)
2132
    {
2133 3
        $this->orphanRemovals[spl_object_hash($entity)] = $entity;
2134
    }
2135
2136
    /**
2137
     * INTERNAL:
2138
     * Cancels a previously scheduled orphan removal.
2139 3
     *
2140
     * @ignore
2141
     *
2142
     * @param object $entity
2143 13
     *
2144
     * @return void
2145
     */
2146
    public function cancelOrphanRemoval($entity)
2147
    {
2148
        unset($this->orphanRemovals[spl_object_hash($entity)]);
2149
    }
2150
2151
    /**
2152
     * INTERNAL:
2153
     * Schedules a complete collection for removal when this UnitOfWork commits.
2154 38
     *
2155
     * @param PersistentCollection $coll
2156 38
     *
2157
     * @return void
2158 38
     */
2159 38
    public function scheduleCollectionDeletion(PersistentCollection $coll)
2160
    {
2161
        $coid = spl_object_hash($coll);
2162
2163 38
        // TODO: if $coll is already scheduled for recreation ... what to do?
2164 15
        // Just remove $coll from the scheduled recreations?
2165
        unset($this->collectionUpdates[$coid]);
2166 15
2167 9
        $this->collectionDeletions[$coid] = $coll;
2168 1
    }
2169
2170
    /**
2171 8
     * @param PersistentCollection $coll
2172
     *
2173 5
     * @return bool
2174
     */
2175
    public function isCollectionScheduledForDeletion(PersistentCollection $coll)
2176 8
    {
2177 8
        return isset($this->collectionDeletions[spl_object_hash($coll)]);
2178
    }
2179 7
2180 14
    /**
2181
     * INTERNAL:
2182
     * Creates a new instance of the mapped class, without invoking the constructor.
2183 38
     * This is only meant to be used internally, and should not be consumed by end users.
2184
     *
2185
     * @ignore
2186
     *
2187
     * @param ClassMetadata $class
2188
     *
2189
     * @return EntityManagerAware|object
2190
     */
2191
    public function newInstance(ClassMetadata $class)
2192
    {
2193 1028
        $entity = $this->instantiator->instantiate($class->getClassName());
2194
2195 1028
        if ($entity instanceof EntityManagerAware) {
2196
            $entity->injectEntityManager($this->em, $class);
2197 1028
        }
2198 1028
2199
        return $entity;
2200
    }
2201
2202 1028
    /**
2203 650
     * INTERNAL:
2204
     * Creates an entity. Used for reconstitution of persistent entities.
2205
     *
2206 650
     * Internal note: Highly performance-sensitive method.
2207
     *
2208 21
     * @ignore
2209
     *
2210
     * @param string $className The name of the entity class.
2211
     * @param array  $data      The data for the entity.
2212 589
     * @param array  $hints     Any hints to account for during reconstitution/lookup of the entity.
2213 554
     *
2214 3
     * @return object The managed entity instance.
2215 3
     *
2216
     * @todo Rename: getOrCreateEntity
2217
     */
2218
    public function createEntity($className, array $data, &$hints = [])
2219
    {
2220
        $class  = $this->em->getClassMetadata($className);
2221 551
        $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...
2222 282
        $idHash = implode(' ', $id);
2223
2224
        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...
2225 551
            $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...
2226
            $oid = spl_object_hash($entity);
2227 579
2228 246
            if (
2229 4
                isset($hints[Query::HINT_REFRESH])
2230 4
                && isset($hints[Query::HINT_REFRESH_ENTITY])
2231
                && ($unmanagedProxy = $hints[Query::HINT_REFRESH_ENTITY]) !== $entity
2232
                && $unmanagedProxy instanceof GhostObjectInterface
2233
                && $this->isIdentifierEquals($unmanagedProxy, $entity)
2234
            ) {
2235
                // We will hydrate the given un-managed proxy anyway:
2236 242
                // continue work, but consider it the entity from now on
2237 242
                $entity = $unmanagedProxy;
2238
            }
2239 644
2240
            if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
2241
                $entity->setProxyInitializer(null);
2242
2243 1021
                if ($entity instanceof NotifyPropertyChanged) {
2244
                    $entity->addPropertyChangedListener($this);
2245
                }
2246
            } else {
2247
                if ( ! isset($hints[Query::HINT_REFRESH])
2248
                    || (isset($hints[Query::HINT_REFRESH_ENTITY]) && $hints[Query::HINT_REFRESH_ENTITY] !== $entity)) {
2249
                    return $entity;
2250
                }
2251
            }
2252
2253 64
            // inject EntityManager upon refresh.
2254
            if ($entity instanceof EntityManagerAware) {
2255 64
                $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...
2256
            }
2257 64
2258 64
            $this->originalEntityData[$oid] = $data;
2259
        } else {
2260
            $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...
2261
            $oid    = spl_object_hash($entity);
2262 64
2263
            $this->entityIdentifiers[$oid]  = $id;
2264 64
            $this->entityStates[$oid]       = self::STATE_MANAGED;
2265 26
            $this->originalEntityData[$oid] = $data;
2266 6
2267
            $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...
2268
        }
2269 26
2270
        if ($entity instanceof NotifyPropertyChanged) {
2271
            $entity->addPropertyChangedListener($this);
2272 26
        }
2273 19
2274
        foreach ($data as $field => $value) {
2275 20
            $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...
2276 10
2277
            if ($property instanceof FieldMetadata) {
2278 20
                $property->setValue($entity, $value);
2279
            }
2280 19
        }
2281 7
2282 7
        // Loading the entity right here, if its in the eager loading map get rid of it there.
2283
        unset($this->eagerLoadingEntities[$class->getRootClassName()][$idHash]);
2284 26
2285
        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...
2286
            unset($this->eagerLoadingEntities[$class->getRootClassName()]);
2287
        }
2288
2289 64
        // Properly initialize any unfetched associations, if partial objects are not allowed.
2290 16
        if (isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) {
2291
            return $entity;
2292 64
        }
2293
2294
        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...
2295
            if (! ($association instanceof AssociationMetadata)) {
2296
                continue;
2297
            }
2298
2299
            // Check if the association is not among the fetch-joined associations already.
2300
            if (isset($hints['fetchAlias']) && isset($hints['fetched'][$hints['fetchAlias']][$field])) {
2301
                continue;
2302
            }
2303
2304
            $targetEntity = $association->getTargetEntity();
2305
            $targetClass  = $this->em->getClassMetadata($targetEntity);
2306
2307 11
            if ($association instanceof ToManyAssociationMetadata) {
2308
                $associationValue = $association->getValue($entity);
2309 11
2310 1
                // Ignore if its a cached collection
2311
                if (isset($hints[Query::HINT_CACHE_ENABLED]) &&
2312
                    $associationValue instanceof PersistentCollection
2313 10
                    && $associationValue->isInitialized()
2314 1
                    && ! $associationValue->isDirty()
2315
                ) {
2316
                    continue;
2317 9
                }
2318
2319
                $hasDataField = isset($data[$field]);
2320 9
2321 6
                // use the given collection
2322 2
                if ($hasDataField && $data[$field] instanceof PersistentCollection) {
2323
                    $data[$field]->setOwner($entity, $association);
2324
2325 4
                    $association->setValue($entity, $data[$field]);
2326
2327
                    $this->originalEntityData[$oid][$field] = $data[$field];
2328
2329 4
                    continue;
2330 1
                }
2331
2332
                // Inject collection
2333 4
                $pColl = $association->wrap($entity, $hasDataField ? $data[$field] : [], $this->em);
2334
2335 4
                $pColl->setInitialized($hasDataField);
2336 2
2337
                $association->setValue($entity, $pColl);
2338
2339 2
                if ($association->getFetchMode() === FetchMode::EAGER) {
2340
                    $this->loadCollection($pColl);
2341 3
                    $pColl->takeSnapshot();
2342 3
                }
2343 1
2344 3
                $this->originalEntityData[$oid][$field] = $pColl;
2345 2
2346
                continue;
2347
            }
2348 1
2349
            if (! $association->isOwningSide()) {
2350 1
                // use the given entity association
2351 1
                if (isset($data[$field]) && is_object($data[$field]) &&
2352
                    isset($this->entityStates[spl_object_hash($data[$field])])) {
2353
                    $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...
2354 1
2355
                    $association->setValue($entity, $data[$field]);
2356
                    $inverseAssociation->setValue($data[$field], $entity);
2357
2358
                    $this->originalEntityData[$oid][$field] = $data[$field];
2359 3
2360
                    continue;
2361
                }
2362
2363
                // Inverse side of x-to-one can never be lazy
2364
                $persister = $this->getEntityPersister($targetEntity);
2365
2366 1008
                $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...
2367
2368 1008
                continue;
2369
            }
2370
2371
            // use the entity association
2372
            if (isset($data[$field]) && is_object($data[$field]) && isset($this->entityStates[spl_object_hash($data[$field])])) {
2373
                $association->setValue($entity, $data[$field]);
2374
2375
                $this->originalEntityData[$oid][$field] = $data[$field];
2376
2377
                continue;
2378 1218
            }
2379
2380 1218
            $associatedId = [];
2381 1217
2382 1217
            // TODO: Is this even computed right in all cases of composite keys?
2383 1217
            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...
2384 1217
                /** @var JoinColumnMetadata $joinColumn */
2385 1217
                $joinColumnName = $joinColumn->getColumnName();
2386 1217
                $joinColumnValue = $data[$joinColumnName] ?? null;
2387 1217
                $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...
2388 1217
2389 1217
                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...
2390 1217
                    // the missing key is part of target's entity primary key
2391 1217
                    $associatedId = [];
2392 1217
2393 1217
                    continue;
2394 1217
                }
2395 1217
2396
                $associatedId[$targetField] = $joinColumnValue;
2397 3
            }
2398 3
2399
            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...
2400
                // Foreign key is NULL
2401 1218
                $association->setValue($entity, null);
2402 7
                $this->originalEntityData[$oid][$field] = null;
2403
2404 1218
                continue;
2405
            }
2406
2407
            // @todo guilhermeblanco Can we remove the need of this somehow?
2408
            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...
2409
                $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...
2410
            }
2411
2412
            // Foreign key is set
2413
            // Check identity map first
2414
            // FIXME: Can break easily with composite keys if join column values are in
2415
            //        wrong order. The correct order is the one in ClassMetadata#identifier.
2416
            $relatedIdHash = implode(' ', $associatedId);
2417
2418 17
            switch (true) {
2419
                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...
Coding Style introduced by
case statements should be defined using a colon.

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

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

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

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

Loading history...
2420 17
                    $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...
2421 17
2422
                    // If this is an uninitialized proxy, we are deferring eager loads,
2423
                    // this association is marked as eager fetch, and its an uninitialized proxy (wtf!)
2424
                    // then we can append this entity for eager loading!
2425
                    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...
2426
                        $newValue instanceof GhostObjectInterface &&
2427
                        isset($hints[self::HINT_DEFEREAGERLOAD]) &&
2428
                        $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...
2429
                        ! $newValue->isProxyInitialized()
2430
                    ) {
2431
2432
                        $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...
2433 112
                    }
2434
2435 112
                    break;
2436 112
2437
                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...
Coding Style introduced by
case statements should be defined using a colon.

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

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

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

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

Loading history...
2438
                    // If it might be a subtype, it can not be lazy. There isn't even
2439
                    // a way to solve this with deferred eager loading, which means putting
2440
                    // an entity with subclasses at a *-to-one location is really bad! (performance-wise)
2441
                    $persister = $this->getEntityPersister($targetEntity);
2442
                    $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...
2443
                    break;
2444
2445
                default:
2446 13
                    // Proxies do not carry any kind of original entity data until they're fully loaded/initialized
2447
                    $managedData = [];
2448 13
2449
                    $normalizedAssociatedId = $this->normalizeIdentifier->__invoke(
2450
                        $this->em,
2451
                        $targetClass,
0 ignored issues
show
Documentation introduced by
$targetClass is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2452 13
                        $associatedId
2453
                    );
2454 13
2455 13
                    switch (true) {
2456
                        // We are negating the condition here. Other cases will assume it is valid!
2457
                        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...
2458
                            $newValue = $this->em->getProxyFactory()->getProxy($targetClass, $normalizedAssociatedId);
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...
2459
                            break;
2460
2461
                        // Deferred eager load only works for single identifier classes
2462
                        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...
2463
                            // TODO: Is there a faster approach?
2464
                            $this->eagerLoadingEntities[$targetClass->getRootClassName()][$relatedIdHash] = current($normalizedAssociatedId);
0 ignored issues
show
Bug introduced by
The method getRootClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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

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

Loading history...
2465
2466
                            $newValue = $this->em->getProxyFactory()->getProxy($targetClass, $normalizedAssociatedId);
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...
2467
                            break;
2468
2469
                        default:
2470
                            // TODO: This is very imperformant, ignore it?
2471
                            $newValue    = $this->em->find($targetEntity, $normalizedAssociatedId);
2472 668
                            // Needed to re-assign original entity data for freshly loaded entity
2473
                            $managedData = $this->originalEntityData[spl_object_hash($newValue)];
2474 668
                            break;
2475
                    }
2476 668
2477 4
                    // @TODO using `$associatedId` here seems to be risky.
2478
                    $this->registerManaged($newValue, $associatedId, $managedData);
2479
2480 668
                    break;
2481
            }
2482
2483
            $this->originalEntityData[$oid][$field] = $newValue;
2484
            $association->setValue($entity, $newValue);
2485
2486
            if (
2487
                $association->getInversedBy()
2488
                && $association instanceof OneToOneAssociationMetadata
2489
                // @TODO refactor this
2490
                // we don't want to set any values in un-initialized proxies
2491
                && ! (
2492
                    $newValue instanceof GhostObjectInterface
2493
                    && ! $newValue->isProxyInitialized()
2494
                )
2495
            ) {
2496
                $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...
2497
2498
                $inverseAssociation->setValue($newValue, $entity);
2499 806
            }
2500
        }
2501 806
2502
        // defer invoking of postLoad event to hydration complete step
2503
        $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...
2504 806
2505 806
        return $entity;
2506
    }
2507 806
2508 310
    /**
2509 310
     * @return void
2510
     */
2511
    public function triggerEagerLoads()
2512 310
    {
2513 310
        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...
2514 310
            return;
2515 310
        }
2516 310
2517
        // avoid infinite recursion
2518
        $eagerLoadingEntities       = $this->eagerLoadingEntities;
2519
        $this->eagerLoadingEntities = [];
2520
2521
        foreach ($eagerLoadingEntities as $entityName => $ids) {
2522 2
            if ( ! $ids) {
2523 2
                continue;
2524
            }
2525
2526 2
            $class = $this->em->getClassMetadata($entityName);
2527
2528
            $this->getEntityPersister($entityName)->loadAll(
2529 308
                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...
2530 21
            );
2531
        }
2532 21
    }
2533
2534 21
    /**
2535 21
     * Initializes (loads) an uninitialized persistent collection of an entity.
2536
     *
2537
     * @param \Doctrine\ORM\PersistentCollection $collection The collection to initialize.
2538 289
     *
2539
     * @return void
2540
     *
2541 289
     * @todo Maybe later move to EntityManager#initialize($proxyOrCollection). See DDC-733.
2542 71
     */
2543
    public function loadCollection(PersistentCollection $collection)
2544
    {
2545
        $association = $collection->getMapping();
2546 308
        $persister   = $this->getEntityPersister($association->getTargetEntity());
2547
2548 111
        if ($association instanceof OneToManyAssociationMetadata) {
2549 3
            $persister->loadOneToManyCollection($association, $collection->getOwner(), $collection);
2550
        } else {
2551
            $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...
2552 308
        }
2553
2554
        $collection->setInitialized(true);
2555 665
    }
2556 665
2557
    /**
2558 665
     * Gets the identity map of the UnitOfWork.
2559 665
     *
2560 665
     * @return array
2561
     */
2562 665
    public function getIdentityMap()
2563
    {
2564 665
        return $this->identityMap;
2565 2
    }
2566
2567
    /**
2568 665
     * Gets the original data of an entity. The original data is the data that was
2569
     * present at the time the entity was reconstituted from the database.
2570
     *
2571 805
     * @param object $entity
2572 219
     *
2573
     * @return array
2574
     */
2575 702
    public function getOriginalEntityData($entity)
2576 702
    {
2577 702
        $oid = spl_object_hash($entity);
2578
2579
        return $this->originalEntityData[$oid] ?? [];
2580
    }
2581
2582 702
    /**
2583
     * @ignore
2584 702
     *
2585
     * @param object $entity
2586
     * @param array  $data
2587
     *
2588
     * @return void
2589 702
     */
2590 33
    public function setOriginalEntityData($entity, array $data)
2591
    {
2592
        $this->originalEntityData[spl_object_hash($entity)] = $data;
2593 669
    }
2594
2595 586
    /**
2596 260
     * INTERNAL:
2597
     * Sets a property value of the original data array of an entity.
2598
     *
2599 564
     * @ignore
2600
     *
2601
     * @param string $oid
2602 564
     * @param string $property
2603 485
     * @param mixed  $value
2604
     *
2605
     * @return void
2606 64
     */
2607
    public function setOriginalEntityProperty($oid, $property, $value)
2608 2
    {
2609
        $this->originalEntityData[$oid][$property] = $value;
2610 2
    }
2611 2
2612
    /**
2613 2
     * Gets the identifier of an entity.
2614
     * The returned value is always an array of identifier values. If the entity
2615
     * has a composite identifier then the identifier values are in the same
2616
     * order as the identifier field names as returned by ClassMetadata#getIdentifierFieldNames().
2617 62
     *
2618
     * @param object $entity
2619 62
     *
2620
     * @return array The identifier values.
2621
     */
2622
    public function getEntityIdentifier($entity)
2623 485
    {
2624 38
        return $this->entityIdentifiers[spl_object_hash($entity)];
2625 38
    }
2626
2627 38
    /**
2628
     * Processes an entity instance to extract their identifier values.
2629
     *
2630 478
     * @param object $entity The entity instance.
2631
     *
2632
     * @return mixed A scalar value.
2633 478
     *
2634 478
     * @throws \Doctrine\ORM\ORMInvalidArgumentException
2635 478
     */
2636
    public function getSingleIdentifierValue($entity)
2637 478
    {
2638
        $class     = $this->em->getClassMetadata(get_class($entity));
2639 287
        $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...
2640
2641 287
        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...
2642
            throw ORMInvalidArgumentException::invalidCompositeIdentifier();
2643
        }
2644 284
2645
        $values = $this->isInIdentityMap($entity)
2646
            ? $this->getEntityIdentifier($entity)
2647 478
            : $persister->getIdentifier($entity);
2648
2649 287
        return $values[$class->identifier[0]] ?? null;
0 ignored issues
show
Bug introduced by
Accessing identifier on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

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...
2650 287
    }
2651
2652 287
    /**
2653
     * @param array  $id
2654
     * @param string $rootClassName
2655 284
     *
2656 281
     * @return GhostObjectInterface|object
2657
     */
2658
    private function tryGetByIdOrLoadProxy(array $id, string $rootClassName)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
2659
    {
2660
        if ($fetched = $this->tryGetById($id, $rootClassName)) {
2661
            return $fetched;
2662
        }
2663 284
2664
        $class = $this->em->getClassMetadata($rootClassName);
2665
2666 284
        if ($class->getSubClasses()) {
0 ignored issues
show
Bug introduced by
The method getSubClasses() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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

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

Loading history...
2667 166
            // can't do this for inheritance trees!
2668
            // @TODO fetching from the EntityManager feels dirty here
2669
            return $this->em->find($rootClassName, $id);
2670
        }
2671
2672 166
        $sortedId = [];
2673 166
2674 166
        foreach ($class->identifier as $idField) {
0 ignored issues
show
Bug introduced by
Accessing identifier on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2675 166
            $sortedId[$idField] = $id[$idField];
2676 166
        }
2677
2678
        $proxy = $this->em->getProxyFactory()->getProxy($class, $sortedId);
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...
2679
2680
        $this->registerManaged($proxy, $sortedId, []);
2681 166
2682
        return $proxy;
2683 192
    }
2684
2685
    /**
2686
     * Tries to find an entity with the given identifier in the identity map of
2687 30
     * this UnitOfWork.
2688 30
     *
2689
     * @param mixed  $id            The entity identifier to look for.
2690
     * @param string $rootClassName The name of the root class of the mapped entity hierarchy.
2691
     *
2692
     * @return object|bool Returns the entity with the specified identifier if it exists in
2693 163
     *                     this UnitOfWork, FALSE otherwise.
2694 157
     */
2695 157
    public function tryGetById($id, $rootClassName)
2696
    {
2697
        $idHash = implode(' ', (array) $id);
2698 6
2699
        return $this->identityMap[$rootClassName][$idHash] ?? false;
2700 6
    }
2701
2702 6
    /**
2703 6
     * Schedules an entity for dirty-checking at commit-time.
2704
     *
2705
     * @param object $entity The entity to schedule for dirty-checking.
2706
     *
2707
     * @return void
2708
     */
2709
    public function scheduleForSynchronization($entity)
2710
    {
2711
        $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...
2712 163
2713 163
        $this->scheduledForSynchronization[$rootClassName][spl_object_hash($entity)] = $entity;
2714 163
    }
2715
2716
    /**
2717 163
     * Checks whether the UnitOfWork has any pending insertions.
2718 163
     *
2719
     * @return boolean TRUE if this UnitOfWork has pending insertions, FALSE otherwise.
2720
     */
2721
    public function hasPendingInsertions()
2722 163
    {
2723
        return ! empty($this->entityInsertions);
2724 163
    }
2725
2726
    /**
2727 284
     * Calculates the size of the UnitOfWork. The size of the UnitOfWork is the
2728 284
     * number of entities in the identity map.
2729
     *
2730 284
     * @return integer
2731 49
     */
2732 49
    public function size()
2733
    {
2734
        return \array_sum(\array_map('count', $this->identityMap));
2735 284
    }
2736
2737
    /**
2738
     * Gets the EntityPersister for an Entity.
2739 486
     *
2740
     * @param string $entityName The name of the Entity.
2741
     *
2742
     * @return \Doctrine\ORM\Persisters\Entity\EntityPersister
2743
     */
2744 486
    public function getEntityPersister($entityName)
2745
    {
2746 3
        if (isset($this->entityPersisters[$entityName])) {
2747
            return $this->entityPersisters[$entityName];
2748 3
        }
2749 3
2750
        $class = $this->em->getClassMetadata($entityName);
2751 3
2752
        switch (true) {
2753
            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...
Coding Style introduced by
case statements should be defined using a colon.

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

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

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

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

Loading history...
2754
                $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...
2755 486
                break;
2756 486
2757 486
            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...
Coding Style introduced by
case statements should be defined using a colon.

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

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

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

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

Loading history...
2758
                $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...
2759 486
                break;
2760 486
2761
            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...
Coding Style introduced by
case statements should be defined using a colon.

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

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

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

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

Loading history...
2762 486
                $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...
2763 4
                break;
2764 4
2765
            default:
0 ignored issues
show
Coding Style introduced by
DEFAULT statements must be defined using a colon

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

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

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

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

Loading history...
2766
                throw new \RuntimeException('No persister found for entity.');
2767 486
        }
2768 564
2769
        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...
2770
            $persister = $this->em->getConfiguration()
2771
                ->getSecondLevelCacheConfiguration()
2772 669
                ->getCacheFactory()
2773
                ->buildCachedEntityPersister($this->em, $persister, $class);
2774 669
        }
2775
2776
        $this->entityPersisters[$entityName] = $persister;
2777 669
2778
        return $this->entityPersisters[$entityName];
2779
    }
2780
2781
    /**
2782
     * Gets a collection persister for a collection-valued association.
2783 861
     *
2784
     * @param ToManyAssociationMetadata $association
2785 861
     *
2786 861
     * @return \Doctrine\ORM\Persisters\Collection\CollectionPersister
2787
     */
2788
    public function getCollectionPersister(ToManyAssociationMetadata $association)
2789
    {
2790 6
        $role = $association->getCache()
2791 6
            ? sprintf('%s::%s', $association->getSourceEntity(), $association->getName())
2792
            : get_class($association);
2793 6
2794 6
        if (isset($this->collectionPersisters[$role])) {
2795
            return $this->collectionPersisters[$role];
2796
        }
2797
2798 6
        $persister = $association instanceof OneToManyAssociationMetadata
2799
            ? new OneToManyPersister($this->em)
2800 6
            : new ManyToManyPersister($this->em);
2801 6
2802
        if ($this->hasCache && $association->getCache()) {
2803
            $persister = $this->em->getConfiguration()
2804 6
                ->getSecondLevelCacheConfiguration()
2805
                ->getCacheFactory()
2806
                ->buildCachedCollectionPersister($this->em, $persister, $association);
2807
        }
2808
2809
        $this->collectionPersisters[$role] = $persister;
2810
2811
        return $this->collectionPersisters[$role];
2812
    }
2813
2814
    /**
2815 142
     * INTERNAL:
2816
     * Registers an entity as managed.
2817 142
     *
2818 142
     * @param object $entity The entity.
2819
     * @param array  $id     Map containing identifier field names as key and its associated values.
2820 142
     * @param array  $data   The original entity data.
2821 142
     *
2822 76
     * @return void
2823 76
     */
2824
    public function registerManaged($entity, array $id, array $data)
2825 80
    {
2826 80
        $isProxy = $entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized();
2827 80
        $oid     = spl_object_hash($entity);
2828
2829
        $this->entityIdentifiers[$oid]  = $id;
2830 142
        $this->entityStates[$oid]       = self::STATE_MANAGED;
2831 142
        $this->originalEntityData[$oid] = $data;
2832
2833
        $this->addToIdentityMap($entity);
2834
2835
        if ($entity instanceof NotifyPropertyChanged && ! $isProxy) {
2836
            $entity->addPropertyChangedListener($this);
2837
        }
2838 2
    }
2839
2840 2
    /**
2841
     * INTERNAL:
2842
     * Clears the property changeset of the entity with the given OID.
2843
     *
2844
     * @param string $oid The entity's OID.
2845
     *
2846
     * @return void
2847
     */
2848
    public function clearEntityChangeSet($oid)
2849
    {
2850
        unset($this->entityChangeSets[$oid]);
2851 115
    }
2852
2853 115
    /* PropertyChangedListener implementation */
2854
2855 115
    /**
2856 112
     * Notifies this UnitOfWork of a property change in an entity.
2857 115
     *
2858
     * @param object $entity       The entity that owns the property.
2859
     * @param string $propertyName The name of the property that changed.
2860
     * @param mixed  $oldValue     The old value of the property.
2861
     * @param mixed  $newValue     The new value of the property.
2862
     *
2863
     * @return void
2864
     */
2865
    public function propertyChanged($entity, $propertyName, $oldValue, $newValue)
2866
    {
2867
        $class = $this->em->getClassMetadata(get_class($entity));
2868
2869
        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...
2870
            return; // ignore non-persistent fields
2871
        }
2872
2873
        $oid = spl_object_hash($entity);
2874
2875
        // Update changeset and mark entity for synchronization
2876
        $this->entityChangeSets[$oid][$propertyName] = [$oldValue, $newValue];
2877
2878
        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...
2879
            $this->scheduleForSynchronization($entity);
2880
        }
2881
    }
2882
2883
    /**
2884
     * Gets the currently scheduled entity insertions in this UnitOfWork.
2885 313
     *
2886
     * @return array
2887 313
     */
2888 313
    public function getScheduledEntityInsertions()
2889
    {
2890
        return $this->entityInsertions;
2891
    }
2892
2893
    /**
2894
     * Gets the currently scheduled entity updates in this UnitOfWork.
2895
     *
2896
     * @return array
2897
     */
2898
    public function getScheduledEntityUpdates()
2899
    {
2900 842
        return $this->entityUpdates;
2901
    }
2902 842
2903
    /**
2904
     * Gets the currently scheduled entity deletions in this UnitOfWork.
2905
     *
2906
     * @return array
2907
     */
2908
    public function getScheduledEntityDeletions()
2909
    {
2910
        return $this->entityDeletions;
2911
    }
2912
2913
    /**
2914 126
     * Gets the currently scheduled complete collection deletions
2915
     *
2916 126
     * @return array
2917
     */
2918 126
    public function getScheduledCollectionDeletions()
2919
    {
2920
        return $this->collectionDeletions;
2921
    }
2922 126
2923 113
    /**
2924 126
     * Gets the currently scheduled collection inserts, updates and deletes.
2925
     *
2926 126
     * @return array
2927
     */
2928
    public function getScheduledCollectionUpdates()
2929
    {
2930
        return $this->collectionUpdates;
2931
    }
2932
2933
    /**
2934
     * Helper method to initialize a lazy loading proxy or persistent collection.
2935
     *
2936
     * @param object $obj
2937
     *
2938
     * @return void
2939 522
     */
2940
    public function initializeObject($obj)
2941 522
    {
2942
        if ($obj instanceof GhostObjectInterface) {
2943 522
            $obj->initializeProxy();
2944 79
2945 522
            return;
2946
        }
2947
2948
        if ($obj instanceof PersistentCollection) {
2949
            $obj->initialize();
2950
        }
2951
    }
2952
2953
    /**
2954
     * Helper method to show an object as string.
2955
     *
2956
     * @param object $obj
2957 5
     *
2958
     * @return string
2959 5
     */
2960
    private static function objToStr($obj)
2961 5
    {
2962 5
        return method_exists($obj, '__toString') ? (string) $obj : get_class($obj).'@'.spl_object_hash($obj);
2963
    }
2964
2965
    /**
2966
     * Marks an entity as read-only so that it will not be considered for updates during UnitOfWork#commit().
2967
     *
2968
     * This operation cannot be undone as some parts of the UnitOfWork now keep gathering information
2969
     * on this object that might be necessary to perform a correct update.
2970
     *
2971
     * @param object $object
2972
     *
2973
     * @return void
2974
     *
2975
     * @throws ORMInvalidArgumentException
2976
     */
2977
    public function markReadOnly($object)
2978
    {
2979
        if ( ! is_object($object) || ! $this->isInIdentityMap($object)) {
2980 1
            throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object);
2981
        }
2982 1
2983
        $this->readOnlyObjects[spl_object_hash($object)] = true;
2984 1
    }
2985
2986
    /**
2987
     * Is this entity read only?
2988
     *
2989
     * @param object $object
2990
     *
2991
     * @return bool
2992
     *
2993
     * @throws ORMInvalidArgumentException
2994 1063
     */
2995
    public function isReadOnly($object)
2996 1063
    {
2997 845
        if ( ! is_object($object)) {
2998
            throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object);
2999
        }
3000 1063
3001
        return isset($this->readOnlyObjects[spl_object_hash($object)]);
3002
    }
3003 1063
3004 1031
    /**
3005 1031
     * Perform whatever processing is encapsulated here after completion of the transaction.
3006
     */
3007 354
    private function afterTransactionComplete()
3008 213
    {
3009 213
        $this->performCallbackOnCachedPersister(function (CachedPersister $persister) {
3010
            $persister->afterTransactionComplete();
3011 333
        });
3012 333
    }
3013 333
3014
    /**
3015
     * Perform whatever processing is encapsulated here after completion of the rolled-back.
3016
     */
3017
    private function afterTransactionRolledBack()
3018
    {
3019 1063
        $this->performCallbackOnCachedPersister(function (CachedPersister $persister) {
3020 119
            $persister->afterTransactionRolledBack();
3021 119
        });
3022 119
    }
3023 119
3024
    /**
3025
     * Performs an action after the transaction.
3026 1063
     *
3027
     * @param callable $callback
3028 1063
     */
3029
    private function performCallbackOnCachedPersister(callable $callback)
3030
    {
3031
        if ( ! $this->hasCache) {
3032
            return;
3033
        }
3034
3035
        foreach (array_merge($this->entityPersisters, $this->collectionPersisters) as $persister) {
3036
            if ($persister instanceof CachedPersister) {
3037
                $callback($persister);
3038 569
            }
3039
        }
3040 569
    }
3041 76
3042 569
    private function dispatchOnFlushEvent()
3043
    {
3044 569
        if ($this->eventManager->hasListeners(Events::onFlush)) {
3045 448
            $this->eventManager->dispatchEvent(Events::onFlush, new OnFlushEventArgs($this->em));
3046
        }
3047
    }
3048 569
3049 403
    private function dispatchPostFlushEvent()
3050 569
    {
3051
        if ($this->eventManager->hasListeners(Events::postFlush)) {
3052 569
            $this->eventManager->dispatchEvent(Events::postFlush, new PostFlushEventArgs($this->em));
3053 75
        }
3054 75
    }
3055 75
3056 75
    /**
3057
     * Verifies if two given entities actually are the same based on identifier comparison
3058
     *
3059 569
     * @param object $entity1
3060
     * @param object $entity2
3061 569
     *
3062
     * @return bool
3063
     */
3064
    private function isIdentifierEquals($entity1, $entity2)
3065
    {
3066
        if ($entity1 === $entity2) {
3067
            return true;
3068
        }
3069
3070
        $class     = $this->em->getClassMetadata(get_class($entity1));
3071
        $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...
3072
3073
        if ($class !== $this->em->getClassMetadata(get_class($entity2))) {
3074 206
            return false;
3075
        }
3076 206
3077
        $identifierFlattener = $this->em->getIdentifierFlattener();
3078 206
3079 206
        $oid1 = spl_object_hash($entity1);
3080 206
        $oid2 = spl_object_hash($entity2);
3081
3082 206
        $id1 = $this->entityIdentifiers[$oid1]
3083
            ?? $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...
3084 200
        $id2 = $this->entityIdentifiers[$oid2]
3085 2
            ?? $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...
3086
3087 200
        return $id1 === $id2 || implode(' ', $id1) === implode(' ', $id2);
3088
    }
3089
3090
    /**
3091
     * @throws ORMInvalidArgumentException
3092
     */
3093
    private function assertThatThereAreNoUnintentionallyNonPersistedAssociations() : void
3094
    {
3095
        $entitiesNeedingCascadePersist = \array_diff_key($this->nonCascadedNewDetectedEntities, $this->entityInsertions);
3096
3097
        $this->nonCascadedNewDetectedEntities = [];
3098
3099
        if ($entitiesNeedingCascadePersist) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $entitiesNeedingCascadePersist of type array<object|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...
3100
            throw ORMInvalidArgumentException::newEntitiesFoundThroughRelationships(
3101
                \array_values($entitiesNeedingCascadePersist)
3102
            );
3103
        }
3104
    }
3105
3106
    /**
3107
     * This method called by hydrators, and indicates that hydrator totally completed current hydration cycle.
3108
     * Unit of work able to fire deferred events, related to loading events here.
3109
     *
3110
     * @internal should be called internally from object hydrators
3111
     */
3112
    public function hydrationComplete()
3113
    {
3114 3
        $this->hydrationCompleteHandler->hydrationComplete();
3115
    }
3116
}
3117