Test Failed
Pull Request — develop (#6719)
by Marco
63:23
created

UnitOfWork::isScheduledForDelete()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM;
6
7
use Doctrine\Common\Collections\Collection;
8
use Doctrine\Common\NotifyPropertyChanged;
9
use Doctrine\Common\PropertyChangedListener;
10
use Doctrine\DBAL\LockMode;
11
use Doctrine\Instantiator\Instantiator;
12
use Doctrine\ORM\Cache\Persister\CachedPersister;
13
use Doctrine\ORM\Event\LifecycleEventArgs;
14
use Doctrine\ORM\Event\ListenersInvoker;
15
use Doctrine\ORM\Event\OnFlushEventArgs;
16
use Doctrine\ORM\Event\PostFlushEventArgs;
17
use Doctrine\ORM\Event\PreFlushEventArgs;
18
use Doctrine\ORM\Event\PreUpdateEventArgs;
19
use Doctrine\ORM\Internal\HydrationCompleteHandler;
20
use Doctrine\ORM\Mapping\AssociationMetadata;
21
use Doctrine\ORM\Mapping\ChangeTrackingPolicy;
22
use Doctrine\ORM\Mapping\ClassMetadata;
23
use Doctrine\ORM\Mapping\FetchMode;
24
use Doctrine\ORM\Mapping\FieldMetadata;
25
use Doctrine\ORM\Mapping\GeneratorType;
26
use Doctrine\ORM\Mapping\InheritanceType;
27
use Doctrine\ORM\Mapping\JoinColumnMetadata;
28
use Doctrine\ORM\Mapping\ManyToManyAssociationMetadata;
29
use Doctrine\ORM\Mapping\OneToManyAssociationMetadata;
30
use Doctrine\ORM\Mapping\OneToOneAssociationMetadata;
31
use Doctrine\ORM\Mapping\ToManyAssociationMetadata;
32
use Doctrine\ORM\Mapping\ToOneAssociationMetadata;
33
use Doctrine\ORM\Mapping\VersionFieldMetadata;
34
use Doctrine\ORM\Persisters\Collection\ManyToManyPersister;
35
use Doctrine\ORM\Persisters\Collection\OneToManyPersister;
36
use Doctrine\ORM\Persisters\Entity\BasicEntityPersister;
37
use Doctrine\ORM\Persisters\Entity\JoinedSubclassPersister;
38
use Doctrine\ORM\Persisters\Entity\SingleTablePersister;
39
use Exception;
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
     * All pending collection deletions.
179
     *
180
     * @var array
181
     */
182
    private $collectionDeletions = [];
183
184
    /**
185
     * All pending collection updates.
186
     *
187
     * @var array
188
     */
189
    private $collectionUpdates = [];
190
191
    /**
192
     * List of collections visited during changeset calculation on a commit-phase of a UnitOfWork.
193
     * At the end of the UnitOfWork all these collections will make new snapshots
194
     * of their data.
195
     *
196
     * @var array
197
     */
198
    private $visitedCollections = [];
199
200
    /**
201
     * The EntityManager that "owns" this UnitOfWork instance.
202
     *
203
     * @var EntityManagerInterface
204
     */
205
    private $em;
206
207
    /**
208
     * The entity persister instances used to persist entity instances.
209
     *
210
     * @var array
211
     */
212
    private $entityPersisters = [];
213
214
    /**
215
     * The collection persister instances used to persist collections.
216
     *
217
     * @var array
218
     */
219
    private $collectionPersisters = [];
220
221
    /**
222
     * The EventManager used for dispatching events.
223
     *
224
     * @var \Doctrine\Common\EventManager
225
     */
226
    private $eventManager;
227
228
    /**
229
     * The ListenersInvoker used for dispatching events.
230
     *
231
     * @var \Doctrine\ORM\Event\ListenersInvoker
232
     */
233
    private $listenersInvoker;
234
235
    /**
236
     * @var Instantiator
237
     */
238
    private $instantiator;
239
240
    /**
241
     * Orphaned entities that are scheduled for removal.
242
     *
243
     * @var array
244
     */
245
    private $orphanRemovals = [];
246
247
    /**
248
     * Read-Only objects are never evaluated
249
     *
250
     * @var array
251
     */
252
    private $readOnlyObjects = [];
253
254
    /**
255
     * Map of Entity Class-Names and corresponding IDs that should eager loaded when requested.
256
     *
257
     * @var array
258
     */
259
    private $eagerLoadingEntities = [];
260
261
    /**
262
     * @var boolean
263
     */
264
    protected $hasCache = false;
265
266
    /**
267
     * Helper for handling completion of hydration
268
     *
269
     * @var HydrationCompleteHandler
270
     */
271
    private $hydrationCompleteHandler;
272
273
    /**
274
     * Initializes a new UnitOfWork instance, bound to the given EntityManager.
275
     *
276
     * @param EntityManagerInterface $em
277
     */
278
    public function __construct(EntityManagerInterface $em)
279
    {
280
        $this->em                       = $em;
281
        $this->eventManager             = $em->getEventManager();
282
        $this->listenersInvoker         = new ListenersInvoker($em);
283
        $this->hasCache                 = $em->getConfiguration()->isSecondLevelCacheEnabled();
284
        $this->instantiator             = new Instantiator();
285
        $this->hydrationCompleteHandler = new HydrationCompleteHandler($this->listenersInvoker, $em);
286
    }
287
288
    /**
289
     * Commits the UnitOfWork, executing all operations that have been postponed
290
     * up to this point. The state of all managed entities will be synchronized with
291
     * the database.
292 2290
     *
293
     * The operations are executed in the following order:
294 2290
     *
295 2290
     * 1) All entity insertions
296 2290
     * 2) All entity updates
297 2290
     * 3) All collection deletions
298 2290
     * 4) All collection updates
299 2290
     * 5) All entity deletions
300 2290
     *
301 2290
     * @return void
302
     *
303
     * @throws \Exception
304
     */
305
    public function commit()
306
    {
307
        // Raise preFlush
308
        if ($this->eventManager->hasListeners(Events::preFlush)) {
309
            $this->eventManager->dispatchEvent(Events::preFlush, new PreFlushEventArgs($this->em));
310
        }
311
312
        $this->computeChangeSets();
313
314
        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...
315
                $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...
316
                $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...
317
                $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...
318
                $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...
319
                $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...
320
            $this->dispatchOnFlushEvent();
321
            $this->dispatchPostFlushEvent();
322 1015
323
            return; // Nothing to do.
324
        }
325 1015
326 2
        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...
327
            foreach ($this->orphanRemovals as $orphan) {
328
                $this->remove($orphan);
329
            }
330 1015
        }
331 1007
332 16
        $this->dispatchOnFlushEvent();
333 15
334 1
        // Now we need a commit order to maintain referential integrity
335 1
        $commitOrder = $this->getCommitOrder();
336 1
337
        $conn = $this->em->getConnection();
338
        $conn->beginTransaction();
339
340 1012
        try {
341 165
            // Collection deletions (deletions of complete collections)
342 129
            foreach ($this->collectionDeletions as $collectionToDelete) {
343 40
                $this->getCollectionPersister($collectionToDelete->getMapping())->delete($collectionToDelete);
344 37
            }
345 1012
346 25
            if ($this->entityInsertions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->entityInsertions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
422 1002
        }
423 1002
    }
424 1002
425 1002
    /**
426 1002
     * Executes any extra updates that have been scheduled.
427 1002
     */
428 1002
    private function executeExtraUpdates()
429 1002
    {
430 1002
        foreach ($this->extraUpdates as $oid => $update) {
431 1002
            list ($entity, $changeset) = $update;
432
433
            $this->entityChangeSets[$oid] = $changeset;
434
435
//            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...
436
//            \Doctrine\Common\Util\Debug::dump($changeset, 3);
437
438 1014
            $this->getEntityPersister(get_class($entity))->update($entity);
439
        }
440 1014
441 1006
        $this->extraUpdates = [];
442
    }
443 1006
444
    /**
445 1012
     * Gets the changeset for an entity.
446
     *
447
     * @param object $entity
448
     *
449
     * @return array
450
     */
451
    public function & getEntityChangeSet($entity)
452
    {
453
        $oid  = spl_object_hash($entity);
454
        $data = [];
455
456
        if (!isset($this->entityChangeSets[$oid])) {
457
            return $data;
458
        }
459
460
        return $this->entityChangeSets[$oid];
461 16
    }
462
463 16
    /**
464
     * Computes the changes that happened to a single entity.
465 16
     *
466 1
     * Modifies/populates the following properties:
467
     *
468
     * {@link originalEntityData}
469 15
     * If the entity is NEW or MANAGED but not yet fully persisted (only has an id)
470
     * then it was not fetched from the database and therefore we have no original
471 15
     * entity data yet. All of the current entity data is stored as the original entity data.
472 14
     *
473
     * {@link entityChangeSets}
474
     * The changes detected on all properties of the entity are stored there.
475
     * A change is a tuple array where the first entry is the old value and the second
476 15
     * entry is the new value of the property. Changesets are used by persisters
477
     * to INSERT/UPDATE the persistent entity state.
478 15
     *
479
     * {@link entityUpdates}
480
     * If the entity is already fully MANAGED (has been fetched from the database before)
481
     * and any changes to its properties are detected, then a reference to the entity is stored
482
     * there to mark it for an update.
483 15
     *
484 2
     * {@link collectionDeletions}
485
     * If a PersistentCollection has been de-referenced in a fully MANAGED entity,
486
     * then this collection is marked for deletion.
487
     *
488 13
     * @ignore
489
     *
490 13
     * @internal Don't call from the outside.
491 6
     *
492
     * @param ClassMetadata $class  The class descriptor of the entity.
493 12
     * @param object        $entity The entity for which to compute the changes.
494
     *
495
     * @return void
496
     */
497
    public function computeChangeSet(ClassMetadata $class, $entity)
498 40
    {
499
        $oid = spl_object_hash($entity);
500 40
501 40
        if (isset($this->readOnlyObjects[$oid])) {
502
            return;
503 40
        }
504
505
        if ($class->inheritanceType !== InheritanceType::NONE) {
506
            $class = $this->em->getClassMetadata(get_class($entity));
507
        }
508 40
509
        $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 506 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...
510
511 40
        if ($invoke !== ListenersInvoker::INVOKE_NONE) {
512 40
            $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 506 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...
513
        }
514
515
        $actualData = [];
516
517
        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...
518
            $value = $property->getValue($entity);
519
520
            if ($property instanceof ToManyAssociationMetadata && $value !== null) {
521 1006
                if ($value instanceof PersistentCollection && $value->getOwner() === $entity) {
522
                    continue;
523 1006
                }
524 1006
525
                $value = $property->wrap($entity, $value, $this->em);
526 1006
527 1
                $property->setValue($entity, $value);
528
529
                $actualData[$name] = $value;
530 1006
531
                continue;
532
            }
533
534
            if (
535
                ( ! $class->isIdentifier($name)
536
                    || ! $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...
537
                    || ! $class->getProperty($name)->hasValueGenerator()
538
                    || $class->getProperty($name)->getValueGenerator()->getType() !== GeneratorType::IDENTITY
539
                ) && (! $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...
540
                $actualData[$name] = $value;
541
            }
542
        }
543
544
        if ( ! isset($this->originalEntityData[$oid])) {
545
            // Entity is either NEW or MANAGED but not yet fully persisted (only has an id).
546
            // These result in an INSERT.
547
            $this->originalEntityData[$oid] = $actualData;
548
            $changeSet = [];
549
550
            foreach ($actualData as $propName => $actualValue) {
551
                $property = $class->getProperty($propName);
552
553
                if (($property instanceof FieldMetadata) ||
554
                    ($property instanceof ToOneAssociationMetadata && $property->isOwningSide())) {
555
                    $changeSet[$propName] = [null, $actualValue];
556
                }
557
            }
558
559
            $this->entityChangeSets[$oid] = $changeSet;
560
        } else {
561
            // Entity is "fully" MANAGED: it was already fully persisted before
562
            // and we have a copy of the original data
563
            $originalData           = $this->originalEntityData[$oid];
564
            $isChangeTrackingNotify = $class->changeTrackingPolicy === ChangeTrackingPolicy::NOTIFY;
565
            $changeSet              = ($isChangeTrackingNotify && isset($this->entityChangeSets[$oid]))
566
                ? $this->entityChangeSets[$oid]
567 1016
                : [];
568
569 1016
            foreach ($actualData as $propName => $actualValue) {
570
                // skip field, its a partially omitted one!
571 1016
                if ( ! (isset($originalData[$propName]) || array_key_exists($propName, $originalData))) {
572 2
                    continue;
573
                }
574
575 1016
                $orgValue = $originalData[$propName];
576 307
577
                // skip if value haven't changed
578
                if ($orgValue === $actualValue) {
579 1016
                    continue;
580
                }
581 1016
582 137
                $property = $class->getProperty($propName);
583
584
                // Persistent collection was exchanged with the "originally"
585 1016
                // created one. This can only mean it was cloned and replaced
586
                // on another entity.
587 1016
                if ($actualValue instanceof PersistentCollection) {
588 1016
                    $owner = $actualValue->getOwner();
589
590 1016
                    if ($owner === null) { // cloned
591 777
                        $actualValue->setOwner($entity, $property);
592 200
                    } else if ($owner !== $entity) { // no clone, we have to fix
593 200
                        if (! $actualValue->isInitialized()) {
594
                            $actualValue->initialize(); // we have to do this otherwise the cols share state
595
                        }
596 5
597
                        $newValue = clone $actualValue;
598
599
                        $newValue->setOwner($entity, $property);
600 772
601 242
                        $property->setValue($entity, $newValue);
602
                    }
603
                }
604 772
605
                switch (true) {
606
                    case ($property instanceof FieldMetadata):
607 772
                        if ($isChangeTrackingNotify) {
608 772
                            // Continue inside switch behaves as break.
609
                            // We are required to use continue 2, since we need to continue to next $actualData item
610 772
                            continue 2;
611 772
                        }
612
613 772
                        $changeSet[$propName] = [$orgValue, $actualValue];
614
                        break;
615 772
616
                    case ($property instanceof ToOneAssociationMetadata):
617 772
                        if ($property->isOwningSide()) {
618
                            $changeSet[$propName] = [$orgValue, $actualValue];
619
                        }
620 1016
621 1016
                        if ($orgValue !== null && $property->isOrphanRemoval()) {
622 1016
                            $this->scheduleOrphanRemoval($orgValue);
623
                        }
624
625
                        break;
626 1016
627
                    case ($property instanceof ToManyAssociationMetadata):
628
                        // Check if original value exists
629 1012
                        if ($orgValue instanceof PersistentCollection) {
630 1012
                            // A PersistentCollection was de-referenced, so delete it.
631
                            $coid = spl_object_hash($orgValue);
632 1012
633 996
                            if (!isset($this->collectionDeletions[$coid])) {
634 945
                                $this->collectionDeletions[$coid] = $orgValue;
635
                                $changeSet[$propName] = $orgValue; // Signal changeset, to-many associations will be ignored
636 945
                            }
637
                        }
638
639 894
                        break;
640
641 894
                    default:
642 894
                        // Do nothing
643
                }
644
            }
645
646 1012
            if ($changeSet) {
647
                $this->entityChangeSets[$oid]   = $changeSet;
648
                $this->originalEntityData[$oid] = $actualData;
649
                $this->entityUpdates[$oid]      = $entity;
650 263
            }
651 263
        }
652 263
653
        // Look for changes in associations of the entity
654 263
        foreach ($class->getDeclaredPropertiesIterator() as $property) {
655
            if (! ($property instanceof AssociationMetadata) || ($value = $property->getValue($entity)) === null) {
656 263
                continue;
657
            }
658 248
659 7
            $this->computeAssociationChanges($property, $value);
660
661
            if ($property instanceof ManyToManyAssociationMetadata &&
662 248
                $value instanceof PersistentCollection &&
663
                ! isset($this->entityChangeSets[$oid]) &&
664
                $property->isOwningSide() &&
665 248
                $value->isDirty()) {
666 232
667
                $this->entityChangeSets[$oid]   = [];
668
                $this->originalEntityData[$oid] = $actualData;
669
                $this->entityUpdates[$oid]      = $entity;
670 111
            }
671 57
        }
672
    }
673
674
    /**
675 57
     * Computes all the changes that have been done to entities and collections
676
     * since the last commit and stores these changes in the _entityChangeSet map
677 57
     * temporarily for access by the persisters, until the UoW commit is finished.
678
     *
679
     * @return void
680 58
     */
681
    public function computeChangeSets()
682
    {
683
        // Compute changes for INSERTed entities first. This must always happen.
684
        $this->computeScheduleInsertsChangeSets();
685 58
686 8
        // Compute changes for other MANAGED entities. Change tracking policies take effect here.
687 8
        foreach ($this->identityMap as $className => $entities) {
688
            $class = $this->em->getClassMetadata($className);
689 8
690
            // Skip class if instances are read-only
691
            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...
692
                continue;
693
            }
694
695
            // If change tracking is explicit or happens through notification, then only compute
696
            // changes on entities of that type that are explicitly marked for synchronization.
697
            switch (true) {
698
                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...
699 58
                    $entitiesToProcess = $entities;
700
                    break;
701 8
702
                case (isset($this->scheduledForSynchronization[$className])):
703 8
                    $entitiesToProcess = $this->scheduledForSynchronization[$className];
704
                    break;
705
706
                default:
707 8
                    $entitiesToProcess = [];
708 8
709
            }
710 8
711
            foreach ($entitiesToProcess as $entity) {
712
                // Ignore uninitialized proxy objects
713 50
                if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
714 49
                    continue;
715 21
                }
716
717
                // Only MANAGED entities that are NOT SCHEDULED FOR INSERTION OR DELETION are processed here.
718 49
                $oid = spl_object_hash($entity);
719 50
720
                if ( ! isset($this->entityInsertions[$oid]) && ! isset($this->entityDeletions[$oid]) && isset($this->entityStates[$oid])) {
721
                    $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...
722
                }
723
            }
724 263
        }
725 84
    }
726 84
727 84
    /**
728
     * Computes the changes of an association.
729
     *
730
     * @param AssociationMetadata $association The association mapping.
731
     * @param mixed               $value       The value of the association.
732 1016
     *
733 894
     * @throws ORMInvalidArgumentException
734 639
     * @throws ORMException
735
     *
736
     * @return void
737 865
     */
738
    private function computeAssociationChanges(AssociationMetadata $association, $value)
739 857
    {
740 857
        if ($value instanceof GhostObjectInterface && ! $value->isProxyInitialized()) {
741 857
            return;
742 857
        }
743 857
744
        if ($value instanceof PersistentCollection && $value->isDirty()) {
745 35
            $coid = spl_object_hash($value);
746 35
747 857
            $this->collectionUpdates[$coid] = $value;
748
            $this->visitedCollections[$coid] = $value;
749
        }
750 1008
751
        // Look through the entities, and in any of their associations,
752
        // 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...
753
        // Unwrap. Uninitialized collections will simply be empty.
754
        $unwrappedValue = ($association instanceof ToOneAssociationMetadata) ? [$value] : $value->unwrap();
755
        $targetEntity   = $association->getTargetEntity();
756
        $targetClass    = $this->em->getClassMetadata($targetEntity);
757
758
        foreach ($unwrappedValue as $key => $entry) {
759 1007
            if (! ($entry instanceof $targetEntity)) {
760
                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...
761
            }
762 1007
763
            $state = $this->getEntityState($entry, self::STATE_NEW);
764
765 1005
            if (! ($entry instanceof $targetEntity)) {
766 447
                throw ORMException::unexpectedAssociationValue(
767
                    $association->getSourceEntity(),
768
                    $association->getName(),
769 447
                    get_class($entry),
770 1
                    $targetEntity
771
                );
772
            }
773
774
            switch ($state) {
775
                case self::STATE_NEW:
776 446
                    if ( ! in_array('persist', $association->getCascade())) {
777 444
                        throw ORMInvalidArgumentException::newEntityFoundThroughRelationship($association, $entry);
778 444
                    }
779
780 3
                    $this->persistNew($targetClass, $entry);
0 ignored issues
show
Documentation introduced by
$targetClass is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
782 3
                    break;
783
784
                case self::STATE_REMOVED:
785 1
                    // Consume the $value as array (it's either an array or an ArrayAccess)
786
                    // and remove the element from Collection.
787
                    if ($association instanceof ToManyAssociationMetadata) {
788
                        unset($value[$key]);
789 446
                    }
790
                    break;
791 426
792 35
                case self::STATE_DETACHED:
793
                    // Can actually not happen right now as we assume STATE_NEW,
794
                    // so the exception will be raised from the DBAL layer (constraint violation).
795
                    throw ORMInvalidArgumentException::detachedEntityFoundThroughRelationship($association, $entry);
796 425
                    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...
797
798 425
                default:
799 446
                    // MANAGED associated entities are already taken into account
800
                    // during changeset calculation anyway, since they are in the identity map.
801
            }
802
        }
803 1005
    }
804
805
    /**
806
     * @param \Doctrine\ORM\Mapping\ClassMetadata $class
807
     * @param object                              $entity
808
     *
809
     * @return void
810
     */
811
    private function persistNew($class, $entity)
812
    {
813
        $oid    = spl_object_hash($entity);
814
        $invoke = $this->listenersInvoker->getSubscribedSystems($class, Events::prePersist);
815
816 865
        if ($invoke !== ListenersInvoker::INVOKE_NONE) {
817
            $this->listenersInvoker->invoke($class, Events::prePersist, $entity, new LifecycleEventArgs($entity, $this->em), $invoke);
818 865
        }
819 28
820
        $generationPlan = $class->getValueGenerationPlan();
821
        $persister = $this->getEntityPersister($class->getClassName());
822 864
        $generationPlan->executeImmediate($this->em, $entity);
823 533
824
        if (! $generationPlan->containsDeferred()) {
825 533
            $id = $this->em->getIdentifierFlattener()->flattenIdentifier($class, $persister->getIdentifier($entity));
826 533
            $this->entityIdentifiers[$oid] = $id;
827
        }
828
829
        $this->entityStates[$oid] = self::STATE_MANAGED;
830
831
        $this->scheduleForInsert($entity);
832 864
    }
833 864
834
    /**
835 864
     * INTERNAL:
836 722
     * Computes the changeset of an individual entity, independently of the
837 6
     * computeChangeSets() routine that is used at the beginning of a UnitOfWork#commit().
838
     *
839
     * The passed entity must be a managed entity. If the entity already has a change set
840 716
     * because this method is invoked during a commit cycle then the change sets are added.
841
     * whereby changes detected in this method prevail.
842 716
     *
843
     * @ignore
844
     *
845
     * @param ClassMetadata $class  The class descriptor of the entity.
846
     * @param object        $entity The entity for which to (re)calculate the change set.
847 716
     *
848 39
     * @return void
849 4
     *
850
     * @throws ORMInvalidArgumentException If the passed entity is not MANAGED.
851
     * @throws \RuntimeException
852 35
     */
853 35
    public function recomputeSingleEntityChangeSet(ClassMetadata $class, $entity) : void
854 35
    {
855
        $oid = spl_object_hash($entity);
856 710
857
        if (! isset($this->entityStates[$oid]) || $this->entityStates[$oid] !== self::STATE_MANAGED) {
858
            throw ORMInvalidArgumentException::entityNotManaged($entity);
859 4
        }
860 3
861
        // skip if change tracking is "NOTIFY"
862 4
        if ($class->changeTrackingPolicy === ChangeTrackingPolicy::NOTIFY) {
863
            return;
864 710
        }
865
866
        if ($class->inheritanceType !== InheritanceType::NONE) {
867
            $class = $this->em->getClassMetadata(get_class($entity));
868
        }
869
870 713
        $actualData = [];
871
872
        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...
873
            switch (true) {
874
                case ($property instanceof VersionFieldMetadata):
875 856
                    // Ignore version field
876
                    break;
877
878
                case ($property instanceof FieldMetadata):
879
                    if (! $property->isPrimaryKey()
880
                        || ! $property->getValueGenerator()
881
                        || $property->getValueGenerator()->getType() !== GeneratorType::IDENTITY) {
882
                        $actualData[$name] = $property->getValue($entity);
883 1031
                    }
884
885 1031
                    break;
886 1031
887
                case ($property instanceof ToOneAssociationMetadata):
888 1031
                    $actualData[$name] = $property->getValue($entity);
889 139
                    break;
890
            }
891
        }
892 1031
893
        if ( ! isset($this->originalEntityData[$oid])) {
894 1031
            throw new \RuntimeException('Cannot call recomputeSingleEntityChangeSet before computeChangeSet on an entity.');
895 269
        }
896
897 269
        $originalData = $this->originalEntityData[$oid];
898 1
        $changeSet = [];
899
900 1
        foreach ($actualData as $propName => $actualValue) {
901
            $orgValue = isset($originalData[$propName]) ? $originalData[$propName] : null;
902
903 269
            if ($orgValue !== $actualValue) {
904
                $changeSet[$propName] = [$orgValue, $actualValue];
905
            }
906 1031
        }
907
908 1031
        if ($changeSet) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $changeSet of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
909 1031
            if (isset($this->entityChangeSets[$oid])) {
910
                $this->entityChangeSets[$oid] = array_merge($this->entityChangeSets[$oid], $changeSet);
911
            } else if ( ! isset($this->entityInsertions[$oid])) {
912
                $this->entityChangeSets[$oid] = $changeSet;
913
                $this->entityUpdates[$oid]    = $entity;
914
            }
915
            $this->originalEntityData[$oid] = $actualData;
916
        }
917
    }
918
919
    /**
920
     * Executes all entity insertions for entities of the specified type.
921
     *
922
     * @param ClassMetadata $class
923
     *
924
     * @return void
925
     */
926
    private function executeInserts(ClassMetadata $class) : void
927
    {
928
        $className      = $class->getClassName();
929 16
        $persister      = $this->getEntityPersister($className);
930
        $invoke         = $this->listenersInvoker->getSubscribedSystems($class, Events::postPersist);
931 16
        $generationPlan = $class->getValueGenerationPlan();
932
933 16
        foreach ($this->entityInsertions as $oid => $entity) {
934
            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...
935
                continue;
936
            }
937
938 16
            $persister->insert($entity);
939
940
            if ($generationPlan->containsDeferred()) {
941
                // Entity has post-insert IDs
942 16
                $oid = spl_object_hash($entity);
943 3
                $id  = $this->em->getIdentifierFlattener()->flattenIdentifier($class, $persister->getIdentifier($entity));
944
945
                $this->entityIdentifiers[$oid] = $id;
946 16
                $this->entityStates[$oid] = self::STATE_MANAGED;
947
                $this->originalEntityData[$oid] = $id + $this->originalEntityData[$oid];
948 16
949 16
                $this->addToIdentityMap($entity);
950 16
            }
951 16
952 16
            unset($this->entityInsertions[$oid]);
953
954
            if ($invoke !== ListenersInvoker::INVOKE_NONE) {
955
                $eventArgs = new LifecycleEventArgs($entity, $this->em);
956 16
957
                $this->listenersInvoker->invoke($class, Events::postPersist, $entity, $eventArgs, $invoke);
958
            }
959
        }
960 16
    }
961 16
962
    /**
963 16
     * Executes all entity updates for entities of the specified type.
964 16
     *
965
     * @param \Doctrine\ORM\Mapping\ClassMetadata $class
966 16
     *
967 16
     * @return void
968
     */
969
    private function executeUpdates($class)
970
    {
971 16
        $className          = $class->getClassName();
972 7
        $persister          = $this->getEntityPersister($className);
973 6
        $preUpdateInvoke    = $this->listenersInvoker->getSubscribedSystems($class, Events::preUpdate);
974 1
        $postUpdateInvoke   = $this->listenersInvoker->getSubscribedSystems($class, Events::postUpdate);
975 1
976 1
        foreach ($this->entityUpdates as $oid => $entity) {
977
            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...
978 7
                continue;
979
            }
980 16
981
            if ($preUpdateInvoke != ListenersInvoker::INVOKE_NONE) {
982
                $this->listenersInvoker->invoke($class, Events::preUpdate, $entity, new PreUpdateEventArgs($entity, $this->em, $this->getEntityChangeSet($entity)), $preUpdateInvoke);
983
984
                $this->recomputeSingleEntityChangeSet($class, $entity);
985
            }
986
987
            if ( ! empty($this->entityChangeSets[$oid])) {
988
//                echo 'Update: ';
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

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

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

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

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

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

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

Loading history...
1086 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...
1087 62
1088
                    $newNodes[] = $targetClass;
1089 62
                }
1090 62
1091 26
                $weight = ! array_filter(
1092
                    $property->getJoinColumns(),
1093
                    function (JoinColumnMetadata $joinColumn) { return $joinColumn->isNullable(); }
1094 62
                );
1095
1096
                $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...
1097 62
1098 62
                // If the target class has mapped subclasses, these share the same dependency.
1099 62
                if ( ! $targetClass->getSubClasses()) {
0 ignored issues
show
Bug introduced by
The method getSubClasses() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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

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

Loading history...
1100 62
                    continue;
1101
                }
1102
1103
                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...
1104
                    $targetSubClass = $this->em->getClassMetadata($subClassName);
1105
1106 62
                    if ( ! $calc->hasNode($subClassName)) {
1107 52
                        $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...
1108
1109
                        $newNodes[] = $targetSubClass;
1110 62
                    }
1111 62
1112
                    $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...
1113
                }
1114 61
            }
1115
        }
1116
1117
        return $calc->sort();
1118
    }
1119
1120
    /**
1121
     * Schedules an entity for insertion into the database.
1122
     * If the entity already has an identifier, it will be added to the identity map.
1123 1008
     *
1124
     * @param object $entity The entity to schedule for insertion.
1125 1008
     *
1126 1008
     * @return void
1127
     *
1128
     * @throws ORMInvalidArgumentException
1129 1008
     * @throws \InvalidArgumentException
1130
     */
1131
    public function scheduleForInsert($entity)
1132
    {
1133
        $oid = spl_object_hash($entity);
1134
1135
        if (isset($this->entityUpdates[$oid])) {
1136 1008
            throw new InvalidArgumentException("Dirty entity can not be scheduled for insertion.");
1137
        }
1138 1008
1139 1008
        if (isset($this->entityDeletions[$oid])) {
1140
            throw ORMInvalidArgumentException::scheduleInsertForRemovedEntity($entity);
1141 1008
        }
1142 624
        if (isset($this->originalEntityData[$oid]) && ! isset($this->entityInsertions[$oid])) {
1143
            throw ORMInvalidArgumentException::scheduleInsertForManagedEntity($entity);
1144
        }
1145 1008
1146
        if (isset($this->entityInsertions[$oid])) {
1147 1008
            throw ORMInvalidArgumentException::scheduleInsertTwice($entity);
1148
        }
1149
1150
        $this->entityInsertions[$oid] = $entity;
1151 1008
1152 1008
        if (isset($this->entityIdentifiers[$oid])) {
1153 886
            $this->addToIdentityMap($entity);
1154 846
        }
1155
1156
        if ($entity instanceof NotifyPropertyChanged) {
1157 839
            $entity->addPropertyChangedListener($this);
1158
        }
1159 839
    }
1160 649
1161
    /**
1162 649
     * Checks whether an entity is scheduled for insertion.
1163
     *
1164
     * @param object $entity
1165 839
     *
1166
     * @return boolean
1167 839
     */
1168
    public function isScheduledForInsert($entity)
1169
    {
1170 839
        return isset($this->entityInsertions[spl_object_hash($entity)]);
1171 832
    }
1172
1173
    /**
1174 217
     * Schedules an entity for being updated.
1175 217
     *
1176
     * @param object $entity The entity to schedule for being updated.
1177 217
     *
1178 189
     * @return void
1179
     *
1180 189
     * @throws ORMInvalidArgumentException
1181
     */
1182
    public function scheduleForUpdate($entity) : void
1183 217
    {
1184
        $oid = spl_object_hash($entity);
1185
1186
        if ( ! isset($this->entityIdentifiers[$oid])) {
1187
            throw ORMInvalidArgumentException::entityHasNoIdentity($entity, "scheduling for update");
1188 1008
        }
1189
1190
        if (isset($this->entityDeletions[$oid])) {
1191
            throw ORMInvalidArgumentException::entityIsRemoved($entity, "schedule for update");
1192
        }
1193
1194
        if ( ! isset($this->entityUpdates[$oid]) && ! isset($this->entityInsertions[$oid])) {
1195
            $this->entityUpdates[$oid] = $entity;
1196
        }
1197
    }
1198
1199
    /**
1200
     * INTERNAL:
1201
     * Schedules an extra update that will be executed immediately after the
1202 1032
     * regular entity updates within the currently running commit cycle.
1203
     *
1204 1032
     * Extra updates for entities are stored as (entity, changeset) tuples.
1205
     *
1206 1032
     * @ignore
1207
     *
1208
     * @param object $entity    The entity for which to schedule an extra update.
1209
     * @param array  $changeset The changeset of the entity (what to update).
1210 1032
     *
1211 1
     * @return void
1212
     */
1213 1032
    public function scheduleExtraUpdate($entity, array $changeset) : void
1214 1
    {
1215
        $oid         = spl_object_hash($entity);
1216
        $extraUpdate = [$entity, $changeset];
1217 1032
1218 1
        if (isset($this->extraUpdates[$oid])) {
1219
            [$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...
1220
1221 1032
            $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...
1222
        }
1223 1032
1224 269
        $this->extraUpdates[$oid] = $extraUpdate;
1225
    }
1226
1227 1032
    /**
1228 5
     * Checks whether an entity is registered as dirty in the unit of work.
1229
     * Note: Is not very useful currently as dirty entities are only registered
1230 1032
     * at commit time.
1231
     *
1232
     * @param object $entity
1233
     *
1234
     * @return boolean
1235
     */
1236
    public function isScheduledForUpdate($entity) : bool
1237
    {
1238
        return isset($this->entityUpdates[spl_object_hash($entity)]);
1239 631
    }
1240
1241 631
    /**
1242
     * Checks whether an entity is registered to be checked in the unit of work.
1243
     *
1244
     * @param object $entity
1245
     *
1246
     * @return boolean
1247
     */
1248
    public function isScheduledForDirtyCheck($entity) : bool
1249
    {
1250
        $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...
1251
1252
        return isset($this->scheduledForSynchronization[$rootEntityName][spl_object_hash($entity)]);
1253 1
    }
1254
1255 1
    /**
1256
     * INTERNAL:
1257 1
     * Schedules an entity for deletion.
1258
     *
1259
     * @param object $entity
1260
     *
1261 1
     * @return void
1262
     */
1263
    public function scheduleForDelete($entity)
1264
    {
1265 1
        $oid = spl_object_hash($entity);
1266 1
1267
        if (isset($this->entityInsertions[$oid])) {
1268 1
            if ($this->isInIdentityMap($entity)) {
1269
                $this->removeFromIdentityMap($entity);
1270
            }
1271
1272
            unset($this->entityInsertions[$oid], $this->entityStates[$oid]);
1273
1274
            return; // entity has not been persisted yet, so nothing more to do.
1275
        }
1276
1277
        if ( ! $this->isInIdentityMap($entity)) {
1278
            return;
1279
        }
1280
1281
        $this->removeFromIdentityMap($entity);
1282
1283
        unset($this->entityUpdates[$oid]);
1284 40
1285
        if ( ! isset($this->entityDeletions[$oid])) {
1286 40
            $this->entityDeletions[$oid] = $entity;
1287 40
            $this->entityStates[$oid]    = self::STATE_REMOVED;
1288
        }
1289 40
    }
1290 1
1291
    /**
1292 1
     * Checks whether an entity is registered as removed/deleted with the unit
1293
     * of work.
1294
     *
1295 40
     * @param object $entity
1296 40
     *
1297
     * @return boolean
1298
     */
1299
    public function isScheduledForDelete($entity)
1300
    {
1301
        return isset($this->entityDeletions[spl_object_hash($entity)]);
1302
    }
1303
1304
    /**
1305
     * Checks whether an entity is scheduled for insertion, update or deletion.
1306
     *
1307
     * @param object $entity
1308
     *
1309
     * @return boolean
1310
     */
1311
    public function isEntityScheduled($entity)
1312
    {
1313
        $oid = spl_object_hash($entity);
1314
1315
        return isset($this->entityInsertions[$oid])
1316
            || isset($this->entityUpdates[$oid])
1317
            || isset($this->entityDeletions[$oid]);
1318
    }
1319 1
1320
    /**
1321 1
     * INTERNAL:
1322
     * Registers an entity in the identity map.
1323 1
     * Note that entities in a hierarchy are registered with the class name of
1324
     * the root entity.
1325
     *
1326
     * @ignore
1327
     *
1328
     * @param object $entity The entity to register.
1329
     *
1330
     * @return boolean TRUE if the registration was successful, FALSE if the identity of
1331
     *                 the entity in question is already managed.
1332
     *
1333
     * @throws ORMInvalidArgumentException
1334 65
     */
1335
    public function addToIdentityMap($entity)
1336 65
    {
1337
        $classMetadata = $this->em->getClassMetadata(get_class($entity));
1338 65
        $identifier    = $this->entityIdentifiers[spl_object_hash($entity)];
1339 1
1340
        if (empty($identifier) || in_array(null, $identifier, true)) {
1341
            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...
1342
        }
1343 1
1344
        $idHash    = implode(' ', $identifier);
1345 1
        $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...
1346
1347
        if (isset($this->identityMap[$className][$idHash])) {
1348 65
            return false;
1349 1
        }
1350
1351
        $this->identityMap[$className][$idHash] = $entity;
1352 64
1353
        return true;
1354 64
    }
1355
1356 64
    /**
1357 64
     * Gets the state of an entity with regard to the current unit of work.
1358 64
     *
1359
     * @param object   $entity
1360 64
     * @param int|null $assume The state to assume if the state is not yet known (not MANAGED or REMOVED).
1361
     *                         This parameter can be set to improve performance of entity state detection
1362
     *                         by potentially avoiding a database lookup if the distinction between NEW and DETACHED
1363
     *                         is either known or does not matter for the caller of the method.
1364
     *
1365
     * @return int The entity state.
1366
     */
1367
    public function getEntityState($entity, $assume = null)
1368
    {
1369
        $oid = spl_object_hash($entity);
1370 17
1371
        if (isset($this->entityStates[$oid])) {
1372 17
            return $this->entityStates[$oid];
1373
        }
1374
1375
        if ($assume !== null) {
1376
            return $assume;
1377
        }
1378
1379
        // State can only be NEW or DETACHED, because MANAGED/REMOVED states are known.
1380
        // Note that you can not remember the NEW or DETACHED state in _entityStates since
1381
        // the UoW does not hold references to such objects and the object hash can be reused.
1382
        // More generally because the state may "change" between NEW/DETACHED without the UoW being aware of it.
1383
        $class     = $this->em->getClassMetadata(get_class($entity));
1384
        $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...
1385
        $id        = $persister->getIdentifier($entity);
1386
1387
        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...
1388
            return self::STATE_NEW;
1389
        }
1390
1391
        $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...
1392
1393
        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...
1394
            || ! $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...
1395
            || ! $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...
1396
        ) {
1397
            // Check for a version field, if available, to avoid a db lookup.
1398
            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...
1399
                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...
1400
                    ? self::STATE_DETACHED
1401
                    : self::STATE_NEW;
1402
            }
1403
1404
            // Last try before db lookup: check the identity map.
1405
            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...
1406 1098
                return self::STATE_DETACHED;
1407
            }
1408 1098
1409 1098
            // db lookup
1410
            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...
1411 1098
                return self::STATE_DETACHED;
1412 6
            }
1413
1414
            return self::STATE_NEW;
1415 1092
        }
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->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...
1420
            // if we have a pre insert generator we can't be sure that having an id
1421
            // really means that the entity exists. We have to verify this through
1422 1092
            // the last resort: a db lookup
1423
1424 1092
            // Last try before db lookup: check the identity map.
1425
            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...
1426
                return self::STATE_DETACHED;
1427
            }
1428
1429
            // db lookup
1430
            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...
1431
                return self::STATE_DETACHED;
1432
            }
1433
1434
            return self::STATE_NEW;
1435
        }
1436
1437
        return self::STATE_DETACHED;
1438 1045
    }
1439
1440 1045
    /**
1441
     * INTERNAL:
1442 1045
     * Removes an entity from the identity map. This effectively detaches the
1443 783
     * entity from the persistence management of Doctrine.
1444
     *
1445
     * @ignore
1446 1039
     *
1447 1035
     * @param object $entity
1448
     *
1449
     * @return boolean
1450
     *
1451
     * @throws ORMInvalidArgumentException
1452
     */
1453
    public function removeFromIdentityMap($entity)
1454 13
    {
1455 13
        $oid           = spl_object_hash($entity);
1456
        $classMetadata = $this->em->getClassMetadata(get_class($entity));
1457 13
        $idHash        = implode(' ', $this->entityIdentifiers[$oid]);
1458 5
1459
        if ($idHash === '') {
1460
            throw ORMInvalidArgumentException::entityHasNoIdentity($entity, "remove from identity map");
1461 10
        }
1462 1
1463
        $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...
1464
1465
        if (isset($this->identityMap[$className][$idHash])) {
1466 10
            unset($this->identityMap[$className][$idHash]);
1467
            unset($this->readOnlyObjects[$oid]);
1468 5
1469 1
            //$this->entityStates[$oid] = self::STATE_DETACHED;
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1470
1471 1
            return true;
1472
        }
1473
1474
        return false;
1475 4
    }
1476 1
1477
    /**
1478
     * INTERNAL:
1479
     * Gets an entity in the identity map by its identifier hash.
1480 4
     *
1481
     * @ignore
1482
     *
1483
     * @param string $idHash
1484 4
     * @param string $rootClassName
1485
     *
1486 5
     * @return object
1487
     */
1488
    public function getByIdHash($idHash, $rootClassName)
1489
    {
1490
        return $this->identityMap[$rootClassName][$idHash];
1491
    }
1492
1493
    /**
1494
     * INTERNAL:
1495
     * Tries to get an entity by its identifier hash. If no entity is found for
1496
     * the given hash, FALSE is returned.
1497
     *
1498
     * @ignore
1499
     *
1500
     * @param mixed  $idHash        (must be possible to cast it to string)
1501
     * @param string $rootClassName
1502
     *
1503
     * @return object|bool The found entity or FALSE.
1504 5
     */
1505
    public function tryGetByIdHash($idHash, $rootClassName)
1506
    {
1507
        $stringIdHash = (string) $idHash;
1508
1509
        return isset($this->identityMap[$rootClassName][$stringIdHash])
1510
            ? $this->identityMap[$rootClassName][$stringIdHash]
1511
            : false;
1512
    }
1513
1514
    /**
1515
     * Checks whether an entity is registered in the identity map of this UnitOfWork.
1516
     *
1517
     * @param object $entity
1518
     *
1519
     * @return boolean
1520
     */
1521 76
    public function isInIdentityMap($entity)
1522
    {
1523 76
        $oid = spl_object_hash($entity);
1524 76
1525 76
        if (empty($this->entityIdentifiers[$oid])) {
1526
            return false;
1527 76
        }
1528
1529
        $classMetadata = $this->em->getClassMetadata(get_class($entity));
1530
        $idHash        = implode(' ', $this->entityIdentifiers[$oid]);
1531 76
1532
        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...
1533 76
    }
1534 76
1535 76
    /**
1536
     * INTERNAL:
1537
     * Checks whether an identifier hash exists in the identity map.
1538
     *
1539 76
     * @ignore
1540
     *
1541
     * @param string $idHash
1542
     * @param string $rootClassName
1543
     *
1544
     * @return boolean
1545
     */
1546
    public function containsIdHash($idHash, $rootClassName)
1547
    {
1548
        return isset($this->identityMap[$rootClassName][$idHash]);
1549
    }
1550
1551
    /**
1552
     * Persists an entity as part of the current unit of work.
1553
     *
1554
     * @param object $entity The entity to persist.
1555
     *
1556 6
     * @return void
1557
     */
1558 6
    public function persist($entity)
1559
    {
1560
        $visited = [];
1561
1562
        $this->doPersist($entity, $visited);
1563
    }
1564
1565
    /**
1566
     * Persists an entity as part of the current unit of work.
1567
     *
1568
     * This method is internally called during persist() cascades as it tracks
1569
     * the already visited entities to prevent infinite recursions.
1570
     *
1571
     * @param object $entity  The entity to persist.
1572
     * @param array  $visited The already visited entities.
1573 34
     *
1574
     * @return void
1575 34
     *
1576
     * @throws ORMInvalidArgumentException
1577 34
     * @throws UnexpectedValueException
1578 34
     */
1579 34
    private function doPersist($entity, array &$visited)
1580
    {
1581
        $oid = spl_object_hash($entity);
1582
1583
        if (isset($visited[$oid])) {
1584
            return; // Prevent infinite recursion
1585
        }
1586
1587
        $visited[$oid] = $entity; // Mark visited
1588
1589 212
        $class = $this->em->getClassMetadata(get_class($entity));
1590
1591 212
        // We assume NEW, so DETACHED entities result in an exception on flush (constraint violation).
1592
        // If we would detect DETACHED here we would throw an exception anyway with the same
1593 212
        // consequences (not recoverable/programming error), so just assuming NEW here
1594 31
        // lets us avoid some database lookups for entities with natural identifiers.
1595
        $entityState = $this->getEntityState($entity, self::STATE_NEW);
1596
1597 197
        switch ($entityState) {
1598 197
            case self::STATE_MANAGED:
1599
                // Nothing to do, except if policy is "deferred explicit"
1600 197
                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...
1601
                    $this->scheduleForSynchronization($entity);
1602
                }
1603
                break;
1604 197
1605
            case self::STATE_NEW:
1606
                $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...
1607
                break;
1608
1609
            case self::STATE_REMOVED:
1610
                // Entity becomes managed again
1611
                unset($this->entityDeletions[$oid]);
1612
                $this->addToIdentityMap($entity);
1613
1614
                $this->entityStates[$oid] = self::STATE_MANAGED;
1615
                break;
1616
1617
            case self::STATE_DETACHED:
1618
                // Can actually not happen right now since we assume STATE_NEW.
1619
                throw ORMInvalidArgumentException::detachedEntityCannot($entity, "persisted");
1620
1621
            default:
1622
                throw new UnexpectedValueException("Unexpected entity state: $entityState." . self::objToStr($entity));
1623
        }
1624
1625
        $this->cascadePersist($entity, $visited);
1626
    }
1627
1628
    /**
1629
     * Deletes an entity as part of the current unit of work.
1630 1028
     *
1631
     * @param object $entity The entity to remove.
1632 1028
     *
1633
     * @return void
1634 1028
     */
1635 1021
    public function remove($entity)
1636
    {
1637
        $visited = [];
1638
1639
        $this->doRemove($entity, $visited);
1640
    }
1641
1642
    /**
1643
     * Deletes an entity as part of the current unit of work.
1644
     *
1645
     * This method is internally called during delete() cascades as it tracks
1646
     * the already visited entities to prevent infinite recursions.
1647
     *
1648
     * @param object $entity  The entity to delete.
1649
     * @param array  $visited The map of the already visited entities.
1650
     *
1651 1028
     * @return void
1652
     *
1653 1028
     * @throws ORMInvalidArgumentException If the instance is a detached entity.
1654
     * @throws UnexpectedValueException
1655 1028
     */
1656 109
    private function doRemove($entity, array &$visited)
1657
    {
1658
        $oid = spl_object_hash($entity);
1659 1028
1660
        if (isset($visited[$oid])) {
1661 1028
            return; // Prevent infinite recursion
1662
        }
1663
1664
        $visited[$oid] = $entity; // mark visited
1665
1666
        // Cascade first, because scheduleForDelete() removes the entity from the identity map, which
1667 1028
        // can cause problems when a lazy proxy has to be initialized for the cascade operation.
1668
        $this->cascadeRemove($entity, $visited);
1669
1670 1028
        $class       = $this->em->getClassMetadata(get_class($entity));
1671
        $entityState = $this->getEntityState($entity);
1672 234
1673 2
        switch ($entityState) {
1674
            case self::STATE_NEW:
1675 234
            case self::STATE_REMOVED:
1676
                // nothing to do
1677 1028
                break;
1678 1027
1679 1027
            case self::STATE_MANAGED:
1680
                $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...
1681 1
1682
                if ($invoke !== ListenersInvoker::INVOKE_NONE) {
1683 1
                    $this->listenersInvoker->invoke($class, Events::preRemove, $entity, new LifecycleEventArgs($entity, $this->em), $invoke);
0 ignored issues
show
Documentation introduced by
$class is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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

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

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

Loading history...
1741
            array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
1742 64
            $entity
1743 64
        );
1744
1745
        $this->cascadeRefresh($entity, $visited);
1746 64
    }
1747 64
1748
    /**
1749 2
     * Cascades a refresh operation to associated entities.
1750
     *
1751 64
     * @param object $entity
1752 64
     * @param array  $visited
1753
     *
1754 64
     * @return void
1755 8
     */
1756
    private function cascadeRefresh($entity, array &$visited)
1757
    {
1758 64
        $class = $this->em->getClassMetadata(get_class($entity));
1759 64
1760
        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...
1761
            if (! ($association instanceof AssociationMetadata && in_array('refresh', $association->getCascade()))) {
1762
                continue;
1763
            }
1764
1765
            $relatedEntities = $association->getValue($entity);
1766
1767 64
            switch (true) {
1768
                case ($relatedEntities instanceof PersistentCollection):
1769
                    // Unwrap so that foreach() does not initialize
1770
                    $relatedEntities = $relatedEntities->unwrap();
1771
                    // break; is commented intentionally!
1772
1773
                case ($relatedEntities instanceof Collection):
1774
                case (is_array($relatedEntities)):
1775
                    foreach ($relatedEntities as $relatedEntity) {
1776
                        $this->doRefresh($relatedEntity, $visited);
1777
                    }
1778
                    break;
1779
1780
                case ($relatedEntities !== null):
1781 40
                    $this->doRefresh($relatedEntities, $visited);
1782
                    break;
1783 40
1784
                default:
1785 40
                    // Do nothing
1786
            }
1787
        }
1788
    }
1789
1790
    /**
1791
     * Cascades the save operation to associated entities.
1792
     *
1793
     * @param object $entity
1794
     * @param array  $visited
1795
     *
1796
     * @return void
1797
     */
1798
    private function cascadePersist($entity, array &$visited)
1799
    {
1800
        $class = $this->em->getClassMetadata(get_class($entity));
1801
1802
        if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
1803 40
            // nothing to do - proxy is not initialized, therefore we don't do anything with it
1804
            return;
1805 40
        }
1806
1807 40
        foreach ($class->getDeclaredPropertiesIterator() as $association) {
0 ignored issues
show
Bug introduced by
The method getDeclaredPropertiesIterator() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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

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

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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

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

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

Loading history...
1870 4
            if (! ($association instanceof AssociationMetadata && in_array('remove', $association->getCascade()))) {
1871
                continue;
1872
            }
1873 4
1874 1
            if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
1875
                $entity->initializeProxy();
1876
            }
1877
1878 37
            $relatedEntities = $association->getValue($entity);
1879
1880 37
            switch (true) {
1881 30
                case ($relatedEntities instanceof Collection):
1882 4
                case (is_array($relatedEntities)):
1883
                    // If its a PersistentCollection initialization is intended! No unwrap!
1884
                    foreach ($relatedEntities as $relatedEntity) {
1885 30
                        $entitiesToCascade[] = $relatedEntity;
1886
                    }
1887
                    break;
1888 37
1889
                case ($relatedEntities !== null):
1890
                    $entitiesToCascade[] = $relatedEntities;
1891
                    break;
1892
1893 38
                default:
1894 6
                    // Do nothing
1895
            }
1896
        }
1897
1898 38
        foreach ($entitiesToCascade as $relatedEntity) {
1899
            $this->doRemove($relatedEntity, $visited);
1900 38
        }
1901
    }
1902 38
1903
    /**
1904
     * Acquire a lock on the given entity.
1905
     *
1906
     * @param object $entity
1907
     * @param int    $lockMode
1908
     * @param int    $lockVersion
1909
     *
1910
     * @return void
1911
     *
1912 38
     * @throws ORMInvalidArgumentException
1913
     * @throws TransactionRequiredException
1914 38
     * @throws OptimisticLockException
1915
     */
1916
    public function lock($entity, $lockMode, $lockVersion = null)
1917
    {
1918
        if ($entity === null) {
1919
            throw new \InvalidArgumentException("No entity passed to UnitOfWork#lock().");
1920
        }
1921
1922
        if ($this->getEntityState($entity, self::STATE_DETACHED) != self::STATE_MANAGED) {
1923
            throw ORMInvalidArgumentException::entityNotManaged($entity);
1924
        }
1925
1926
        $class = $this->em->getClassMetadata(get_class($entity));
1927 6
1928
        switch (true) {
1929 6
            case LockMode::OPTIMISTIC === $lockMode:
1930 6
                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...
1931
                    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...
1932 6
                }
1933 6
1934
                if ($lockVersion === null) {
1935 6
                    return;
1936
                }
1937
1938
                if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
1939 1
                    $entity->initializeProxy();
1940 1
                }
1941
1942 1
                $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...
1943 1
1944
                if ($entityVersion != $lockVersion) {
1945 1
                    throw OptimisticLockException::lockFailedVersionMismatch($entity, $lockVersion, $entityVersion);
1946
                }
1947 1
1948
                break;
1949
1950
            case LockMode::NONE === $lockMode:
1951
            case LockMode::PESSIMISTIC_READ === $lockMode:
1952
            case LockMode::PESSIMISTIC_WRITE === $lockMode:
1953
                if (!$this->em->getConnection()->isTransactionActive()) {
1954
                    throw TransactionRequiredException::transactionRequired();
1955
                }
1956
1957 12
                $oid = spl_object_hash($entity);
1958
1959 12
                $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...
1960
                    array_combine($class->getIdentifierFieldNames(), $this->entityIdentifiers[$oid]),
1961 12
                    $lockMode
1962 12
                );
1963
                break;
1964
1965
            default:
1966
                // Do nothing
1967
        }
1968
    }
1969
1970
    /**
1971
     * Clears the UnitOfWork.
1972
     *
1973 15
     * @return void
1974
     */
1975 15
    public function clear()
1976
    {
1977 15
        $this->entityPersisters =
1978
        $this->collectionPersisters =
1979
        $this->eagerLoadingEntities =
1980
        $this->identityMap =
1981 15
        $this->entityIdentifiers =
1982
        $this->originalEntityData =
1983 15
        $this->entityChangeSets =
1984 15
        $this->entityStates =
1985 13
        $this->scheduledForSynchronization =
1986 12
        $this->entityInsertions =
1987
        $this->entityUpdates =
1988
        $this->entityDeletions =
1989
        $this->collectionDeletions =
1990 13
        $this->collectionUpdates =
1991 13
        $this->extraUpdates =
1992 13
        $this->readOnlyObjects =
1993 13
        $this->visitedCollections =
1994 13
        $this->orphanRemovals = [];
1995 13
1996
        if ($this->eventManager->hasListeners(Events::onClear)) {
1997 13
            $this->eventManager->dispatchEvent(Events::onClear, new Event\OnClearEventArgs($this->em));
1998 3
        }
1999 3
    }
2000 3
2001
    /**
2002
     * INTERNAL:
2003 13
     * Schedules an orphaned entity for removal. The remove() operation will be
2004 13
     * invoked on that entity at the beginning of the next commit of this
2005
     * UnitOfWork.
2006 13
     *
2007
     * @ignore
2008
     *
2009
     * @param object $entity
2010
     *
2011
     * @return void
2012
     */
2013
    public function scheduleOrphanRemoval($entity)
2014
    {
2015
        $this->orphanRemovals[spl_object_hash($entity)] = $entity;
2016
    }
2017
2018 16
    /**
2019
     * INTERNAL:
2020 16
     * Cancels a previously scheduled orphan removal.
2021
     *
2022 16
     * @ignore
2023 16
     *
2024
     * @param object $entity
2025
     *
2026
     * @return void
2027
     */
2028
    public function cancelOrphanRemoval($entity)
2029
    {
2030
        unset($this->orphanRemovals[spl_object_hash($entity)]);
2031
    }
2032
2033
    /**
2034
     * INTERNAL:
2035 16
     * Schedules a complete collection for removal when this UnitOfWork commits.
2036
     *
2037 16
     * @param PersistentCollection $coll
2038
     *
2039 16
     * @return void
2040
     */
2041
    public function scheduleCollectionDeletion(PersistentCollection $coll)
2042
    {
2043 16
        $coid = spl_object_hash($coll);
2044
2045 16
        // TODO: if $coll is already scheduled for recreation ... what to do?
2046
        // Just remove $coll from the scheduled recreations?
2047 16
        unset($this->collectionUpdates[$coid]);
2048
2049
        $this->collectionDeletions[$coid] = $coll;
2050
    }
2051 16
2052 16
    /**
2053
     * @param PersistentCollection $coll
2054
     *
2055
     * @return bool
2056 16
     */
2057 16
    public function isCollectionScheduledForDeletion(PersistentCollection $coll)
2058
    {
2059
        return isset($this->collectionDeletions[spl_object_hash($coll)]);
2060
    }
2061
2062
    /**
2063
     * INTERNAL:
2064
     * Creates a new instance of the mapped class, without invoking the constructor.
2065
     * This is only meant to be used internally, and should not be consumed by end users.
2066
     *
2067 16
     * @ignore
2068
     *
2069 16
     * @param ClassMetadata $class
2070
     *
2071 16
     * @return EntityManagerAware|object
2072 16
     */
2073
    public function newInstance(ClassMetadata $class)
2074
    {
2075
        $entity = $this->instantiator->instantiate($class->getClassName());
2076 16
2077 5
        if ($entity instanceof EntityManagerAware) {
2078
            $entity->injectEntityManager($this->em, $class);
2079
        }
2080 5
2081
        return $entity;
2082 5
    }
2083
2084
    /**
2085
     * INTERNAL:
2086
     * Creates an entity. Used for reconstitution of persistent entities.
2087 5
     *
2088
     * Internal note: Highly performance-sensitive method.
2089
     *
2090 5
     * @ignore
2091
     *
2092
     * @param string $className The name of the entity class.
2093
     * @param array  $data      The data for the entity.
2094
     * @param array  $hints     Any hints to account for during reconstitution/lookup of the entity.
2095
     *
2096 5
     * @return object The managed entity instance.
2097
     *
2098
     * @todo Rename: getOrCreateEntity
2099
     */
2100 16
    public function createEntity($className, array $data, &$hints = [])
2101
    {
2102
        $class  = $this->em->getClassMetadata($className);
2103
        $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...
2104
        $idHash = implode(' ', $id);
2105
        //$isReadOnly = isset($hints[Query::HINT_READ_ONLY]);
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
2106
2107
        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...
2108
            $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...
2109
            $oid = spl_object_hash($entity);
2110 13
2111
            if (
2112 13
                isset($hints[Query::HINT_REFRESH])
2113
                && isset($hints[Query::HINT_REFRESH_ENTITY])
2114 13
                && ($unmanagedProxy = $hints[Query::HINT_REFRESH_ENTITY]) !== $entity
2115 13
                && $unmanagedProxy instanceof GhostObjectInterface
2116
                && $this->isIdentifierEquals($unmanagedProxy, $entity)
2117
            ) {
2118
                // @TODO messy, but for now we try continuing operation as normal
2119 13
                // @TODO ideally, all this scenario should be completely gone
2120 3
                $entity = $unmanagedProxy;
2121
                // DDC-1238 - we have a managed instance, but it isn't the provided one.
2122
                // Therefore we clear its identifier. Also, we must re-fetch metadata since the
2123 3
                // refreshed object may be anything
2124
                // @TODO DDC-1238 seems to be invalid.
2125 2
                // @TODO We shouldn't touch the object state, but instead just consider it "unmanaged" and skip further hydration
2126
//                foreach ($class->identifier as $fieldName) {
2127
//                    $property = $class->getProperty($fieldName);
2128
//
2129
//                    $property->setValue($unmanagedProxy, null);
2130 3
//                }
2131 1
2132
                // @TODO overall, this logic seems wrong and harmful. Even if the proxy is un-managed, we want to load its data here
2133 3
//                return $unmanagedProxy;
2134
            }
2135
2136
            if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
2137
                $entity->setProxyInitializer(null);
2138
2139 3
                $overrideLocalValues = true;
2140
            } else {
2141
                $overrideLocalValues = isset($hints[Query::HINT_REFRESH]);
2142
2143 13
                // If only a specific entity is set to refresh, check that it's the one
2144
                if (isset($hints[Query::HINT_REFRESH_ENTITY])) {
2145
                    $overrideLocalValues = $hints[Query::HINT_REFRESH_ENTITY] === $entity;
2146
                }
2147
            }
2148
2149
            if ($overrideLocalValues) {
2150
                // inject EntityManager upon refresh.
2151
                if ($entity instanceof EntityManagerAware) {
2152
                    $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...
2153
                }
2154 38
2155
                $this->originalEntityData[$oid] = $data;
2156 38
            }
2157
        } else {
2158 38
            $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...
2159 38
            $oid    = spl_object_hash($entity);
2160
2161
            $this->entityIdentifiers[$oid]  = $id;
2162
            $this->entityStates[$oid]       = self::STATE_MANAGED;
2163 38
            $this->originalEntityData[$oid] = $data;
2164 15
2165
            $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...
2166 15
2167 9
            $overrideLocalValues = true;
2168 1
        }
2169
2170
        if ( ! $overrideLocalValues) {
2171 8
            return $entity;
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 = isset($data[$joinColumnName]) ? $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...
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...
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
                    // @TODO refactor: `getProxy`: no scalars if the identifier is an association, use real value
2349
                    // @TODO note that this mess is recursive, so we need to fix it somewhere else. An identifier
2350 1
                    //       may be composed by multiple levels of association fetching, where:
2351 1
                    //       A#id = B(to-one)
2352
                    //       B#id = C(to-one)
2353
                    //       C#id = D(to-one)
2354 1
                    //       E#id = E(to-one)
2355
                    //       E#id = composite scalar
2356
                    //       that is a mess, but it is the real world scenario
2357
                    // @TODO we need to therefore have a generic utility that, given a scalar identifier, gives us
2358
                    //       a "normalized" identifier. This is pretty much the opposite of what the
2359 3
                    //       `IdentifierFlattener` does
2360
                    // @TODO note that we also need to sort the identifier fields here
2361
2362
                    // @TODO Start of the block to be refactored into "make a deep identifier from a flat one"
2363
2364
                    $normalizedAssociatedId = $this->convertFlatIdentifierIntoRealIdentifierFieldValues(
2365
                        $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...
2366 1008
                        $associatedId
2367
                    );
2368 1008
2369
                    switch (true) {
2370
                        // We are negating the condition here. Other cases will assume it is valid!
2371
                        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...
2372
                            $newValue = $this->em->getProxyFactory()->getProxy($targetEntity, $normalizedAssociatedId);
2373
                            break;
2374
2375
                        // Deferred eager load only works for single identifier classes
2376
                        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...
2377
                            // TODO: Is there a faster approach?
2378 1218
                            $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...
2379
2380 1218
                            $newValue = $this->em->getProxyFactory()->getProxy($targetEntity, $normalizedAssociatedId);
2381 1217
                            break;
2382 1217
2383 1217
                        default:
2384 1217
                            // TODO: This is very imperformant, ignore it?
2385 1217
                            $newValue    = $this->em->find($targetEntity, $normalizedAssociatedId);
2386 1217
                            // Needed to re-assign original entity data for freshly loaded entity
2387 1217
                            $managedData = $this->originalEntityData[spl_object_hash($newValue)];
2388 1217
                            break;
2389 1217
                    }
2390 1217
2391 1217
                    // @TODO using `$associatedId` here seems to be risky.
2392 1217
                    $this->registerManaged($newValue, $associatedId, $managedData);
2393 1217
2394 1217
                    break;
2395 1217
            }
2396
2397 3
            $this->originalEntityData[$oid][$field] = $newValue;
2398 3
            $association->setValue($entity, $newValue);
2399
2400
            if (
2401 1218
                $association->getInversedBy()
2402 7
                && $association instanceof OneToOneAssociationMetadata
2403
                // @TODO refactor this
2404 1218
                // we don't want to set any values in un-initialized proxies
2405
                && ! (
2406
                    $newValue instanceof GhostObjectInterface
2407
                    && ! $newValue->isProxyInitialized()
2408
                )
2409
            ) {
2410
                $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...
2411
2412
                $inverseAssociation->setValue($newValue, $entity);
2413
            }
2414
        }
2415
2416
        if ($overrideLocalValues) {
2417
            // defer invoking of postLoad event to hydration complete step
2418 17
            $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...
2419
        }
2420 17
2421 17
        return $entity;
2422
    }
2423
2424
    /**
2425
     * @return void
2426
     */
2427
    public function triggerEagerLoads()
2428
    {
2429
        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...
2430
            return;
2431
        }
2432
2433 112
        // avoid infinite recursion
2434
        $eagerLoadingEntities       = $this->eagerLoadingEntities;
2435 112
        $this->eagerLoadingEntities = [];
2436 112
2437
        foreach ($eagerLoadingEntities as $entityName => $ids) {
2438
            if ( ! $ids) {
2439
                continue;
2440
            }
2441
2442
            $class = $this->em->getClassMetadata($entityName);
2443
2444
            $this->getEntityPersister($entityName)->loadAll(
2445
                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...
2446 13
            );
2447
        }
2448 13
    }
2449
2450
    /**
2451
     * Initializes (loads) an uninitialized persistent collection of an entity.
2452 13
     *
2453
     * @param \Doctrine\ORM\PersistentCollection $collection The collection to initialize.
2454 13
     *
2455 13
     * @return void
2456
     *
2457
     * @todo Maybe later move to EntityManager#initialize($proxyOrCollection). See DDC-733.
2458
     */
2459
    public function loadCollection(PersistentCollection $collection)
2460
    {
2461
        $association = $collection->getMapping();
2462
        $persister   = $this->getEntityPersister($association->getTargetEntity());
2463
2464
        if ($association instanceof OneToManyAssociationMetadata) {
2465
            $persister->loadOneToManyCollection($association, $collection->getOwner(), $collection);
2466
        } else {
2467
            $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...
2468
        }
2469
2470
        $collection->setInitialized(true);
2471
    }
2472 668
2473
    /**
2474 668
     * Gets the identity map of the UnitOfWork.
2475
     *
2476 668
     * @return array
2477 4
     */
2478
    public function getIdentityMap()
2479
    {
2480 668
        return $this->identityMap;
2481
    }
2482
2483
    /**
2484
     * Gets the original data of an entity. The original data is the data that was
2485
     * present at the time the entity was reconstituted from the database.
2486
     *
2487
     * @param object $entity
2488
     *
2489
     * @return array
2490
     */
2491
    public function getOriginalEntityData($entity)
2492
    {
2493
        $oid = spl_object_hash($entity);
2494
2495
        return isset($this->originalEntityData[$oid])
2496
            ? $this->originalEntityData[$oid]
2497
            : [];
2498
    }
2499 806
2500
    /**
2501 806
     * @ignore
2502
     *
2503
     * @param object $entity
2504 806
     * @param array  $data
2505 806
     *
2506
     * @return void
2507 806
     */
2508 310
    public function setOriginalEntityData($entity, array $data)
2509 310
    {
2510
        $this->originalEntityData[spl_object_hash($entity)] = $data;
2511
    }
2512 310
2513 310
    /**
2514 310
     * INTERNAL:
2515 310
     * Sets a property value of the original data array of an entity.
2516 310
     *
2517
     * @ignore
2518
     *
2519
     * @param string $oid
2520
     * @param string $property
2521
     * @param mixed  $value
2522 2
     *
2523 2
     * @return void
2524
     */
2525
    public function setOriginalEntityProperty($oid, $property, $value)
2526 2
    {
2527
        $this->originalEntityData[$oid][$property] = $value;
2528
    }
2529 308
2530 21
    /**
2531
     * Gets the identifier of an entity.
2532 21
     * The returned value is always an array of identifier values. If the entity
2533
     * has a composite identifier then the identifier values are in the same
2534 21
     * order as the identifier field names as returned by ClassMetadata#getIdentifierFieldNames().
2535 21
     *
2536
     * @param object $entity
2537
     *
2538 289
     * @return array The identifier values.
2539
     */
2540
    public function getEntityIdentifier($entity)
2541 289
    {
2542 71
        return $this->entityIdentifiers[spl_object_hash($entity)];
2543
    }
2544
2545
    /**
2546 308
     * Processes an entity instance to extract their identifier values.
2547
     *
2548 111
     * @param object $entity The entity instance.
2549 3
     *
2550
     * @return mixed A scalar value.
2551
     *
2552 308
     * @throws \Doctrine\ORM\ORMInvalidArgumentException
2553
     */
2554
    public function getSingleIdentifierValue($entity)
2555 665
    {
2556 665
        $class     = $this->em->getClassMetadata(get_class($entity));
2557
        $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...
2558 665
2559 665
        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...
2560 665
            throw ORMInvalidArgumentException::invalidCompositeIdentifier();
2561
        }
2562 665
2563
        $values = $this->isInIdentityMap($entity)
2564 665
            ? $this->getEntityIdentifier($entity)
2565 2
            : $persister->getIdentifier($entity);
2566
2567
        return isset($values[$class->identifier[0]]) ? $values[$class->identifier[0]] : null;
0 ignored issues
show
Bug introduced by
Accessing identifier on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2568 665
    }
2569
2570
    /**
2571 805
     * @param array  $id
2572 219
     * @param string $rootClassName
2573
     *
2574
     * @return GhostObjectInterface|object
2575 702
     */
2576 702
    private function tryGetByIdOrLoadProxy(array $id, string $rootClassName)
2577 702
    {
2578
        if ($fetched = $this->tryGetById($id, $rootClassName)) {
2579
            return $fetched;
2580
        }
2581
2582 702
        $class = $this->em->getClassMetadata($rootClassName);
2583
2584 702
        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...
2585
            // can't do this for inheritance trees!
2586
            // @TODO fetching from the EntityManager feels dirty here
2587
            return $this->em->find($rootClassName, $id);
2588
        }
2589 702
2590 33
        $sortedId = [];
2591
2592
        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...
2593 669
            $sortedId[$idField] = $id[$idField];
2594
        }
2595 586
2596 260
        $proxy = $this->em->getProxyFactory()->getProxy($rootClassName, $sortedId);
2597
2598
        $this->registerManaged($proxy, $sortedId, []);
2599 564
2600
        return $proxy;
2601
    }
2602 564
    /**
2603 485
     * @TODO refactor: `getProxy`: no scalars if the identifier is an association, use real value
2604
     * @TODO note that this mess is recursive, so we need to fix it somewhere else. An identifier
2605
     *       may be composed by multiple levels of association fetching, where:
2606 64
     *       A#id = B(to-one)
2607
     *       B#id = C(to-one)
2608 2
     *       C#id = D(to-one)
2609
     *       E#id = E(to-one)
2610 2
     *       E#id = composite scalar
2611 2
     *       that is a mess, but it is the real world scenario
2612
     * @TODO we need to therefore have a generic utility that, given a scalar identifier, gives us
2613 2
     *       a "normalized" identifier. This is pretty much the opposite of what the
2614
     *       `IdentifierFlattener` does
2615
     * @TODO note that we also need to sort the identifier fields here
2616
     *
2617 62
     * @TODO Start of the block to be refactored into "make a deep identifier from a flat one"
2618
     *
2619 62
     * @return mixed[]
2620
     */
2621
    private function convertFlatIdentifierIntoRealIdentifierFieldValues(
2622
        ClassMetadata $targetClass,
2623 485
        array $flatIdentifier
2624 38
    ) {
2625 38
2626
        $normalizedAssociatedId = [];
2627 38
2628
        foreach ($targetClass->getDeclaredPropertiesIterator() as $name => $declaredProperty) {
2629
            if (! \array_key_exists($name, $flatIdentifier)) {
2630 478
                continue;
2631
            }
2632
2633 478
            if ($declaredProperty instanceof ToOneAssociationMetadata) {
2634 478
                $targetIdMetadata = $this->em->getClassMetadata($declaredProperty->getTargetEntity());
2635 478
2636
                $normalizedAssociatedId[$name] = $this->tryGetByIdOrLoadProxy(
2637 478
                    [reset($targetIdMetadata->identifier) => $flatIdentifier[$name]], // @TODO this is where stuff breaks with composite 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...
2638
                    $declaredProperty->getTargetEntity()
2639 287
                );
2640
            }
2641 287
2642
            if ($declaredProperty instanceof FieldMetadata) {
2643
                $normalizedAssociatedId[$name] = $flatIdentifier[$name];
2644 284
            }
2645
        }
2646
2647 478
        return $normalizedAssociatedId;
2648
    }
2649 287
2650 287
    /**
2651
     * Tries to find an entity with the given identifier in the identity map of
2652 287
     * this UnitOfWork.
2653
     *
2654
     * @param mixed  $id            The entity identifier to look for.
2655 284
     * @param string $rootClassName The name of the root class of the mapped entity hierarchy.
2656 281
     *
2657
     * @return object|bool Returns the entity with the specified identifier if it exists in
2658
     *                     this UnitOfWork, FALSE otherwise.
2659
     */
2660
    public function tryGetById($id, $rootClassName)
2661
    {
2662
        $idHash = implode(' ', (array) $id);
2663 284
2664
        return isset($this->identityMap[$rootClassName][$idHash])
2665
            ? $this->identityMap[$rootClassName][$idHash]
2666 284
            : false;
2667 166
    }
2668
2669
    /**
2670
     * Schedules an entity for dirty-checking at commit-time.
2671
     *
2672 166
     * @param object $entity The entity to schedule for dirty-checking.
2673 166
     *
2674 166
     * @return void
2675 166
     */
2676 166
    public function scheduleForSynchronization($entity)
2677
    {
2678
        $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...
2679
2680
        $this->scheduledForSynchronization[$rootClassName][spl_object_hash($entity)] = $entity;
2681 166
    }
2682
2683 192
    /**
2684
     * Checks whether the UnitOfWork has any pending insertions.
2685
     *
2686
     * @return boolean TRUE if this UnitOfWork has pending insertions, FALSE otherwise.
2687 30
     */
2688 30
    public function hasPendingInsertions()
2689
    {
2690
        return ! empty($this->entityInsertions);
2691
    }
2692
2693 163
    /**
2694 157
     * Calculates the size of the UnitOfWork. The size of the UnitOfWork is the
2695 157
     * number of entities in the identity map.
2696
     *
2697
     * @return integer
2698 6
     */
2699
    public function size()
2700 6
    {
2701
        return \array_sum(\array_map('count', $this->identityMap));
2702 6
    }
2703 6
2704
    /**
2705
     * Gets the EntityPersister for an Entity.
2706
     *
2707
     * @param string $entityName The name of the Entity.
2708
     *
2709
     * @return \Doctrine\ORM\Persisters\Entity\EntityPersister
2710
     */
2711
    public function getEntityPersister($entityName)
2712 163
    {
2713 163
        if (isset($this->entityPersisters[$entityName])) {
2714 163
            return $this->entityPersisters[$entityName];
2715
        }
2716
2717 163
        $class = $this->em->getClassMetadata($entityName);
2718 163
2719
        switch (true) {
2720
            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...
2721
                $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...
2722 163
                break;
2723
2724 163
            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...
2725
                $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...
2726
                break;
2727 284
2728 284
            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...
2729
                $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...
2730 284
                break;
2731 49
2732 49
            default:
2733
                throw new \RuntimeException('No persister found for entity.');
2734
        }
2735 284
2736
        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...
2737
            $persister = $this->em->getConfiguration()
2738
                ->getSecondLevelCacheConfiguration()
2739 486
                ->getCacheFactory()
2740
                ->buildCachedEntityPersister($this->em, $persister, $class);
2741
        }
2742
2743
        $this->entityPersisters[$entityName] = $persister;
2744 486
2745
        return $this->entityPersisters[$entityName];
2746 3
    }
2747
2748 3
    /**
2749 3
     * Gets a collection persister for a collection-valued association.
2750
     *
2751 3
     * @param ToManyAssociationMetadata $association
2752
     *
2753
     * @return \Doctrine\ORM\Persisters\Collection\CollectionPersister
2754
     */
2755 486
    public function getCollectionPersister(ToManyAssociationMetadata $association)
2756 486
    {
2757 486
        $role = $association->getCache()
2758
            ? sprintf('%s::%s', $association->getSourceEntity(), $association->getName())
2759 486
            : get_class($association);
2760 486
2761
        if (isset($this->collectionPersisters[$role])) {
2762 486
            return $this->collectionPersisters[$role];
2763 4
        }
2764 4
2765
        $persister = $association instanceof OneToManyAssociationMetadata
2766
            ? new OneToManyPersister($this->em)
2767 486
            : new ManyToManyPersister($this->em);
2768 564
2769
        if ($this->hasCache && $association->getCache()) {
2770
            $persister = $this->em->getConfiguration()
2771
                ->getSecondLevelCacheConfiguration()
2772 669
                ->getCacheFactory()
2773
                ->buildCachedCollectionPersister($this->em, $persister, $association);
2774 669
        }
2775
2776
        $this->collectionPersisters[$role] = $persister;
2777 669
2778
        return $this->collectionPersisters[$role];
2779
    }
2780
2781
    /**
2782
     * INTERNAL:
2783 861
     * Registers an entity as managed.
2784
     *
2785 861
     * @param object $entity The entity.
2786 861
     * @param array  $id     Map containing identifier field names as key and its associated values.
2787
     * @param array  $data   The original entity data.
2788
     *
2789
     * @return void
2790 6
     */
2791 6
    public function registerManaged($entity, array $id, array $data)
2792
    {
2793 6
        $isProxy = $entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized();
2794 6
        $oid     = spl_object_hash($entity);
2795
2796
        $this->entityIdentifiers[$oid]  = $id;
2797
        $this->entityStates[$oid]       = self::STATE_MANAGED;
2798 6
        $this->originalEntityData[$oid] = $data;
2799
2800 6
        $this->addToIdentityMap($entity);
2801 6
2802
        if ($entity instanceof NotifyPropertyChanged && ! $isProxy) {
2803
            $entity->addPropertyChangedListener($this);
2804 6
        }
2805
    }
2806
2807
    /**
2808
     * INTERNAL:
2809
     * Clears the property changeset of the entity with the given OID.
2810
     *
2811
     * @param string $oid The entity's OID.
2812
     *
2813
     * @return void
2814
     */
2815 142
    public function clearEntityChangeSet($oid)
2816
    {
2817 142
        $this->entityChangeSets[$oid] = [];
2818 142
    }
2819
2820 142
    /* PropertyChangedListener implementation */
2821 142
2822 76
    /**
2823 76
     * Notifies this UnitOfWork of a property change in an entity.
2824
     *
2825 80
     * @param object $entity       The entity that owns the property.
2826 80
     * @param string $propertyName The name of the property that changed.
2827 80
     * @param mixed  $oldValue     The old value of the property.
2828
     * @param mixed  $newValue     The new value of the property.
2829
     *
2830 142
     * @return void
2831 142
     */
2832
    public function propertyChanged($entity, $propertyName, $oldValue, $newValue)
2833
    {
2834
        $class = $this->em->getClassMetadata(get_class($entity));
2835
2836
        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...
2837
            return; // ignore non-persistent fields
2838 2
        }
2839
2840 2
        $oid = spl_object_hash($entity);
2841
2842
        // Update changeset and mark entity for synchronization
2843
        $this->entityChangeSets[$oid][$propertyName] = [$oldValue, $newValue];
2844
2845
        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...
2846
            $this->scheduleForSynchronization($entity);
2847
        }
2848
    }
2849
2850
    /**
2851 115
     * Gets the currently scheduled entity insertions in this UnitOfWork.
2852
     *
2853 115
     * @return array
2854
     */
2855 115
    public function getScheduledEntityInsertions()
2856 112
    {
2857 115
        return $this->entityInsertions;
2858
    }
2859
2860
    /**
2861
     * Gets the currently scheduled entity updates in this UnitOfWork.
2862
     *
2863
     * @return array
2864
     */
2865
    public function getScheduledEntityUpdates()
2866
    {
2867
        return $this->entityUpdates;
2868
    }
2869
2870
    /**
2871
     * Gets the currently scheduled entity deletions in this UnitOfWork.
2872
     *
2873
     * @return array
2874
     */
2875
    public function getScheduledEntityDeletions()
2876
    {
2877
        return $this->entityDeletions;
2878
    }
2879
2880
    /**
2881
     * Gets the currently scheduled complete collection deletions
2882
     *
2883
     * @return array
2884
     */
2885 313
    public function getScheduledCollectionDeletions()
2886
    {
2887 313
        return $this->collectionDeletions;
2888 313
    }
2889
2890
    /**
2891
     * Gets the currently scheduled collection inserts, updates and deletes.
2892
     *
2893
     * @return array
2894
     */
2895
    public function getScheduledCollectionUpdates()
2896
    {
2897
        return $this->collectionUpdates;
2898
    }
2899
2900 842
    /**
2901
     * Helper method to initialize a lazy loading proxy or persistent collection.
2902 842
     *
2903
     * @param object $obj
2904
     *
2905
     * @return void
2906
     */
2907
    public function initializeObject($obj)
2908
    {
2909
        if ($obj instanceof GhostObjectInterface) {
2910
            $obj->initializeProxy();
2911
2912
            return;
2913
        }
2914 126
2915
        if ($obj instanceof PersistentCollection) {
2916 126
            $obj->initialize();
2917
        }
2918 126
    }
2919
2920
    /**
2921
     * Helper method to show an object as string.
2922 126
     *
2923 113
     * @param object $obj
2924 126
     *
2925
     * @return string
2926 126
     */
2927
    private static function objToStr($obj)
2928
    {
2929
        return method_exists($obj, '__toString') ? (string) $obj : get_class($obj).'@'.spl_object_hash($obj);
2930
    }
2931
2932
    /**
2933
     * Marks an entity as read-only so that it will not be considered for updates during UnitOfWork#commit().
2934
     *
2935
     * This operation cannot be undone as some parts of the UnitOfWork now keep gathering information
2936
     * on this object that might be necessary to perform a correct update.
2937
     *
2938
     * @param object $object
2939 522
     *
2940
     * @return void
2941 522
     *
2942
     * @throws ORMInvalidArgumentException
2943 522
     */
2944 79
    public function markReadOnly($object)
2945 522
    {
2946
        if ( ! is_object($object) || ! $this->isInIdentityMap($object)) {
2947
            throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object);
2948
        }
2949
2950
        $this->readOnlyObjects[spl_object_hash($object)] = true;
2951
    }
2952
2953
    /**
2954
     * Is this entity read only?
2955
     *
2956
     * @param object $object
2957 5
     *
2958
     * @return bool
2959 5
     *
2960
     * @throws ORMInvalidArgumentException
2961 5
     */
2962 5
    public function isReadOnly($object)
2963
    {
2964
        if ( ! is_object($object)) {
2965
            throw ORMInvalidArgumentException::readOnlyRequiresManagedEntity($object);
2966
        }
2967
2968
        return isset($this->readOnlyObjects[spl_object_hash($object)]);
2969
    }
2970
2971
    /**
2972
     * Perform whatever processing is encapsulated here after completion of the transaction.
2973
     */
2974
    private function afterTransactionComplete()
2975
    {
2976
        $this->performCallbackOnCachedPersister(function (CachedPersister $persister) {
2977
            $persister->afterTransactionComplete();
2978
        });
2979
    }
2980 1
2981
    /**
2982 1
     * Perform whatever processing is encapsulated here after completion of the rolled-back.
2983
     */
2984 1
    private function afterTransactionRolledBack()
2985
    {
2986
        $this->performCallbackOnCachedPersister(function (CachedPersister $persister) {
2987
            $persister->afterTransactionRolledBack();
2988
        });
2989
    }
2990
2991
    /**
2992
     * Performs an action after the transaction.
2993
     *
2994 1063
     * @param callable $callback
2995
     */
2996 1063
    private function performCallbackOnCachedPersister(callable $callback)
2997 845
    {
2998
        if ( ! $this->hasCache) {
2999
            return;
3000 1063
        }
3001
3002
        foreach (array_merge($this->entityPersisters, $this->collectionPersisters) as $persister) {
3003 1063
            if ($persister instanceof CachedPersister) {
3004 1031
                $callback($persister);
3005 1031
            }
3006
        }
3007 354
    }
3008 213
3009 213
    private function dispatchOnFlushEvent()
3010
    {
3011 333
        if ($this->eventManager->hasListeners(Events::onFlush)) {
3012 333
            $this->eventManager->dispatchEvent(Events::onFlush, new OnFlushEventArgs($this->em));
3013 333
        }
3014
    }
3015
3016
    private function dispatchPostFlushEvent()
3017
    {
3018
        if ($this->eventManager->hasListeners(Events::postFlush)) {
3019 1063
            $this->eventManager->dispatchEvent(Events::postFlush, new PostFlushEventArgs($this->em));
3020 119
        }
3021 119
    }
3022 119
3023 119
    /**
3024
     * Verifies if two given entities actually are the same based on identifier comparison
3025
     *
3026 1063
     * @param object $entity1
3027
     * @param object $entity2
3028 1063
     *
3029
     * @return bool
3030
     */
3031
    private function isIdentifierEquals($entity1, $entity2)
3032
    {
3033
        if ($entity1 === $entity2) {
3034
            return true;
3035
        }
3036
3037
        $class     = $this->em->getClassMetadata(get_class($entity1));
3038 569
        $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...
3039
3040 569
        if ($class !== $this->em->getClassMetadata(get_class($entity2))) {
3041 76
            return false;
3042 569
        }
3043
3044 569
        $identifierFlattener = $this->em->getIdentifierFlattener();
3045 448
3046
        $oid1 = spl_object_hash($entity1);
3047
        $oid2 = spl_object_hash($entity2);
3048 569
3049 403
        $id1 = isset($this->entityIdentifiers[$oid1])
3050 569
            ? $this->entityIdentifiers[$oid1]
3051
            : $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...
3052 569
        $id2 = isset($this->entityIdentifiers[$oid2])
3053 75
            ? $this->entityIdentifiers[$oid2]
3054 75
            : $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...
3055 75
3056 75
        return $id1 === $id2 || implode(' ', $id1) === implode(' ', $id2);
3057
    }
3058
3059 569
    /**
3060
     * This method called by hydrators, and indicates that hydrator totally completed current hydration cycle.
3061 569
     * Unit of work able to fire deferred events, related to loading events here.
3062
     *
3063
     * @internal should be called internally from object hydrators
3064
     */
3065
    public function hydrationComplete()
3066
    {
3067
        $this->hydrationCompleteHandler->hydrationComplete();
3068
    }
3069
}
3070