Completed
Push — master ( 4b5952...b8f7dd )
by Andreas
11s
created

UnitOfWork::getVisitedCollections()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ODM\MongoDB;
6
7
use Doctrine\Common\Collections\ArrayCollection;
8
use Doctrine\Common\Collections\Collection;
9
use Doctrine\Common\EventManager;
10
use Doctrine\Common\NotifyPropertyChanged;
11
use Doctrine\Common\PropertyChangedListener;
12
use Doctrine\ODM\MongoDB\Hydrator\HydratorFactory;
13
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
14
use Doctrine\ODM\MongoDB\Mapping\MappingException;
15
use Doctrine\ODM\MongoDB\PersistentCollection\PersistentCollectionInterface;
16
use Doctrine\ODM\MongoDB\Persisters\CollectionPersister;
17
use Doctrine\ODM\MongoDB\Persisters\PersistenceBuilder;
18
use Doctrine\ODM\MongoDB\Query\Query;
19
use Doctrine\ODM\MongoDB\Types\DateType;
20
use Doctrine\ODM\MongoDB\Types\Type;
21
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
22
use Doctrine\ODM\MongoDB\Utility\LifecycleEventManager;
23
use InvalidArgumentException;
24
use MongoDB\BSON\UTCDateTime;
25
use ProxyManager\Proxy\GhostObjectInterface;
26
use UnexpectedValueException;
27
use function array_filter;
28
use function count;
29
use function get_class;
30
use function in_array;
31
use function is_array;
32
use function is_object;
33
use function method_exists;
34
use function preg_match;
35
use function serialize;
36
use function spl_object_hash;
37
use function sprintf;
38
39
/**
40
 * The UnitOfWork is responsible for tracking changes to objects during an
41
 * "object-level" transaction and for writing out changes to the database
42
 * in the correct order.
43
 */
44
class UnitOfWork implements PropertyChangedListener
45
{
46
    /**
47
     * A document is in MANAGED state when its persistence is managed by a DocumentManager.
48
     */
49
    public const STATE_MANAGED = 1;
50
51
    /**
52
     * A document is new if it has just been instantiated (i.e. using the "new" operator)
53
     * and is not (yet) managed by a DocumentManager.
54
     */
55
    public const STATE_NEW = 2;
56
57
    /**
58
     * A detached document is an instance with a persistent identity that is not
59
     * (or no longer) associated with a DocumentManager (and a UnitOfWork).
60
     */
61
    public const STATE_DETACHED = 3;
62
63
    /**
64
     * A removed document instance is an instance with a persistent identity,
65
     * associated with a DocumentManager, whose persistent state has been
66
     * deleted (or is scheduled for deletion).
67
     */
68
    public const STATE_REMOVED = 4;
69
70
    /**
71
     * The identity map holds references to all managed documents.
72
     *
73
     * Documents are grouped by their class name, and then indexed by the
74
     * serialized string of their database identifier field or, if the class
75
     * has no identifier, the SPL object hash. Serializing the identifier allows
76
     * differentiation of values that may be equal (via type juggling) but not
77
     * identical.
78
     *
79
     * Since all classes in a hierarchy must share the same identifier set,
80
     * we always take the root class name of the hierarchy.
81
     *
82
     * @var array
83
     */
84
    private $identityMap = [];
85
86
    /**
87
     * Map of all identifiers of managed documents.
88
     * Keys are object ids (spl_object_hash).
89
     *
90
     * @var array
91
     */
92
    private $documentIdentifiers = [];
93
94
    /**
95
     * Map of the original document data of managed documents.
96
     * Keys are object ids (spl_object_hash). This is used for calculating changesets
97
     * at commit time.
98
     *
99
     * @internal Note that PHPs "copy-on-write" behavior helps a lot with memory usage.
100
     *           A value will only really be copied if the value in the document is modified
101
     *           by the user.
102
     *
103
     * @var array
104
     */
105
    private $originalDocumentData = [];
106
107
    /**
108
     * Map of document changes. Keys are object ids (spl_object_hash).
109
     * Filled at the beginning of a commit of the UnitOfWork and cleaned at the end.
110
     *
111
     * @var array
112
     */
113
    private $documentChangeSets = [];
114
115
    /**
116
     * The (cached) states of any known documents.
117
     * Keys are object ids (spl_object_hash).
118
     *
119
     * @var array
120
     */
121
    private $documentStates = [];
122
123
    /**
124
     * Map of documents that are scheduled for dirty checking at commit time.
125
     *
126
     * Documents are grouped by their class name, and then indexed by their SPL
127
     * object hash. This is only used for documents with a change tracking
128
     * policy of DEFERRED_EXPLICIT.
129
     *
130
     * @var array
131
     * @todo rename: scheduledForSynchronization
132
     */
133
    private $scheduledForDirtyCheck = [];
134
135
    /**
136
     * A list of all pending document insertions.
137
     *
138
     * @var array
139
     */
140
    private $documentInsertions = [];
141
142
    /**
143
     * A list of all pending document updates.
144
     *
145
     * @var array
146
     */
147
    private $documentUpdates = [];
148
149
    /**
150
     * A list of all pending document upserts.
151
     *
152
     * @var array
153
     */
154
    private $documentUpserts = [];
155
156
    /**
157
     * A list of all pending document deletions.
158
     *
159
     * @var array
160
     */
161
    private $documentDeletions = [];
162
163
    /**
164
     * All pending collection deletions.
165
     *
166
     * @var array
167
     */
168
    private $collectionDeletions = [];
169
170
    /**
171
     * All pending collection updates.
172
     *
173
     * @var array
174
     */
175
    private $collectionUpdates = [];
176
177
    /**
178
     * A list of documents related to collections scheduled for update or deletion
179
     *
180
     * @var array
181
     */
182
    private $hasScheduledCollections = [];
183
184
    /**
185
     * List of collections visited during changeset calculation on a commit-phase of a UnitOfWork.
186
     * At the end of the UnitOfWork all these collections will make new snapshots
187
     * of their data.
188
     *
189
     * @var array
190
     */
191
    private $visitedCollections = [];
192
193
    /**
194
     * The DocumentManager that "owns" this UnitOfWork instance.
195
     *
196
     * @var DocumentManager
197
     */
198
    private $dm;
199
200
    /**
201
     * The EventManager used for dispatching events.
202
     *
203
     * @var EventManager
204
     */
205
    private $evm;
206
207
    /**
208
     * Additional documents that are scheduled for removal.
209
     *
210
     * @var array
211
     */
212
    private $orphanRemovals = [];
213
214
    /**
215
     * The HydratorFactory used for hydrating array Mongo documents to Doctrine object documents.
216
     *
217
     * @var HydratorFactory
218
     */
219
    private $hydratorFactory;
220
221
    /**
222
     * The document persister instances used to persist document instances.
223
     *
224
     * @var array
225
     */
226
    private $persisters = [];
227
228
    /**
229
     * The collection persister instance used to persist changes to collections.
230
     *
231
     * @var Persisters\CollectionPersister
232
     */
233
    private $collectionPersister;
234
235
    /**
236
     * The persistence builder instance used in DocumentPersisters.
237
     *
238
     * @var PersistenceBuilder
239
     */
240
    private $persistenceBuilder;
241
242
    /**
243
     * Array of parent associations between embedded documents.
244
     *
245
     * @var array
246
     */
247
    private $parentAssociations = [];
248
249
    /** @var LifecycleEventManager */
250
    private $lifecycleEventManager;
251
252
    /**
253
     * Array of embedded documents known to UnitOfWork. We need to hold them to prevent spl_object_hash
254
     * collisions in case already managed object is lost due to GC (so now it won't). Embedded documents
255
     * found during doDetach are removed from the registry, to empty it altogether clear() can be utilized.
256
     *
257
     * @var array
258
     */
259
    private $embeddedDocumentsRegistry = [];
260
261
    /** @var int */
262
    private $commitsInProgress = 0;
263
264
    /**
265
     * Initializes a new UnitOfWork instance, bound to the given DocumentManager.
266
     */
267 1634
    public function __construct(DocumentManager $dm, EventManager $evm, HydratorFactory $hydratorFactory)
268
    {
269 1634
        $this->dm                    = $dm;
270 1634
        $this->evm                   = $evm;
271 1634
        $this->hydratorFactory       = $hydratorFactory;
272 1634
        $this->lifecycleEventManager = new LifecycleEventManager($dm, $this, $evm);
273 1634
    }
274
275
    /**
276
     * Factory for returning new PersistenceBuilder instances used for preparing data into
277
     * queries for insert persistence.
278
     */
279 1122
    public function getPersistenceBuilder() : PersistenceBuilder
280
    {
281 1122
        if (! $this->persistenceBuilder) {
282 1122
            $this->persistenceBuilder = new PersistenceBuilder($this->dm, $this);
283
        }
284 1122
        return $this->persistenceBuilder;
285
    }
286
287
    /**
288
     * Sets the parent association for a given embedded document.
289
     */
290 202
    public function setParentAssociation(object $document, array $mapping, object $parent, string $propertyPath) : void
291
    {
292 202
        $oid                                   = spl_object_hash($document);
293 202
        $this->embeddedDocumentsRegistry[$oid] = $document;
294 202
        $this->parentAssociations[$oid]        = [$mapping, $parent, $propertyPath];
295 202
    }
296
297
    /**
298
     * Gets the parent association for a given embedded document.
299
     *
300
     *     <code>
301
     *     list($mapping, $parent, $propertyPath) = $this->getParentAssociation($embeddedDocument);
302
     *     </code>
303
     */
304 226
    public function getParentAssociation(object $document) : ?array
305
    {
306 226
        $oid = spl_object_hash($document);
307
308 226
        return $this->parentAssociations[$oid] ?? null;
309
    }
310
311
    /**
312
     * Get the document persister instance for the given document name
313
     */
314 1120
    public function getDocumentPersister(string $documentName) : Persisters\DocumentPersister
315
    {
316 1120
        if (! isset($this->persisters[$documentName])) {
317 1107
            $class                           = $this->dm->getClassMetadata($documentName);
318 1107
            $pb                              = $this->getPersistenceBuilder();
319 1107
            $this->persisters[$documentName] = new Persisters\DocumentPersister($pb, $this->dm, $this, $this->hydratorFactory, $class);
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
320
        }
321 1120
        return $this->persisters[$documentName];
322
    }
323
324
    /**
325
     * Get the collection persister instance.
326
     */
327 1120
    public function getCollectionPersister() : CollectionPersister
328
    {
329 1120
        if (! isset($this->collectionPersister)) {
330 1120
            $pb                        = $this->getPersistenceBuilder();
331 1120
            $this->collectionPersister = new Persisters\CollectionPersister($this->dm, $pb, $this);
332
        }
333 1120
        return $this->collectionPersister;
334
    }
335
336
    /**
337
     * Set the document persister instance to use for the given document name
338
     */
339 13
    public function setDocumentPersister(string $documentName, Persisters\DocumentPersister $persister) : void
340
    {
341 13
        $this->persisters[$documentName] = $persister;
342 13
    }
343
344
    /**
345
     * Commits the UnitOfWork, executing all operations that have been postponed
346
     * up to this point. The state of all managed documents will be synchronized with
347
     * the database.
348
     *
349
     * The operations are executed in the following order:
350
     *
351
     * 1) All document insertions
352
     * 2) All document updates
353
     * 3) All document deletions
354
     *
355
     * @param array $options Array of options to be used with batchInsert(), update() and remove()
356
     */
357 602
    public function commit(array $options = []) : void
358
    {
359
        // Raise preFlush
360 602
        if ($this->evm->hasListeners(Events::preFlush)) {
361
            $this->evm->dispatchEvent(Events::preFlush, new Event\PreFlushEventArgs($this->dm));
362
        }
363
364
        // Compute changes done since last commit.
365 602
        $this->computeChangeSets();
366
367 601
        if (! ($this->documentInsertions ||
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->documentInsertions 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...
368 255
            $this->documentUpserts ||
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->documentUpserts of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

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

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

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

Loading history...
371 22
            $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...
372 22
            $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...
373 601
            $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...
374
        ) {
375 22
            return; // Nothing to do.
376
        }
377
378 598
        $this->commitsInProgress++;
379 598
        if ($this->commitsInProgress > 1) {
380
            throw MongoDBException::commitInProgress();
381
        }
382
        try {
383 598
            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...
384 50
                foreach ($this->orphanRemovals as $removal) {
385 50
                    $this->remove($removal);
386
                }
387
            }
388
389
            // Raise onFlush
390 598
            if ($this->evm->hasListeners(Events::onFlush)) {
391 5
                $this->evm->dispatchEvent(Events::onFlush, new Event\OnFlushEventArgs($this->dm));
392
            }
393
394 597
            foreach ($this->getClassesForCommitAction($this->documentUpserts) as $classAndDocuments) {
395 86
                [$class, $documents] = $classAndDocuments;
0 ignored issues
show
Bug introduced by
The variable $class 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 $documents does not exist. Did you mean $classAndDocuments?

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...
396 86
                $this->executeUpserts($class, $documents, $options);
0 ignored issues
show
Bug introduced by
The variable $documents does not exist. Did you mean $classAndDocuments?

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...
397
            }
398
399 597
            foreach ($this->getClassesForCommitAction($this->documentInsertions) as $classAndDocuments) {
400 521
                [$class, $documents] = $classAndDocuments;
0 ignored issues
show
Bug introduced by
The variable $documents does not exist. Did you mean $classAndDocuments?

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...
401 521
                $this->executeInserts($class, $documents, $options);
0 ignored issues
show
Bug introduced by
The variable $documents does not exist. Did you mean $classAndDocuments?

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...
402
            }
403
404 596
            foreach ($this->getClassesForCommitAction($this->documentUpdates) as $classAndDocuments) {
405 228
                [$class, $documents] = $classAndDocuments;
0 ignored issues
show
Bug introduced by
The variable $documents does not exist. Did you mean $classAndDocuments?

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...
406 228
                $this->executeUpdates($class, $documents, $options);
0 ignored issues
show
Bug introduced by
The variable $documents does not exist. Did you mean $classAndDocuments?

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...
407
            }
408
409 596
            foreach ($this->getClassesForCommitAction($this->documentDeletions, true) as $classAndDocuments) {
410 74
                [$class, $documents] = $classAndDocuments;
0 ignored issues
show
Bug introduced by
The variable $documents does not exist. Did you mean $classAndDocuments?

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...
411 74
                $this->executeDeletions($class, $documents, $options);
0 ignored issues
show
Bug introduced by
The variable $documents does not exist. Did you mean $classAndDocuments?

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...
412
            }
413
414
            // Raise postFlush
415 596
            if ($this->evm->hasListeners(Events::postFlush)) {
416
                $this->evm->dispatchEvent(Events::postFlush, new Event\PostFlushEventArgs($this->dm));
417
            }
418
419
            // Clear up
420 596
            $this->documentInsertions      =
421 596
            $this->documentUpserts         =
422 596
            $this->documentUpdates         =
423 596
            $this->documentDeletions       =
424 596
            $this->documentChangeSets      =
425 596
            $this->collectionUpdates       =
426 596
            $this->collectionDeletions     =
427 596
            $this->visitedCollections      =
428 596
            $this->scheduledForDirtyCheck  =
429 596
            $this->orphanRemovals          =
430 596
            $this->hasScheduledCollections = [];
431 596
        } finally {
432 598
            $this->commitsInProgress--;
433
        }
434 596
    }
435
436
    /**
437
     * Groups a list of scheduled documents by their class.
438
     */
439 597
    private function getClassesForCommitAction(array $documents, bool $includeEmbedded = false) : array
440
    {
441 597
        if (empty($documents)) {
442 597
            return [];
443
        }
444 596
        $divided = [];
445 596
        $embeds  = [];
446 596
        foreach ($documents as $oid => $d) {
447 596
            $className = get_class($d);
448 596
            if (isset($embeds[$className])) {
449 74
                continue;
450
            }
451 596
            if (isset($divided[$className])) {
452 159
                $divided[$className][1][$oid] = $d;
453 159
                continue;
454
            }
455 596
            $class = $this->dm->getClassMetadata($className);
456 596
            if ($class->isEmbeddedDocument && ! $includeEmbedded) {
0 ignored issues
show
Bug introduced by
Accessing isEmbeddedDocument 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...
457 176
                $embeds[$className] = true;
458 176
                continue;
459
            }
460 596
            if (empty($divided[$class->name])) {
0 ignored issues
show
Bug introduced by
Accessing name 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...
461 596
                $divided[$class->name] = [$class, [$oid => $d]];
0 ignored issues
show
Bug introduced by
Accessing name 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...
462
            } else {
463 596
                $divided[$class->name][1][$oid] = $d;
0 ignored issues
show
Bug introduced by
Accessing name 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...
464
            }
465
        }
466 596
        return $divided;
467
    }
468
469
    /**
470
     * Compute changesets of all documents scheduled for insertion.
471
     *
472
     * Embedded documents will not be processed.
473
     */
474 607
    private function computeScheduleInsertsChangeSets() : void
475
    {
476 607
        foreach ($this->documentInsertions as $document) {
477 534
            $class = $this->dm->getClassMetadata(get_class($document));
478 534
            if ($class->isEmbeddedDocument) {
0 ignored issues
show
Bug introduced by
Accessing isEmbeddedDocument 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...
479 158
                continue;
480
            }
481
482 528
            $this->computeChangeSet($class, $document);
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
483
        }
484 606
    }
485
486
    /**
487
     * Compute changesets of all documents scheduled for upsert.
488
     *
489
     * Embedded documents will not be processed.
490
     */
491 606
    private function computeScheduleUpsertsChangeSets() : void
492
    {
493 606
        foreach ($this->documentUpserts as $document) {
494 85
            $class = $this->dm->getClassMetadata(get_class($document));
495 85
            if ($class->isEmbeddedDocument) {
0 ignored issues
show
Bug introduced by
Accessing isEmbeddedDocument 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...
496
                continue;
497
            }
498
499 85
            $this->computeChangeSet($class, $document);
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
500
        }
501 606
    }
502
503
    /**
504
     * Gets the changeset for a document.
505
     *
506
     * @return array array('property' => array(0 => mixed|null, 1 => mixed|null))
507
     */
508 602
    public function getDocumentChangeSet(object $document) : array
509
    {
510 602
        $oid = spl_object_hash($document);
511
512 602
        return $this->documentChangeSets[$oid] ?? [];
513
    }
514
515
    /**
516
     * INTERNAL:
517
     * Sets the changeset for a document.
518
     */
519 1
    public function setDocumentChangeSet(object $document, array $changeset) : void
520
    {
521 1
        $this->documentChangeSets[spl_object_hash($document)] = $changeset;
522 1
    }
523
524
    /**
525
     * Get a documents actual data, flattening all the objects to arrays.
526
     *
527
     * @return array
528
     */
529 607
    public function getDocumentActualData(object $document) : array
530
    {
531 607
        $class      = $this->dm->getClassMetadata(get_class($document));
532 607
        $actualData = [];
533 607
        foreach ($class->reflFields as $name => $refProp) {
0 ignored issues
show
Bug introduced by
Accessing reflFields 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...
534 607
            $mapping = $class->fieldMappings[$name];
0 ignored issues
show
Bug introduced by
Accessing fieldMappings 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...
535
            // skip not saved fields
536 607
            if (isset($mapping['notSaved']) && $mapping['notSaved'] === true) {
537 49
                continue;
538
            }
539 607
            $value = $refProp->getValue($document);
540 607
            if ((isset($mapping['association']) && $mapping['type'] === 'many')
541 607
                && $value !== null && ! ($value instanceof PersistentCollectionInterface)) {
542
                // If $actualData[$name] is not a Collection then use an ArrayCollection.
543 393
                if (! $value instanceof Collection) {
544 146
                    $value = new ArrayCollection($value);
545
                }
546
547
                // Inject PersistentCollection
548 393
                $coll = $this->dm->getConfiguration()->getPersistentCollectionFactory()->create($this->dm, $mapping, $value);
549 393
                $coll->setOwner($document, $mapping);
550 393
                $coll->setDirty(! $value->isEmpty());
551 393
                $class->reflFields[$name]->setValue($document, $coll);
0 ignored issues
show
Bug introduced by
Accessing reflFields 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...
552 393
                $actualData[$name] = $coll;
553
            } else {
554 607
                $actualData[$name] = $value;
555
            }
556
        }
557 607
        return $actualData;
558
    }
559
560
    /**
561
     * Computes the changes that happened to a single document.
562
     *
563
     * Modifies/populates the following properties:
564
     *
565
     * {@link originalDocumentData}
566
     * If the document is NEW or MANAGED but not yet fully persisted (only has an id)
567
     * then it was not fetched from the database and therefore we have no original
568
     * document data yet. All of the current document data is stored as the original document data.
569
     *
570
     * {@link documentChangeSets}
571
     * The changes detected on all properties of the document are stored there.
572
     * A change is a tuple array where the first entry is the old value and the second
573
     * entry is the new value of the property. Changesets are used by persisters
574
     * to INSERT/UPDATE the persistent document state.
575
     *
576
     * {@link documentUpdates}
577
     * If the document is already fully MANAGED (has been fetched from the database before)
578
     * and any changes to its properties are detected, then a reference to the document is stored
579
     * there to mark it for an update.
580
     */
581 603
    public function computeChangeSet(ClassMetadata $class, object $document) : void
582
    {
583 603
        if (! $class->isInheritanceTypeNone()) {
584 183
            $class = $this->dm->getClassMetadata(get_class($document));
585
        }
586
587
        // Fire PreFlush lifecycle callbacks
588 603
        if (! empty($class->lifecycleCallbacks[Events::preFlush])) {
0 ignored issues
show
Bug introduced by
Accessing lifecycleCallbacks 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...
589 11
            $class->invokeLifecycleCallbacks(Events::preFlush, $document, [new Event\PreFlushEventArgs($this->dm)]);
590
        }
591
592 603
        $this->computeOrRecomputeChangeSet($class, $document);
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
593 602
    }
594
595
    /**
596
     * Used to do the common work of computeChangeSet and recomputeSingleDocumentChangeSet
597
     */
598 603
    private function computeOrRecomputeChangeSet(ClassMetadata $class, object $document, bool $recompute = false) : void
599
    {
600 603
        $oid           = spl_object_hash($document);
601 603
        $actualData    = $this->getDocumentActualData($document);
602 603
        $isNewDocument = ! isset($this->originalDocumentData[$oid]);
603 603
        if ($isNewDocument) {
604
            // Document is either NEW or MANAGED but not yet fully persisted (only has an id).
605
            // These result in an INSERT.
606 602
            $this->originalDocumentData[$oid] = $actualData;
607 602
            $changeSet                        = [];
608 602
            foreach ($actualData as $propName => $actualValue) {
609
                /* At this PersistentCollection shouldn't be here, probably it
610
                 * was cloned and its ownership must be fixed
611
                 */
612 602
                if ($actualValue instanceof PersistentCollectionInterface && $actualValue->getOwner() !== $document) {
613
                    $actualData[$propName] = $this->fixPersistentCollectionOwnership($actualValue, $document, $class, $propName);
614
                    $actualValue           = $actualData[$propName];
615
                }
616
                // ignore inverse side of reference relationship
617 602
                if (isset($class->fieldMappings[$propName]['reference']) && $class->fieldMappings[$propName]['isInverseSide']) {
618 187
                    continue;
619
                }
620 602
                $changeSet[$propName] = [null, $actualValue];
621
            }
622 602
            $this->documentChangeSets[$oid] = $changeSet;
623
        } else {
624 288
            if ($class->isReadOnly) {
625 2
                return;
626
            }
627
            // Document is "fully" MANAGED: it was already fully persisted before
628
            // and we have a copy of the original data
629 286
            $originalData           = $this->originalDocumentData[$oid];
630 286
            $isChangeTrackingNotify = $class->isChangeTrackingNotify();
631 286
            if ($isChangeTrackingNotify && ! $recompute && isset($this->documentChangeSets[$oid])) {
632 2
                $changeSet = $this->documentChangeSets[$oid];
633
            } else {
634 286
                $changeSet = [];
635
            }
636
637 286
            $gridFSMetadataProperty = null;
638
639 286
            if ($class->isFile) {
640
                try {
641 4
                    $gridFSMetadata         = $class->getFieldMappingByDbFieldName('metadata');
642 3
                    $gridFSMetadataProperty = $gridFSMetadata['fieldName'];
643 1
                } catch (MappingException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
644
                }
645
            }
646
647 286
            foreach ($actualData as $propName => $actualValue) {
648
                // skip not saved fields
649 286
                if ((isset($class->fieldMappings[$propName]['notSaved']) && $class->fieldMappings[$propName]['notSaved'] === true) ||
650 286
                    ($class->isFile && $propName !== $gridFSMetadataProperty)) {
651 4
                    continue;
652
                }
653
654 285
                $orgValue = $originalData[$propName] ?? null;
655
656
                // skip if value has not changed
657 285
                if ($orgValue === $actualValue) {
658 283
                    if (! $actualValue instanceof PersistentCollectionInterface) {
659 283
                        continue;
660
                    }
661
662 199
                    if (! $actualValue->isDirty() && ! $this->isCollectionScheduledForDeletion($actualValue)) {
663
                        // consider dirty collections as changed as well
664 175
                        continue;
665
                    }
666
                }
667
668
                // if relationship is a embed-one, schedule orphan removal to trigger cascade remove operations
669 246
                if (isset($class->fieldMappings[$propName]['embedded']) && $class->fieldMappings[$propName]['type'] === 'one') {
670 14
                    if ($orgValue !== null) {
671 8
                        $this->scheduleOrphanRemoval($orgValue);
672
                    }
673 14
                    $changeSet[$propName] = [$orgValue, $actualValue];
674 14
                    continue;
675
                }
676
677
                // if owning side of reference-one relationship
678 239
                if (isset($class->fieldMappings[$propName]['reference']) && $class->fieldMappings[$propName]['type'] === 'one' && $class->fieldMappings[$propName]['isOwningSide']) {
679 13
                    if ($orgValue !== null && $class->fieldMappings[$propName]['orphanRemoval']) {
680 1
                        $this->scheduleOrphanRemoval($orgValue);
681
                    }
682
683 13
                    $changeSet[$propName] = [$orgValue, $actualValue];
684 13
                    continue;
685
                }
686
687 232
                if ($isChangeTrackingNotify) {
688 3
                    continue;
689
                }
690
691
                // ignore inverse side of reference relationship
692 230
                if (isset($class->fieldMappings[$propName]['reference']) && $class->fieldMappings[$propName]['isInverseSide']) {
693 6
                    continue;
694
                }
695
696
                // Persistent collection was exchanged with the "originally"
697
                // created one. This can only mean it was cloned and replaced
698
                // on another document.
699 228
                if ($actualValue instanceof PersistentCollectionInterface && $actualValue->getOwner() !== $document) {
700 6
                    $actualValue = $this->fixPersistentCollectionOwnership($actualValue, $document, $class, $propName);
701
                }
702
703
                // if embed-many or reference-many relationship
704 228
                if (isset($class->fieldMappings[$propName]['type']) && $class->fieldMappings[$propName]['type'] === 'many') {
705 119
                    $changeSet[$propName] = [$orgValue, $actualValue];
706
                    /* If original collection was exchanged with a non-empty value
707
                     * and $set will be issued, there is no need to $unset it first
708
                     */
709 119
                    if ($actualValue && $actualValue->isDirty() && CollectionHelper::usesSet($class->fieldMappings[$propName]['strategy'])) {
710 27
                        continue;
711
                    }
712 100
                    if ($orgValue !== $actualValue && $orgValue instanceof PersistentCollectionInterface) {
713 18
                        $this->scheduleCollectionDeletion($orgValue);
714
                    }
715 100
                    continue;
716
                }
717
718
                // skip equivalent date values
719 146
                if (isset($class->fieldMappings[$propName]['type']) && $class->fieldMappings[$propName]['type'] === 'date') {
720
                    /** @var DateType $dateType */
721 37
                    $dateType      = Type::getType('date');
722 37
                    $dbOrgValue    = $dateType->convertToDatabaseValue($orgValue);
723 37
                    $dbActualValue = $dateType->convertToDatabaseValue($actualValue);
724
725 37
                    $orgTimestamp    = $dbOrgValue instanceof UTCDateTime ? $dbOrgValue->toDateTime()->getTimestamp() : null;
0 ignored issues
show
Bug introduced by
The class MongoDB\BSON\UTCDateTime does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
726 37
                    $actualTimestamp = $dbActualValue instanceof UTCDateTime ? $dbActualValue->toDateTime()->getTimestamp() : null;
0 ignored issues
show
Bug introduced by
The class MongoDB\BSON\UTCDateTime does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
727
728 37
                    if ($orgTimestamp === $actualTimestamp) {
729 30
                        continue;
730
                    }
731
                }
732
733
                // regular field
734 129
                $changeSet[$propName] = [$orgValue, $actualValue];
735
            }
736 286
            if ($changeSet) {
737 235
                $this->documentChangeSets[$oid] = isset($this->documentChangeSets[$oid])
738 19
                    ? $changeSet + $this->documentChangeSets[$oid]
739 232
                    : $changeSet;
740
741 235
                $this->originalDocumentData[$oid] = $actualData;
742 235
                $this->scheduleForUpdate($document);
743
            }
744
        }
745
746
        // Look for changes in associations of the document
747 603
        $associationMappings = array_filter(
748 603
            $class->associationMappings,
749
            static function ($assoc) {
750 464
                return empty($assoc['notSaved']);
751 603
            }
752
        );
753
754 603
        foreach ($associationMappings as $mapping) {
755 464
            $value = $class->reflFields[$mapping['fieldName']]->getValue($document);
756
757 464
            if ($value === null) {
758 323
                continue;
759
            }
760
761 445
            $this->computeAssociationChanges($document, $mapping, $value);
762
763 444
            if (isset($mapping['reference'])) {
764 336
                continue;
765
            }
766
767 347
            $values = $mapping['type'] === ClassMetadata::ONE ? [$value] : $value->unwrap();
768
769 347
            foreach ($values as $obj) {
770 181
                $oid2 = spl_object_hash($obj);
771
772 181
                if (isset($this->documentChangeSets[$oid2])) {
773 179
                    if (empty($this->documentChangeSets[$oid][$mapping['fieldName']])) {
774
                        // instance of $value is the same as it was previously otherwise there would be
775
                        // change set already in place
776 41
                        $this->documentChangeSets[$oid][$mapping['fieldName']] = [$value, $value];
777
                    }
778
779 179
                    if (! $isNewDocument) {
780 81
                        $this->scheduleForUpdate($document);
781
                    }
782
783 347
                    break;
784
                }
785
            }
786
        }
787 602
    }
788
789
    /**
790
     * Computes all the changes that have been done to documents and collections
791
     * since the last commit and stores these changes in the _documentChangeSet map
792
     * temporarily for access by the persisters, until the UoW commit is finished.
793
     */
794 607
    public function computeChangeSets() : void
795
    {
796 607
        $this->computeScheduleInsertsChangeSets();
797 606
        $this->computeScheduleUpsertsChangeSets();
798
799
        // Compute changes for other MANAGED documents. Change tracking policies take effect here.
800 606
        foreach ($this->identityMap as $className => $documents) {
801 606
            $class = $this->dm->getClassMetadata($className);
802 606
            if ($class->isEmbeddedDocument) {
0 ignored issues
show
Bug introduced by
Accessing isEmbeddedDocument 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...
803
                /* we do not want to compute changes to embedded documents up front
804
                 * in case embedded document was replaced and its changeset
805
                 * would corrupt data. Embedded documents' change set will
806
                 * be calculated by reachability from owning document.
807
                 */
808 171
                continue;
809
            }
810
811
            // If change tracking is explicit or happens through notification, then only compute
812
            // changes on document of that type that are explicitly marked for synchronization.
813
            switch (true) {
814 606
                case $class->isChangeTrackingDeferredImplicit():
815 605
                    $documentsToProcess = $documents;
816 605
                    break;
817
818 4
                case isset($this->scheduledForDirtyCheck[$className]):
819 3
                    $documentsToProcess = $this->scheduledForDirtyCheck[$className];
820 3
                    break;
821
822
                default:
823 4
                    $documentsToProcess = [];
824
            }
825
826 606
            foreach ($documentsToProcess as $document) {
827
                // Ignore uninitialized proxy objects
828 601
                if ($document instanceof GhostObjectInterface && ! $document->isProxyInitialized()) {
829 9
                    continue;
830
                }
831
                // Only MANAGED documents that are NOT SCHEDULED FOR INSERTION, UPSERT OR DELETION are processed here.
832 601
                $oid = spl_object_hash($document);
833 601
                if (isset($this->documentInsertions[$oid])
834 329
                    || isset($this->documentUpserts[$oid])
835 283
                    || isset($this->documentDeletions[$oid])
836 601
                    || ! isset($this->documentStates[$oid])
837
                ) {
838 599
                    continue;
839
                }
840
841 606
                $this->computeChangeSet($class, $document);
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
842
            }
843
        }
844 606
    }
845
846
    /**
847
     * Computes the changes of an association.
848
     *
849
     * @param mixed $value The value of the association.
850
     *
851
     * @throws InvalidArgumentException
852
     */
853 445
    private function computeAssociationChanges(object $parentDocument, array $assoc, $value) : void
854
    {
855 445
        $isNewParentDocument   = isset($this->documentInsertions[spl_object_hash($parentDocument)]);
856 445
        $class                 = $this->dm->getClassMetadata(get_class($parentDocument));
857 445
        $topOrExistingDocument = ( ! $isNewParentDocument || ! $class->isEmbeddedDocument);
0 ignored issues
show
Bug introduced by
Accessing isEmbeddedDocument 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...
858
859 445
        if ($value instanceof GhostObjectInterface && ! $value->isProxyInitialized()) {
860 7
            return;
861
        }
862
863 444
        if ($value instanceof PersistentCollectionInterface && $value->isDirty() && ($assoc['isOwningSide'] || isset($assoc['embedded']))) {
864 251
            if ($topOrExistingDocument || CollectionHelper::usesSet($assoc['strategy'])) {
865 247
                $this->scheduleCollectionUpdate($value);
866
            }
867 251
            $topmostOwner                                               = $this->getOwningDocument($value->getOwner());
868 251
            $this->visitedCollections[spl_object_hash($topmostOwner)][] = $value;
869 251
            if (! empty($assoc['orphanRemoval']) || isset($assoc['embedded'])) {
870 144
                $value->initialize();
871 144
                foreach ($value->getDeletedDocuments() as $orphan) {
872 23
                    $this->scheduleOrphanRemoval($orphan);
873
                }
874
            }
875
        }
876
877
        // Look through the documents, and in any of their associations,
878
        // for transient (new) documents, recursively. ("Persistence by reachability")
879
        // Unwrap. Uninitialized collections will simply be empty.
880 444
        $unwrappedValue = $assoc['type'] === ClassMetadata::ONE ? [$value] : $value->unwrap();
881
882 444
        $count = 0;
883 444
        foreach ($unwrappedValue as $key => $entry) {
884 360
            if (! is_object($entry)) {
885 1
                throw new InvalidArgumentException(
886 1
                    sprintf('Expected object, found "%s" in %s::%s', $entry, get_class($parentDocument), $assoc['name'])
887
                );
888
            }
889
890 359
            $targetClass = $this->dm->getClassMetadata(get_class($entry));
891
892 359
            $state = $this->getDocumentState($entry, self::STATE_NEW);
893
894
            // Handle "set" strategy for multi-level hierarchy
895 359
            $pathKey = ! isset($assoc['strategy']) || CollectionHelper::isList($assoc['strategy']) ? $count : $key;
896 359
            $path    = $assoc['type'] === 'many' ? $assoc['name'] . '.' . $pathKey : $assoc['name'];
897
898 359
            $count++;
899
900
            switch ($state) {
901 359
                case self::STATE_NEW:
902 67
                    if (! $assoc['isCascadePersist']) {
903
                        throw new InvalidArgumentException('A new document was found through a relationship that was not'
904
                            . ' configured to cascade persist operations: ' . $this->objToStr($entry) . '.'
905
                            . ' Explicitly persist the new document or configure cascading persist operations'
906
                            . ' on the relationship.');
907
                    }
908
909 67
                    $this->persistNew($targetClass, $entry);
0 ignored issues
show
Compatibility introduced by
$targetClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
910 67
                    $this->setParentAssociation($entry, $assoc, $parentDocument, $path);
911 67
                    $this->computeChangeSet($targetClass, $entry);
0 ignored issues
show
Compatibility introduced by
$targetClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
912 67
                    break;
913
914 355
                case self::STATE_MANAGED:
915 355
                    if ($targetClass->isEmbeddedDocument) {
0 ignored issues
show
Bug introduced by
Accessing isEmbeddedDocument 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...
916 172
                        [, $knownParent ] = $this->getParentAssociation($entry);
0 ignored issues
show
Bug introduced by
The variable $knownParent 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...
917 172
                        if ($knownParent && $knownParent !== $parentDocument) {
918 6
                            $entry = clone $entry;
919 6
                            if ($assoc['type'] === ClassMetadata::ONE) {
920 3
                                $class->setFieldValue($parentDocument, $assoc['fieldName'], $entry);
921 3
                                $this->setOriginalDocumentProperty(spl_object_hash($parentDocument), $assoc['fieldName'], $entry);
922 3
                                $poid = spl_object_hash($parentDocument);
923 3
                                if (isset($this->documentChangeSets[$poid][$assoc['fieldName']])) {
924 3
                                    $this->documentChangeSets[$poid][$assoc['fieldName']][1] = $entry;
925
                                }
926
                            } else {
927
                                // must use unwrapped value to not trigger orphan removal
928 4
                                $unwrappedValue[$key] = $entry;
929
                            }
930 6
                            $this->persistNew($targetClass, $entry);
0 ignored issues
show
Compatibility introduced by
$targetClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
931
                        }
932 172
                        $this->setParentAssociation($entry, $assoc, $parentDocument, $path);
933 172
                        $this->computeChangeSet($targetClass, $entry);
0 ignored issues
show
Compatibility introduced by
$targetClass of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
934
                    }
935 355
                    break;
936
937 1
                case self::STATE_REMOVED:
938
                    // Consume the $value as array (it's either an array or an ArrayAccess)
939
                    // and remove the element from Collection.
940 1
                    if ($assoc['type'] === ClassMetadata::MANY) {
941
                        unset($value[$key]);
942
                    }
943 1
                    break;
944
945
                case self::STATE_DETACHED:
946
                    // Can actually not happen right now as we assume STATE_NEW,
947
                    // so the exception will be raised from the DBAL layer (constraint violation).
948
                    throw new InvalidArgumentException('A detached document was found through a '
949
                        . 'relationship during cascading a persist operation.');
950
951 359
                default:
952
                    // MANAGED associated documents are already taken into account
953
                    // during changeset calculation anyway, since they are in the identity map.
954
            }
955
        }
956 443
    }
957
958
    /**
959
     * INTERNAL:
960
     * Computes the changeset of an individual document, independently of the
961
     * computeChangeSets() routine that is used at the beginning of a UnitOfWork#commit().
962
     *
963
     * The passed document must be a managed document. If the document already has a change set
964
     * because this method is invoked during a commit cycle then the change sets are added.
965
     * whereby changes detected in this method prevail.
966
     *
967
     * @throws InvalidArgumentException If the passed document is not MANAGED.
968
     *
969
     * @ignore
970
     */
971 19
    public function recomputeSingleDocumentChangeSet(ClassMetadata $class, object $document) : void
972
    {
973
        // Ignore uninitialized proxy objects
974 19
        if ($document instanceof GhostObjectInterface && ! $document->isProxyInitialized()) {
975 1
            return;
976
        }
977
978 18
        $oid = spl_object_hash($document);
979
980 18
        if (! isset($this->documentStates[$oid]) || $this->documentStates[$oid] !== self::STATE_MANAGED) {
981
            throw new InvalidArgumentException('Document must be managed.');
982
        }
983
984 18
        if (! $class->isInheritanceTypeNone()) {
985 2
            $class = $this->dm->getClassMetadata(get_class($document));
986
        }
987
988 18
        $this->computeOrRecomputeChangeSet($class, $document, true);
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
989 18
    }
990
991
    /**
992
     * @throws InvalidArgumentException If there is something wrong with document's identifier.
993
     */
994 632
    private function persistNew(ClassMetadata $class, object $document) : void
995
    {
996 632
        $this->lifecycleEventManager->prePersist($class, $document);
997 632
        $oid    = spl_object_hash($document);
998 632
        $upsert = false;
999 632
        if ($class->identifier) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $class->identifier of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1000 632
            $idValue = $class->getIdentifierValue($document);
1001 632
            $upsert  = ! $class->isEmbeddedDocument && $idValue !== null;
1002
1003 632
            if ($class->generatorType === ClassMetadata::GENERATOR_TYPE_NONE && $idValue === null) {
1004 3
                throw new InvalidArgumentException(sprintf(
1005 3
                    '%s uses NONE identifier generation strategy but no identifier was provided when persisting.',
1006 3
                    get_class($document)
1007
                ));
1008
            }
1009
1010 631
            if ($class->generatorType === ClassMetadata::GENERATOR_TYPE_AUTO && $idValue !== null && ! preg_match('#^[0-9a-f]{24}$#', (string) $idValue)) {
1011 1
                throw new InvalidArgumentException(sprintf(
1012 1
                    '%s uses AUTO identifier generation strategy but provided identifier is not a valid ObjectId.',
1013 1
                    get_class($document)
1014
                ));
1015
            }
1016
1017 630
            if ($class->generatorType !== ClassMetadata::GENERATOR_TYPE_NONE && $idValue === null) {
1018 551
                $idValue = $class->idGenerator->generate($this->dm, $document);
1019 551
                $idValue = $class->getPHPIdentifierValue($class->getDatabaseIdentifierValue($idValue));
1020 551
                $class->setIdentifierValue($document, $idValue);
1021
            }
1022
1023 630
            $this->documentIdentifiers[$oid] = $idValue;
1024
        } else {
1025
            // this is for embedded documents without identifiers
1026 151
            $this->documentIdentifiers[$oid] = $oid;
1027
        }
1028
1029 630
        $this->documentStates[$oid] = self::STATE_MANAGED;
1030
1031 630
        if ($upsert) {
1032 89
            $this->scheduleForUpsert($class, $document);
1033
        } else {
1034 560
            $this->scheduleForInsert($class, $document);
1035
        }
1036 630
    }
1037
1038
    /**
1039
     * Executes all document insertions for documents of the specified type.
1040
     */
1041 521
    private function executeInserts(ClassMetadata $class, array $documents, array $options = []) : void
1042
    {
1043 521
        $persister = $this->getDocumentPersister($class->name);
1044
1045 521
        foreach ($documents as $oid => $document) {
1046 521
            $persister->addInsert($document);
1047 521
            unset($this->documentInsertions[$oid]);
1048
        }
1049
1050 521
        $persister->executeInserts($options);
1051
1052 520
        foreach ($documents as $document) {
1053 520
            $this->lifecycleEventManager->postPersist($class, $document);
1054
        }
1055 520
    }
1056
1057
    /**
1058
     * Executes all document upserts for documents of the specified type.
1059
     */
1060 86
    private function executeUpserts(ClassMetadata $class, array $documents, array $options = []) : void
1061
    {
1062 86
        $persister = $this->getDocumentPersister($class->name);
1063
1064 86
        foreach ($documents as $oid => $document) {
1065 86
            $persister->addUpsert($document);
1066 86
            unset($this->documentUpserts[$oid]);
1067
        }
1068
1069 86
        $persister->executeUpserts($options);
1070
1071 86
        foreach ($documents as $document) {
1072 86
            $this->lifecycleEventManager->postPersist($class, $document);
1073
        }
1074 86
    }
1075
1076
    /**
1077
     * Executes all document updates for documents of the specified type.
1078
     */
1079 228
    private function executeUpdates(ClassMetadata $class, array $documents, array $options = []) : void
1080
    {
1081 228
        if ($class->isReadOnly) {
1082
            return;
1083
        }
1084
1085 228
        $className = $class->name;
1086 228
        $persister = $this->getDocumentPersister($className);
1087
1088 228
        foreach ($documents as $oid => $document) {
1089 228
            $this->lifecycleEventManager->preUpdate($class, $document);
1090
1091 228
            if (! empty($this->documentChangeSets[$oid]) || $this->hasScheduledCollections($document)) {
1092 227
                $persister->update($document, $options);
1093
            }
1094
1095 222
            unset($this->documentUpdates[$oid]);
1096
1097 222
            $this->lifecycleEventManager->postUpdate($class, $document);
1098
        }
1099 221
    }
1100
1101
    /**
1102
     * Executes all document deletions for documents of the specified type.
1103
     */
1104 74
    private function executeDeletions(ClassMetadata $class, array $documents, array $options = []) : void
1105
    {
1106 74
        $persister = $this->getDocumentPersister($class->name);
1107
1108 74
        foreach ($documents as $oid => $document) {
1109 74
            if (! $class->isEmbeddedDocument) {
1110 36
                $persister->delete($document, $options);
1111
            }
1112
            unset(
1113 72
                $this->documentDeletions[$oid],
1114 72
                $this->documentIdentifiers[$oid],
1115 72
                $this->originalDocumentData[$oid]
1116
            );
1117
1118
            // Clear snapshot information for any referenced PersistentCollection
1119
            // http://www.doctrine-project.org/jira/browse/MODM-95
1120 72
            foreach ($class->associationMappings as $fieldMapping) {
1121 48
                if (! isset($fieldMapping['type']) || $fieldMapping['type'] !== ClassMetadata::MANY) {
1122 38
                    continue;
1123
                }
1124
1125 28
                $value = $class->reflFields[$fieldMapping['fieldName']]->getValue($document);
1126 28
                if (! ($value instanceof PersistentCollectionInterface)) {
1127 7
                    continue;
1128
                }
1129
1130 24
                $value->clearSnapshot();
1131
            }
1132
1133
            // Document with this $oid after deletion treated as NEW, even if the $oid
1134
            // is obtained by a new document because the old one went out of scope.
1135 72
            $this->documentStates[$oid] = self::STATE_NEW;
1136
1137 72
            $this->lifecycleEventManager->postRemove($class, $document);
1138
        }
1139 72
    }
1140
1141
    /**
1142
     * Schedules a document for insertion into the database.
1143
     * If the document already has an identifier, it will be added to the
1144
     * identity map.
1145
     *
1146
     * @throws InvalidArgumentException
1147
     */
1148 563
    public function scheduleForInsert(ClassMetadata $class, object $document) : void
0 ignored issues
show
Unused Code introduced by
The parameter $class is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1149
    {
1150 563
        $oid = spl_object_hash($document);
1151
1152 563
        if (isset($this->documentUpdates[$oid])) {
1153
            throw new InvalidArgumentException('Dirty document can not be scheduled for insertion.');
1154
        }
1155 563
        if (isset($this->documentDeletions[$oid])) {
1156
            throw new InvalidArgumentException('Removed document can not be scheduled for insertion.');
1157
        }
1158 563
        if (isset($this->documentInsertions[$oid])) {
1159
            throw new InvalidArgumentException('Document can not be scheduled for insertion twice.');
1160
        }
1161
1162 563
        $this->documentInsertions[$oid] = $document;
1163
1164 563
        if (! isset($this->documentIdentifiers[$oid])) {
1165 3
            return;
1166
        }
1167
1168 560
        $this->addToIdentityMap($document);
1169 560
    }
1170
1171
    /**
1172
     * Schedules a document for upsert into the database and adds it to the
1173
     * identity map
1174
     *
1175
     * @throws InvalidArgumentException
1176
     */
1177 92
    public function scheduleForUpsert(ClassMetadata $class, object $document) : void
1178
    {
1179 92
        $oid = spl_object_hash($document);
1180
1181 92
        if ($class->isEmbeddedDocument) {
1182
            throw new InvalidArgumentException('Embedded document can not be scheduled for upsert.');
1183
        }
1184 92
        if (isset($this->documentUpdates[$oid])) {
1185
            throw new InvalidArgumentException('Dirty document can not be scheduled for upsert.');
1186
        }
1187 92
        if (isset($this->documentDeletions[$oid])) {
1188
            throw new InvalidArgumentException('Removed document can not be scheduled for upsert.');
1189
        }
1190 92
        if (isset($this->documentUpserts[$oid])) {
1191
            throw new InvalidArgumentException('Document can not be scheduled for upsert twice.');
1192
        }
1193
1194 92
        $this->documentUpserts[$oid]     = $document;
1195 92
        $this->documentIdentifiers[$oid] = $class->getIdentifierValue($document);
1196 92
        $this->addToIdentityMap($document);
1197 92
    }
1198
1199
    /**
1200
     * Checks whether a document is scheduled for insertion.
1201
     */
1202 104
    public function isScheduledForInsert(object $document) : bool
1203
    {
1204 104
        return isset($this->documentInsertions[spl_object_hash($document)]);
1205
    }
1206
1207
    /**
1208
     * Checks whether a document is scheduled for upsert.
1209
     */
1210 5
    public function isScheduledForUpsert(object $document) : bool
1211
    {
1212 5
        return isset($this->documentUpserts[spl_object_hash($document)]);
1213
    }
1214
1215
    /**
1216
     * Schedules a document for being updated.
1217
     *
1218
     * @throws InvalidArgumentException
1219
     */
1220 236
    public function scheduleForUpdate(object $document) : void
1221
    {
1222 236
        $oid = spl_object_hash($document);
1223 236
        if (! isset($this->documentIdentifiers[$oid])) {
1224
            throw new InvalidArgumentException('Document has no identity.');
1225
        }
1226
1227 236
        if (isset($this->documentDeletions[$oid])) {
1228
            throw new InvalidArgumentException('Document is removed.');
1229
        }
1230
1231 236
        if (isset($this->documentUpdates[$oid])
1232 236
            || isset($this->documentInsertions[$oid])
1233 236
            || isset($this->documentUpserts[$oid])) {
1234 98
            return;
1235
        }
1236
1237 234
        $this->documentUpdates[$oid] = $document;
1238 234
    }
1239
1240
    /**
1241
     * Checks whether a document is registered as dirty in the unit of work.
1242
     * Note: Is not very useful currently as dirty documents are only registered
1243
     * at commit time.
1244
     */
1245 21
    public function isScheduledForUpdate(object $document) : bool
1246
    {
1247 21
        return isset($this->documentUpdates[spl_object_hash($document)]);
1248
    }
1249
1250 1
    public function isScheduledForDirtyCheck(object $document) : bool
1251
    {
1252 1
        $class = $this->dm->getClassMetadata(get_class($document));
1253 1
        return isset($this->scheduledForDirtyCheck[$class->name][spl_object_hash($document)]);
0 ignored issues
show
Bug introduced by
Accessing name 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...
1254
    }
1255
1256
    /**
1257
     * INTERNAL:
1258
     * Schedules a document for deletion.
1259
     */
1260 79
    public function scheduleForDelete(object $document) : void
1261
    {
1262 79
        $oid = spl_object_hash($document);
1263
1264 79
        if (isset($this->documentInsertions[$oid])) {
1265 2
            if ($this->isInIdentityMap($document)) {
1266 2
                $this->removeFromIdentityMap($document);
1267
            }
1268 2
            unset($this->documentInsertions[$oid]);
1269 2
            return; // document has not been persisted yet, so nothing more to do.
1270
        }
1271
1272 78
        if (! $this->isInIdentityMap($document)) {
1273 2
            return; // ignore
1274
        }
1275
1276 77
        $this->removeFromIdentityMap($document);
1277 77
        $this->documentStates[$oid] = self::STATE_REMOVED;
1278
1279 77
        if (isset($this->documentUpdates[$oid])) {
1280
            unset($this->documentUpdates[$oid]);
1281
        }
1282 77
        if (isset($this->documentDeletions[$oid])) {
1283
            return;
1284
        }
1285
1286 77
        $this->documentDeletions[$oid] = $document;
1287 77
    }
1288
1289
    /**
1290
     * Checks whether a document is registered as removed/deleted with the unit
1291
     * of work.
1292
     */
1293 5
    public function isScheduledForDelete(object $document) : bool
1294
    {
1295 5
        return isset($this->documentDeletions[spl_object_hash($document)]);
1296
    }
1297
1298
    /**
1299
     * Checks whether a document is scheduled for insertion, update or deletion.
1300
     */
1301 250
    public function isDocumentScheduled(object $document) : bool
1302
    {
1303 250
        $oid = spl_object_hash($document);
1304 250
        return isset($this->documentInsertions[$oid]) ||
1305 132
            isset($this->documentUpserts[$oid]) ||
1306 122
            isset($this->documentUpdates[$oid]) ||
1307 250
            isset($this->documentDeletions[$oid]);
1308
    }
1309
1310
    /**
1311
     * INTERNAL:
1312
     * Registers a document in the identity map.
1313
     *
1314
     * Note that documents in a hierarchy are registered with the class name of
1315
     * the root document. Identifiers are serialized before being used as array
1316
     * keys to allow differentiation of equal, but not identical, values.
1317
     *
1318
     * @ignore
1319
     */
1320 671
    public function addToIdentityMap(object $document) : bool
1321
    {
1322 671
        $class = $this->dm->getClassMetadata(get_class($document));
1323 671
        $id    = $this->getIdForIdentityMap($document);
1324
1325 671
        if (isset($this->identityMap[$class->name][$id])) {
0 ignored issues
show
Bug introduced by
Accessing name 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...
1326 47
            return false;
1327
        }
1328
1329 671
        $this->identityMap[$class->name][$id] = $document;
0 ignored issues
show
Bug introduced by
Accessing name 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...
1330
1331 671
        if ($document instanceof NotifyPropertyChanged &&
1332 671
            ( ! $document instanceof GhostObjectInterface || $document->isProxyInitialized())) {
1333 3
            $document->addPropertyChangedListener($this);
1334
        }
1335
1336 671
        return true;
1337
    }
1338
1339
    /**
1340
     * Gets the state of a document with regard to the current unit of work.
1341
     *
1342
     * @param int|null $assume The state to assume if the state is not yet known (not MANAGED or REMOVED).
1343
     *                         This parameter can be set to improve performance of document state detection
1344
     *                         by potentially avoiding a database lookup if the distinction between NEW and DETACHED
1345
     *                         is either known or does not matter for the caller of the method.
1346
     */
1347 636
    public function getDocumentState(object $document, ?int $assume = null) : int
1348
    {
1349 636
        $oid = spl_object_hash($document);
1350
1351 636
        if (isset($this->documentStates[$oid])) {
1352 397
            return $this->documentStates[$oid];
1353
        }
1354
1355 635
        $class = $this->dm->getClassMetadata(get_class($document));
1356
1357 635
        if ($class->isEmbeddedDocument) {
0 ignored issues
show
Bug introduced by
Accessing isEmbeddedDocument 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...
1358 185
            return self::STATE_NEW;
1359
        }
1360
1361 632
        if ($assume !== null) {
1362 630
            return $assume;
1363
        }
1364
1365
        /* State can only be NEW or DETACHED, because MANAGED/REMOVED states are
1366
         * known. Note that you cannot remember the NEW or DETACHED state in
1367
         * _documentStates since the UoW does not hold references to such
1368
         * objects and the object hash can be reused. More generally, because
1369
         * the state may "change" between NEW/DETACHED without the UoW being
1370
         * aware of it.
1371
         */
1372 3
        $id = $class->getIdentifierObject($document);
0 ignored issues
show
Bug introduced by
The method getIdentifierObject() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. Did you maybe mean getIdentifier()?

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...
1373
1374 3
        if ($id === null) {
1375 2
            return self::STATE_NEW;
1376
        }
1377
1378
        // Check for a version field, if available, to avoid a DB lookup.
1379 2
        if ($class->isVersioned) {
0 ignored issues
show
Bug introduced by
Accessing isVersioned 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...
1380
            return $class->getFieldValue($document, $class->versionField)
0 ignored issues
show
Bug introduced by
Accessing versionField 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...
1381
                ? self::STATE_DETACHED
1382
                : self::STATE_NEW;
1383
        }
1384
1385
        // Last try before DB lookup: check the identity map.
1386 2
        if ($this->tryGetById($id, $class)) {
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
1387 1
            return self::STATE_DETACHED;
1388
        }
1389
1390
        // DB lookup
1391 2
        if ($this->getDocumentPersister($class->name)->exists($document)) {
0 ignored issues
show
Bug introduced by
Accessing name 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...
1392 1
            return self::STATE_DETACHED;
1393
        }
1394
1395 1
        return self::STATE_NEW;
1396
    }
1397
1398
    /**
1399
     * INTERNAL:
1400
     * Removes a document from the identity map. This effectively detaches the
1401
     * document from the persistence management of Doctrine.
1402
     *
1403
     * @throws InvalidArgumentException
1404
     *
1405
     * @ignore
1406
     */
1407 91
    public function removeFromIdentityMap(object $document) : bool
1408
    {
1409 91
        $oid = spl_object_hash($document);
1410
1411
        // Check if id is registered first
1412 91
        if (! isset($this->documentIdentifiers[$oid])) {
1413
            return false;
1414
        }
1415
1416 91
        $class = $this->dm->getClassMetadata(get_class($document));
1417 91
        $id    = $this->getIdForIdentityMap($document);
1418
1419 91
        if (isset($this->identityMap[$class->name][$id])) {
0 ignored issues
show
Bug introduced by
Accessing name 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...
1420 91
            unset($this->identityMap[$class->name][$id]);
1421 91
            $this->documentStates[$oid] = self::STATE_DETACHED;
1422 91
            return true;
1423
        }
1424
1425
        return false;
1426
    }
1427
1428
    /**
1429
     * INTERNAL:
1430
     * Gets a document in the identity map by its identifier hash.
1431
     *
1432
     * @param mixed $id Document identifier
1433
     *
1434
     * @throws InvalidArgumentException If the class does not have an identifier.
1435
     *
1436
     * @ignore
1437
     */
1438 40
    public function getById($id, ClassMetadata $class) : object
1439
    {
1440 40
        if (! $class->identifier) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $class->identifier of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1441
            throw new InvalidArgumentException(sprintf('Class "%s" does not have an identifier', $class->name));
1442
        }
1443
1444 40
        $serializedId = serialize($class->getDatabaseIdentifierValue($id));
1445
1446 40
        return $this->identityMap[$class->name][$serializedId];
1447
    }
1448
1449
    /**
1450
     * INTERNAL:
1451
     * Tries to get a document by its identifier hash. If no document is found
1452
     * for the given hash, FALSE is returned.
1453
     *
1454
     * @param mixed $id Document identifier
1455
     *
1456
     * @return mixed The found document or FALSE.
1457
     *
1458
     * @throws InvalidArgumentException If the class does not have an identifier.
1459
     *
1460
     * @ignore
1461
     */
1462 318
    public function tryGetById($id, ClassMetadata $class)
1463
    {
1464 318
        if (! $class->identifier) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $class->identifier of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1465
            throw new InvalidArgumentException(sprintf('Class "%s" does not have an identifier', $class->name));
1466
        }
1467
1468 318
        $serializedId = serialize($class->getDatabaseIdentifierValue($id));
1469
1470 318
        return $this->identityMap[$class->name][$serializedId] ?? false;
1471
    }
1472
1473
    /**
1474
     * Schedules a document for dirty-checking at commit-time.
1475
     *
1476
     * @todo Rename: scheduleForSynchronization
1477
     */
1478 3
    public function scheduleForDirtyCheck(object $document) : void
1479
    {
1480 3
        $class                                                                  = $this->dm->getClassMetadata(get_class($document));
1481 3
        $this->scheduledForDirtyCheck[$class->name][spl_object_hash($document)] = $document;
0 ignored issues
show
Bug introduced by
Accessing name 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...
1482 3
    }
1483
1484
    /**
1485
     * Checks whether a document is registered in the identity map.
1486
     */
1487 87
    public function isInIdentityMap(object $document) : bool
1488
    {
1489 87
        $oid = spl_object_hash($document);
1490
1491 87
        if (! isset($this->documentIdentifiers[$oid])) {
1492 6
            return false;
1493
        }
1494
1495 85
        $class = $this->dm->getClassMetadata(get_class($document));
1496 85
        $id    = $this->getIdForIdentityMap($document);
1497
1498 85
        return isset($this->identityMap[$class->name][$id]);
0 ignored issues
show
Bug introduced by
Accessing name 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...
1499
    }
1500
1501 671
    private function getIdForIdentityMap(object $document) : string
1502
    {
1503 671
        $class = $this->dm->getClassMetadata(get_class($document));
1504
1505 671
        if (! $class->identifier) {
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...
1506 157
            $id = spl_object_hash($document);
1507
        } else {
1508 670
            $id = $this->documentIdentifiers[spl_object_hash($document)];
1509 670
            $id = serialize($class->getDatabaseIdentifierValue($id));
1510
        }
1511
1512 671
        return $id;
1513
    }
1514
1515
    /**
1516
     * INTERNAL:
1517
     * Checks whether an identifier exists in the identity map.
1518
     *
1519
     * @ignore
1520
     */
1521
    public function containsId($id, string $rootClassName) : bool
1522
    {
1523
        return isset($this->identityMap[$rootClassName][serialize($id)]);
1524
    }
1525
1526
    /**
1527
     * Persists a document as part of the current unit of work.
1528
     *
1529
     * @throws MongoDBException If trying to persist MappedSuperclass.
1530
     * @throws InvalidArgumentException If there is something wrong with document's identifier.
1531
     */
1532 633
    public function persist(object $document) : void
1533
    {
1534 633
        $class = $this->dm->getClassMetadata(get_class($document));
1535 633
        if ($class->isMappedSuperclass || $class->isQueryResultDocument) {
0 ignored issues
show
Bug introduced by
Accessing isMappedSuperclass 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...
Bug introduced by
Accessing isQueryResultDocument 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...
1536 1
            throw MongoDBException::cannotPersistMappedSuperclass($class->name);
0 ignored issues
show
Bug introduced by
Accessing name 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...
1537
        }
1538 632
        $visited = [];
1539 632
        $this->doPersist($document, $visited);
1540 627
    }
1541
1542
    /**
1543
     * Saves a document as part of the current unit of work.
1544
     * This method is internally called during save() cascades as it tracks
1545
     * the already visited documents to prevent infinite recursions.
1546
     *
1547
     * NOTE: This method always considers documents that are not yet known to
1548
     * this UnitOfWork as NEW.
1549
     *
1550
     * @throws InvalidArgumentException
1551
     * @throws MongoDBException
1552
     */
1553 632
    private function doPersist(object $document, array &$visited) : void
1554
    {
1555 632
        $oid = spl_object_hash($document);
1556 632
        if (isset($visited[$oid])) {
1557 25
            return; // Prevent infinite recursion
1558
        }
1559
1560 632
        $visited[$oid] = $document; // Mark visited
1561
1562 632
        $class = $this->dm->getClassMetadata(get_class($document));
1563
1564 632
        $documentState = $this->getDocumentState($document, self::STATE_NEW);
1565
        switch ($documentState) {
1566 632
            case self::STATE_MANAGED:
1567
                // Nothing to do, except if policy is "deferred explicit"
1568 52
                if ($class->isChangeTrackingDeferredExplicit()) {
1569
                    $this->scheduleForDirtyCheck($document);
1570
                }
1571 52
                break;
1572 632
            case self::STATE_NEW:
1573 632
                if ($class->isFile) {
0 ignored issues
show
Bug introduced by
Accessing isFile 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...
1574 1
                    throw MongoDBException::cannotPersistGridFSFile($class->name);
0 ignored issues
show
Bug introduced by
Accessing name 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...
1575
                }
1576
1577 631
                $this->persistNew($class, $document);
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
1578 629
                break;
1579
1580 2
            case self::STATE_REMOVED:
1581
                // Document becomes managed again
1582 2
                unset($this->documentDeletions[$oid]);
1583
1584 2
                $this->documentStates[$oid] = self::STATE_MANAGED;
1585 2
                break;
1586
1587
            case self::STATE_DETACHED:
1588
                throw new InvalidArgumentException(
1589
                    'Behavior of persist() for a detached document is not yet defined.'
1590
                );
1591
1592
            default:
1593
                throw MongoDBException::invalidDocumentState($documentState);
1594
        }
1595
1596 629
        $this->cascadePersist($document, $visited);
1597 627
    }
1598
1599
    /**
1600
     * Deletes a document as part of the current unit of work.
1601
     */
1602 78
    public function remove(object $document)
1603
    {
1604 78
        $visited = [];
1605 78
        $this->doRemove($document, $visited);
1606 78
    }
1607
1608
    /**
1609
     * Deletes a document as part of the current unit of work.
1610
     *
1611
     * This method is internally called during delete() cascades as it tracks
1612
     * the already visited documents to prevent infinite recursions.
1613
     *
1614
     * @throws MongoDBException
1615
     */
1616 78
    private function doRemove(object $document, array &$visited) : void
1617
    {
1618 78
        $oid = spl_object_hash($document);
1619 78
        if (isset($visited[$oid])) {
1620 1
            return; // Prevent infinite recursion
1621
        }
1622
1623 78
        $visited[$oid] = $document; // mark visited
1624
1625
        /* Cascade first, because scheduleForDelete() removes the entity from
1626
         * the identity map, which can cause problems when a lazy Proxy has to
1627
         * be initialized for the cascade operation.
1628
         */
1629 78
        $this->cascadeRemove($document, $visited);
1630
1631 78
        $class         = $this->dm->getClassMetadata(get_class($document));
1632 78
        $documentState = $this->getDocumentState($document);
1633
        switch ($documentState) {
1634 78
            case self::STATE_NEW:
1635 78
            case self::STATE_REMOVED:
1636
                // nothing to do
1637 1
                break;
1638 78
            case self::STATE_MANAGED:
1639 78
                $this->lifecycleEventManager->preRemove($class, $document);
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
1640 78
                $this->scheduleForDelete($document);
1641 78
                break;
1642
            case self::STATE_DETACHED:
1643
                throw MongoDBException::detachedDocumentCannotBeRemoved();
1644
            default:
1645
                throw MongoDBException::invalidDocumentState($documentState);
1646
        }
1647 78
    }
1648
1649
    /**
1650
     * Merges the state of the given detached document into this UnitOfWork.
1651
     */
1652 11
    public function merge(object $document) : object
1653
    {
1654 11
        $visited = [];
1655
1656 11
        return $this->doMerge($document, $visited);
1657
    }
1658
1659
    /**
1660
     * Executes a merge operation on a document.
1661
     *
1662
     * @throws InvalidArgumentException If the entity instance is NEW.
1663
     * @throws LockException If the document uses optimistic locking through a
1664
     *                       version attribute and the version check against the
1665
     *                       managed copy fails.
1666
     */
1667 11
    private function doMerge(object $document, array &$visited, ?object $prevManagedCopy = null, ?array $assoc = null) : object
1668
    {
1669 11
        $oid = spl_object_hash($document);
1670
1671 11
        if (isset($visited[$oid])) {
1672 1
            return $visited[$oid]; // Prevent infinite recursion
1673
        }
1674
1675 11
        $visited[$oid] = $document; // mark visited
1676
1677 11
        $class = $this->dm->getClassMetadata(get_class($document));
1678
1679
        /* First we assume DETACHED, although it can still be NEW but we can
1680
         * avoid an extra DB round trip this way. If it is not MANAGED but has
1681
         * an identity, we need to fetch it from the DB anyway in order to
1682
         * merge. MANAGED documents are ignored by the merge operation.
1683
         */
1684 11
        $managedCopy = $document;
1685
1686 11
        if ($this->getDocumentState($document, self::STATE_DETACHED) !== self::STATE_MANAGED) {
1687 11
            if ($document instanceof GhostObjectInterface && ! $document->isProxyInitialized()) {
1688
                $document->initializeProxy();
1689
            }
1690
1691 11
            $identifier = $class->getIdentifier();
1692
            // We always have one element in the identifier array but it might be null
1693 11
            $id          = $identifier[0] !== null ? $class->getIdentifierObject($document) : null;
0 ignored issues
show
Bug introduced by
The method getIdentifierObject() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. Did you maybe mean getIdentifier()?

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...
1694 11
            $managedCopy = null;
1695
1696
            // Try to fetch document from the database
1697 11
            if (! $class->isEmbeddedDocument && $id !== null) {
0 ignored issues
show
Bug introduced by
Accessing isEmbeddedDocument 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...
1698 11
                $managedCopy = $this->dm->find($class->name, $id);
0 ignored issues
show
Bug introduced by
Accessing name 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...
1699
1700
                // Managed copy may be removed in which case we can't merge
1701 11
                if ($managedCopy && $this->getDocumentState($managedCopy) === self::STATE_REMOVED) {
1702
                    throw new InvalidArgumentException('Removed entity detected during merge. Cannot merge with a removed entity.');
1703
                }
1704
1705 11
                if ($managedCopy instanceof GhostObjectInterface && ! $managedCopy->isProxyInitialized()) {
1706
                    $managedCopy->initializeProxy();
1707
                }
1708
            }
1709
1710 11
            if ($managedCopy === null) {
1711
                // Create a new managed instance
1712 4
                $managedCopy = $class->newInstance();
1713 4
                if ($id !== null) {
1714 3
                    $class->setIdentifierValue($managedCopy, $id);
0 ignored issues
show
Bug introduced by
The method setIdentifierValue() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. Did you maybe mean getIdentifierValues()?

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...
1715
                }
1716 4
                $this->persistNew($class, $managedCopy);
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
1717
            }
1718
1719 11
            if ($class->isVersioned) {
0 ignored issues
show
Bug introduced by
Accessing isVersioned 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...
1720
                $managedCopyVersion = $class->reflFields[$class->versionField]->getValue($managedCopy);
0 ignored issues
show
Bug introduced by
Accessing reflFields 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...
Bug introduced by
Accessing versionField 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...
1721
                $documentVersion    = $class->reflFields[$class->versionField]->getValue($document);
0 ignored issues
show
Bug introduced by
Accessing reflFields 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...
Bug introduced by
Accessing versionField 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...
1722
1723
                // Throw exception if versions don't match
1724
                if ($managedCopyVersion !== $documentVersion) {
1725
                    throw LockException::lockFailedVersionMissmatch($document, $documentVersion, $managedCopyVersion);
1726
                }
1727
            }
1728
1729
            // Merge state of $document into existing (managed) document
1730 11
            foreach ($class->reflClass->getProperties() as $prop) {
0 ignored issues
show
Bug introduced by
Accessing reflClass 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...
1731 11
                $name = $prop->name;
1732 11
                $prop->setAccessible(true);
1733 11
                if (! isset($class->associationMappings[$name])) {
0 ignored issues
show
Bug introduced by
Accessing associationMappings 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...
1734 11
                    if (! $class->isIdentifier($name)) {
1735 11
                        $prop->setValue($managedCopy, $prop->getValue($document));
1736
                    }
1737
                } else {
1738 11
                    $assoc2 = $class->associationMappings[$name];
0 ignored issues
show
Bug introduced by
Accessing associationMappings 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...
1739
1740 11
                    if ($assoc2['type'] === 'one') {
1741 5
                        $other = $prop->getValue($document);
1742
1743 5
                        if ($other === null) {
1744 2
                            $prop->setValue($managedCopy, null);
1745 4
                        } elseif ($other instanceof GhostObjectInterface && ! $other->isProxyInitialized()) {
1746
                            // Do not merge fields marked lazy that have not been fetched
1747
                            continue;
1748 4
                        } elseif (! $assoc2['isCascadeMerge']) {
1749
                            if ($this->getDocumentState($other) === self::STATE_DETACHED) {
1750
                                $targetDocument = $assoc2['targetDocument'] ?? get_class($other);
1751
                                /** @var ClassMetadata $targetClass */
1752
                                $targetClass = $this->dm->getClassMetadata($targetDocument);
1753
                                $relatedId   = $targetClass->getIdentifierObject($other);
1754
1755
                                if ($targetClass->subClasses) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $targetClass->subClasses 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...
1756
                                    $other = $this->dm->find($targetClass->name, $relatedId);
1757
                                } else {
1758
                                    $other = $this
1759
                                        ->dm
1760
                                        ->getProxyFactory()
1761
                                        ->getProxy($targetClass, $relatedId);
1762
                                    $this->registerManaged($other, $relatedId, []);
0 ignored issues
show
Documentation introduced by
$other is of type object<ProxyManager\Proxy\GhostObjectInterface>, but the function expects a object<Doctrine\ODM\MongoDB\object>.

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...
1763
                                }
1764
                            }
1765
1766 5
                            $prop->setValue($managedCopy, $other);
1767
                        }
1768
                    } else {
1769 10
                        $mergeCol = $prop->getValue($document);
1770
1771 10
                        if ($mergeCol instanceof PersistentCollectionInterface && ! $mergeCol->isInitialized() && ! $assoc2['isCascadeMerge']) {
1772
                            /* Do not merge fields marked lazy that have not
1773
                             * been fetched. Keep the lazy persistent collection
1774
                             * of the managed copy.
1775
                             */
1776 3
                            continue;
1777
                        }
1778
1779 10
                        $managedCol = $prop->getValue($managedCopy);
1780
1781 10
                        if (! $managedCol) {
1782 1
                            $managedCol = $this->dm->getConfiguration()->getPersistentCollectionFactory()->create($this->dm, $assoc2, null);
1783 1
                            $managedCol->setOwner($managedCopy, $assoc2);
1784 1
                            $prop->setValue($managedCopy, $managedCol);
1785 1
                            $this->originalDocumentData[$oid][$name] = $managedCol;
1786
                        }
1787
1788
                        /* Note: do not process association's target documents.
1789
                         * They will be handled during the cascade. Initialize
1790
                         * and, if necessary, clear $managedCol for now.
1791
                         */
1792 10
                        if ($assoc2['isCascadeMerge']) {
1793 10
                            $managedCol->initialize();
1794
1795
                            // If $managedCol differs from the merged collection, clear and set dirty
1796 10
                            if (! $managedCol->isEmpty() && $managedCol !== $mergeCol) {
1797 3
                                $managedCol->unwrap()->clear();
1798 3
                                $managedCol->setDirty(true);
1799
1800 3
                                if ($assoc2['isOwningSide'] && $class->isChangeTrackingNotify()) {
1801
                                    $this->scheduleForDirtyCheck($managedCopy);
1802
                                }
1803
                            }
1804
                        }
1805
                    }
1806
                }
1807
1808 11
                if (! $class->isChangeTrackingNotify()) {
1809 11
                    continue;
1810
                }
1811
1812
                // Just treat all properties as changed, there is no other choice.
1813
                $this->propertyChanged($managedCopy, $name, null, $prop->getValue($managedCopy));
1814
            }
1815
1816 11
            if ($class->isChangeTrackingDeferredExplicit()) {
1817
                $this->scheduleForDirtyCheck($document);
1818
            }
1819
        }
1820
1821 11
        if ($prevManagedCopy !== null) {
1822 5
            $assocField = $assoc['fieldName'];
1823 5
            $prevClass  = $this->dm->getClassMetadata(get_class($prevManagedCopy));
1824
1825 5
            if ($assoc['type'] === 'one') {
1826 3
                $prevClass->reflFields[$assocField]->setValue($prevManagedCopy, $managedCopy);
0 ignored issues
show
Bug introduced by
Accessing reflFields 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...
1827
            } else {
1828 4
                $prevClass->reflFields[$assocField]->getValue($prevManagedCopy)->add($managedCopy);
0 ignored issues
show
Bug introduced by
Accessing reflFields 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...
1829
1830 4
                if ($assoc['type'] === 'many' && isset($assoc['mappedBy'])) {
1831 1
                    $class->reflFields[$assoc['mappedBy']]->setValue($managedCopy, $prevManagedCopy);
0 ignored issues
show
Bug introduced by
Accessing reflFields 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...
1832
                }
1833
            }
1834
        }
1835
1836
        // Mark the managed copy visited as well
1837 11
        $visited[spl_object_hash($managedCopy)] = $managedCopy;
1838
1839 11
        $this->cascadeMerge($document, $managedCopy, $visited);
1840
1841 11
        return $managedCopy;
1842
    }
1843
1844
    /**
1845
     * Detaches a document from the persistence management. It's persistence will
1846
     * no longer be managed by Doctrine.
1847
     */
1848 11
    public function detach(object $document) : void
1849
    {
1850 11
        $visited = [];
1851 11
        $this->doDetach($document, $visited);
1852 11
    }
1853
1854
    /**
1855
     * Executes a detach operation on the given document.
1856
     *
1857
     * @internal This method always considers documents with an assigned identifier as DETACHED.
1858
     */
1859 17
    private function doDetach(object $document, array &$visited) : void
1860
    {
1861 17
        $oid = spl_object_hash($document);
1862 17
        if (isset($visited[$oid])) {
1863 3
            return; // Prevent infinite recursion
1864
        }
1865
1866 17
        $visited[$oid] = $document; // mark visited
1867
1868 17
        switch ($this->getDocumentState($document, self::STATE_DETACHED)) {
1869 17
            case self::STATE_MANAGED:
1870 17
                $this->removeFromIdentityMap($document);
1871
                unset(
1872 17
                    $this->documentInsertions[$oid],
1873 17
                    $this->documentUpdates[$oid],
1874 17
                    $this->documentDeletions[$oid],
1875 17
                    $this->documentIdentifiers[$oid],
1876 17
                    $this->documentStates[$oid],
1877 17
                    $this->originalDocumentData[$oid],
1878 17
                    $this->parentAssociations[$oid],
1879 17
                    $this->documentUpserts[$oid],
1880 17
                    $this->hasScheduledCollections[$oid],
1881 17
                    $this->embeddedDocumentsRegistry[$oid]
1882
                );
1883 17
                break;
1884 3
            case self::STATE_NEW:
1885 3
            case self::STATE_DETACHED:
1886 3
                return;
1887
        }
1888
1889 17
        $this->cascadeDetach($document, $visited);
1890 17
    }
1891
1892
    /**
1893
     * Refreshes the state of the given document from the database, overwriting
1894
     * any local, unpersisted changes.
1895
     *
1896
     * @throws InvalidArgumentException If the document is not MANAGED.
1897
     */
1898 24
    public function refresh(object $document) : void
1899
    {
1900 24
        $visited = [];
1901 24
        $this->doRefresh($document, $visited);
1902 23
    }
1903
1904
    /**
1905
     * Executes a refresh operation on a document.
1906
     *
1907
     * @throws InvalidArgumentException If the document is not MANAGED.
1908
     */
1909 24
    private function doRefresh(object $document, array &$visited) : void
1910
    {
1911 24
        $oid = spl_object_hash($document);
1912 24
        if (isset($visited[$oid])) {
1913
            return; // Prevent infinite recursion
1914
        }
1915
1916 24
        $visited[$oid] = $document; // mark visited
1917
1918 24
        $class = $this->dm->getClassMetadata(get_class($document));
1919
1920 24
        if (! $class->isEmbeddedDocument) {
0 ignored issues
show
Bug introduced by
Accessing isEmbeddedDocument 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...
1921 24
            if ($this->getDocumentState($document) !== self::STATE_MANAGED) {
1922 1
                throw new InvalidArgumentException('Document is not MANAGED.');
1923
            }
1924
1925 23
            $this->getDocumentPersister($class->name)->refresh($document);
0 ignored issues
show
Bug introduced by
Accessing name 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...
1926
        }
1927
1928 23
        $this->cascadeRefresh($document, $visited);
1929 23
    }
1930
1931
    /**
1932
     * Cascades a refresh operation to associated documents.
1933
     */
1934 23
    private function cascadeRefresh(object $document, array &$visited) : void
1935
    {
1936 23
        $class = $this->dm->getClassMetadata(get_class($document));
1937
1938 23
        $associationMappings = array_filter(
1939 23
            $class->associationMappings,
0 ignored issues
show
Bug introduced by
Accessing associationMappings 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...
1940
            static function ($assoc) {
1941 18
                return $assoc['isCascadeRefresh'];
1942 23
            }
1943
        );
1944
1945 23
        foreach ($associationMappings as $mapping) {
1946 15
            $relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document);
0 ignored issues
show
Bug introduced by
Accessing reflFields 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...
1947 15
            if ($relatedDocuments instanceof Collection || is_array($relatedDocuments)) {
1948 15
                if ($relatedDocuments instanceof PersistentCollectionInterface) {
1949
                    // Unwrap so that foreach() does not initialize
1950 15
                    $relatedDocuments = $relatedDocuments->unwrap();
1951
                }
1952 15
                foreach ($relatedDocuments as $relatedDocument) {
1953 15
                    $this->doRefresh($relatedDocument, $visited);
1954
                }
1955 10
            } elseif ($relatedDocuments !== null) {
1956 15
                $this->doRefresh($relatedDocuments, $visited);
1957
            }
1958
        }
1959 23
    }
1960
1961
    /**
1962
     * Cascades a detach operation to associated documents.
1963
     */
1964 17
    private function cascadeDetach(object $document, array &$visited) : void
1965
    {
1966 17
        $class = $this->dm->getClassMetadata(get_class($document));
1967 17
        foreach ($class->fieldMappings as $mapping) {
0 ignored issues
show
Bug introduced by
Accessing fieldMappings on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
1968 17
            if (! $mapping['isCascadeDetach']) {
1969 17
                continue;
1970
            }
1971 11
            $relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document);
0 ignored issues
show
Bug introduced by
Accessing reflFields 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...
1972 11
            if ($relatedDocuments instanceof Collection || is_array($relatedDocuments)) {
1973 11
                if ($relatedDocuments instanceof PersistentCollectionInterface) {
1974
                    // Unwrap so that foreach() does not initialize
1975 8
                    $relatedDocuments = $relatedDocuments->unwrap();
1976
                }
1977 11
                foreach ($relatedDocuments as $relatedDocument) {
1978 11
                    $this->doDetach($relatedDocument, $visited);
1979
                }
1980 11
            } elseif ($relatedDocuments !== null) {
1981 11
                $this->doDetach($relatedDocuments, $visited);
1982
            }
1983
        }
1984 17
    }
1985
    /**
1986
     * Cascades a merge operation to associated documents.
1987
     */
1988 11
    private function cascadeMerge(object $document, object $managedCopy, array &$visited) : void
1989
    {
1990 11
        $class = $this->dm->getClassMetadata(get_class($document));
1991
1992 11
        $associationMappings = array_filter(
1993 11
            $class->associationMappings,
0 ignored issues
show
Bug introduced by
Accessing associationMappings 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...
1994
            static function ($assoc) {
1995 11
                return $assoc['isCascadeMerge'];
1996 11
            }
1997
        );
1998
1999 11
        foreach ($associationMappings as $assoc) {
2000 11
            $relatedDocuments = $class->reflFields[$assoc['fieldName']]->getValue($document);
0 ignored issues
show
Bug introduced by
Accessing reflFields 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...
2001
2002 11
            if ($relatedDocuments instanceof Collection || is_array($relatedDocuments)) {
2003 8
                if ($relatedDocuments === $class->reflFields[$assoc['fieldName']]->getValue($managedCopy)) {
0 ignored issues
show
Bug introduced by
Accessing reflFields 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...
2004
                    // Collections are the same, so there is nothing to do
2005 1
                    continue;
2006
                }
2007
2008 8
                foreach ($relatedDocuments as $relatedDocument) {
2009 8
                    $this->doMerge($relatedDocument, $visited, $managedCopy, $assoc);
2010
                }
2011 6
            } elseif ($relatedDocuments !== null) {
2012 11
                $this->doMerge($relatedDocuments, $visited, $managedCopy, $assoc);
2013
            }
2014
        }
2015 11
    }
2016
2017
    /**
2018
     * Cascades the save operation to associated documents.
2019
     */
2020 629
    private function cascadePersist(object $document, array &$visited) : void
2021
    {
2022 629
        $class = $this->dm->getClassMetadata(get_class($document));
2023
2024 629
        $associationMappings = array_filter(
2025 629
            $class->associationMappings,
0 ignored issues
show
Bug introduced by
Accessing associationMappings 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...
2026
            static function ($assoc) {
2027 486
                return $assoc['isCascadePersist'];
2028 629
            }
2029
        );
2030
2031 629
        foreach ($associationMappings as $fieldName => $mapping) {
2032 434
            $relatedDocuments = $class->reflFields[$fieldName]->getValue($document);
0 ignored issues
show
Bug introduced by
Accessing reflFields 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...
2033
2034 434
            if ($relatedDocuments instanceof Collection || is_array($relatedDocuments)) {
2035 363
                if ($relatedDocuments instanceof PersistentCollectionInterface) {
2036 12
                    if ($relatedDocuments->getOwner() !== $document) {
2037 2
                        $relatedDocuments = $this->fixPersistentCollectionOwnership($relatedDocuments, $document, $class, $mapping['fieldName']);
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata 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...
2038
                    }
2039
                    // Unwrap so that foreach() does not initialize
2040 12
                    $relatedDocuments = $relatedDocuments->unwrap();
2041
                }
2042
2043 363
                $count = 0;
2044 363
                foreach ($relatedDocuments as $relatedKey => $relatedDocument) {
2045 194
                    if (! empty($mapping['embedded'])) {
2046 123
                        [, $knownParent ] = $this->getParentAssociation($relatedDocument);
0 ignored issues
show
Bug introduced by
The variable $knownParent 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...
2047 123
                        if ($knownParent && $knownParent !== $document) {
2048 1
                            $relatedDocument               = clone $relatedDocument;
2049 1
                            $relatedDocuments[$relatedKey] = $relatedDocument;
2050
                        }
2051 123
                        $pathKey = CollectionHelper::isList($mapping['strategy']) ? $count++ : $relatedKey;
2052 123
                        $this->setParentAssociation($relatedDocument, $mapping, $document, $mapping['fieldName'] . '.' . $pathKey);
2053
                    }
2054 363
                    $this->doPersist($relatedDocument, $visited);
2055
                }
2056 344
            } elseif ($relatedDocuments !== null) {
2057 129
                if (! empty($mapping['embedded'])) {
2058 69
                    [, $knownParent ] = $this->getParentAssociation($relatedDocuments);
2059 69
                    if ($knownParent && $knownParent !== $document) {
2060 3
                        $relatedDocuments = clone $relatedDocuments;
2061 3
                        $class->setFieldValue($document, $mapping['fieldName'], $relatedDocuments);
2062
                    }
2063 69
                    $this->setParentAssociation($relatedDocuments, $mapping, $document, $mapping['fieldName']);
2064
                }
2065 434
                $this->doPersist($relatedDocuments, $visited);
2066
            }
2067
        }
2068 627
    }
2069
2070
    /**
2071
     * Cascades the delete operation to associated documents.
2072
     */
2073 78
    private function cascadeRemove(object $document, array &$visited) : void
2074
    {
2075 78
        $class = $this->dm->getClassMetadata(get_class($document));
2076 78
        foreach ($class->fieldMappings as $mapping) {
0 ignored issues
show
Bug introduced by
Accessing fieldMappings 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...
2077 78
            if (! $mapping['isCascadeRemove'] && ( ! isset($mapping['orphanRemoval']) || ! $mapping['orphanRemoval'])) {
2078 77
                continue;
2079
            }
2080 38
            if ($document instanceof GhostObjectInterface && ! $document->isProxyInitialized()) {
2081 3
                $document->initializeProxy();
2082
            }
2083
2084 38
            $relatedDocuments = $class->reflFields[$mapping['fieldName']]->getValue($document);
0 ignored issues
show
Bug introduced by
Accessing reflFields 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...
2085 38
            if ($relatedDocuments instanceof Collection || is_array($relatedDocuments)) {
2086
                // If its a PersistentCollection initialization is intended! No unwrap!
2087 26
                foreach ($relatedDocuments as $relatedDocument) {
2088 26
                    $this->doRemove($relatedDocument, $visited);
2089
                }
2090 27
            } elseif ($relatedDocuments !== null) {
2091 38
                $this->doRemove($relatedDocuments, $visited);
2092
            }
2093
        }
2094 78
    }
2095
2096
    /**
2097
     * Acquire a lock on the given document.
2098
     *
2099
     * @throws LockException
2100
     * @throws InvalidArgumentException
2101
     */
2102 8
    public function lock(object $document, int $lockMode, ?int $lockVersion = null) : void
2103
    {
2104 8
        if ($this->getDocumentState($document) !== self::STATE_MANAGED) {
2105 1
            throw new InvalidArgumentException('Document is not MANAGED.');
2106
        }
2107
2108 7
        $documentName = get_class($document);
2109 7
        $class        = $this->dm->getClassMetadata($documentName);
2110
2111 7
        if ($lockMode === LockMode::OPTIMISTIC) {
2112 2
            if (! $class->isVersioned) {
0 ignored issues
show
Bug introduced by
Accessing isVersioned 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...
2113 1
                throw LockException::notVersioned($documentName);
2114
            }
2115
2116 1
            if ($lockVersion !== null) {
2117 1
                $documentVersion = $class->reflFields[$class->versionField]->getValue($document);
0 ignored issues
show
Bug introduced by
Accessing reflFields 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...
Bug introduced by
Accessing versionField on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2118 1
                if ($documentVersion !== $lockVersion) {
2119 1
                    throw LockException::lockFailedVersionMissmatch($document, $lockVersion, $documentVersion);
2120
                }
2121
            }
2122 5
        } elseif (in_array($lockMode, [LockMode::PESSIMISTIC_READ, LockMode::PESSIMISTIC_WRITE])) {
2123 5
            $this->getDocumentPersister($class->name)->lock($document, $lockMode);
0 ignored issues
show
Bug introduced by
Accessing name 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...
2124
        }
2125 5
    }
2126
2127
    /**
2128
     * Releases a lock on the given document.
2129
     *
2130
     * @throws InvalidArgumentException
2131
     */
2132 1
    public function unlock(object $document) : void
2133
    {
2134 1
        if ($this->getDocumentState($document) !== self::STATE_MANAGED) {
2135
            throw new InvalidArgumentException('Document is not MANAGED.');
2136
        }
2137 1
        $documentName = get_class($document);
2138 1
        $this->getDocumentPersister($documentName)->unlock($document);
2139 1
    }
2140
2141
    /**
2142
     * Clears the UnitOfWork.
2143
     */
2144 390
    public function clear(?string $documentName = null) : void
2145
    {
2146 390
        if ($documentName === null) {
2147 384
            $this->identityMap               =
2148 384
            $this->documentIdentifiers       =
2149 384
            $this->originalDocumentData      =
2150 384
            $this->documentChangeSets        =
2151 384
            $this->documentStates            =
2152 384
            $this->scheduledForDirtyCheck    =
2153 384
            $this->documentInsertions        =
2154 384
            $this->documentUpserts           =
2155 384
            $this->documentUpdates           =
2156 384
            $this->documentDeletions         =
2157 384
            $this->collectionUpdates         =
2158 384
            $this->collectionDeletions       =
2159 384
            $this->parentAssociations        =
2160 384
            $this->embeddedDocumentsRegistry =
2161 384
            $this->orphanRemovals            =
2162 384
            $this->hasScheduledCollections   = [];
2163
        } else {
2164 6
            $visited = [];
2165 6
            foreach ($this->identityMap as $className => $documents) {
2166 6
                if ($className !== $documentName) {
2167 3
                    continue;
2168
                }
2169
2170 6
                foreach ($documents as $document) {
2171 6
                    $this->doDetach($document, $visited);
2172
                }
2173
            }
2174
        }
2175
2176 390
        if (! $this->evm->hasListeners(Events::onClear)) {
2177 390
            return;
2178
        }
2179
2180
        $this->evm->dispatchEvent(Events::onClear, new Event\OnClearEventArgs($this->dm, $documentName));
2181
    }
2182
2183
    /**
2184
     * INTERNAL:
2185
     * Schedules an embedded document for removal. The remove() operation will be
2186
     * invoked on that document at the beginning of the next commit of this
2187
     * UnitOfWork.
2188
     *
2189
     * @ignore
2190
     */
2191 53
    public function scheduleOrphanRemoval(object $document) : void
2192
    {
2193 53
        $this->orphanRemovals[spl_object_hash($document)] = $document;
2194 53
    }
2195
2196
    /**
2197
     * INTERNAL:
2198
     * Unschedules an embedded or referenced object for removal.
2199
     *
2200
     * @ignore
2201
     */
2202 120
    public function unscheduleOrphanRemoval(object $document) : void
2203
    {
2204 120
        $oid = spl_object_hash($document);
2205 120
        unset($this->orphanRemovals[$oid]);
2206 120
    }
2207
2208
    /**
2209
     * Fixes PersistentCollection state if it wasn't used exactly as we had in mind:
2210
     *  1) sets owner if it was cloned
2211
     *  2) clones collection, sets owner, updates document's property and, if necessary, updates originalData
2212
     *  3) NOP if state is OK
2213
     * Returned collection should be used from now on (only important with 2nd point)
2214
     */
2215 8
    private function fixPersistentCollectionOwnership(PersistentCollectionInterface $coll, object $document, ClassMetadata $class, string $propName) : PersistentCollectionInterface
2216
    {
2217 8
        $owner = $coll->getOwner();
2218 8
        if ($owner === null) { // cloned
2219 6
            $coll->setOwner($document, $class->fieldMappings[$propName]);
2220 2
        } elseif ($owner !== $document) { // no clone, we have to fix
2221 2
            if (! $coll->isInitialized()) {
2222 1
                $coll->initialize(); // we have to do this otherwise the cols share state
2223
            }
2224 2
            $newValue = clone $coll;
2225 2
            $newValue->setOwner($document, $class->fieldMappings[$propName]);
2226 2
            $class->reflFields[$propName]->setValue($document, $newValue);
2227 2
            if ($this->isScheduledForUpdate($document)) {
2228
                // @todo following line should be superfluous once collections are stored in change sets
2229
                $this->setOriginalDocumentProperty(spl_object_hash($document), $propName, $newValue);
2230
            }
2231 2
            return $newValue;
2232
        }
2233 6
        return $coll;
2234
    }
2235
2236
    /**
2237
     * INTERNAL:
2238
     * Schedules a complete collection for removal when this UnitOfWork commits.
2239
     */
2240 43
    public function scheduleCollectionDeletion(PersistentCollectionInterface $coll) : void
2241
    {
2242 43
        $oid = spl_object_hash($coll);
2243 43
        unset($this->collectionUpdates[$oid]);
2244 43
        if (isset($this->collectionDeletions[$oid])) {
2245
            return;
2246
        }
2247
2248 43
        $this->collectionDeletions[$oid] = $coll;
2249 43
        $this->scheduleCollectionOwner($coll);
2250 43
    }
2251
2252
    /**
2253
     * Checks whether a PersistentCollection is scheduled for deletion.
2254
     */
2255 213
    public function isCollectionScheduledForDeletion(PersistentCollectionInterface $coll) : bool
2256
    {
2257 213
        return isset($this->collectionDeletions[spl_object_hash($coll)]);
2258
    }
2259
2260
    /**
2261
     * INTERNAL:
2262
     * Unschedules a collection from being deleted when this UnitOfWork commits.
2263
     */
2264 225
    public function unscheduleCollectionDeletion(PersistentCollectionInterface $coll) : void
2265
    {
2266 225
        $oid = spl_object_hash($coll);
2267 225
        if (! isset($this->collectionDeletions[$oid])) {
2268 225
            return;
2269
        }
2270
2271 12
        $topmostOwner = $this->getOwningDocument($coll->getOwner());
2272 12
        unset($this->collectionDeletions[$oid]);
2273 12
        unset($this->hasScheduledCollections[spl_object_hash($topmostOwner)][$oid]);
2274 12
    }
2275
2276
    /**
2277
     * INTERNAL:
2278
     * Schedules a collection for update when this UnitOfWork commits.
2279
     */
2280 247
    public function scheduleCollectionUpdate(PersistentCollectionInterface $coll) : void
2281
    {
2282 247
        $mapping = $coll->getMapping();
2283 247
        if (CollectionHelper::usesSet($mapping['strategy'])) {
2284
            /* There is no need to $unset collection if it will be $set later
2285
             * This is NOP if collection is not scheduled for deletion
2286
             */
2287 40
            $this->unscheduleCollectionDeletion($coll);
2288
        }
2289 247
        $oid = spl_object_hash($coll);
2290 247
        if (isset($this->collectionUpdates[$oid])) {
2291 11
            return;
2292
        }
2293
2294 247
        $this->collectionUpdates[$oid] = $coll;
2295 247
        $this->scheduleCollectionOwner($coll);
2296 247
    }
2297
2298
    /**
2299
     * INTERNAL:
2300
     * Unschedules a collection from being updated when this UnitOfWork commits.
2301
     */
2302 225
    public function unscheduleCollectionUpdate(PersistentCollectionInterface $coll) : void
2303
    {
2304 225
        $oid = spl_object_hash($coll);
2305 225
        if (! isset($this->collectionUpdates[$oid])) {
2306 45
            return;
2307
        }
2308
2309 215
        $topmostOwner = $this->getOwningDocument($coll->getOwner());
2310 215
        unset($this->collectionUpdates[$oid]);
2311 215
        unset($this->hasScheduledCollections[spl_object_hash($topmostOwner)][$oid]);
2312 215
    }
2313
2314
    /**
2315
     * Checks whether a PersistentCollection is scheduled for update.
2316
     */
2317 133
    public function isCollectionScheduledForUpdate(PersistentCollectionInterface $coll) : bool
2318
    {
2319 133
        return isset($this->collectionUpdates[spl_object_hash($coll)]);
2320
    }
2321
2322
    /**
2323
     * INTERNAL:
2324
     * Gets PersistentCollections that have been visited during computing change
2325
     * set of $document
2326
     *
2327
     * @return PersistentCollectionInterface[]
2328
     */
2329 582
    public function getVisitedCollections(object $document) : array
2330
    {
2331 582
        $oid = spl_object_hash($document);
2332
2333 582
        return $this->visitedCollections[$oid] ?? [];
2334
    }
2335
2336
    /**
2337
     * INTERNAL:
2338
     * Gets PersistentCollections that are scheduled to update and related to $document
2339
     *
2340
     * @return PersistentCollectionInterface[]
2341
     */
2342 582
    public function getScheduledCollections(object $document) : array
2343
    {
2344 582
        $oid = spl_object_hash($document);
2345
2346 582
        return $this->hasScheduledCollections[$oid] ?? [];
2347
    }
2348
2349
    /**
2350
     * Checks whether the document is related to a PersistentCollection
2351
     * scheduled for update or deletion.
2352
     */
2353 51
    public function hasScheduledCollections(object $document) : bool
2354
    {
2355 51
        return isset($this->hasScheduledCollections[spl_object_hash($document)]);
2356
    }
2357
2358
    /**
2359
     * Marks the PersistentCollection's top-level owner as having a relation to
2360
     * a collection scheduled for update or deletion.
2361
     *
2362
     * If the owner is not scheduled for any lifecycle action, it will be
2363
     * scheduled for update to ensure that versioning takes place if necessary.
2364
     *
2365
     * If the collection is nested within atomic collection, it is immediately
2366
     * unscheduled and atomic one is scheduled for update instead. This makes
2367
     * calculating update data way easier.
2368
     */
2369 249
    private function scheduleCollectionOwner(PersistentCollectionInterface $coll) : void
2370
    {
2371 249
        $document                                                                          = $this->getOwningDocument($coll->getOwner());
2372 249
        $this->hasScheduledCollections[spl_object_hash($document)][spl_object_hash($coll)] = $coll;
2373
2374 249
        if ($document !== $coll->getOwner()) {
2375 25
            $parent  = $coll->getOwner();
2376 25
            $mapping = [];
2377 25
            while (($parentAssoc = $this->getParentAssociation($parent)) !== null) {
2378 25
                [$mapping, $parent ] = $parentAssoc;
2379
            }
2380 25
            if (CollectionHelper::isAtomic($mapping['strategy'])) {
2381 8
                $class            = $this->dm->getClassMetadata(get_class($document));
2382 8
                $atomicCollection = $class->getFieldValue($document, $mapping['fieldName']);
2383 8
                $this->scheduleCollectionUpdate($atomicCollection);
2384 8
                $this->unscheduleCollectionDeletion($coll);
2385 8
                $this->unscheduleCollectionUpdate($coll);
2386
            }
2387
        }
2388
2389 249
        if ($this->isDocumentScheduled($document)) {
2390 244
            return;
2391
        }
2392
2393 50
        $this->scheduleForUpdate($document);
2394 50
    }
2395
2396
    /**
2397
     * Get the top-most owning document of a given document
2398
     *
2399
     * If a top-level document is provided, that same document will be returned.
2400
     * For an embedded document, we will walk through parent associations until
2401
     * we find a top-level document.
2402
     *
2403
     * @throws UnexpectedValueException When a top-level document could not be found.
2404
     */
2405 251
    public function getOwningDocument(object $document) : object
2406
    {
2407 251
        $class = $this->dm->getClassMetadata(get_class($document));
2408 251
        while ($class->isEmbeddedDocument) {
0 ignored issues
show
Bug introduced by
Accessing isEmbeddedDocument on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
2409 40
            $parentAssociation = $this->getParentAssociation($document);
2410
2411 40
            if (! $parentAssociation) {
2412
                throw new UnexpectedValueException('Could not determine parent association for ' . get_class($document));
2413
            }
2414
2415 40
            [, $document ] = $parentAssociation;
2416 40
            $class         = $this->dm->getClassMetadata(get_class($document));
2417
        }
2418
2419 251
        return $document;
2420
    }
2421
2422
    /**
2423
     * Gets the class name for an association (embed or reference) with respect
2424
     * to any discriminator value.
2425
     *
2426
     * @param array|object|null $data
2427
     */
2428 239
    public function getClassNameForAssociation(array $mapping, $data) : string
2429
    {
2430 239
        $discriminatorField = $mapping['discriminatorField'] ?? null;
2431
2432 239
        $discriminatorValue = null;
2433 239
        if (isset($discriminatorField, $data[$discriminatorField])) {
2434 21
            $discriminatorValue = $data[$discriminatorField];
2435 219
        } elseif (isset($mapping['defaultDiscriminatorValue'])) {
2436
            $discriminatorValue = $mapping['defaultDiscriminatorValue'];
2437
        }
2438
2439 239
        if ($discriminatorValue !== null) {
2440 21
            return $mapping['discriminatorMap'][$discriminatorValue]
2441 21
                ?? $discriminatorValue;
2442
        }
2443
2444 219
        $class = $this->dm->getClassMetadata($mapping['targetDocument']);
2445
2446 219
        if (isset($class->discriminatorField, $data[$class->discriminatorField])) {
0 ignored issues
show
Bug introduced by
Accessing discriminatorField 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...
2447 15
            $discriminatorValue = $data[$class->discriminatorField];
0 ignored issues
show
Bug introduced by
Accessing discriminatorField 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...
2448 205
        } elseif ($class->defaultDiscriminatorValue !== null) {
0 ignored issues
show
Bug introduced by
Accessing defaultDiscriminatorValue 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...
2449 1
            $discriminatorValue = $class->defaultDiscriminatorValue;
0 ignored issues
show
Bug introduced by
Accessing defaultDiscriminatorValue 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...
2450
        }
2451
2452 219
        if ($discriminatorValue !== null) {
2453 16
            return $class->discriminatorMap[$discriminatorValue] ?? $discriminatorValue;
0 ignored issues
show
Bug introduced by
Accessing discriminatorMap 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...
2454
        }
2455
2456 204
        return $mapping['targetDocument'];
2457
    }
2458
2459
    /**
2460
     * INTERNAL:
2461
     * Creates a document. Used for reconstitution of documents during hydration.
2462
     *
2463
     * @internal Highly performance-sensitive method.
2464
     *
2465
     * @ignore
2466
     */
2467 408
    public function getOrCreateDocument(string $className, array $data, array &$hints = [], ?object $document = null) : object
2468
    {
2469 408
        $class = $this->dm->getClassMetadata($className);
2470
2471
        // @TODO figure out how to remove this
2472 408
        $discriminatorValue = null;
2473 408
        if (isset($class->discriminatorField, $data[$class->discriminatorField])) {
0 ignored issues
show
Bug introduced by
Accessing discriminatorField 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...
2474 17
            $discriminatorValue = $data[$class->discriminatorField];
0 ignored issues
show
Bug introduced by
Accessing discriminatorField 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...
2475 400
        } elseif (isset($class->defaultDiscriminatorValue)) {
0 ignored issues
show
Bug introduced by
Accessing defaultDiscriminatorValue 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...
2476 2
            $discriminatorValue = $class->defaultDiscriminatorValue;
0 ignored issues
show
Bug introduced by
Accessing defaultDiscriminatorValue 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...
2477
        }
2478
2479 408
        if ($discriminatorValue !== null) {
2480 18
            $className =  $class->discriminatorMap[$discriminatorValue] ?? $discriminatorValue;
0 ignored issues
show
Bug introduced by
Accessing discriminatorMap 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...
2481
2482 18
            $class = $this->dm->getClassMetadata($className);
2483
2484 18
            unset($data[$class->discriminatorField]);
2485
        }
2486
2487 408
        if (! empty($hints[Query::HINT_READ_ONLY])) {
2488 2
            $document = $class->newInstance();
2489 2
            $this->hydratorFactory->hydrate($document, $data, $hints);
2490 2
            return $document;
2491
        }
2492
2493 407
        $isManagedObject = false;
2494 407
        $serializedId    = null;
2495 407
        $id              = null;
2496 407
        if (! $class->isQueryResultDocument) {
0 ignored issues
show
Bug introduced by
Accessing isQueryResultDocument 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...
2497 404
            $id              = $class->getDatabaseIdentifierValue($data['_id']);
2498 404
            $serializedId    = serialize($id);
2499 404
            $isManagedObject = isset($this->identityMap[$class->name][$serializedId]);
0 ignored issues
show
Bug introduced by
Accessing name 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...
2500
        }
2501
2502 407
        $oid = null;
2503 407
        if ($isManagedObject) {
2504 98
            $document = $this->identityMap[$class->name][$serializedId];
0 ignored issues
show
Bug introduced by
Accessing name 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...
2505 98
            $oid      = spl_object_hash($document);
2506 98
            if ($document instanceof GhostObjectInterface && ! $document->isProxyInitialized()) {
2507 16
                $document->setProxyInitializer(null);
2508 16
                $overrideLocalValues = true;
2509 16
                if ($document instanceof NotifyPropertyChanged) {
2510 16
                    $document->addPropertyChangedListener($this);
2511
                }
2512
            } else {
2513 88
                $overrideLocalValues = ! empty($hints[Query::HINT_REFRESH]);
2514
            }
2515 98
            if ($overrideLocalValues) {
2516 45
                $data                             = $this->hydratorFactory->hydrate($document, $data, $hints);
2517 98
                $this->originalDocumentData[$oid] = $data;
2518
            }
2519
        } else {
2520 364
            if ($document === null) {
2521 364
                $document = $class->newInstance();
2522
            }
2523
2524 364
            if (! $class->isQueryResultDocument) {
0 ignored issues
show
Bug introduced by
Accessing isQueryResultDocument 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...
2525 360
                $this->registerManaged($document, $id, $data);
2526 360
                $oid                                            = spl_object_hash($document);
2527 360
                $this->documentStates[$oid]                     = self::STATE_MANAGED;
2528 360
                $this->identityMap[$class->name][$serializedId] = $document;
0 ignored issues
show
Bug introduced by
Accessing name 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...
2529
            }
2530
2531 364
            $data = $this->hydratorFactory->hydrate($document, $data, $hints);
2532
2533 364
            if (! $class->isQueryResultDocument) {
0 ignored issues
show
Bug introduced by
Accessing isQueryResultDocument 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...
2534 360
                $this->originalDocumentData[$oid] = $data;
2535
            }
2536
        }
2537
2538 407
        return $document;
2539
    }
2540
2541
    /**
2542
     * Initializes (loads) an uninitialized persistent collection of a document.
2543
     */
2544 184
    public function loadCollection(PersistentCollectionInterface $collection) : void
2545
    {
2546 184
        $this->getDocumentPersister(get_class($collection->getOwner()))->loadCollection($collection);
2547 184
        $this->lifecycleEventManager->postCollectionLoad($collection);
2548 184
    }
2549
2550
    /**
2551
     * Gets the identity map of the UnitOfWork.
2552
     */
2553
    public function getIdentityMap() : array
2554
    {
2555
        return $this->identityMap;
2556
    }
2557
2558
    /**
2559
     * Gets the original data of a document. The original data is the data that was
2560
     * present at the time the document was reconstituted from the database.
2561
     *
2562
     * @return array
2563
     */
2564 1
    public function getOriginalDocumentData(object $document) : array
2565
    {
2566 1
        $oid = spl_object_hash($document);
2567
2568 1
        return $this->originalDocumentData[$oid] ?? [];
2569
    }
2570
2571 63
    public function setOriginalDocumentData(object $document, array $data) : void
2572
    {
2573 63
        $oid                              = spl_object_hash($document);
2574 63
        $this->originalDocumentData[$oid] = $data;
2575 63
        unset($this->documentChangeSets[$oid]);
2576 63
    }
2577
2578
    /**
2579
     * INTERNAL:
2580
     * Sets a property value of the original data array of a document.
2581
     *
2582
     * @param mixed $value
2583
     *
2584
     * @ignore
2585
     */
2586 3
    public function setOriginalDocumentProperty(string $oid, string $property, $value) : void
2587
    {
2588 3
        $this->originalDocumentData[$oid][$property] = $value;
2589 3
    }
2590
2591
    /**
2592
     * Gets the identifier of a document.
2593
     *
2594
     * @return mixed The identifier value
2595
     */
2596 443
    public function getDocumentIdentifier(object $document)
2597
    {
2598 443
        return $this->documentIdentifiers[spl_object_hash($document)] ?? null;
2599
    }
2600
2601
    /**
2602
     * Checks whether the UnitOfWork has any pending insertions.
2603
     *
2604
     * @return bool TRUE if this UnitOfWork has pending insertions, FALSE otherwise.
2605
     */
2606
    public function hasPendingInsertions() : bool
2607
    {
2608
        return ! empty($this->documentInsertions);
2609
    }
2610
2611
    /**
2612
     * Calculates the size of the UnitOfWork. The size of the UnitOfWork is the
2613
     * number of documents in the identity map.
2614
     */
2615 2
    public function size() : int
2616
    {
2617 2
        $count = 0;
2618 2
        foreach ($this->identityMap as $documentSet) {
2619 2
            $count += count($documentSet);
2620
        }
2621 2
        return $count;
2622
    }
2623
2624
    /**
2625
     * INTERNAL:
2626
     * Registers a document as managed.
2627
     *
2628
     * TODO: This method assumes that $id is a valid PHP identifier for the
2629
     * document class. If the class expects its database identifier to be an
2630
     * ObjectId, and an incompatible $id is registered (e.g. an integer), the
2631
     * document identifiers map will become inconsistent with the identity map.
2632
     * In the future, we may want to round-trip $id through a PHP and database
2633
     * conversion and throw an exception if it's inconsistent.
2634
     *
2635
     * @param mixed $id The identifier values.
2636
     */
2637 393
    public function registerManaged(object $document, $id, array $data) : void
2638
    {
2639 393
        $oid   = spl_object_hash($document);
2640 393
        $class = $this->dm->getClassMetadata(get_class($document));
2641
2642 393
        if (! $class->identifier || $id === 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...
2643 112
            $this->documentIdentifiers[$oid] = $oid;
2644
        } else {
2645 387
            $this->documentIdentifiers[$oid] = $class->getPHPIdentifierValue($id);
0 ignored issues
show
Bug introduced by
The method getPHPIdentifierValue() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. Did you maybe mean getIdentifierValues()?

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...
2646
        }
2647
2648 393
        $this->documentStates[$oid]       = self::STATE_MANAGED;
2649 393
        $this->originalDocumentData[$oid] = $data;
2650 393
        $this->addToIdentityMap($document);
2651 393
    }
2652
2653
    /**
2654
     * INTERNAL:
2655
     * Clears the property changeset of the document with the given OID.
2656
     */
2657
    public function clearDocumentChangeSet(string $oid)
2658
    {
2659
        $this->documentChangeSets[$oid] = [];
2660
    }
2661
2662
    /* PropertyChangedListener implementation */
2663
2664
    /**
2665
     * Notifies this UnitOfWork of a property change in a document.
2666
     *
2667
     * @param object $document     The document that owns the property.
2668
     * @param string $propertyName The name of the property that changed.
2669
     * @param mixed  $oldValue     The old value of the property.
2670
     * @param mixed  $newValue     The new value of the property.
2671
     */
2672 2
    public function propertyChanged($document, $propertyName, $oldValue, $newValue)
2673
    {
2674 2
        $oid   = spl_object_hash($document);
2675 2
        $class = $this->dm->getClassMetadata(get_class($document));
2676
2677 2
        if (! isset($class->fieldMappings[$propertyName])) {
0 ignored issues
show
Bug introduced by
Accessing fieldMappings 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...
2678 1
            return; // ignore non-persistent fields
2679
        }
2680
2681
        // Update changeset and mark document for synchronization
2682 2
        $this->documentChangeSets[$oid][$propertyName] = [$oldValue, $newValue];
2683 2
        if (isset($this->scheduledForDirtyCheck[$class->name][$oid])) {
0 ignored issues
show
Bug introduced by
Accessing name 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...
2684
            return;
2685
        }
2686
2687 2
        $this->scheduleForDirtyCheck($document);
2688 2
    }
2689
2690
    /**
2691
     * Gets the currently scheduled document insertions in this UnitOfWork.
2692
     */
2693 3
    public function getScheduledDocumentInsertions() : array
2694
    {
2695 3
        return $this->documentInsertions;
2696
    }
2697
2698
    /**
2699
     * Gets the currently scheduled document upserts in this UnitOfWork.
2700
     */
2701 1
    public function getScheduledDocumentUpserts() : array
2702
    {
2703 1
        return $this->documentUpserts;
2704
    }
2705
2706
    /**
2707
     * Gets the currently scheduled document updates in this UnitOfWork.
2708
     */
2709 2
    public function getScheduledDocumentUpdates() : array
2710
    {
2711 2
        return $this->documentUpdates;
2712
    }
2713
2714
    /**
2715
     * Gets the currently scheduled document deletions in this UnitOfWork.
2716
     */
2717
    public function getScheduledDocumentDeletions() : array
2718
    {
2719
        return $this->documentDeletions;
2720
    }
2721
2722
    /**
2723
     * Get the currently scheduled complete collection deletions
2724
     */
2725
    public function getScheduledCollectionDeletions() : array
2726
    {
2727
        return $this->collectionDeletions;
2728
    }
2729
2730
    /**
2731
     * Gets the currently scheduled collection inserts, updates and deletes.
2732
     */
2733
    public function getScheduledCollectionUpdates() : array
2734
    {
2735
        return $this->collectionUpdates;
2736
    }
2737
2738
    /**
2739
     * Helper method to initialize a lazy loading proxy or persistent collection.
2740
     */
2741
    public function initializeObject(object $obj) : void
2742
    {
2743
        if ($obj instanceof GhostObjectInterface) {
2744
            $obj->initializeProxy();
2745
        } elseif ($obj instanceof PersistentCollectionInterface) {
2746
            $obj->initialize();
2747
        }
2748
    }
2749
2750
    private function objToStr(object $obj) : string
2751
    {
2752
        return method_exists($obj, '__toString') ? (string) $obj : get_class($obj) . '@' . spl_object_hash($obj);
2753
    }
2754
}
2755