Failed Conditions
Pull Request — develop (#6719)
by Marco
61:19
created

UnitOfWork::addToIdentityMap()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4.016

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 9
cts 10
cp 0.9
rs 9.2
c 0
b 0
f 0
cc 4
eloc 11
nc 3
nop 1
crap 4.016
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
2106
        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...
2107
            $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...
2108
            $oid = spl_object_hash($entity);
2109
2110 13
            if (
2111
                isset($hints[Query::HINT_REFRESH])
2112 13
                && isset($hints[Query::HINT_REFRESH_ENTITY])
2113
                && ($unmanagedProxy = $hints[Query::HINT_REFRESH_ENTITY]) !== $entity
2114 13
                && $unmanagedProxy instanceof GhostObjectInterface
2115 13
                && $this->isIdentifierEquals($unmanagedProxy, $entity)
2116
            ) {
2117
                // We will hydrate the given un-managed proxy anyway:
2118
                // continue work, but consider it the entity from now on
2119 13
                $entity = $unmanagedProxy;
2120 3
            }
2121
2122
            if ($entity instanceof GhostObjectInterface && ! $entity->isProxyInitialized()) {
2123 3
                $entity->setProxyInitializer(null);
2124
2125 2
                $overrideLocalValues = true;
2126
            } else {
2127
                $overrideLocalValues = isset($hints[Query::HINT_REFRESH]);
2128
2129
                // If only a specific entity is set to refresh, check that it's the one
2130 3
                if (isset($hints[Query::HINT_REFRESH_ENTITY])) {
2131 1
                    $overrideLocalValues = $hints[Query::HINT_REFRESH_ENTITY] === $entity;
2132
                }
2133 3
            }
2134
2135
            if ($overrideLocalValues) {
2136
                // inject EntityManager upon refresh.
2137
                if ($entity instanceof EntityManagerAware) {
2138
                    $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...
2139 3
                }
2140
2141
                $this->originalEntityData[$oid] = $data;
2142
            }
2143 13
        } else {
2144
            $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...
2145
            $oid    = spl_object_hash($entity);
2146
2147
            $this->entityIdentifiers[$oid]  = $id;
2148
            $this->entityStates[$oid]       = self::STATE_MANAGED;
2149
            $this->originalEntityData[$oid] = $data;
2150
2151
            $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...
2152
2153
            $overrideLocalValues = true;
2154 38
        }
2155
2156 38
        if ( ! $overrideLocalValues) {
2157
            return $entity;
2158 38
        }
2159 38
2160
        if ($entity instanceof NotifyPropertyChanged) {
2161
            $entity->addPropertyChangedListener($this);
2162
        }
2163 38
2164 15
        foreach ($data as $field => $value) {
2165
            $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...
2166 15
2167 9
            if ($property instanceof FieldMetadata) {
2168 1
                $property->setValue($entity, $value);
2169
            }
2170
        }
2171 8
2172
        // Loading the entity right here, if its in the eager loading map get rid of it there.
2173 5
        unset($this->eagerLoadingEntities[$class->getRootClassName()][$idHash]);
2174
2175
        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...
2176 8
            unset($this->eagerLoadingEntities[$class->getRootClassName()]);
2177 8
        }
2178
2179 7
        // Properly initialize any unfetched associations, if partial objects are not allowed.
2180 14
        if (isset($hints[Query::HINT_FORCE_PARTIAL_LOAD])) {
2181
            return $entity;
2182
        }
2183 38
2184
        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...
2185
            if (! ($association instanceof AssociationMetadata)) {
2186
                continue;
2187
            }
2188
2189
            // Check if the association is not among the fetch-joined associations already.
2190
            if (isset($hints['fetchAlias']) && isset($hints['fetched'][$hints['fetchAlias']][$field])) {
2191
                continue;
2192
            }
2193 1028
2194
            $targetEntity = $association->getTargetEntity();
2195 1028
            $targetClass  = $this->em->getClassMetadata($targetEntity);
2196
2197 1028
            if ($association instanceof ToManyAssociationMetadata) {
2198 1028
                // Ignore if its a cached collection
2199
                if (isset($hints[Query::HINT_CACHE_ENABLED]) &&
2200
                    $association->getValue($entity) instanceof PersistentCollection) {
2201
                    continue;
2202 1028
                }
2203 650
2204
                $hasDataField = isset($data[$field]);
2205
2206 650
                // use the given collection
2207
                if ($hasDataField && $data[$field] instanceof PersistentCollection) {
2208 21
                    $data[$field]->setOwner($entity, $association);
2209
2210
                    $association->setValue($entity, $data[$field]);
2211
2212 589
                    $this->originalEntityData[$oid][$field] = $data[$field];
2213 554
2214 3
                    continue;
2215 3
                }
2216
2217
                // Inject collection
2218
                $pColl = $association->wrap($entity, $hasDataField ? $data[$field] : [], $this->em);
2219
2220
                $pColl->setInitialized($hasDataField);
2221 551
2222 282
                $association->setValue($entity, $pColl);
2223
2224
                if ($association->getFetchMode() === FetchMode::EAGER) {
2225 551
                    $this->loadCollection($pColl);
2226
                    $pColl->takeSnapshot();
2227 579
                }
2228 246
2229 4
                $this->originalEntityData[$oid][$field] = $pColl;
2230 4
2231
                continue;
2232
            }
2233
2234
            if (! $association->isOwningSide()) {
2235
                // use the given entity association
2236 242
                if (isset($data[$field]) && is_object($data[$field]) &&
2237 242
                    isset($this->entityStates[spl_object_hash($data[$field])])) {
2238
                    $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...
2239 644
2240
                    $association->setValue($entity, $data[$field]);
2241
                    $inverseAssociation->setValue($data[$field], $entity);
2242
2243 1021
                    $this->originalEntityData[$oid][$field] = $data[$field];
2244
2245
                    continue;
2246
                }
2247
2248
                // Inverse side of x-to-one can never be lazy
2249
                $persister = $this->getEntityPersister($targetEntity);
2250
2251
                $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...
2252
2253 64
                continue;
2254
            }
2255 64
2256
            // use the entity association
2257 64
            if (isset($data[$field]) && is_object($data[$field]) && isset($this->entityStates[spl_object_hash($data[$field])])) {
2258 64
                $association->setValue($entity, $data[$field]);
2259
2260
                $this->originalEntityData[$oid][$field] = $data[$field];
2261
2262 64
                continue;
2263
            }
2264 64
2265 26
            $associatedId = [];
2266 6
2267
            // TODO: Is this even computed right in all cases of composite keys?
2268
            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...
2269 26
                /** @var JoinColumnMetadata $joinColumn */
2270
                $joinColumnName = $joinColumn->getColumnName();
2271
                $joinColumnValue = isset($data[$joinColumnName]) ? $data[$joinColumnName] : null;
2272 26
                $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...
2273 19
2274
                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...
2275 20
                    // the missing key is part of target's entity primary key
2276 10
                    $associatedId = [];
2277
2278 20
                    continue;
2279
                }
2280 19
2281 7
                $associatedId[$targetField] = $joinColumnValue;
2282 7
            }
2283
2284 26
            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...
2285
                // Foreign key is NULL
2286
                $association->setValue($entity, null);
2287
                $this->originalEntityData[$oid][$field] = null;
2288
2289 64
                continue;
2290 16
            }
2291
2292 64
            // @todo guilhermeblanco Can we remove the need of this somehow?
2293
            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...
2294
                $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...
2295
            }
2296
2297
            // Foreign key is set
2298
            // Check identity map first
2299
            // FIXME: Can break easily with composite keys if join column values are in
2300
            //        wrong order. The correct order is the one in ClassMetadata#identifier.
2301
            $relatedIdHash = implode(' ', $associatedId);
2302
2303
            switch (true) {
2304
                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...
2305
                    $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...
2306
2307 11
                    // If this is an uninitialized proxy, we are deferring eager loads,
2308
                    // this association is marked as eager fetch, and its an uninitialized proxy (wtf!)
2309 11
                    // then we can append this entity for eager loading!
2310 1
                    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...
2311
                        $newValue instanceof GhostObjectInterface &&
2312
                        isset($hints[self::HINT_DEFEREAGERLOAD]) &&
2313 10
                        $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...
2314 1
                        ! $newValue->isProxyInitialized()
2315
                    ) {
2316
2317 9
                        $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...
2318
                    }
2319
2320 9
                    break;
2321 6
2322 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...
2323
                    // If it might be a subtype, it can not be lazy. There isn't even
2324
                    // a way to solve this with deferred eager loading, which means putting
2325 4
                    // an entity with subclasses at a *-to-one location is really bad! (performance-wise)
2326
                    $persister = $this->getEntityPersister($targetEntity);
2327
                    $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...
2328
                    break;
2329 4
2330 1
                default:
2331
                    // Proxies do not carry any kind of original entity data until they're fully loaded/initialized
2332
                    $managedData = [];
2333 4
2334
                    $normalizedAssociatedId = $this->convertFlatIdentifierIntoRealIdentifierFieldValues(
2335 4
                        $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...
2336 2
                        $associatedId
2337
                    );
2338
2339 2
                    switch (true) {
2340
                        // We are negating the condition here. Other cases will assume it is valid!
2341 3
                        case ($hints['fetchMode'][$class->getClassName()][$field] !== FetchMode::EAGER):
0 ignored issues
show
Bug introduced by
The method getClassName() does not seem to exist on object<Doctrine\Common\P...\Mapping\ClassMetadata>.

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

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

Loading history...
2342 3
                            $newValue = $this->em->getProxyFactory()->getProxy($targetEntity, $normalizedAssociatedId);
2343 1
                            break;
2344 3
2345 2
                        // Deferred eager load only works for single identifier classes
2346
                        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...
2347
                            // TODO: Is there a faster approach?
2348 1
                            $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...
2349
2350 1
                            $newValue = $this->em->getProxyFactory()->getProxy($targetEntity, $normalizedAssociatedId);
2351 1
                            break;
2352
2353
                        default:
2354 1
                            // TODO: This is very imperformant, ignore it?
2355
                            $newValue    = $this->em->find($targetEntity, $normalizedAssociatedId);
2356
                            // Needed to re-assign original entity data for freshly loaded entity
2357
                            $managedData = $this->originalEntityData[spl_object_hash($newValue)];
2358
                            break;
2359 3
                    }
2360
2361
                    // @TODO using `$associatedId` here seems to be risky.
2362
                    $this->registerManaged($newValue, $associatedId, $managedData);
2363
2364
                    break;
2365
            }
2366 1008
2367
            $this->originalEntityData[$oid][$field] = $newValue;
2368 1008
            $association->setValue($entity, $newValue);
2369
2370
            if (
2371
                $association->getInversedBy()
2372
                && $association instanceof OneToOneAssociationMetadata
2373
                // @TODO refactor this
2374
                // we don't want to set any values in un-initialized proxies
2375
                && ! (
2376
                    $newValue instanceof GhostObjectInterface
2377
                    && ! $newValue->isProxyInitialized()
2378 1218
                )
2379
            ) {
2380 1218
                $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...
2381 1217
2382 1217
                $inverseAssociation->setValue($newValue, $entity);
2383 1217
            }
2384 1217
        }
2385 1217
2386 1217
        if ($overrideLocalValues) {
2387 1217
            // defer invoking of postLoad event to hydration complete step
2388 1217
            $this->hydrationCompleteHandler->deferPostLoadInvoking($class, $entity);
0 ignored issues
show
Documentation introduced by
$class is of type object<Doctrine\Common\P...\Mapping\ClassMetadata>, but the function expects a object<Doctrine\ORM\Mapping\ClassMetadata>.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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