Failed Conditions
Push — develop ( 3b446f...d1b453 )
by Guilherme
62:33
created

UnitOfWork::clear()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 6
cts 6
cp 1
rs 9.2
c 0
b 0
f 0
cc 1
eloc 20
nc 1
nop 0
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
        $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...
532
533
        if ($invoke !== ListenersInvoker::INVOKE_NONE) {
534
            $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...
535
        }
536
537
        $actualData = [];
538
539
        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...
540
            $value = $property->getValue($entity);
541
542
            if ($property instanceof ToManyAssociationMetadata && $value !== null) {
543
                if ($value instanceof PersistentCollection && $value->getOwner() === $entity) {
544
                    continue;
545
                }
546
547
                $value = $property->wrap($entity, $value, $this->em);
548
549
                $property->setValue($entity, $value);
550
551
                $actualData[$name] = $value;
552
553
                continue;
554
            }
555
556
            if (
557
                ( ! $class->isIdentifier($name)
558
                    || ! $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...
559
                    || ! $class->getProperty($name)->hasValueGenerator()
560
                    || $class->getProperty($name)->getValueGenerator()->getType() !== GeneratorType::IDENTITY
561
                ) && (! $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...
562
                $actualData[$name] = $value;
563
            }
564
        }
565
566
        if ( ! isset($this->originalEntityData[$oid])) {
567 1016
            // Entity is either NEW or MANAGED but not yet fully persisted (only has an id).
568
            // These result in an INSERT.
569 1016
            $this->originalEntityData[$oid] = $actualData;
570
            $changeSet = [];
571 1016
572 2
            foreach ($actualData as $propName => $actualValue) {
573
                $property = $class->getProperty($propName);
574
575 1016
                if (($property instanceof FieldMetadata) ||
576 307
                    ($property instanceof ToOneAssociationMetadata && $property->isOwningSide())) {
577
                    $changeSet[$propName] = [null, $actualValue];
578
                }
579 1016
            }
580
581 1016
            $this->entityChangeSets[$oid] = $changeSet;
582 137
        } else {
583
            // Entity is "fully" MANAGED: it was already fully persisted before
584
            // and we have a copy of the original data
585 1016
            $originalData           = $this->originalEntityData[$oid];
586
            $isChangeTrackingNotify = $class->changeTrackingPolicy === ChangeTrackingPolicy::NOTIFY;
587 1016
            $changeSet              = ($isChangeTrackingNotify && isset($this->entityChangeSets[$oid]))
588 1016
                ? $this->entityChangeSets[$oid]
589
                : [];
590 1016
591 777
            foreach ($actualData as $propName => $actualValue) {
592 200
                // skip field, its a partially omitted one!
593 200
                if ( ! (isset($originalData[$propName]) || array_key_exists($propName, $originalData))) {
594
                    continue;
595
                }
596 5
597
                $orgValue = $originalData[$propName];
598
599
                // skip if value haven't changed
600 772
                if ($orgValue === $actualValue) {
601 242
                    continue;
602
                }
603
604 772
                $property = $class->getProperty($propName);
605
606
                // Persistent collection was exchanged with the "originally"
607 772
                // created one. This can only mean it was cloned and replaced
608 772
                // on another entity.
609
                if ($actualValue instanceof PersistentCollection) {
610 772
                    $owner = $actualValue->getOwner();
611 772
612
                    if ($owner === null) { // cloned
613 772
                        $actualValue->setOwner($entity, $property);
614
                    } else if ($owner !== $entity) { // no clone, we have to fix
615 772
                        if (! $actualValue->isInitialized()) {
616
                            $actualValue->initialize(); // we have to do this otherwise the cols share state
617 772
                        }
618
619
                        $newValue = clone $actualValue;
620 1016
621 1016
                        $newValue->setOwner($entity, $property);
622 1016
623
                        $property->setValue($entity, $newValue);
624
                    }
625
                }
626 1016
627
                switch (true) {
628
                    case ($property instanceof FieldMetadata):
629 1012
                        if ($isChangeTrackingNotify) {
630 1012
                            // Continue inside switch behaves as break.
631
                            // We are required to use continue 2, since we need to continue to next $actualData item
632 1012
                            continue 2;
633 996
                        }
634 945
635
                        $changeSet[$propName] = [$orgValue, $actualValue];
636 945
                        break;
637
638
                    case ($property instanceof ToOneAssociationMetadata):
639 894
                        if ($property->isOwningSide()) {
640
                            $changeSet[$propName] = [$orgValue, $actualValue];
641 894
                        }
642 894
643
                        if ($orgValue !== null && $property->isOrphanRemoval()) {
644
                            $this->scheduleOrphanRemoval($orgValue);
645
                        }
646 1012
647
                        break;
648
649
                    case ($property instanceof ToManyAssociationMetadata):
650 263
                        // Check if original value exists
651 263
                        if ($orgValue instanceof PersistentCollection) {
652 263
                            // A PersistentCollection was de-referenced, so delete it.
653
                            if (! $this->isCollectionScheduledForDeletion($orgValue)) {
654 263
                                $this->scheduleCollectionDeletion($orgValue);
655
656 263
                                $changeSet[$propName] = $orgValue; // Signal changeset, to-many associations will be ignored
657
                            }
658 248
                        }
659 7
660
                        break;
661
662 248
                    default:
663
                        // Do nothing
664
                }
665 248
            }
666 232
667
            if ($changeSet) {
668
                $this->entityChangeSets[$oid]   = $changeSet;
669
                $this->originalEntityData[$oid] = $actualData;
670 111
                $this->entityUpdates[$oid]      = $entity;
671 57
            }
672
        }
673
674
        // Look for changes in associations of the entity
675 57
        foreach ($class->getDeclaredPropertiesIterator() as $property) {
676
            if (! ($property instanceof AssociationMetadata) || ($value = $property->getValue($entity)) === null) {
677 57
                continue;
678
            }
679
680 58
            $this->computeAssociationChanges($property, $value);
681
682
            if ($property instanceof ManyToManyAssociationMetadata &&
683
                $value instanceof PersistentCollection &&
684
                ! isset($this->entityChangeSets[$oid]) &&
685 58
                $property->isOwningSide() &&
686 8
                $value->isDirty()) {
687 8
688
                $this->entityChangeSets[$oid]   = [];
689 8
                $this->originalEntityData[$oid] = $actualData;
690
                $this->entityUpdates[$oid]      = $entity;
691
            }
692
        }
693
    }
694
695
    /**
696
     * Computes all the changes that have been done to entities and collections
697
     * since the last commit and stores these changes in the _entityChangeSet map
698
     * temporarily for access by the persisters, until the UoW commit is finished.
699 58
     *
700
     * @return void
701 8
     */
702
    public function computeChangeSets()
703 8
    {
704
        // Compute changes for INSERTed entities first. This must always happen.
705
        $this->computeScheduleInsertsChangeSets();
706
707 8
        // Compute changes for other MANAGED entities. Change tracking policies take effect here.
708 8
        foreach ($this->identityMap as $className => $entities) {
709
            $class = $this->em->getClassMetadata($className);
710 8
711
            // Skip class if instances are read-only
712
            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...
713 50
                continue;
714 49
            }
715 21
716
            // If change tracking is explicit or happens through notification, then only compute
717
            // changes on entities of that type that are explicitly marked for synchronization.
718 49
            switch (true) {
719 50
                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...
720
                    $entitiesToProcess = $entities;
721
                    break;
722
723
                case (isset($this->scheduledForSynchronization[$className])):
724 263
                    $entitiesToProcess = $this->scheduledForSynchronization[$className];
725 84
                    break;
726 84
727 84
                default:
728
                    $entitiesToProcess = [];
729
730
            }
731
732 1016
            foreach ($entitiesToProcess as $entity) {
733 894
                // Ignore uninitialized proxy objects
734 639
                if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
735
                    continue;
736
                }
737 865
738
                // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION OR DELETION are processed here.
739 857
                $oid = spl_object_hash($entity);
740 857
741 857
                if ( ! isset($this->entityInsertions[$oid]) && ! isset($this->entityDeletions[$oid]) && isset($this->entityStates[$oid])) {
742 857
                    $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...
743 857
                }
744
            }
745 35
        }
746 35
    }
747 857
748
    /**
749
     * Computes the changes of an association.
750 1008
     *
751
     * @param AssociationMetadata $association The association mapping.
752
     * @param mixed               $value       The value of the association.
753
     *
754
     * @throws ORMInvalidArgumentException
755
     * @throws ORMException
756
     *
757
     * @return void
758
     */
759 1007
    private function computeAssociationChanges(AssociationMetadata $association, $value)
760
    {
761
        if ($value instanceof GhostObjectInterface && ! $value->isProxyInitialized()) {
762 1007
            return;
763
        }
764
765 1005
        if ($value instanceof PersistentCollection && $value->isDirty()) {
766 447
            $coid = spl_object_hash($value);
767
768
            $this->collectionUpdates[$coid] = $value;
769 447
            $this->visitedCollections[$coid] = $value;
770 1
        }
771
772
        // Look through the entities, and in any of their associations,
773
        // 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...
774
        // Unwrap. Uninitialized collections will simply be empty.
775
        $unwrappedValue = ($association instanceof ToOneAssociationMetadata) ? [$value] : $value->unwrap();
776 446
        $targetEntity   = $association->getTargetEntity();
777 444
        $targetClass    = $this->em->getClassMetadata($targetEntity);
778 444
779
        foreach ($unwrappedValue as $key => $entry) {
780 3
            if (! ($entry instanceof $targetEntity)) {
781 3
                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...
782 3
            }
783
784
            $state = $this->getEntityState($entry, self::STATE_NEW);
785 1
786
            if (! ($entry instanceof $targetEntity)) {
787
                throw ORMException::unexpectedAssociationValue(
788
                    $association->getSourceEntity(),
789 446
                    $association->getName(),
790
                    get_class($entry),
791 426
                    $targetEntity
792 35
                );
793
            }
794
795
            switch ($state) {
796 425
                case self::STATE_NEW:
797
                    if ( ! in_array('persist', $association->getCascade())) {
798 425
                        $this->nonCascadedNewDetectedEntities[\spl_object_hash($entry)] = [$association, $entry];
799 446
800
                        break;
801
                    }
802
803 1005
                    $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...
804
                    $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...
805
806
                    break;
807
808
                case self::STATE_REMOVED:
809
                    // Consume the $value as array (it's either an array or an ArrayAccess)
810
                    // and remove the element from Collection.
811
                    if ($association instanceof ToManyAssociationMetadata) {
812
                        unset($value[$key]);
813
                    }
814
                    break;
815
816 865
                case self::STATE_DETACHED:
817
                    // Can actually not happen right now as we assume STATE_NEW,
818 865
                    // so the exception will be raised from the DBAL layer (constraint violation).
819 28
                    throw ORMInvalidArgumentException::detachedEntityFoundThroughRelationship($association, $entry);
820
                    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...
821
822 864
                default:
823 533
                    // MANAGED associated entities are already taken into account
824
                    // during changeset calculation anyway, since they are in the identity map.
825 533
            }
826 533
        }
827
    }
828
829
    /**
830
     * @param \Doctrine\ORM\Mapping\ClassMetadata $class
831
     * @param object                              $entity
832 864
     *
833 864
     * @return void
834
     */
835 864
    private function persistNew($class, $entity)
836 722
    {
837 6
        $oid    = spl_object_hash($entity);
838
        $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::prePersist);
839
840 716
        if ($invoke !== ListenersInvoker::INVOKE_NONE) {
841
            $this->listenersInvoker->invoke($class, Events::prePersist, $entity, new LifecycleEventArgs($entity, $this->em), $invoke);
842 716
        }
843
844
        $generationPlan = $class->getValueGenerationPlan();
845
        $persister = $this->getEntityPersister($class->getClassName());
846
        $generationPlan->executeImmediate($this->em, $entity);
847 716
848 39
        if (! $generationPlan->containsDeferred()) {
849 4
            $id = $this->em->getIdentifierFlattener()->flattenIdentifier($class, $persister->getIdentifier($entity));
850
            $this->entityIdentifiers[$oid] = $id;
851
        }
852 35
853 35
        $this->entityStates[$oid] = self::STATE_MANAGED;
854 35
855
        $this->scheduleForInsert($entity);
856 710
    }
857
858
    /**
859 4
     * INTERNAL:
860 3
     * Computes the changeset of an individual entity, independently of the
861
     * computeChangeSets() routine that is used at the beginning of a UnitOfWork#commit().
862 4
     *
863
     * The passed entity must be a managed entity. If the entity already has a change set
864 710
     * because this method is invoked during a commit cycle then the change sets are added.
865
     * whereby changes detected in this method prevail.
866
     *
867
     * @ignore
868
     *
869
     * @param ClassMetadata $class  The class descriptor of the entity.
870 713
     * @param object        $entity The entity for which to (re)calculate the change set.
871
     *
872
     * @return void
873
     *
874
     * @throws ORMInvalidArgumentException If the passed entity is not MANAGED.
875 856
     * @throws \RuntimeException
876
     */
877
    public function recomputeSingleEntityChangeSet(ClassMetadata $class, $entity) : void
878
    {
879
        $oid = spl_object_hash($entity);
880
881
        if (! isset($this->entityStates[$oid]) || $this->entityStates[$oid] !== self::STATE_MANAGED) {
882
            throw ORMInvalidArgumentException::entityNotManaged($entity);
883 1031
        }
884
885 1031
        // skip if change tracking is "NOTIFY"
886 1031
        if ($class->changeTrackingPolicy === ChangeTrackingPolicy::NOTIFY) {
887
            return;
888 1031
        }
889 139
890
        if ($class->inheritanceType !== InheritanceType::NONE) {
891
            $class = $this->em->getClassMetadata(get_class($entity));
892 1031
        }
893
894 1031
        $actualData = [];
895 269
896
        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...
897 269
            switch (true) {
898 1
                case ($property instanceof VersionFieldMetadata):
899
                    // Ignore version field
900 1
                    break;
901
902
                case ($property instanceof FieldMetadata):
903 269
                    if (! $property->isPrimaryKey()
904
                        || ! $property->getValueGenerator()
905
                        || $property->getValueGenerator()->getType() !== GeneratorType::IDENTITY) {
906 1031
                        $actualData[$name] = $property->getValue($entity);
907
                    }
908 1031
909 1031
                    break;
910
911
                case ($property instanceof ToOneAssociationMetadata):
912
                    $actualData[$name] = $property->getValue($entity);
913
                    break;
914
            }
915
        }
916
917
        if ( ! isset($this->originalEntityData[$oid])) {
918
            throw new \RuntimeException('Cannot call recomputeSingleEntityChangeSet before computeChangeSet on an entity.');
919
        }
920
921
        $originalData = $this->originalEntityData[$oid];
922
        $changeSet = [];
923
924
        foreach ($actualData as $propName => $actualValue) {
925
            $orgValue = $originalData[$propName] ?? null;
926
927
            if ($orgValue !== $actualValue) {
928
                $changeSet[$propName] = [$orgValue, $actualValue];
929 16
            }
930
        }
931 16
932
        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...
933 16
            if (isset($this->entityChangeSets[$oid])) {
934
                $this->entityChangeSets[$oid] = array_merge($this->entityChangeSets[$oid], $changeSet);
935
            } else if ( ! isset($this->entityInsertions[$oid])) {
936
                $this->entityChangeSets[$oid] = $changeSet;
937
                $this->entityUpdates[$oid]    = $entity;
938 16
            }
939
            $this->originalEntityData[$oid] = $actualData;
940
        }
941
    }
942 16
943 3
    /**
944
     * Executes all entity insertions for entities of the specified type.
945
     *
946 16
     * @param ClassMetadata $class
947
     *
948 16
     * @return void
949 16
     */
950 16
    private function executeInserts(ClassMetadata $class) : void
951 16
    {
952 16
        $className      = $class->getClassName();
953
        $persister      = $this->getEntityPersister($className);
954
        $invoke         = $this->listenersInvoker->getSubscribedSystems($class, Events::postPersist);
955
        $generationPlan = $class->getValueGenerationPlan();
956 16
957
        foreach ($this->entityInsertions as $oid => $entity) {
958
            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...
959
                continue;
960 16
            }
961 16
962
            $persister->insert($entity);
963 16
964 16
            if ($generationPlan->containsDeferred()) {
965
                // Entity has post-insert IDs
966 16
                $oid = spl_object_hash($entity);
967 16
                $id  = $this->em->getIdentifierFlattener()->flattenIdentifier($class, $persister->getIdentifier($entity));
968
969
                $this->entityIdentifiers[$oid] = $id;
970
                $this->entityStates[$oid] = self::STATE_MANAGED;
971 16
                $this->originalEntityData[$oid] = $id + $this->originalEntityData[$oid];
972 7
973 6
                $this->addToIdentityMap($entity);
974 1
            }
975 1
976 1
            unset($this->entityInsertions[$oid]);
977
978 7
            if ($invoke !== ListenersInvoker::INVOKE_NONE) {
979
                $eventArgs = new LifecycleEventArgs($entity, $this->em);
980 16
981
                $this->listenersInvoker->invoke($class, Events::postPersist, $entity, $eventArgs, $invoke);
982
            }
983
        }
984
    }
985
986
    /**
987
     * Executes all entity updates for entities of the specified type.
988
     *
989 1004
     * @param \Doctrine\ORM\Mapping\ClassMetadata $class
990
     *
991 1004
     * @return void
992 1004
     */
993 1004
    private function executeUpdates($class)
994 1004
    {
995
        $className          = $class->getClassName();
996 1004
        $persister          = $this->getEntityPersister($className);
997
        $preUpdateInvoke    = $this->listenersInvoker->getSubscribedSystems($class, Events::preUpdate);
998 1004
        $postUpdateInvoke   = $this->listenersInvoker->getSubscribedSystems($class, Events::postUpdate);
999 855
1000
        foreach ($this->entityUpdates as $oid => $entity) {
1001
            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...
1002 1004
                continue;
1003
            }
1004 1004
1005
            if ($preUpdateInvoke != ListenersInvoker::INVOKE_NONE) {
1006 1004
                $this->listenersInvoker->invoke($class, Events::preUpdate, $entity, new PreUpdateEventArgs($entity, $this->em, $this->getEntityChangeSet($entity)), $preUpdateInvoke);
1007 1004
1008
                $this->recomputeSingleEntityChangeSet($class, $entity);
1009
            }
1010
1011 1004
            if ( ! empty($this->entityChangeSets[$oid])) {
1012
//                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...
1013 1004
//                \Doctrine\Common\Util\Debug::dump($this->entityChangeSets[$oid], 3);
1014
1015 918
                $persister->update($entity);
1016 918
            }
1017 918
1018 918
            unset($this->entityUpdates[$oid]);
1019 918
1020
            if ($postUpdateInvoke != ListenersInvoker::INVOKE_NONE) {
1021 918
                $this->listenersInvoker->invoke($class, Events::postUpdate, $entity, new LifecycleEventArgs($entity, $this->em), $postUpdateInvoke);
1022
            }
1023 918
        }
1024 918
    }
1025 918
1026
    /**
1027 918
     * Executes all entity deletions for entities of the specified type.
1028
     *
1029
     * @param \Doctrine\ORM\Mapping\ClassMetadata $class
1030
     *
1031 1004
     * @return void
1032 135
     */
1033
    private function executeDeletions($class)
1034 1004
    {
1035
        $className  = $class->getClassName();
1036
        $persister  = $this->getEntityPersister($className);
1037
        $invoke     = $this->listenersInvoker->getSubscribedSystems($class, Events::postRemove);
1038
1039
        foreach ($this->entityDeletions as $oid => $entity) {
1040
            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...
1041
                continue;
1042
            }
1043 115
1044
            $persister->delete($entity);
1045 115
1046 115
            unset(
1047 115
                $this->entityDeletions[$oid],
1048 115
                $this->entityIdentifiers[$oid],
1049
                $this->originalEntityData[$oid],
1050 115
                $this->entityStates[$oid]
1051 115
            );
1052 74
1053
            // Entity with this $oid after deletion treated as NEW, even if the $oid
1054
            // is obtained by a new entity because the old one went out of scope.
1055 115
            //$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...
1056 13
            if (! $class->isIdentifierComposite()) {
1057
                $property = $class->getProperty($class->getSingleIdentifierFieldName());
1058 13
1059
                if ($property instanceof FieldMetadata && $property->hasValueGenerator()) {
1060
                    $property->setValue($entity, null);
1061 115
                }
1062
            }
1063
1064
            if ($invoke !== ListenersInvoker::INVOKE_NONE) {
1065 81
                $eventArgs = new LifecycleEventArgs($entity, $this->em);
1066
1067
                $this->listenersInvoker->invoke($class, Events::postRemove, $entity, $eventArgs, $invoke);
1068 111
            }
1069
        }
1070 111
    }
1071 111
1072
    /**
1073
     * Gets the commit order.
1074 111
     *
1075
     * @return array
1076
     */
1077
    private function getCommitOrder()
1078
    {
1079
        $calc = new Internal\CommitOrderCalculator();
1080
1081
        // See if there are any new classes in the changeset, that are not in the
1082
        // commit order graph yet (don't have a node).
1083 62
        // We have to inspect changeSet to be able to correctly build dependencies.
1084
        // It is not possible to use IdentityMap here because post inserted ids
1085 62
        // are not yet available.
1086 62
        $newNodes = [];
1087 62
1088
        foreach (\array_merge($this->entityInsertions, $this->entityUpdates, $this->entityDeletions) as $entity) {
1089 62
            $class = $this->em->getClassMetadata(get_class($entity));
1090 62
1091 26
            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...
1092
                continue;
1093
            }
1094 62
1095
            $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...
1096
1097 62
            $newNodes[] = $class;
1098 62
        }
1099 62
1100 62
        // Calculate dependencies for new nodes
1101
        while ($class = array_pop($newNodes)) {
1102
            foreach ($class->getDeclaredPropertiesIterator() as $property) {
1103
                if (! ($property instanceof ToOneAssociationMetadata && $property->isOwningSide())) {
1104
                    continue;
1105
                }
1106 62
1107 52
                $targetClass = $this->em->getClassMetadata($property->getTargetEntity());
1108
1109
                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...
1110 62
                    $calc->addNode($targetClass->getClassName(), $targetClass);
0 ignored issues
show
Bug introduced by
The method getClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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

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

Loading history...
1111 62
1112
                    $newNodes[] = $targetClass;
1113
                }
1114 61
1115
                $weight = ! array_filter(
1116
                    $property->getJoinColumns(),
1117
                    function (JoinColumnMetadata $joinColumn) { return $joinColumn->isNullable(); }
1118
                );
1119
1120
                $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...
1121
1122
                // If the target class has mapped subclasses, these share the same dependency.
1123 1008
                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...
1124
                    continue;
1125 1008
                }
1126 1008
1127
                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...
1128
                    $targetSubClass = $this->em->getClassMetadata($subClassName);
1129 1008
1130
                    if ( ! $calc->hasNode($subClassName)) {
1131
                        $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...
1132
1133
                        $newNodes[] = $targetSubClass;
1134
                    }
1135
1136 1008
                    $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...
1137
                }
1138 1008
            }
1139 1008
        }
1140
1141 1008
        return $calc->sort();
1142 624
    }
1143
1144
    /**
1145 1008
     * Schedules an entity for insertion into the database.
1146
     * If the entity already has an identifier, it will be added to the identity map.
1147 1008
     *
1148
     * @param object $entity The entity to schedule for insertion.
1149
     *
1150
     * @return void
1151 1008
     *
1152 1008
     * @throws ORMInvalidArgumentException
1153 886
     * @throws \InvalidArgumentException
1154 846
     */
1155
    public function scheduleForInsert($entity)
1156
    {
1157 839
        $oid = spl_object_hash($entity);
1158
1159 839
        if (isset($this->entityUpdates[$oid])) {
1160 649
            throw new InvalidArgumentException("Dirty entity can not be scheduled for insertion.");
1161
        }
1162 649
1163
        if (isset($this->entityDeletions[$oid])) {
1164
            throw ORMInvalidArgumentException::scheduleInsertForRemovedEntity($entity);
1165 839
        }
1166
        if (isset($this->originalEntityData[$oid]) && ! isset($this->entityInsertions[$oid])) {
1167 839
            throw ORMInvalidArgumentException::scheduleInsertForManagedEntity($entity);
1168
        }
1169
1170 839
        if (isset($this->entityInsertions[$oid])) {
1171 832
            throw ORMInvalidArgumentException::scheduleInsertTwice($entity);
1172
        }
1173
1174 217
        $this->entityInsertions[$oid] = $entity;
1175 217
1176
        if (isset($this->entityIdentifiers[$oid])) {
1177 217
            $this->addToIdentityMap($entity);
1178 189
        }
1179
1180 189
        if ($entity instanceof NotifyPropertyChanged) {
1181
            $entity->addPropertyChangedListener($this);
1182
        }
1183 217
    }
1184
1185
    /**
1186
     * Checks whether an entity is scheduled for insertion.
1187
     *
1188 1008
     * @param object $entity
1189
     *
1190
     * @return boolean
1191
     */
1192
    public function isScheduledForInsert($entity)
1193
    {
1194
        return isset($this->entityInsertions[spl_object_hash($entity)]);
1195
    }
1196
1197
    /**
1198
     * Schedules an entity for being updated.
1199
     *
1200
     * @param object $entity The entity to schedule for being updated.
1201
     *
1202 1032
     * @return void
1203
     *
1204 1032
     * @throws ORMInvalidArgumentException
1205
     */
1206 1032
    public function scheduleForUpdate($entity) : void
1207
    {
1208
        $oid = spl_object_hash($entity);
1209
1210 1032
        if ( ! isset($this->entityIdentifiers[$oid])) {
1211 1
            throw ORMInvalidArgumentException::entityHasNoIdentity($entity, "scheduling for update");
1212
        }
1213 1032
1214 1
        if (isset($this->entityDeletions[$oid])) {
1215
            throw ORMInvalidArgumentException::entityIsRemoved($entity, "schedule for update");
1216
        }
1217 1032
1218 1
        if ( ! isset($this->entityUpdates[$oid]) && ! isset($this->entityInsertions[$oid])) {
1219
            $this->entityUpdates[$oid] = $entity;
1220
        }
1221 1032
    }
1222
1223 1032
    /**
1224 269
     * INTERNAL:
1225
     * Schedules an extra update that will be executed immediately after the
1226
     * regular entity updates within the currently running commit cycle.
1227 1032
     *
1228 5
     * Extra updates for entities are stored as (entity, changeset) tuples.
1229
     *
1230 1032
     * @ignore
1231
     *
1232
     * @param object $entity    The entity for which to schedule an extra update.
1233
     * @param array  $changeset The changeset of the entity (what to update).
1234
     *
1235
     * @return void
1236
     */
1237
    public function scheduleExtraUpdate($entity, array $changeset) : void
1238
    {
1239 631
        $oid         = spl_object_hash($entity);
1240
        $extraUpdate = [$entity, $changeset];
1241 631
1242
        if (isset($this->extraUpdates[$oid])) {
1243
            [$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...
1244
1245
            $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...
1246
        }
1247
1248
        $this->extraUpdates[$oid] = $extraUpdate;
1249
    }
1250
1251
    /**
1252
     * Checks whether an entity is registered as dirty in the unit of work.
1253 1
     * Note: Is not very useful currently as dirty entities are only registered
1254
     * at commit time.
1255 1
     *
1256
     * @param object $entity
1257 1
     *
1258
     * @return boolean
1259
     */
1260
    public function isScheduledForUpdate($entity) : bool
1261 1
    {
1262
        return isset($this->entityUpdates[spl_object_hash($entity)]);
1263
    }
1264
1265 1
    /**
1266 1
     * Checks whether an entity is registered to be checked in the unit of work.
1267
     *
1268 1
     * @param object $entity
1269
     *
1270
     * @return boolean
1271
     */
1272
    public function isScheduledForDirtyCheck($entity) : bool
1273
    {
1274
        $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...
1275
1276
        return isset($this->scheduledForSynchronization[$rootEntityName][spl_object_hash($entity)]);
1277
    }
1278
1279
    /**
1280
     * INTERNAL:
1281
     * Schedules an entity for deletion.
1282
     *
1283
     * @param object $entity
1284 40
     *
1285
     * @return void
1286 40
     */
1287 40
    public function scheduleForDelete($entity)
1288
    {
1289 40
        $oid = spl_object_hash($entity);
1290 1
1291
        if (isset($this->entityInsertions[$oid])) {
1292 1
            if ($this->isInIdentityMap($entity)) {
1293
                $this->removeFromIdentityMap($entity);
1294
            }
1295 40
1296 40
            unset($this->entityInsertions[$oid], $this->entityStates[$oid]);
1297
1298
            return; // entity has not been persisted yet, so nothing more to do.
1299
        }
1300
1301
        if ( ! $this->isInIdentityMap($entity)) {
1302
            return;
1303
        }
1304
1305
        $this->removeFromIdentityMap($entity);
1306
1307
        unset($this->entityUpdates[$oid]);
1308
1309
        if ( ! isset($this->entityDeletions[$oid])) {
1310
            $this->entityDeletions[$oid] = $entity;
1311
            $this->entityStates[$oid]    = self::STATE_REMOVED;
1312
        }
1313
    }
1314
1315
    /**
1316
     * Checks whether an entity is registered as removed/deleted with the unit
1317
     * of work.
1318
     *
1319 1
     * @param object $entity
1320
     *
1321 1
     * @return boolean
1322
     */
1323 1
    public function isScheduledForDelete($entity)
1324
    {
1325
        return isset($this->entityDeletions[spl_object_hash($entity)]);
1326
    }
1327
1328
    /**
1329
     * Checks whether an entity is scheduled for insertion, update or deletion.
1330
     *
1331
     * @param object $entity
1332
     *
1333
     * @return boolean
1334 65
     */
1335
    public function isEntityScheduled($entity)
1336 65
    {
1337
        $oid = spl_object_hash($entity);
1338 65
1339 1
        return isset($this->entityInsertions[$oid])
1340
            || isset($this->entityUpdates[$oid])
1341
            || isset($this->entityDeletions[$oid]);
1342
    }
1343 1
1344
    /**
1345 1
     * INTERNAL:
1346
     * Registers an entity in the identity map.
1347
     * Note that entities in a hierarchy are registered with the class name of
1348 65
     * the root entity.
1349 1
     *
1350
     * @ignore
1351
     *
1352 64
     * @param object $entity The entity to register.
1353
     *
1354 64
     * @return boolean TRUE if the registration was successful, FALSE if the identity of
1355
     *                 the entity in question is already managed.
1356 64
     *
1357 64
     * @throws ORMInvalidArgumentException
1358 64
     */
1359
    public function addToIdentityMap($entity)
1360 64
    {
1361
        $classMetadata = $this->em->getClassMetadata(get_class($entity));
1362
        $identifier    = $this->entityIdentifiers[spl_object_hash($entity)];
1363
1364
        if (empty($identifier) || in_array(null, $identifier, true)) {
1365
            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...
1366
        }
1367
1368
        $idHash    = implode(' ', $identifier);
1369
        $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...
1370 17
1371
        if (isset($this->identityMap[$className][$idHash])) {
1372 17
            return false;
1373
        }
1374
1375
        $this->identityMap[$className][$idHash] = $entity;
1376
1377
        return true;
1378
    }
1379
1380
    /**
1381
     * Gets the state of an entity with regard to the current unit of work.
1382
     *
1383
     * @param object   $entity
1384
     * @param int|null $assume The state to assume if the state is not yet known (not MANAGED or REMOVED).
1385
     *                         This parameter can be set to improve performance of entity state detection
1386
     *                         by potentially avoiding a database lookup if the distinction between NEW and DETACHED
1387
     *                         is either known or does not matter for the caller of the method.
1388
     *
1389
     * @return int The entity state.
1390
     */
1391
    public function getEntityState($entity, $assume = null)
1392
    {
1393
        $oid = spl_object_hash($entity);
1394
1395
        if (isset($this->entityStates[$oid])) {
1396
            return $this->entityStates[$oid];
1397
        }
1398
1399
        if ($assume !== null) {
1400
            return $assume;
1401
        }
1402
1403
        // State can only be NEW or DETACHED, because MANAGED/REMOVED states are known.
1404
        // Note that you can not remember the NEW or DETACHED state in _entityStates since
1405
        // the UoW does not hold references to such objects and the object hash can be reused.
1406 1098
        // More generally because the state may "change" between NEW/DETACHED without the UoW being aware of it.
1407
        $class     = $this->em->getClassMetadata(get_class($entity));
1408 1098
        $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...
1409 1098
        $id        = $persister->getIdentifier($entity);
1410
1411 1098
        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...
1412 6
            return self::STATE_NEW;
1413
        }
1414
1415 1092
        $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...
1416 1092
1417
        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...
1418 1092
            || ! $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...
1419 83
            || ! $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...
1420
        ) {
1421
            // Check for a version field, if available, to avoid a db lookup.
1422 1092
            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...
1423
                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...
1424 1092
                    ? self::STATE_DETACHED
1425
                    : self::STATE_NEW;
1426
            }
1427
1428
            // Last try before db lookup: check the identity map.
1429
            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...
1430
                return self::STATE_DETACHED;
1431
            }
1432
1433
            // db lookup
1434
            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...
1435
                return self::STATE_DETACHED;
1436
            }
1437
1438 1045
            return self::STATE_NEW;
1439
        }
1440 1045
1441
        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...
1442 1045
            || ! $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...
1443 783
            || ! $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...
1444
            // if we have a pre insert generator we can't be sure that having an id
1445
            // really means that the entity exists. We have to verify this through
1446 1039
            // the last resort: a db lookup
1447 1035
1448
            // Last try before db lookup: check the identity map.
1449
            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...
1450
                return self::STATE_DETACHED;
1451
            }
1452
1453
            // db lookup
1454 13
            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...
1455 13
                return self::STATE_DETACHED;
1456
            }
1457 13
1458 5
            return self::STATE_NEW;
1459
        }
1460
1461 10
        return self::STATE_DETACHED;
1462 1
    }
1463
1464
    /**
1465
     * INTERNAL:
1466 10
     * Removes an entity from the identity map. This effectively detaches the
1467
     * entity from the persistence management of Doctrine.
1468 5
     *
1469 1
     * @ignore
1470
     *
1471 1
     * @param object $entity
1472
     *
1473
     * @return boolean
1474
     *
1475 4
     * @throws ORMInvalidArgumentException
1476 1
     */
1477
    public function removeFromIdentityMap($entity)
1478
    {
1479
        $oid           = spl_object_hash($entity);
1480 4
        $classMetadata = $this->em->getClassMetadata(get_class($entity));
1481
        $idHash        = implode(' ', $this->entityIdentifiers[$oid]);
1482
1483
        if ($idHash === '') {
1484 4
            throw ORMInvalidArgumentException::entityHasNoIdentity($entity, "remove from identity map");
1485
        }
1486 5
1487
        $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...
1488
1489
        if (isset($this->identityMap[$className][$idHash])) {
1490
            unset($this->identityMap[$className][$idHash]);
1491
            unset($this->readOnlyObjects[$oid]);
1492
1493
            //$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...
1494
1495
            return true;
1496
        }
1497
1498
        return false;
1499
    }
1500
1501
    /**
1502
     * INTERNAL:
1503
     * Gets an entity in the identity map by its identifier hash.
1504 5
     *
1505
     * @ignore
1506
     *
1507
     * @param string $idHash
1508
     * @param string $rootClassName
1509
     *
1510
     * @return object
1511
     */
1512
    public function getByIdHash($idHash, $rootClassName)
1513
    {
1514
        return $this->identityMap[$rootClassName][$idHash];
1515
    }
1516
1517
    /**
1518
     * INTERNAL:
1519
     * Tries to get an entity by its identifier hash. If no entity is found for
1520
     * the given hash, FALSE is returned.
1521 76
     *
1522
     * @ignore
1523 76
     *
1524 76
     * @param mixed  $idHash        (must be possible to cast it to string)
1525 76
     * @param string $rootClassName
1526
     *
1527 76
     * @return object|bool The found entity or FALSE.
1528
     */
1529
    public function tryGetByIdHash($idHash, $rootClassName)
1530
    {
1531 76
        $stringIdHash = (string) $idHash;
1532
1533 76
        return $this->identityMap[$rootClassName][$stringIdHash] ?? false;
1534 76
    }
1535 76
1536
    /**
1537
     * Checks whether an entity is registered in the identity map of this UnitOfWork.
1538
     *
1539 76
     * @param object $entity
1540
     *
1541
     * @return boolean
1542
     */
1543
    public function isInIdentityMap($entity)
1544
    {
1545
        $oid = spl_object_hash($entity);
1546
1547
        if (empty($this->entityIdentifiers[$oid])) {
1548
            return false;
1549
        }
1550
1551
        $classMetadata = $this->em->getClassMetadata(get_class($entity));
1552
        $idHash        = implode(' ', $this->entityIdentifiers[$oid]);
1553
1554
        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...
1555
    }
1556 6
1557
    /**
1558 6
     * INTERNAL:
1559
     * Checks whether an identifier hash exists in the identity map.
1560
     *
1561
     * @ignore
1562
     *
1563
     * @param string $idHash
1564
     * @param string $rootClassName
1565
     *
1566
     * @return boolean
1567
     */
1568
    public function containsIdHash($idHash, $rootClassName)
1569
    {
1570
        return isset($this->identityMap[$rootClassName][$idHash]);
1571
    }
1572
1573 34
    /**
1574
     * Persists an entity as part of the current unit of work.
1575 34
     *
1576
     * @param object $entity The entity to persist.
1577 34
     *
1578 34
     * @return void
1579 34
     */
1580
    public function persist($entity)
1581
    {
1582
        $visited = [];
1583
1584
        $this->doPersist($entity, $visited);
1585
    }
1586
1587
    /**
1588
     * Persists an entity as part of the current unit of work.
1589 212
     *
1590
     * This method is internally called during persist() cascades as it tracks
1591 212
     * the already visited entities to prevent infinite recursions.
1592
     *
1593 212
     * @param object $entity  The entity to persist.
1594 31
     * @param array  $visited The already visited entities.
1595
     *
1596
     * @return void
1597 197
     *
1598 197
     * @throws ORMInvalidArgumentException
1599
     * @throws UnexpectedValueException
1600 197
     */
1601
    private function doPersist($entity, array &$visited)
1602
    {
1603
        $oid = spl_object_hash($entity);
1604 197
1605
        if (isset($visited[$oid])) {
1606
            return; // Prevent infinite recursion
1607
        }
1608
1609
        $visited[$oid] = $entity; // Mark visited
1610
1611
        $class = $this->em->getClassMetadata(get_class($entity));
1612
1613
        // We assume NEW, so DETACHED entities result in an exception on flush (constraint violation).
1614
        // If we would detect DETACHED here we would throw an exception anyway with the same
1615
        // consequences (not recoverable/programming error), so just assuming NEW here
1616
        // lets us avoid some database lookups for entities with natural identifiers.
1617
        $entityState = $this->getEntityState($entity, self::STATE_NEW);
1618
1619
        switch ($entityState) {
1620
            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...
1621
                // Nothing to do, except if policy is "deferred explicit"
1622
                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...
1623
                    $this->scheduleForSynchronization($entity);
1624
                }
1625
                break;
1626
1627
            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...
1628
                $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...
1629
                break;
1630 1028
1631
            case self::STATE_REMOVED:
1632 1028
                // Entity becomes managed again
1633
                unset($this->entityDeletions[$oid]);
1634 1028
                $this->addToIdentityMap($entity);
1635 1021
1636
                $this->entityStates[$oid] = self::STATE_MANAGED;
1637
                break;
1638
1639
            case self::STATE_DETACHED:
1640
                // Can actually not happen right now since we assume STATE_NEW.
1641
                throw ORMInvalidArgumentException::detachedEntityCannot($entity, "persisted");
1642
1643
            default:
1644
                throw new UnexpectedValueException("Unexpected entity state: $entityState." . self::objToStr($entity));
1645
        }
1646
1647
        $this->cascadePersist($entity, $visited);
1648
    }
1649
1650
    /**
1651 1028
     * Deletes an entity as part of the current unit of work.
1652
     *
1653 1028
     * @param object $entity The entity to remove.
1654
     *
1655 1028
     * @return void
1656 109
     */
1657
    public function remove($entity)
1658
    {
1659 1028
        $visited = [];
1660
1661 1028
        $this->doRemove($entity, $visited);
1662
    }
1663
1664
    /**
1665
     * Deletes an entity as part of the current unit of work.
1666
     *
1667 1028
     * This method is internally called during delete() cascades as it tracks
1668
     * the already visited entities to prevent infinite recursions.
1669
     *
1670 1028
     * @param object $entity  The entity to delete.
1671
     * @param array  $visited The map of the already visited entities.
1672 234
     *
1673 2
     * @return void
1674
     *
1675 234
     * @throws ORMInvalidArgumentException If the instance is a detached entity.
1676
     * @throws UnexpectedValueException
1677 1028
     */
1678 1027
    private function doRemove($entity, array &$visited)
1679 1027
    {
1680
        $oid = spl_object_hash($entity);
1681 1
1682
        if (isset($visited[$oid])) {
1683 1
            return; // Prevent infinite recursion
1684 1
        }
1685
1686 1
        $visited[$oid] = $entity; // mark visited
1687 1
1688
        // Cascade first, because scheduleForDelete() removes the entity from the identity map, which
1689
        // can cause problems when a lazy proxy has to be initialized for the cascade operation.
1690
        $this->cascadeRemove($entity, $visited);
1691
1692
        $class       = $this->em->getClassMetadata(get_class($entity));
1693
        $entityState = $this->getEntityState($entity);
1694
1695
        switch ($entityState) {
1696
            case self::STATE_NEW:
1697 1028
            case self::STATE_REMOVED:
1698 1021
                // nothing to do
1699
                break;
1700
1701
            case self::STATE_MANAGED:
1702
                $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...
1703
1704
                if ($invoke !== ListenersInvoker::INVOKE_NONE) {
1705
                    $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...
1706
                }
1707 64
1708
                $this->scheduleForDelete($entity);
1709 64
                break;
1710
1711 64
            case self::STATE_DETACHED:
1712 64
                throw ORMInvalidArgumentException::detachedEntityCannot($entity, "removed");
1713
            default:
1714
                throw new UnexpectedValueException("Unexpected entity state: $entityState." . self::objToStr($entity));
1715
        }
1716
1717
    }
1718
1719
    /**
1720
     * Refreshes the state of the given entity from the database, overwriting
1721
     * any local, unpersisted changes.
1722
     *
1723
     * @param object $entity The entity to refresh.
1724
     *
1725
     * @return void
1726
     *
1727
     * @throws InvalidArgumentException If the entity is not MANAGED.
1728 64
     */
1729
    public function refresh($entity)
1730 64
    {
1731
        $visited = [];
1732 64
1733 1
        $this->doRefresh($entity, $visited);
1734
    }
1735
1736 64
    /**
1737
     * Executes a refresh operation on an entity.
1738
     *
1739
     * @param object $entity  The entity to refresh.
1740 64
     * @param array  $visited The already visited entities during cascades.
1741
     *
1742 64
     * @return void
1743 64
     *
1744
     * @throws ORMInvalidArgumentException If the entity is not MANAGED.
1745
     */
1746 64
    private function doRefresh($entity, array &$visited)
1747 64
    {
1748
        $oid = spl_object_hash($entity);
1749 2
1750
        if (isset($visited[$oid])) {
1751 64
            return; // Prevent infinite recursion
1752 64
        }
1753
1754 64
        $visited[$oid] = $entity; // mark visited
1755 8
1756
        $class = $this->em->getClassMetadata(get_class($entity));
1757
1758 64
        if ($this->getEntityState($entity) !== self::STATE_MANAGED) {
1759 64
            throw ORMInvalidArgumentException::entityNotManaged($entity);
1760
        }
1761
1762
        $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...
1763
            array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
1764
            $entity
1765
        );
1766
1767 64
        $this->cascadeRefresh($entity, $visited);
1768
    }
1769
1770
    /**
1771
     * Cascades a refresh operation to associated entities.
1772
     *
1773
     * @param object $entity
1774
     * @param array  $visited
1775
     *
1776
     * @return void
1777
     */
1778
    private function cascadeRefresh($entity, array &$visited)
1779
    {
1780
        $class = $this->em->getClassMetadata(get_class($entity));
1781 40
1782
        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...
1783 40
            if (! ($association instanceof AssociationMetadata && in_array('refresh', $association->getCascade()))) {
1784
                continue;
1785 40
            }
1786
1787
            $relatedEntities = $association->getValue($entity);
1788
1789
            switch (true) {
1790
                case ($relatedEntities instanceof PersistentCollection):
1791
                    // Unwrap so that foreach() does not initialize
1792
                    $relatedEntities = $relatedEntities->unwrap();
1793
                    // break; is commented intentionally!
1794
1795
                case ($relatedEntities instanceof Collection):
1796
                case (is_array($relatedEntities)):
1797
                    foreach ($relatedEntities as $relatedEntity) {
1798
                        $this->doRefresh($relatedEntity, $visited);
1799
                    }
1800
                    break;
1801
1802
                case ($relatedEntities !== null):
1803 40
                    $this->doRefresh($relatedEntities, $visited);
1804
                    break;
1805 40
1806
                default:
1807 40
                    // Do nothing
1808 4
            }
1809
        }
1810 4
    }
1811 4
1812
    /**
1813
     * Cascades the save operation to associated entities.
1814 4
     *
1815
     * @param object $entity
1816
     * @param array  $visited
1817 40
     *
1818
     * @throws ORMInvalidArgumentException
1819
     *
1820
     * @return void
1821
     */
1822
    private function cascadePersist($entity, array &$visited)
1823 40
    {
1824
        $class = $this->em->getClassMetadata(get_class($entity));
1825 40
1826
        if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
1827 39
            // nothing to do - proxy is not initialized, therefore we don't do anything with it
1828
            return;
1829
        }
1830 39
1831 5
        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...
1832
            if (! ($association instanceof AssociationMetadata && in_array('persist', $association->getCascade()))) {
1833 5
                continue;
1834
            }
1835 35
1836 3
            /** @var AssociationMetadata $association */
1837 35
            $relatedEntities = $association->getValue($entity);
1838
            $targetEntity    = $association->getTargetEntity();
1839 35
1840
            switch (true) {
1841 35
                case ($relatedEntities instanceof PersistentCollection):
1842
                    // Unwrap so that foreach() does not initialize
1843 14
                    $relatedEntities = $relatedEntities->unwrap();
1844 14
                    // break; is commented intentionally!
1845
1846
                case ($relatedEntities instanceof Collection):
1847
                case (is_array($relatedEntities)):
1848 24
                    if (! ($association instanceof ToManyAssociationMetadata)) {
1849
                        throw ORMInvalidArgumentException::invalidAssociation(
1850
                            $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...
1851 35
                            $association,
1852
                            $relatedEntities
1853
                        );
1854 2
                    }
1855 1
1856 1
                    foreach ($relatedEntities as $relatedEntity) {
1857 1
                        $this->doPersist($relatedEntity, $visited);
1858
                    }
1859
1860
                    break;
1861 1
1862 1
                case ($relatedEntities !== null):
1863
                    if (! $relatedEntities instanceof $targetEntity) {
1864 1
                        throw ORMInvalidArgumentException::invalidAssociation(
1865
                            $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...
1866
                            $association,
1867
                            $relatedEntities
1868 38
                        );
1869 4
                    }
1870 4
1871
                    $this->doPersist($relatedEntities, $visited);
1872
                    break;
1873 4
1874 1
                default:
1875
                    // Do nothing
1876
            }
1877
        }
1878 37
    }
1879
1880 37
    /**
1881 30
     * Cascades the delete operation to associated entities.
1882 4
     *
1883
     * @param object $entity
1884
     * @param array  $visited
1885 30
     *
1886
     * @return void
1887
     */
1888 37
    private function cascadeRemove($entity, array &$visited)
1889
    {
1890
        $entitiesToCascade = [];
1891
        $class             = $this->em->getClassMetadata(get_class($entity));
1892
1893 38
        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...
1894 6
            if (! ($association instanceof AssociationMetadata && in_array('remove', $association->getCascade()))) {
1895
                continue;
1896
            }
1897
1898 38
            if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
1899
                $entity->initializeProxy();
1900 38
            }
1901
1902 38
            $relatedEntities = $association->getValue($entity);
1903
1904
            switch (true) {
1905
                case ($relatedEntities instanceof Collection):
1906
                case (\is_array($relatedEntities)):
1907
                    // If its a PersistentCollection initialization is intended! No unwrap!
1908
                    foreach ($relatedEntities as $relatedEntity) {
1909
                        $entitiesToCascade[] = $relatedEntity;
1910
                    }
1911
                    break;
1912 38
1913
                case ($relatedEntities !== null):
1914 38
                    $entitiesToCascade[] = $relatedEntities;
1915
                    break;
1916
1917
                default:
1918
                    // Do nothing
1919
            }
1920
        }
1921
1922
        foreach ($entitiesToCascade as $relatedEntity) {
1923
            $this->doRemove($relatedEntity, $visited);
1924
        }
1925
    }
1926
1927 6
    /**
1928
     * Acquire a lock on the given entity.
1929 6
     *
1930 6
     * @param object $entity
1931
     * @param int    $lockMode
1932 6
     * @param int    $lockVersion
1933 6
     *
1934
     * @return void
1935 6
     *
1936
     * @throws ORMInvalidArgumentException
1937
     * @throws TransactionRequiredException
1938
     * @throws OptimisticLockException
1939 1
     * @throws \InvalidArgumentException
1940 1
     */
1941
    public function lock($entity, $lockMode, $lockVersion = null)
1942 1
    {
1943 1
        if ($entity === null) {
1944
            throw new \InvalidArgumentException("No entity passed to UnitOfWork#lock().");
1945 1
        }
1946
1947 1
        if ($this->getEntityState($entity, self::STATE_DETACHED) !== self::STATE_MANAGED) {
1948
            throw ORMInvalidArgumentException::entityNotManaged($entity);
1949
        }
1950
1951
        $class = $this->em->getClassMetadata(get_class($entity));
1952
1953
        switch (true) {
1954
            case LockMode::OPTIMISTIC === $lockMode:
1955
                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...
1956
                    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...
1957 12
                }
1958
1959 12
                if ($lockVersion === null) {
1960
                    return;
1961 12
                }
1962 12
1963
                if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
1964
                    $entity->initializeProxy();
1965
                }
1966
1967
                $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...
1968
1969
                if ($entityVersion !== $lockVersion) {
1970
                    throw OptimisticLockException::lockFailedVersionMismatch($entity, $lockVersion, $entityVersion);
1971
                }
1972
1973 15
                break;
1974
1975 15
            case LockMode::NONE === $lockMode:
1976
            case LockMode::PESSIMISTIC_READ === $lockMode:
1977 15
            case LockMode::PESSIMISTIC_WRITE === $lockMode:
1978
                if (!$this->em->getConnection()->isTransactionActive()) {
1979
                    throw TransactionRequiredException::transactionRequired();
1980
                }
1981 15
1982
                $oid = spl_object_hash($entity);
1983 15
1984 15
                $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...
1985 13
                    array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
1986 12
                    $lockMode
1987
                );
1988
                break;
1989
1990 13
            default:
1991 13
                // Do nothing
1992 13
        }
1993 13
    }
1994 13
1995 13
    /**
1996
     * Clears the UnitOfWork.
1997 13
     *
1998 3
     * @return void
1999 3
     */
2000 3
    public function clear()
2001
    {
2002
        $this->entityPersisters =
2003 13
        $this->collectionPersisters =
2004 13
        $this->eagerLoadingEntities =
2005
        $this->identityMap =
2006 13
        $this->entityIdentifiers =
2007
        $this->originalEntityData =
2008
        $this->entityChangeSets =
2009
        $this->entityStates =
2010
        $this->scheduledForSynchronization =
2011
        $this->entityInsertions =
2012
        $this->entityUpdates =
2013
        $this->entityDeletions =
2014
        $this->collectionDeletions =
2015
        $this->collectionUpdates =
2016
        $this->extraUpdates =
2017
        $this->readOnlyObjects =
2018 16
        $this->visitedCollections =
2019
        $this->nonCascadedNewDetectedEntities =
2020 16
        $this->orphanRemovals = [];
2021
    }
2022 16
2023 16
    /**
2024
     * INTERNAL:
2025
     * Schedules an orphaned entity for removal. The remove() operation will be
2026
     * invoked on that entity at the beginning of the next commit of this
2027
     * UnitOfWork.
2028
     *
2029
     * @ignore
2030
     *
2031
     * @param object $entity
2032
     *
2033
     * @return void
2034
     */
2035 16
    public function scheduleOrphanRemoval($entity)
2036
    {
2037 16
        $this->orphanRemovals[spl_object_hash($entity)] = $entity;
2038
    }
2039 16
2040
    /**
2041
     * INTERNAL:
2042
     * Cancels a previously scheduled orphan removal.
2043 16
     *
2044
     * @ignore
2045 16
     *
2046
     * @param object $entity
2047 16
     *
2048
     * @return void
2049
     */
2050
    public function cancelOrphanRemoval($entity)
2051 16
    {
2052 16
        unset($this->orphanRemovals[spl_object_hash($entity)]);
2053
    }
2054
2055
    /**
2056 16
     * INTERNAL:
2057 16
     * Schedules a complete collection for removal when this UnitOfWork commits.
2058
     *
2059
     * @param PersistentCollection $coll
2060
     *
2061
     * @return void
2062
     */
2063
    public function scheduleCollectionDeletion(PersistentCollection $coll)
2064
    {
2065
        $coid = spl_object_hash($coll);
2066
2067 16
        // TODO: if $coll is already scheduled for recreation ... what to do?
2068
        // Just remove $coll from the scheduled recreations?
2069 16
        unset($this->collectionUpdates[$coid]);
2070
2071 16
        $this->collectionDeletions[$coid] = $coll;
2072 16
    }
2073
2074
    /**
2075
     * @param PersistentCollection $coll
2076 16
     *
2077 5
     * @return bool
2078
     */
2079
    public function isCollectionScheduledForDeletion(PersistentCollection $coll)
2080 5
    {
2081
        return isset($this->collectionDeletions[spl_object_hash($coll)]);
2082 5
    }
2083
2084
    /**
2085
     * INTERNAL:
2086
     * Creates a new instance of the mapped class, without invoking the constructor.
2087 5
     * This is only meant to be used internally, and should not be consumed by end users.
2088
     *
2089
     * @ignore
2090 5
     *
2091
     * @param ClassMetadata $class
2092
     *
2093
     * @return EntityManagerAware|object
2094
     */
2095
    public function newInstance(ClassMetadata $class)
2096 5
    {
2097
        $entity = $this->instantiator->instantiate($class->getClassName());
2098
2099
        if ($entity instanceof EntityManagerAware) {
2100 16
            $entity->injectEntityManager($this->em, $class);
2101
        }
2102
2103
        return $entity;
2104
    }
2105
2106
    /**
2107
     * INTERNAL:
2108
     * Creates an entity. Used for reconstitution of persistent entities.
2109
     *
2110 13
     * Internal note: Highly performance-sensitive method.
2111
     *
2112 13
     * @ignore
2113
     *
2114 13
     * @param string $className The name of the entity class.
2115 13
     * @param array  $data      The data for the entity.
2116
     * @param array  $hints     Any hints to account for during reconstitution/lookup of the entity.
2117
     *
2118
     * @return object The managed entity instance.
2119 13
     *
2120 3
     * @todo Rename: getOrCreateEntity
2121
     */
2122
    public function createEntity($className, array $data, &$hints = [])
2123 3
    {
2124
        $class  = $this->em->getClassMetadata($className);
2125 2
        $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...
2126
        $idHash = implode(' ', $id);
2127
2128
        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...
2129
            $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...
2130 3
            $oid = spl_object_hash($entity);
2131 1
2132
            if (
2133 3
                isset($hints[Query::HINT_REFRESH])
2134
                && isset($hints[Query::HINT_REFRESH_ENTITY])
2135
                && ($unmanagedProxy = $hints[Query::HINT_REFRESH_ENTITY]) !== $entity
2136
                && $unmanagedProxy instanceof GhostObjectInterface
2137
                && $this->isIdentifierEquals($unmanagedProxy, $entity)
2138
            ) {
2139 3
                // We will hydrate the given un-managed proxy anyway:
2140
                // continue work, but consider it the entity from now on
2141
                $entity = $unmanagedProxy;
2142
            }
2143 13
2144
            if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
2145
                $entity->setProxyInitializer(null);
2146
2147
                if ($entity instanceof NotifyPropertyChanged) {
2148
                    $entity->addPropertyChangedListener($this);
2149
                }
2150
            } else {
2151
                if ( ! isset($hints[Query::HINT_REFRESH])
2152
                    || (isset($hints[Query::HINT_REFRESH_ENTITY]) && $hints[Query::HINT_REFRESH_ENTITY] !== $entity)) {
2153
                    return $entity;
2154 38
                }
2155
            }
2156 38
2157
            // inject EntityManager upon refresh.
2158 38
            if ($entity instanceof EntityManagerAware) {
2159 38
                $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...
2160
            }
2161
2162
            $this->originalEntityData[$oid] = $data;
2163 38
        } else {
2164 15
            $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...
2165
            $oid    = spl_object_hash($entity);
2166 15
2167 9
            $this->entityIdentifiers[$oid]  = $id;
2168 1
            $this->entityStates[$oid]       = self::STATE_MANAGED;
2169
            $this->originalEntityData[$oid] = $data;
2170
2171 8
            $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...
2172
        }
2173 5
2174
        if ($entity instanceof NotifyPropertyChanged) {
2175
            $entity->addPropertyChangedListener($this);
2176 8
        }
2177 8
2178
        foreach ($data as $field => $value) {
2179 7
            $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...
2180 14
2181
            if ($property instanceof FieldMetadata) {
2182
                $property->setValue($entity, $value);
2183 38
            }
2184
        }
2185
2186
        // Loading the entity right here, if its in the eager loading map get rid of it there.
2187
        unset($this->eagerLoadingEntities[$class->getRootClassName()][$idHash]);
2188
2189
        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...
2190
            unset($this->eagerLoadingEntities[$class->getRootClassName()]);
2191
        }
2192
2193 1028
        // Properly initialize any unfetched associations, if partial objects are not allowed.
2194
        if (isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) {
2195 1028
            return $entity;
2196
        }
2197 1028
2198 1028
        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...
2199
            if (! ($association instanceof AssociationMetadata)) {
2200
                continue;
2201
            }
2202 1028
2203 650
            // Check if the association is not among the fetch-joined associations already.
2204
            if (isset($hints['fetchAlias']) && isset($hints['fetched'][$hints['fetchAlias']][$field])) {
2205
                continue;
2206 650
            }
2207
2208 21
            $targetEntity = $association->getTargetEntity();
2209
            $targetClass  = $this->em->getClassMetadata($targetEntity);
2210
2211
            if ($association instanceof ToManyAssociationMetadata) {
2212 589
                // Ignore if its a cached collection
2213 554
                if (isset($hints[Query::HINT_CACHE_ENABLED]) &&
2214 3
                    $association->getValue($entity) instanceof PersistentCollection) {
2215 3
                    continue;
2216
                }
2217
2218
                $hasDataField = isset($data[$field]);
2219
2220
                // use the given collection
2221 551
                if ($hasDataField && $data[$field] instanceof PersistentCollection) {
2222 282
                    $data[$field]->setOwner($entity, $association);
2223
2224
                    $association->setValue($entity, $data[$field]);
2225 551
2226
                    $this->originalEntityData[$oid][$field] = $data[$field];
2227 579
2228 246
                    continue;
2229 4
                }
2230 4
2231
                // Inject collection
2232
                $pColl = $association->wrap($entity, $hasDataField ? $data[$field] : [], $this->em);
2233
2234
                $pColl->setInitialized($hasDataField);
2235
2236 242
                $association->setValue($entity, $pColl);
2237 242
2238
                if ($association->getFetchMode() === FetchMode::EAGER) {
2239 644
                    $this->loadCollection($pColl);
2240
                    $pColl->takeSnapshot();
2241
                }
2242
2243 1021
                $this->originalEntityData[$oid][$field] = $pColl;
2244
2245
                continue;
2246
            }
2247
2248
            if (! $association->isOwningSide()) {
2249
                // use the given entity association
2250
                if (isset($data[$field]) && is_object($data[$field]) &&
2251
                    isset($this->entityStates[spl_object_hash($data[$field])])) {
2252
                    $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...
2253 64
2254
                    $association->setValue($entity, $data[$field]);
2255 64
                    $inverseAssociation->setValue($data[$field], $entity);
2256
2257 64
                    $this->originalEntityData[$oid][$field] = $data[$field];
2258 64
2259
                    continue;
2260
                }
2261
2262 64
                // Inverse side of x-to-one can never be lazy
2263
                $persister = $this->getEntityPersister($targetEntity);
2264 64
2265 26
                $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...
2266 6
2267
                continue;
2268
            }
2269 26
2270
            // use the entity association
2271
            if (isset($data[$field]) && is_object($data[$field]) && isset($this->entityStates[spl_object_hash($data[$field])])) {
2272 26
                $association->setValue($entity, $data[$field]);
2273 19
2274
                $this->originalEntityData[$oid][$field] = $data[$field];
2275 20
2276 10
                continue;
2277
            }
2278 20
2279
            $associatedId = [];
2280 19
2281 7
            // TODO: Is this even computed right in all cases of composite keys?
2282 7
            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...
2283
                /** @var JoinColumnMetadata $joinColumn */
2284 26
                $joinColumnName = $joinColumn->getColumnName();
2285
                $joinColumnValue = $data[$joinColumnName] ?? null;
2286
                $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...
2287
2288
                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...
2289 64
                    // the missing key is part of target's entity primary key
2290 16
                    $associatedId = [];
2291
2292 64
                    continue;
2293
                }
2294
2295
                $associatedId[$targetField] = $joinColumnValue;
2296
            }
2297
2298
            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...
2299
                // Foreign key is NULL
2300
                $association->setValue($entity, null);
2301
                $this->originalEntityData[$oid][$field] = null;
2302
2303
                continue;
2304
            }
2305
2306
            // @todo guilhermeblanco Can we remove the need of this somehow?
2307 11
            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...
2308
                $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...
2309 11
            }
2310 1
2311
            // Foreign key is set
2312
            // Check identity map first
2313 10
            // FIXME: Can break easily with composite keys if join column values are in
2314 1
            //        wrong order. The correct order is the one in ClassMetadata#identifier.
2315
            $relatedIdHash = implode(' ', $associatedId);
2316
2317 9
            switch (true) {
2318
                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...
2319
                    $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...
2320 9
2321 6
                    // If this is an uninitialized proxy, we are deferring eager loads,
2322 2
                    // this association is marked as eager fetch, and its an uninitialized proxy (wtf!)
2323
                    // then we can append this entity for eager loading!
2324
                    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...
2325 4
                        $newValue instanceof GhostObjectInterface &&
2326
                        isset($hints[self::HINT_DEFEREAGERLOAD]) &&
2327
                        $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...
2328
                        ! $newValue->isProxyInitialized()
2329 4
                    ) {
2330 1
2331
                        $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...
2332
                    }
2333 4
2334
                    break;
2335 4
2336 2
                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...
2337
                    // If it might be a subtype, it can not be lazy. There isn't even
2338
                    // a way to solve this with deferred eager loading, which means putting
2339 2
                    // an entity with subclasses at a *-to-one location is really bad! (performance-wise)
2340
                    $persister = $this->getEntityPersister($targetEntity);
2341 3
                    $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...
2342 3
                    break;
2343 1
2344 3
                default:
2345 2
                    // Proxies do not carry any kind of original entity data until they're fully loaded/initialized
2346
                    $managedData = [];
2347
2348 1
                    $normalizedAssociatedId = $this->normalizeIdentifier->__invoke(
2349
                        $this->em,
2350 1
                        $targetClass,
0 ignored issues
show
Documentation introduced by
$targetClass is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2351 1
                        $associatedId
2352
                    );
2353
2354 1
                    switch (true) {
2355
                        // We are negating the condition here. Other cases will assume it is valid!
2356
                        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...
2357
                            $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...
2358
                            break;
2359 3
2360
                        // Deferred eager load only works for single identifier classes
2361
                        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...
2362
                            // TODO: Is there a faster approach?
2363
                            $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...
2364
2365
                            $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...
2366 1008
                            break;
2367
2368 1008
                        default:
2369
                            // TODO: This is very imperformant, ignore it?
2370
                            $newValue    = $this->em->find($targetEntity, $normalizedAssociatedId);
2371
                            // Needed to re-assign original entity data for freshly loaded entity
2372
                            $managedData = $this->originalEntityData[spl_object_hash($newValue)];
2373
                            break;
2374
                    }
2375
2376
                    // @TODO using `$associatedId` here seems to be risky.
2377
                    $this->registerManaged($newValue, $associatedId, $managedData);
2378 1218
2379
                    break;
2380 1218
            }
2381 1217
2382 1217
            $this->originalEntityData[$oid][$field] = $newValue;
2383 1217
            $association->setValue($entity, $newValue);
2384 1217
2385 1217
            if (
2386 1217
                $association->getInversedBy()
2387 1217
                && $association instanceof OneToOneAssociationMetadata
2388 1217
                // @TODO refactor this
2389 1217
                // we don't want to set any values in un-initialized proxies
2390 1217
                && ! (
2391 1217
                    $newValue instanceof GhostObjectInterface
2392 1217
                    && ! $newValue->isProxyInitialized()
2393 1217
                )
2394 1217
            ) {
2395 1217
                $inverseAssociation = $targetClass->getProperty($association->getInversedBy());
0 ignored issues
show
Bug introduced by
The method getProperty() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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

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

Loading history...
2396
2397 3
                $inverseAssociation->setValue($newValue, $entity);
2398 3
            }
2399
        }
2400
2401 1218
        // defer invoking of postLoad event to hydration complete step
2402 7
        $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...
2403
2404 1218
        return $entity;
2405
    }
2406
2407
    /**
2408
     * @return void
2409
     */
2410
    public function triggerEagerLoads()
2411
    {
2412
        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...
2413
            return;
2414
        }
2415
2416
        // avoid infinite recursion
2417
        $eagerLoadingEntities       = $this->eagerLoadingEntities;
2418 17
        $this->eagerLoadingEntities = [];
2419
2420 17
        foreach ($eagerLoadingEntities as $entityName => $ids) {
2421 17
            if ( ! $ids) {
2422
                continue;
2423
            }
2424
2425
            $class = $this->em->getClassMetadata($entityName);
2426
2427
            $this->getEntityPersister($entityName)->loadAll(
2428
                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...
2429
            );
2430
        }
2431
    }
2432
2433 112
    /**
2434
     * Initializes (loads) an uninitialized persistent collection of an entity.
2435 112
     *
2436 112
     * @param \Doctrine\ORM\PersistentCollection $collection The collection to initialize.
2437
     *
2438
     * @return void
2439
     *
2440
     * @todo Maybe later move to EntityManager#initialize($proxyOrCollection). See DDC-733.
2441
     */
2442
    public function loadCollection(PersistentCollection $collection)
2443
    {
2444
        $association = $collection->getMapping();
2445
        $persister   = $this->getEntityPersister($association->getTargetEntity());
2446 13
2447
        if ($association instanceof OneToManyAssociationMetadata) {
2448 13
            $persister->loadOneToManyCollection($association, $collection->getOwner(), $collection);
2449
        } else {
2450
            $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...
2451
        }
2452 13
2453
        $collection->setInitialized(true);
2454 13
    }
2455 13
2456
    /**
2457
     * Gets the identity map of the UnitOfWork.
2458
     *
2459
     * @return array
2460
     */
2461
    public function getIdentityMap()
2462
    {
2463
        return $this->identityMap;
2464
    }
2465
2466
    /**
2467
     * Gets the original data of an entity. The original data is the data that was
2468
     * present at the time the entity was reconstituted from the database.
2469
     *
2470
     * @param object $entity
2471
     *
2472 668
     * @return array
2473
     */
2474 668
    public function getOriginalEntityData($entity)
2475
    {
2476 668
        $oid = spl_object_hash($entity);
2477 4
2478
        return $this->originalEntityData[$oid] ?? [];
2479
    }
2480 668
2481
    /**
2482
     * @ignore
2483
     *
2484
     * @param object $entity
2485
     * @param array  $data
2486
     *
2487
     * @return void
2488
     */
2489
    public function setOriginalEntityData($entity, array $data)
2490
    {
2491
        $this->originalEntityData[spl_object_hash($entity)] = $data;
2492
    }
2493
2494
    /**
2495
     * INTERNAL:
2496
     * Sets a property value of the original data array of an entity.
2497
     *
2498
     * @ignore
2499 806
     *
2500
     * @param string $oid
2501 806
     * @param string $property
2502
     * @param mixed  $value
2503
     *
2504 806
     * @return void
2505 806
     */
2506
    public function setOriginalEntityProperty($oid, $property, $value)
2507 806
    {
2508 310
        $this->originalEntityData[$oid][$property] = $value;
2509 310
    }
2510
2511
    /**
2512 310
     * Gets the identifier of an entity.
2513 310
     * The returned value is always an array of identifier values. If the entity
2514 310
     * has a composite identifier then the identifier values are in the same
2515 310
     * order as the identifier field names as returned by ClassMetadata#getIdentifierFieldNames().
2516 310
     *
2517
     * @param object $entity
2518
     *
2519
     * @return array The identifier values.
2520
     */
2521
    public function getEntityIdentifier($entity)
2522 2
    {
2523 2
        return $this->entityIdentifiers[spl_object_hash($entity)];
2524
    }
2525
2526 2
    /**
2527
     * Processes an entity instance to extract their identifier values.
2528
     *
2529 308
     * @param object $entity The entity instance.
2530 21
     *
2531
     * @return mixed A scalar value.
2532 21
     *
2533
     * @throws \Doctrine\ORM\ORMInvalidArgumentException
2534 21
     */
2535 21
    public function getSingleIdentifierValue($entity)
2536
    {
2537
        $class     = $this->em->getClassMetadata(get_class($entity));
2538 289
        $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...
2539
2540
        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...
2541 289
            throw ORMInvalidArgumentException::invalidCompositeIdentifier();
2542 71
        }
2543
2544
        $values = $this->isInIdentityMap($entity)
2545
            ? $this->getEntityIdentifier($entity)
2546 308
            : $persister->getIdentifier($entity);
2547
2548 111
        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...
2549 3
    }
2550
2551
    /**
2552 308
     * @param array  $id
2553
     * @param string $rootClassName
2554
     *
2555 665
     * @return GhostObjectInterface|object
2556 665
     */
2557
    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...
2558 665
    {
2559 665
        if ($fetched = $this->tryGetById($id, $rootClassName)) {
2560 665
            return $fetched;
2561
        }
2562 665
2563
        $class = $this->em->getClassMetadata($rootClassName);
2564 665
2565 2
        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...
2566
            // can't do this for inheritance trees!
2567
            // @TODO fetching from the EntityManager feels dirty here
2568 665
            return $this->em->find($rootClassName, $id);
2569
        }
2570
2571 805
        $sortedId = [];
2572 219
2573
        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...
2574
            $sortedId[$idField] = $id[$idField];
2575 702
        }
2576 702
2577 702
        $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...
2578
2579
        $this->registerManaged($proxy, $sortedId, []);
2580
2581
        return $proxy;
2582 702
    }
2583
2584 702
    /**
2585
     * Tries to find an entity with the given identifier in the identity map of
2586
     * this UnitOfWork.
2587
     *
2588
     * @param mixed  $id            The entity identifier to look for.
2589 702
     * @param string $rootClassName The name of the root class of the mapped entity hierarchy.
2590 33
     *
2591
     * @return object|bool Returns the entity with the specified identifier if it exists in
2592
     *                     this UnitOfWork, FALSE otherwise.
2593 669
     */
2594
    public function tryGetById($id, $rootClassName)
2595 586
    {
2596 260
        $idHash = implode(' ', (array) $id);
2597
2598
        return $this->identityMap[$rootClassName][$idHash] ?? false;
2599 564
    }
2600
2601
    /**
2602 564
     * Schedules an entity for dirty-checking at commit-time.
2603 485
     *
2604
     * @param object $entity The entity to schedule for dirty-checking.
2605
     *
2606 64
     * @return void
2607
     */
2608 2
    public function scheduleForSynchronization($entity)
2609
    {
2610 2
        $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...
2611 2
2612
        $this->scheduledForSynchronization[$rootClassName][spl_object_hash($entity)] = $entity;
2613 2
    }
2614
2615
    /**
2616
     * Checks whether the UnitOfWork has any pending insertions.
2617 62
     *
2618
     * @return boolean TRUE if this UnitOfWork has pending insertions, FALSE otherwise.
2619 62
     */
2620
    public function hasPendingInsertions()
2621
    {
2622
        return ! empty($this->entityInsertions);
2623 485
    }
2624 38
2625 38
    /**
2626
     * Calculates the size of the UnitOfWork. The size of the UnitOfWork is the
2627 38
     * number of entities in the identity map.
2628
     *
2629
     * @return integer
2630 478
     */
2631
    public function size()
2632
    {
2633 478
        return \array_sum(\array_map('count', $this->identityMap));
2634 478
    }
2635 478
2636
    /**
2637 478
     * Gets the EntityPersister for an Entity.
2638
     *
2639 287
     * @param string $entityName The name of the Entity.
2640
     *
2641 287
     * @return \Doctrine\ORM\Persisters\Entity\EntityPersister
2642
     */
2643
    public function getEntityPersister($entityName)
2644 284
    {
2645
        if (isset($this->entityPersisters[$entityName])) {
2646
            return $this->entityPersisters[$entityName];
2647 478
        }
2648
2649 287
        $class = $this->em->getClassMetadata($entityName);
2650 287
2651
        switch (true) {
2652 287
            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...
2653
                $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...
2654
                break;
2655 284
2656 281
            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...
2657
                $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...
2658
                break;
2659
2660
            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...
2661
                $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...
2662
                break;
2663 284
2664
            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...
2665
                throw new \RuntimeException('No persister found for entity.');
2666 284
        }
2667 166
2668
        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...
2669
            $persister = $this->em->getConfiguration()
2670
                ->getSecondLevelCacheConfiguration()
2671
                ->getCacheFactory()
2672 166
                ->buildCachedEntityPersister($this->em, $persister, $class);
2673 166
        }
2674 166
2675 166
        $this->entityPersisters[$entityName] = $persister;
2676 166
2677
        return $this->entityPersisters[$entityName];
2678
    }
2679
2680
    /**
2681 166
     * Gets a collection persister for a collection-valued association.
2682
     *
2683 192
     * @param ToManyAssociationMetadata $association
2684
     *
2685
     * @return \Doctrine\ORM\Persisters\Collection\CollectionPersister
2686
     */
2687 30
    public function getCollectionPersister(ToManyAssociationMetadata $association)
2688 30
    {
2689
        $role = $association->getCache()
2690
            ? sprintf('%s::%s', $association->getSourceEntity(), $association->getName())
2691
            : get_class($association);
2692
2693 163
        if (isset($this->collectionPersisters[$role])) {
2694 157
            return $this->collectionPersisters[$role];
2695 157
        }
2696
2697
        $persister = $association instanceof OneToManyAssociationMetadata
2698 6
            ? new OneToManyPersister($this->em)
2699
            : new ManyToManyPersister($this->em);
2700 6
2701
        if ($this->hasCache && $association->getCache()) {
2702 6
            $persister = $this->em->getConfiguration()
2703 6
                ->getSecondLevelCacheConfiguration()
2704
                ->getCacheFactory()
2705
                ->buildCachedCollectionPersister($this->em, $persister, $association);
2706
        }
2707
2708
        $this->collectionPersisters[$role] = $persister;
2709
2710
        return $this->collectionPersisters[$role];
2711
    }
2712 163
2713 163
    /**
2714 163
     * INTERNAL:
2715
     * Registers an entity as managed.
2716
     *
2717 163
     * @param object $entity The entity.
2718 163
     * @param array  $id     Map containing identifier field names as key and its associated values.
2719
     * @param array  $data   The original entity data.
2720
     *
2721
     * @return void
2722 163
     */
2723
    public function registerManaged($entity, array $id, array $data)
2724 163
    {
2725
        $isProxy = $entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized();
2726
        $oid     = spl_object_hash($entity);
2727 284
2728 284
        $this->entityIdentifiers[$oid]  = $id;
2729
        $this->entityStates[$oid]       = self::STATE_MANAGED;
2730 284
        $this->originalEntityData[$oid] = $data;
2731 49
2732 49
        $this->addToIdentityMap($entity);
2733
2734
        if ($entity instanceof NotifyPropertyChanged && ! $isProxy) {
2735 284
            $entity->addPropertyChangedListener($this);
2736
        }
2737
    }
2738
2739 486
    /**
2740
     * INTERNAL:
2741
     * Clears the property changeset of the entity with the given OID.
2742
     *
2743
     * @param string $oid The entity's OID.
2744 486
     *
2745
     * @return void
2746 3
     */
2747
    public function clearEntityChangeSet($oid)
2748 3
    {
2749 3
        unset($this->entityChangeSets[$oid]);
2750
    }
2751 3
2752
    /* PropertyChangedListener implementation */
2753
2754
    /**
2755 486
     * Notifies this UnitOfWork of a property change in an entity.
2756 486
     *
2757 486
     * @param object $entity       The entity that owns the property.
2758
     * @param string $propertyName The name of the property that changed.
2759 486
     * @param mixed  $oldValue     The old value of the property.
2760 486
     * @param mixed  $newValue     The new value of the property.
2761
     *
2762 486
     * @return void
2763 4
     */
2764 4
    public function propertyChanged($entity, $propertyName, $oldValue, $newValue)
2765
    {
2766
        $class = $this->em->getClassMetadata(get_class($entity));
2767 486
2768 564
        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...
2769
            return; // ignore non-persistent fields
2770
        }
2771
2772 669
        $oid = spl_object_hash($entity);
2773
2774 669
        // Update changeset and mark entity for synchronization
2775
        $this->entityChangeSets[$oid][$propertyName] = [$oldValue, $newValue];
2776
2777 669
        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...
2778
            $this->scheduleForSynchronization($entity);
2779
        }
2780
    }
2781
2782
    /**
2783 861
     * Gets the currently scheduled entity insertions in this UnitOfWork.
2784
     *
2785 861
     * @return array
2786 861
     */
2787
    public function getScheduledEntityInsertions()
2788
    {
2789
        return $this->entityInsertions;
2790 6
    }
2791 6
2792
    /**
2793 6
     * Gets the currently scheduled entity updates in this UnitOfWork.
2794 6
     *
2795
     * @return array
2796
     */
2797
    public function getScheduledEntityUpdates()
2798 6
    {
2799
        return $this->entityUpdates;
2800 6
    }
2801 6
2802
    /**
2803
     * Gets the currently scheduled entity deletions in this UnitOfWork.
2804 6
     *
2805
     * @return array
2806
     */
2807
    public function getScheduledEntityDeletions()
2808
    {
2809
        return $this->entityDeletions;
2810
    }
2811
2812
    /**
2813
     * Gets the currently scheduled complete collection deletions
2814
     *
2815 142
     * @return array
2816
     */
2817 142
    public function getScheduledCollectionDeletions()
2818 142
    {
2819
        return $this->collectionDeletions;
2820 142
    }
2821 142
2822 76
    /**
2823 76
     * Gets the currently scheduled collection inserts, updates and deletes.
2824
     *
2825 80
     * @return array
2826 80
     */
2827 80
    public function getScheduledCollectionUpdates()
2828
    {
2829
        return $this->collectionUpdates;
2830 142
    }
2831 142
2832
    /**
2833
     * Helper method to initialize a lazy loading proxy or persistent collection.
2834
     *
2835
     * @param object $obj
2836
     *
2837
     * @return void
2838 2
     */
2839
    public function initializeObject($obj)
2840 2
    {
2841
        if ($obj instanceof GhostObjectInterface) {
2842
            $obj->initializeProxy();
2843
2844
            return;
2845
        }
2846
2847
        if ($obj instanceof PersistentCollection) {
2848
            $obj->initialize();
2849
        }
2850
    }
2851 115
2852
    /**
2853 115
     * Helper method to show an object as string.
2854
     *
2855 115
     * @param object $obj
2856 112
     *
2857 115
     * @return string
2858
     */
2859
    private static function objToStr($obj)
2860
    {
2861
        return method_exists($obj, '__toString') ? (string) $obj : get_class($obj).'@'.spl_object_hash($obj);
2862
    }
2863
2864
    /**
2865
     * Marks an entity as read-only so that it will not be considered for updates during UnitOfWork#commit().
2866
     *
2867
     * This operation cannot be undone as some parts of the UnitOfWork now keep gathering information
2868
     * on this object that might be necessary to perform a correct update.
2869
     *
2870
     * @param object $object
2871
     *
2872
     * @return void
2873
     *
2874
     * @throws ORMInvalidArgumentException
2875
     */
2876
    public function markReadOnly($object)
2877
    {
2878
        if ( ! is_object($object) || ! $this->isInIdentityMap($object)) {
2879
            throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object);
2880
        }
2881
2882
        $this->readOnlyObjects[spl_object_hash($object)] = true;
2883
    }
2884
2885 313
    /**
2886
     * Is this entity read only?
2887 313
     *
2888 313
     * @param object $object
2889
     *
2890
     * @return bool
2891
     *
2892
     * @throws ORMInvalidArgumentException
2893
     */
2894
    public function isReadOnly($object)
2895
    {
2896
        if ( ! is_object($object)) {
2897
            throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object);
2898
        }
2899
2900 842
        return isset($this->readOnlyObjects[spl_object_hash($object)]);
2901
    }
2902 842
2903
    /**
2904
     * Perform whatever processing is encapsulated here after completion of the transaction.
2905
     */
2906
    private function afterTransactionComplete()
2907
    {
2908
        $this->performCallbackOnCachedPersister(function (CachedPersister $persister) {
2909
            $persister->afterTransactionComplete();
2910
        });
2911
    }
2912
2913
    /**
2914 126
     * Perform whatever processing is encapsulated here after completion of the rolled-back.
2915
     */
2916 126
    private function afterTransactionRolledBack()
2917
    {
2918 126
        $this->performCallbackOnCachedPersister(function (CachedPersister $persister) {
2919
            $persister->afterTransactionRolledBack();
2920
        });
2921
    }
2922 126
2923 113
    /**
2924 126
     * Performs an action after the transaction.
2925
     *
2926 126
     * @param callable $callback
2927
     */
2928
    private function performCallbackOnCachedPersister(callable $callback)
2929
    {
2930
        if ( ! $this->hasCache) {
2931
            return;
2932
        }
2933
2934
        foreach (array_merge($this->entityPersisters, $this->collectionPersisters) as $persister) {
2935
            if ($persister instanceof CachedPersister) {
2936
                $callback($persister);
2937
            }
2938
        }
2939 522
    }
2940
2941 522
    private function dispatchOnFlushEvent()
2942
    {
2943 522
        if ($this->eventManager->hasListeners(Events::onFlush)) {
2944 79
            $this->eventManager->dispatchEvent(Events::onFlush, new OnFlushEventArgs($this->em));
2945 522
        }
2946
    }
2947
2948
    private function dispatchPostFlushEvent()
2949
    {
2950
        if ($this->eventManager->hasListeners(Events::postFlush)) {
2951
            $this->eventManager->dispatchEvent(Events::postFlush, new PostFlushEventArgs($this->em));
2952
        }
2953
    }
2954
2955
    /**
2956
     * Verifies if two given entities actually are the same based on identifier comparison
2957 5
     *
2958
     * @param object $entity1
2959 5
     * @param object $entity2
2960
     *
2961 5
     * @return bool
2962 5
     */
2963
    private function isIdentifierEquals($entity1, $entity2)
2964
    {
2965
        if ($entity1 === $entity2) {
2966
            return true;
2967
        }
2968
2969
        $class     = $this->em->getClassMetadata(get_class($entity1));
2970
        $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...
2971
2972
        if ($class !== $this->em->getClassMetadata(get_class($entity2))) {
2973
            return false;
2974
        }
2975
2976
        $identifierFlattener = $this->em->getIdentifierFlattener();
2977
2978
        $oid1 = spl_object_hash($entity1);
2979
        $oid2 = spl_object_hash($entity2);
2980 1
2981
        $id1 = $this->entityIdentifiers[$oid1]
2982 1
            ?? $identifierFlattener->flattenIdentifier($class, $persister->getIdentifier($entity1));
0 ignored issues
show
Documentation introduced by
$class is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2983
        $id2 = $this->entityIdentifiers[$oid2]
2984 1
            ?? $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...
2985
2986
        return $id1 === $id2 || implode(' ', $id1) === implode(' ', $id2);
2987
    }
2988
2989
    /**
2990
     * @throws ORMInvalidArgumentException
2991
     */
2992
    private function assertThatThereAreNoUnintentionallyNonPersistedAssociations() : void
2993
    {
2994 1063
        $entitiesNeedingCascadePersist = \array_diff_key($this->nonCascadedNewDetectedEntities, $this->entityInsertions);
2995
2996 1063
        $this->nonCascadedNewDetectedEntities = [];
2997 845
2998
        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...
2999
            throw ORMInvalidArgumentException::newEntitiesFoundThroughRelationships(
3000 1063
                \array_values($entitiesNeedingCascadePersist)
3001
            );
3002
        }
3003 1063
    }
3004 1031
3005 1031
    /**
3006
     * This method called by hydrators, and indicates that hydrator totally completed current hydration cycle.
3007 354
     * Unit of work able to fire deferred events, related to loading events here.
3008 213
     *
3009 213
     * @internal should be called internally from object hydrators
3010
     */
3011 333
    public function hydrationComplete()
3012 333
    {
3013 333
        $this->hydrationCompleteHandler->hydrationComplete();
3014
    }
3015
}
3016