Completed
Pull Request — master (#1875)
by Andreas
16:37
created

DocumentManager   F

Complexity

Total Complexity 79

Size/Duplication

Total Lines 762
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 17

Test Coverage

Coverage 93.12%

Importance

Changes 0
Metric Value
wmc 79
lcom 3
cbo 17
dl 0
loc 762
ccs 203
cts 218
cp 0.9312
rs 1.918
c 0
b 0
f 0

39 Methods

Rating   Name   Duplication   Size   Complexity  
A getClient() 0 4 1
A getMetadataFactory() 0 4 1
A initializeObject() 0 4 1
A persist() 0 8 2
A __construct() 0 34 5
A getProxyFactory() 0 4 1
A create() 0 4 1
A getEventManager() 0 4 1
A getUnitOfWork() 0 4 1
A getHydratorFactory() 0 4 1
A getSchemaManager() 0 4 1
A getClassMetadata() 0 4 1
A getDocumentDatabase() 0 16 4
A getDocumentDatabases() 0 4 1
A getDocumentCollection() 0 29 5
A getDocumentBucket() 0 29 5
A getDocumentCollections() 0 4 1
A createQueryBuilder() 0 4 1
A createAggregationBuilder() 0 4 1
A remove() 0 8 2
A refresh() 0 8 2
A detach() 0 7 2
A merge() 0 8 2
A lock() 0 4 1
A unlock() 0 4 1
A getRepository() 0 4 1
A flush() 0 5 1
A getReference() 0 16 2
A getPartialReference() 0 15 2
A find() 0 4 1
A clear() 0 4 1
A close() 0 5 1
A contains() 0 9 4
A getConfiguration() 0 4 1
C createReference() 0 77 11
A errorIfClosed() 0 6 2
A isOpen() 0 4 1
A getFilterCollection() 0 8 2
A checkTypeMap() 0 10 4

How to fix   Complexity   

Complex Class

Complex classes like DocumentManager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DocumentManager, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ODM\MongoDB;
6
7
use Doctrine\Common\EventManager;
8
use Doctrine\Common\Persistence\ObjectManager;
9
use Doctrine\Common\Persistence\ObjectRepository;
10
use Doctrine\ODM\MongoDB\Hydrator\HydratorFactory;
11
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
12
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactory;
13
use Doctrine\ODM\MongoDB\Mapping\MappingException;
14
use Doctrine\ODM\MongoDB\Proxy\ClassNameResolver;
15
use Doctrine\ODM\MongoDB\Proxy\Factory\ProxyFactory;
16
use Doctrine\ODM\MongoDB\Proxy\Factory\StaticProxyFactory;
17
use Doctrine\ODM\MongoDB\Query\FilterCollection;
18
use Doctrine\ODM\MongoDB\Repository\RepositoryFactory;
19
use InvalidArgumentException;
20
use MongoDB\Client;
21
use MongoDB\Collection;
22
use MongoDB\Database;
23
use MongoDB\Driver\ReadPreference;
24
use MongoDB\GridFS\Bucket;
25
use RuntimeException;
26
use function array_search;
27
use function get_class;
28
use function gettype;
29
use function is_object;
30
use function ltrim;
31
use function sprintf;
32
33
/**
34
 * The DocumentManager class is the central access point for managing the
35
 * persistence of documents.
36
 *
37
 *     <?php
38
 *
39
 *     $config = new Configuration();
40
 *     $dm = DocumentManager::create(new Connection(), $config);
41
 */
42
class DocumentManager implements ObjectManager
43
{
44
    public const CLIENT_TYPEMAP = ['root' => 'array', 'document' => 'array'];
45
46
    /**
47
     * The Doctrine MongoDB connection instance.
48
     *
49
     * @var Client
50
     */
51
    private $client;
52
53
    /**
54
     * The used Configuration.
55
     *
56
     * @var Configuration
57
     */
58
    private $config;
59
60
    /**
61
     * The metadata factory, used to retrieve the ODM metadata of document classes.
62
     *
63
     * @var ClassMetadataFactory
64
     */
65
    private $metadataFactory;
66
67
    /**
68
     * The UnitOfWork used to coordinate object-level transactions.
69
     *
70
     * @var UnitOfWork
71
     */
72
    private $unitOfWork;
73
74
    /**
75
     * The event manager that is the central point of the event system.
76
     *
77
     * @var EventManager
78
     */
79
    private $eventManager;
80
81
    /**
82
     * The Hydrator factory instance.
83
     *
84
     * @var HydratorFactory
85
     */
86
    private $hydratorFactory;
87
88
    /**
89
     * The Proxy factory instance.
90
     *
91
     * @var ProxyFactory
92
     */
93
    private $proxyFactory;
94
95
    /**
96
     * The repository factory used to create dynamic repositories.
97
     *
98
     * @var RepositoryFactory
99
     */
100
    private $repositoryFactory;
101
102
    /**
103
     * SchemaManager instance
104
     *
105
     * @var SchemaManager
106
     */
107
    private $schemaManager;
108
109
    /**
110
     * Array of cached document database instances that are lazily loaded.
111
     *
112
     * @var Database[]
113
     */
114
    private $documentDatabases = [];
115
116
    /**
117
     * Array of cached document collection instances that are lazily loaded.
118
     *
119
     * @var Collection[]
120
     */
121
    private $documentCollections = [];
122
123
    /**
124
     * Array of cached document bucket instances that are lazily loaded.
125
     *
126
     * @var Bucket[]
127
     */
128
    private $documentBuckets = [];
129
130
    /**
131
     * Whether the DocumentManager is closed or not.
132
     *
133
     * @var bool
134
     */
135
    private $closed = false;
136
137
    /**
138
     * Collection of query filters.
139
     *
140
     * @var FilterCollection
141
     */
142
    private $filterCollection;
143
144
    /**
145
     * Creates a new Document that operates on the given Mongo connection
146
     * and uses the given Configuration.
147
     */
148 1634
    protected function __construct(?Client $client = null, ?Configuration $config = null, ?EventManager $eventManager = null)
149
    {
150 1634
        $this->config       = $config ?: new Configuration();
151 1634
        $this->eventManager = $eventManager ?: new EventManager();
152 1634
        $this->client       = $client ?: new Client('mongodb://127.0.0.1', [], ['typeMap' => self::CLIENT_TYPEMAP]);
153
154 1634
        $this->checkTypeMap();
155
156 1634
        $metadataFactoryClassName = $this->config->getClassMetadataFactoryName();
157 1634
        $this->metadataFactory    = new $metadataFactoryClassName();
158 1634
        $this->metadataFactory->setDocumentManager($this);
159 1634
        $this->metadataFactory->setConfiguration($this->config);
160
161 1634
        $cacheDriver = $this->config->getMetadataCacheImpl();
162 1634
        if ($cacheDriver) {
163
            $this->metadataFactory->setCacheDriver($cacheDriver);
164
        }
165
166 1634
        $hydratorDir           = $this->config->getHydratorDir();
167 1634
        $hydratorNs            = $this->config->getHydratorNamespace();
168 1634
        $this->hydratorFactory = new HydratorFactory(
169 1634
            $this,
170 1634
            $this->eventManager,
171 1634
            $hydratorDir,
172 1634
            $hydratorNs,
173 1634
            $this->config->getAutoGenerateHydratorClasses()
174
        );
175
176 1634
        $this->unitOfWork = new UnitOfWork($this, $this->eventManager, $this->hydratorFactory);
177 1634
        $this->hydratorFactory->setUnitOfWork($this->unitOfWork);
178 1634
        $this->schemaManager     = new SchemaManager($this, $this->metadataFactory);
179 1634
        $this->proxyFactory      = new StaticProxyFactory($this);
180 1634
        $this->repositoryFactory = $this->config->getRepositoryFactory();
181 1634
    }
182
183
    /**
184
     * Gets the proxy factory used by the DocumentManager to create document proxies.
185
     */
186 1
    public function getProxyFactory() : ProxyFactory
187
    {
188 1
        return $this->proxyFactory;
189
    }
190
191
    /**
192
     * Creates a new Document that operates on the given Mongo connection
193
     * and uses the given Configuration.
194
     */
195 1634
    public static function create(?Client $client = null, ?Configuration $config = null, ?EventManager $eventManager = null) : DocumentManager
196
    {
197 1634
        return new static($client, $config, $eventManager);
198
    }
199
200
    /**
201
     * Gets the EventManager used by the DocumentManager.
202
     */
203 1697
    public function getEventManager() : EventManager
204
    {
205 1697
        return $this->eventManager;
206
    }
207
208
    /**
209
     * Gets the MongoDB client instance that this DocumentManager wraps.
210
     */
211 1634
    public function getClient() : Client
212
    {
213 1634
        return $this->client;
214
    }
215
216
    /**
217
     * Gets the metadata factory used to gather the metadata of classes.
218
     *
219
     * @return ClassMetadataFactory
220
     */
221 5
    public function getMetadataFactory()
222
    {
223 5
        return $this->metadataFactory;
224
    }
225
226
    /**
227
     * Helper method to initialize a lazy loading proxy or persistent collection.
228
     *
229
     * This method is a no-op for other objects.
230
     *
231
     * @param object $obj
232
     */
233
    public function initializeObject($obj)
234
    {
235
        $this->unitOfWork->initializeObject($obj);
236
    }
237
238
    /**
239
     * Gets the UnitOfWork used by the DocumentManager to coordinate operations.
240
     */
241 1641
    public function getUnitOfWork() : UnitOfWork
242
    {
243 1641
        return $this->unitOfWork;
244
    }
245
246
    /**
247
     * Gets the Hydrator factory used by the DocumentManager to generate and get hydrators
248
     * for each type of document.
249
     */
250 70
    public function getHydratorFactory() : HydratorFactory
251
    {
252 70
        return $this->hydratorFactory;
253
    }
254
255
    /**
256
     * Returns SchemaManager, used to create/drop indexes/collections/databases.
257
     */
258 28
    public function getSchemaManager() : SchemaManager
259
    {
260 28
        return $this->schemaManager;
261
    }
262
263
    /**
264
     * Returns the metadata for a class.
265
     *
266
     * @internal Performance-sensitive method.
267
     *
268
     * @param string $className The class name.
269
     */
270 1370
    public function getClassMetadata($className) : ClassMetadata
271
    {
272 1370
        return $this->metadataFactory->getMetadataFor(ClassNameResolver::getRealClass($className));
273
    }
274
275
    /**
276
     * Returns the MongoDB instance for a class.
277
     */
278 1299
    public function getDocumentDatabase(string $className) : Database
279
    {
280 1299
        $className = ClassNameResolver::getRealClass($className);
281
282 1299
        if (isset($this->documentDatabases[$className])) {
283 46
            return $this->documentDatabases[$className];
284
        }
285
286 1294
        $metadata                            = $this->metadataFactory->getMetadataFor($className);
287 1294
        $db                                  = $metadata->getDatabase();
288 1294
        $db                                  = $db ?: $this->config->getDefaultDB();
289 1294
        $db                                  = $db ?: 'doctrine';
290 1294
        $this->documentDatabases[$className] = $this->client->selectDatabase($db);
291
292 1294
        return $this->documentDatabases[$className];
293
    }
294
295
    /**
296
     * Gets the array of instantiated document database instances.
297
     *
298
     * @return Database[]
299
     */
300
    public function getDocumentDatabases() : array
301
    {
302
        return $this->documentDatabases;
303
    }
304
305
    /**
306
     * Returns the collection instance for a class.
307
     *
308
     * @throws MongoDBException When the $className param is not mapped to a collection.
309
     */
310 1302
    public function getDocumentCollection(string $className) : Collection
311
    {
312 1302
        $className = ClassNameResolver::getRealClass($className);
313
314
        /** @var ClassMetadata $metadata */
315 1302
        $metadata = $this->metadataFactory->getMetadataFor($className);
316 1302
        if ($metadata->isFile) {
317 16
            return $this->getDocumentBucket($className)->getFilesCollection();
318
        }
319
320 1291
        $collectionName = $metadata->getCollection();
321
322 1291
        if (! $collectionName) {
323
            throw MongoDBException::documentNotMappedToCollection($className);
324
        }
325
326 1291
        if (! isset($this->documentCollections[$className])) {
327 1281
            $db = $this->getDocumentDatabase($className);
328
329 1281
            $options = [];
330 1281
            if ($metadata->readPreference !== null) {
331 3
                $options['readPreference'] = new ReadPreference($metadata->readPreference, $metadata->readPreferenceTags);
332
            }
333
334 1281
            $this->documentCollections[$className] = $db->selectCollection($collectionName, $options);
335
        }
336
337 1291
        return $this->documentCollections[$className];
338
    }
339
340
    /**
341
     * Returns the bucket instance for a class.
342
     *
343
     * @throws MongoDBException When the $className param is not mapped to a collection.
344
     */
345 16
    public function getDocumentBucket(string $className) : Bucket
346
    {
347 16
        $className = ClassNameResolver::getRealClass($className);
348
349
        /** @var ClassMetadata $metadata */
350 16
        $metadata = $this->metadataFactory->getMetadataFor($className);
351 16
        if (! $metadata->isFile) {
352
            throw MongoDBException::documentBucketOnlyAvailableForGridFSFiles($className);
353
        }
354
355 16
        $bucketName = $metadata->getBucketName();
356
357 16
        if (! $bucketName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $bucketName 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...
358
            throw MongoDBException::documentNotMappedToCollection($className);
359
        }
360
361 16
        if (! isset($this->documentBuckets[$className])) {
362 11
            $db = $this->getDocumentDatabase($className);
363
364 11
            $options = ['bucketName' => $bucketName];
365 11
            if ($metadata->readPreference !== null) {
366
                $options['readPreference'] = new ReadPreference($metadata->readPreference, $metadata->readPreferenceTags);
367
            }
368
369 11
            $this->documentBuckets[$className] = $db->selectGridFSBucket($options);
370
        }
371
372 16
        return $this->documentBuckets[$className];
373
    }
374
375
    /**
376
     * Gets the array of instantiated document collection instances.
377
     *
378
     * @return Collection[]
379
     */
380
    public function getDocumentCollections() : array
381
    {
382
        return $this->documentCollections;
383
    }
384
385
    /**
386
     * Create a new Query instance for a class.
387
     *
388
     * @param string[]|string|null $documentName (optional) an array of document names, the document name, or none
389
     */
390 182
    public function createQueryBuilder($documentName = null) : Query\Builder
391
    {
392 182
        return new Query\Builder($this, $documentName);
393
    }
394
395
    /**
396
     * Creates a new aggregation builder instance for a class.
397
     */
398 41
    public function createAggregationBuilder(string $documentName) : Aggregation\Builder
399
    {
400 41
        return new Aggregation\Builder($this, $documentName);
401
    }
402
403
    /**
404
     * Tells the DocumentManager to make an instance managed and persistent.
405
     *
406
     * The document will be entered into the database at or before transaction
407
     * commit or as a result of the flush operation.
408
     *
409
     * NOTE: The persist operation always considers documents that are not yet known to
410
     * this DocumentManager as NEW. Do not pass detached documents to the persist operation.
411
     *
412
     * @param object $document The instance to make managed and persistent.
413
     *
414
     * @throws InvalidArgumentException When the given $document param is not an object.
415
     */
416 614
    public function persist($document)
417
    {
418 614
        if (! is_object($document)) {
419 1
            throw new InvalidArgumentException(gettype($document));
420
        }
421 613
        $this->errorIfClosed();
422 612
        $this->unitOfWork->persist($document);
423 608
    }
424
425
    /**
426
     * Removes a document instance.
427
     *
428
     * A removed document will be removed from the database at or before transaction commit
429
     * or as a result of the flush operation.
430
     *
431
     * @param object $document The document instance to remove.
432
     *
433
     * @throws InvalidArgumentException When the $document param is not an object.
434
     */
435 27
    public function remove($document)
436
    {
437 27
        if (! is_object($document)) {
438 1
            throw new InvalidArgumentException(gettype($document));
439
        }
440 26
        $this->errorIfClosed();
441 25
        $this->unitOfWork->remove($document);
442 25
    }
443
444
    /**
445
     * Refreshes the persistent state of a document from the database,
446
     * overriding any local changes that have not yet been persisted.
447
     *
448
     * @param object $document The document to refresh.
449
     *
450
     * @throws InvalidArgumentException When the given $document param is not an object.
451
     */
452 26
    public function refresh($document)
453
    {
454 26
        if (! is_object($document)) {
455 1
            throw new InvalidArgumentException(gettype($document));
456
        }
457 25
        $this->errorIfClosed();
458 24
        $this->unitOfWork->refresh($document);
459 23
    }
460
461
    /**
462
     * Detaches a document from the DocumentManager, causing a managed document to
463
     * become detached.  Unflushed changes made to the document if any
464
     * (including removal of the document), will not be synchronized to the database.
465
     * Documents which previously referenced the detached document will continue to
466
     * reference it.
467
     *
468
     * @param object $document The document to detach.
469
     *
470
     * @throws InvalidArgumentException When the $document param is not an object.
471
     */
472 11
    public function detach($document)
473
    {
474 11
        if (! is_object($document)) {
475 1
            throw new InvalidArgumentException(gettype($document));
476
        }
477 10
        $this->unitOfWork->detach($document);
478 10
    }
479
480
    /**
481
     * Merges the state of a detached document into the persistence context
482
     * of this DocumentManager and returns the managed copy of the document.
483
     * The document passed to merge will not become associated/managed with this DocumentManager.
484
     *
485
     * @param object $document The detached document to merge into the persistence context.
486
     *
487
     * @return object The managed copy of the document.
488
     *
489
     * @throws LockException
490
     * @throws InvalidArgumentException If the $document param is not an object.
491
     */
492 13
    public function merge($document)
493
    {
494 13
        if (! is_object($document)) {
495 1
            throw new InvalidArgumentException(gettype($document));
496
        }
497 12
        $this->errorIfClosed();
498 11
        return $this->unitOfWork->merge($document);
499
    }
500
501
    /**
502
     * Acquire a lock on the given document.
503
     *
504
     * @throws InvalidArgumentException
505
     * @throws LockException
506
     */
507 8
    public function lock(object $document, int $lockMode, ?int $lockVersion = null) : void
508
    {
509 8
        $this->unitOfWork->lock($document, $lockMode, $lockVersion);
510 5
    }
511
512
    /**
513
     * Releases a lock on the given document.
514
     */
515 1
    public function unlock(object $document) : void
516
    {
517 1
        $this->unitOfWork->unlock($document);
518 1
    }
519
520
    /**
521
     * Gets the repository for a document class.
522
     *
523
     * @param string $documentName The name of the Document.
524
     *
525
     * @return ObjectRepository  The repository.
526
     */
527 357
    public function getRepository($documentName)
528
    {
529 357
        return $this->repositoryFactory->getRepository($this, $documentName);
530
    }
531
532
    /**
533
     * Flushes all changes to objects that have been queued up to now to the database.
534
     * This effectively synchronizes the in-memory state of managed objects with the
535
     * database.
536
     *
537
     * @param array $options Array of options to be used with batchInsert(), update() and remove()
538
     *
539
     * @throws MongoDBException
540
     */
541 586
    public function flush(array $options = [])
542
    {
543 586
        $this->errorIfClosed();
544 585
        $this->unitOfWork->commit($options);
545 582
    }
546
547
    /**
548
     * Gets a reference to the document identified by the given type and identifier
549
     * without actually loading it.
550
     *
551
     * If partial objects are allowed, this method will return a partial object that only
552
     * has its identifier populated. Otherwise a proxy is returned that automatically
553
     * loads itself on first access.
554
     *
555
     * @param string|object $identifier
556
     */
557 133
    public function getReference(string $documentName, $identifier) : object
558
    {
559
        /** @var ClassMetadata $class */
560 133
        $class    = $this->metadataFactory->getMetadataFor(ltrim($documentName, '\\'));
561 133
        $document = $this->unitOfWork->tryGetById($identifier, $class);
562
563
        // Check identity map first, if its already in there just return it.
564 133
        if ($document) {
565 56
            return $document;
566
        }
567
568 104
        $document = $this->proxyFactory->getProxy($class, $identifier);
569 104
        $this->unitOfWork->registerManaged($document, $identifier, []);
0 ignored issues
show
Documentation introduced by
$document 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...
570
571 104
        return $document;
572
    }
573
574
    /**
575
     * Gets a partial reference to the document identified by the given type and identifier
576
     * without actually loading it, if the document is not yet loaded.
577
     *
578
     * The returned reference may be a partial object if the document is not yet loaded/managed.
579
     * If it is a partial object it will not initialize the rest of the document state on access.
580
     * Thus you can only ever safely access the identifier of a document obtained through
581
     * this method.
582
     *
583
     * The use-cases for partial references involve maintaining bidirectional associations
584
     * without loading one side of the association or to update a document without loading it.
585
     * Note, however, that in the latter case the original (persistent) document data will
586
     * never be visible to the application (especially not event listeners) as it will
587
     * never be loaded in the first place.
588
     *
589
     * @param mixed $identifier The document identifier.
590
     */
591 1
    public function getPartialReference(string $documentName, $identifier) : object
592
    {
593 1
        $class    = $this->metadataFactory->getMetadataFor(ltrim($documentName, '\\'));
594 1
        $document = $this->unitOfWork->tryGetById($identifier, $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...
595
596
        // Check identity map first, if its already in there just return it.
597 1
        if ($document) {
598
            return $document;
599
        }
600 1
        $document = $class->newInstance();
601 1
        $class->setIdentifierValue($document, $identifier);
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...
602 1
        $this->unitOfWork->registerManaged($document, $identifier, []);
603
604 1
        return $document;
605
    }
606
607
    /**
608
     * Finds a Document by its identifier.
609
     *
610
     * This is just a convenient shortcut for getRepository($documentName)->find($id).
611
     *
612
     * @param string $documentName
613
     * @param mixed  $identifier
614
     * @param int    $lockMode
615
     * @param int    $lockVersion
616
     *
617
     * @return object $document
618
     */
619 186
    public function find($documentName, $identifier, $lockMode = LockMode::NONE, $lockVersion = null)
620
    {
621 186
        return $this->getRepository($documentName)->find($identifier, $lockMode, $lockVersion);
0 ignored issues
show
Unused Code introduced by
The call to ObjectRepository::find() has too many arguments starting with $lockMode.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
622
    }
623
624
    /**
625
     * Clears the DocumentManager.
626
     *
627
     * All documents that are currently managed by this DocumentManager become
628
     * detached.
629
     *
630
     * @param string|null $documentName if given, only documents of this type will get detached
631
     */
632 389
    public function clear($documentName = null)
633
    {
634 389
        $this->unitOfWork->clear($documentName);
635 389
    }
636
637
    /**
638
     * Closes the DocumentManager. All documents that are currently managed
639
     * by this DocumentManager become detached. The DocumentManager may no longer
640
     * be used after it is closed.
641
     */
642 6
    public function close()
643
    {
644 6
        $this->clear();
645 6
        $this->closed = true;
646 6
    }
647
648
    /**
649
     * Determines whether a document instance is managed in this DocumentManager.
650
     *
651
     * @param object $document
652
     *
653
     * @return bool TRUE if this DocumentManager currently manages the given document, FALSE otherwise.
654
     *
655
     * @throws InvalidArgumentException When the $document param is not an object.
656
     */
657 3
    public function contains($document)
658
    {
659 3
        if (! is_object($document)) {
660
            throw new InvalidArgumentException(gettype($document));
661
        }
662 3
        return $this->unitOfWork->isScheduledForInsert($document) ||
663 3
            $this->unitOfWork->isInIdentityMap($document) &&
664 3
            ! $this->unitOfWork->isScheduledForDelete($document);
665
    }
666
667
    /**
668
     * Gets the Configuration used by the DocumentManager.
669
     */
670 1697
    public function getConfiguration() : Configuration
671
    {
672 1697
        return $this->config;
673
    }
674
675
    /**
676
     * Returns a reference to the supplied document.
677
     *
678
     * @return mixed The reference for the document in question, according to the desired mapping
679
     *
680
     * @throws MappingException
681
     * @throws RuntimeException
682
     */
683 225
    public function createReference(object $document, array $referenceMapping)
684
    {
685 225
        $class = $this->getClassMetadata(get_class($document));
686 225
        $id    = $this->unitOfWork->getDocumentIdentifier($document);
687
688 225
        if ($id === null) {
689 1
            throw new RuntimeException(
690 1
                sprintf('Cannot create a DBRef for class %s without an identifier. Have you forgotten to persist/merge the document first?', $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...
691
            );
692
        }
693
694 224
        $storeAs   = $referenceMapping['storeAs'] ?? null;
695 224
        $reference = [];
0 ignored issues
show
Unused Code introduced by
$reference is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
696 224
        switch ($storeAs) {
697
            case ClassMetadata::REFERENCE_STORE_AS_ID:
698 46
                if ($class->inheritanceType === ClassMetadata::INHERITANCE_TYPE_SINGLE_COLLECTION) {
0 ignored issues
show
Bug introduced by
Accessing inheritanceType on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

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

Available Fixes

  1. Adding an additional type check:

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

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
699 1
                    throw MappingException::simpleReferenceMustNotTargetDiscriminatedDocument($referenceMapping['targetDocument']);
700
                }
701
702 45
                return $class->getDatabaseIdentifierValue($id);
703
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
704
705
            case ClassMetadata::REFERENCE_STORE_AS_REF:
706 20
                $reference = ['id' => $class->getDatabaseIdentifierValue($id)];
707 20
                break;
708
709
            case ClassMetadata::REFERENCE_STORE_AS_DB_REF:
710
                $reference = [
711 180
                    '$ref' => $class->getCollection(),
712 180
                    '$id'  => $class->getDatabaseIdentifierValue($id),
713
                ];
714 180
                break;
715
716
            case ClassMetadata::REFERENCE_STORE_AS_DB_REF_WITH_DB:
717
                $reference = [
718 17
                    '$ref' => $class->getCollection(),
719 17
                    '$id'  => $class->getDatabaseIdentifierValue($id),
720 17
                    '$db'  => $this->getDocumentDatabase($class->name)->getDatabaseName(),
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...
721
                ];
722 17
                break;
723
724
            default:
725
                throw new InvalidArgumentException(sprintf('Reference type %s is invalid.', $storeAs));
726
        }
727
728
        /* If the class has a discriminator (field and value), use it. A child
729
         * class that is not defined in the discriminator map may only have a
730
         * discriminator field and no value, so default to the full class name.
731
         */
732 202
        if (isset($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...
733 18
            $reference[$class->discriminatorField] = $class->discriminatorValue ?? $class->name;
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...
Bug introduced by
Accessing discriminatorValue 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 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...
734
        }
735
736
        /* Add a discriminator value if the referenced document is not mapped
737
         * explicitly to a targetDocument class.
738
         */
739 202
        if (! isset($referenceMapping['targetDocument'])) {
740 33
            $discriminatorField = $referenceMapping['discriminatorField'];
741 33
            $discriminatorValue = isset($referenceMapping['discriminatorMap'])
742 8
                ? array_search($class->name, $referenceMapping['discriminatorMap'])
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...
743 33
                : $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...
744
745
            /* If the discriminator value was not found in the map, use the full
746
             * class name. In the future, it may be preferable to throw an
747
             * exception here (perhaps based on some strictness option).
748
             *
749
             * @see PersistenceBuilder::prepareEmbeddedDocumentValue()
750
             */
751 33
            if ($discriminatorValue === false) {
752 3
                $discriminatorValue = $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...
753
            }
754
755 33
            $reference[$discriminatorField] = $discriminatorValue;
756
        }
757
758 202
        return $reference;
759
    }
760
761
    /**
762
     * Throws an exception if the DocumentManager is closed or currently not active.
763
     *
764
     * @throws MongoDBException If the DocumentManager is closed.
765
     */
766 619
    private function errorIfClosed() : void
767
    {
768 619
        if ($this->closed) {
769 5
            throw MongoDBException::documentManagerClosed();
770
        }
771 614
    }
772
773
    /**
774
     * Check if the Document manager is open or closed.
775
     */
776 1
    public function isOpen() : bool
777
    {
778 1
        return ! $this->closed;
779
    }
780
781
    /**
782
     * Gets the filter collection.
783
     */
784 536
    public function getFilterCollection() : FilterCollection
785
    {
786 536
        if ($this->filterCollection === null) {
787 536
            $this->filterCollection = new FilterCollection($this);
788
        }
789
790 536
        return $this->filterCollection;
791
    }
792
793 1634
    private function checkTypeMap() : void
794
    {
795 1634
        $typeMap = $this->client->getTypeMap();
796
797 1634
        foreach (self::CLIENT_TYPEMAP as $part => $expectedType) {
798 1634
            if (! isset($typeMap[$part]) || $typeMap[$part] !== $expectedType) {
799 1634
                throw MongoDBException::invalidTypeMap($part, $expectedType);
800
            }
801
        }
802 1634
    }
803
}
804