Completed
Pull Request — master (#1721)
by Maciej
20:59 queued 03:47
created

DocumentManager::createDBRef()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 10
ccs 0
cts 5
cp 0
rs 9.4285
cc 2
eloc 5
nc 2
nop 2
crap 6
1
<?php
2
3
namespace Doctrine\ODM\MongoDB;
4
5
use Doctrine\Common\EventManager;
6
use Doctrine\Common\Persistence\ObjectManager;
7
use Doctrine\Common\Persistence\ObjectRepository;
8
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo;
9
use Doctrine\ODM\MongoDB\Mapping\MappingException;
10
use Doctrine\ODM\MongoDB\Hydrator\HydratorFactory;
11
use Doctrine\ODM\MongoDB\Proxy\ProxyFactory;
12
use Doctrine\ODM\MongoDB\Query\FilterCollection;
13
use Doctrine\ODM\MongoDB\Repository\RepositoryFactory;
14
use MongoDB\Client;
15
use MongoDB\Collection;
16
use MongoDB\Database;
17
use MongoDB\Driver\ReadPreference;
18
19
/**
20
 * The DocumentManager class is the central access point for managing the
21
 * persistence of documents.
22
 *
23
 *     <?php
24
 *
25
 *     $config = new Configuration();
26
 *     $dm = DocumentManager::create(new Connection(), $config);
27
 *
28
 * @since       1.0
29
 */
30
class DocumentManager implements ObjectManager
31
{
32
    /**
33
     * The Doctrine MongoDB connection instance.
34
     *
35
     * @var Client
36
     */
37
    private $client;
38
39
    /**
40
     * The used Configuration.
41
     *
42
     * @var \Doctrine\ODM\MongoDB\Configuration
43
     */
44
    private $config;
45
46
    /**
47
     * The metadata factory, used to retrieve the ODM metadata of document classes.
48
     *
49
     * @var \Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactory
50
     */
51
    private $metadataFactory;
52
53
    /**
54
     * The UnitOfWork used to coordinate object-level transactions.
55
     *
56
     * @var UnitOfWork
57
     */
58
    private $unitOfWork;
59
60
    /**
61
     * The event manager that is the central point of the event system.
62
     *
63
     * @var \Doctrine\Common\EventManager
64
     */
65
    private $eventManager;
66
67
    /**
68
     * The Hydrator factory instance.
69
     *
70
     * @var HydratorFactory
71
     */
72
    private $hydratorFactory;
73
74
    /**
75
     * The Proxy factory instance.
76
     *
77
     * @var ProxyFactory
78
     */
79
    private $proxyFactory;
80
81
    /**
82
     * The repository factory used to create dynamic repositories.
83
     *
84
     * @var RepositoryFactory
85
     */
86
    private $repositoryFactory;
87
88
    /**
89
     * SchemaManager instance
90
     *
91
     * @var SchemaManager
92
     */
93
    private $schemaManager;
94
95
    /**
96
     * Array of cached document database instances that are lazily loaded.
97
     *
98
     * @var Database[]
99
     */
100
    private $documentDatabases = array();
101
102
    /**
103
     * Array of cached document collection instances that are lazily loaded.
104
     *
105
     * @var Collection[]
106
     */
107
    private $documentCollections = array();
108
109
    /**
110
     * Whether the DocumentManager is closed or not.
111
     *
112
     * @var bool
113
     */
114
    private $closed = false;
115
116
    /**
117
     * Collection of query filters.
118
     *
119
     * @var \Doctrine\ODM\MongoDB\Query\FilterCollection
120
     */
121
    private $filterCollection;
122
123
    /**
124
     * Creates a new Document that operates on the given Mongo connection
125
     * and uses the given Configuration.
126
     *
127
     * @param Client|null $client
128
     * @param Configuration|null $config
129
     * @param \Doctrine\Common\EventManager|null $eventManager
130
     */
131 1627
    protected function __construct(Client $client = null, Configuration $config = null, EventManager $eventManager = null)
132
    {
133 1627
        $this->config = $config ?: new Configuration();
134 1627
        $this->eventManager = $eventManager ?: new EventManager();
135 1627
        $this->client = $client ?: new Client('mongodb://127.0.0.1', [], ['typeMap' => ['root' => 'array', 'document' => 'array']]);
136
137 1627
        $metadataFactoryClassName = $this->config->getClassMetadataFactoryName();
138 1627
        $this->metadataFactory = new $metadataFactoryClassName();
139 1627
        $this->metadataFactory->setDocumentManager($this);
140 1627
        $this->metadataFactory->setConfiguration($this->config);
141 1627
        if ($cacheDriver = $this->config->getMetadataCacheImpl()) {
142
            $this->metadataFactory->setCacheDriver($cacheDriver);
143
        }
144
145 1627
        $hydratorDir = $this->config->getHydratorDir();
146 1627
        $hydratorNs = $this->config->getHydratorNamespace();
147 1627
        $this->hydratorFactory = new HydratorFactory(
148 1627
            $this,
149 1627
            $this->eventManager,
150 1627
            $hydratorDir,
151 1627
            $hydratorNs,
152 1627
            $this->config->getAutoGenerateHydratorClasses()
0 ignored issues
show
Bug introduced by
It seems like $this->config->getAutoGenerateHydratorClasses() targeting Doctrine\ODM\MongoDB\Con...nerateHydratorClasses() can also be of type boolean; however, Doctrine\ODM\MongoDB\Hyd...rFactory::__construct() does only seem to accept integer, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
153
        );
154
155 1627
        $this->unitOfWork = new UnitOfWork($this, $this->eventManager, $this->hydratorFactory);
156 1627
        $this->hydratorFactory->setUnitOfWork($this->unitOfWork);
157 1627
        $this->schemaManager = new SchemaManager($this, $this->metadataFactory);
158 1627
        $this->proxyFactory = new ProxyFactory($this,
159 1627
            $this->config->getProxyDir(),
160 1627
            $this->config->getProxyNamespace(),
161 1627
            $this->config->getAutoGenerateProxyClasses()
0 ignored issues
show
Bug introduced by
It seems like $this->config->getAutoGenerateProxyClasses() targeting Doctrine\ODM\MongoDB\Con...oGenerateProxyClasses() can also be of type boolean; however, Doctrine\ODM\MongoDB\Pro...yFactory::__construct() does only seem to accept integer, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
162
        );
163 1627
        $this->repositoryFactory = $this->config->getRepositoryFactory();
164 1627
    }
165
166
    /**
167
     * Gets the proxy factory used by the DocumentManager to create document proxies.
168
     *
169
     * @return ProxyFactory
170
     */
171 1
    public function getProxyFactory()
172
    {
173 1
        return $this->proxyFactory;
174
    }
175
176
    /**
177
     * Creates a new Document that operates on the given Mongo connection
178
     * and uses the given Configuration.
179
     *
180
     * @static
181
     * @param Client|null $client
182
     * @param Configuration|null $config
183
     * @param \Doctrine\Common\EventManager|null $eventManager
184
     * @return DocumentManager
185
     */
186 1627
    public static function create(Client $client = null, Configuration $config = null, EventManager $eventManager = null)
187
    {
188 1627
        return new static($client, $config, $eventManager);
189
    }
190
191
    /**
192
     * Gets the EventManager used by the DocumentManager.
193
     *
194
     * @return \Doctrine\Common\EventManager
195
     */
196 1670
    public function getEventManager()
197
    {
198 1670
        return $this->eventManager;
199
    }
200
201
    /**
202
     * Gets the MongoDB client instance that this DocumentManager wraps.
203
     *
204
     * @return Client
205
     */
206 1627
    public function getClient()
207
    {
208 1627
        return $this->client;
209
    }
210
211
    /**
212
     * Gets the metadata factory used to gather the metadata of classes.
213
     *
214
     * @return \Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactory
215
     */
216 1627
    public function getMetadataFactory()
217
    {
218 1627
        return $this->metadataFactory;
219
    }
220
221
    /**
222
     * Helper method to initialize a lazy loading proxy or persistent collection.
223
     *
224
     * This method is a no-op for other objects.
225
     *
226
     * @param object $obj
227
     */
228
    public function initializeObject($obj)
229
    {
230
        $this->unitOfWork->initializeObject($obj);
231
    }
232
233
    /**
234
     * Gets the UnitOfWork used by the DocumentManager to coordinate operations.
235
     *
236
     * @return UnitOfWork
237
     */
238 1633
    public function getUnitOfWork()
239
    {
240 1633
        return $this->unitOfWork;
241
    }
242
243
    /**
244
     * Gets the Hydrator factory used by the DocumentManager to generate and get hydrators
245
     * for each type of document.
246
     *
247
     * @return HydratorFactory
248
     */
249 66
    public function getHydratorFactory()
250
    {
251 66
        return $this->hydratorFactory;
252
    }
253
254
    /**
255
     * Returns SchemaManager, used to create/drop indexes/collections/databases.
256
     *
257
     * @return \Doctrine\ODM\MongoDB\SchemaManager
258
     */
259 18
    public function getSchemaManager()
260
    {
261 18
        return $this->schemaManager;
262
    }
263
264
    /**
265
     * Returns the metadata for a class.
266
     *
267
     * @param string $className The class name.
268
     * @return \Doctrine\ODM\MongoDB\Mapping\ClassMetadata
269
     * @internal Performance-sensitive method.
270
     */
271 1335
    public function getClassMetadata($className)
272
    {
273 1335
        return $this->metadataFactory->getMetadataFor(ltrim($className, '\\'));
274
    }
275
276
    /**
277
     * Returns the MongoDB instance for a class.
278
     *
279
     * @param string $className The class name.
280
     * @return Database
281
     */
282 1268
    public function getDocumentDatabase($className)
283
    {
284 1268
        $className = ltrim($className, '\\');
285
286 1268
        if (isset($this->documentDatabases[$className])) {
287 35
            return $this->documentDatabases[$className];
288
        }
289
290 1264
        $metadata = $this->metadataFactory->getMetadataFor($className);
291 1264
        $db = $metadata->getDatabase();
292 1264
        $db = $db ?: $this->config->getDefaultDB();
293 1264
        $db = $db ?: 'doctrine';
294 1264
        $this->documentDatabases[$className] = $this->client->selectDatabase($db);
295
296 1264
        return $this->documentDatabases[$className];
297
    }
298
299
    /**
300
     * Gets the array of instantiated document database instances.
301
     *
302
     * @return Database[]
303
     */
304
    public function getDocumentDatabases()
305
    {
306
        return $this->documentDatabases;
307
    }
308
309
    /**
310
     * Returns the MongoCollection instance for a class.
311
     *
312
     * @param string $className The class name.
313
     * @throws MongoDBException When the $className param is not mapped to a collection
314
     * @return Collection
315
     */
316 1270
    public function getDocumentCollection($className)
317
    {
318 1270
        $className = ltrim($className, '\\');
319
320 1270
        $metadata = $this->metadataFactory->getMetadataFor($className);
321 1270
        $collectionName = $metadata->getCollection();
322
323 1270
        if ( ! $collectionName) {
324
            throw MongoDBException::documentNotMappedToCollection($className);
325
        }
326
327 1270
        if ( ! isset($this->documentCollections[$className])) {
328 1260
            $db = $this->getDocumentDatabase($className);
329
330 1260
            $options = [];
331 1260
            if ($metadata->readPreference !== null) {
0 ignored issues
show
Bug introduced by
Accessing readPreference 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...
332 3
                $options['readPreference'] = new ReadPreference($metadata->readPreference, $metadata->readPreferenceTags);
0 ignored issues
show
Bug introduced by
Accessing readPreference 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 readPreferenceTags 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...
333
            }
334
335 1260
            $this->documentCollections[$className] = $db->selectCollection($collectionName, $options);
336
        }
337
338 1270
        return $this->documentCollections[$className];
339
    }
340
341
    /**
342
     * Gets the array of instantiated document collection instances.
343
     *
344
     * @return Collection[]
345
     */
346
    public function getDocumentCollections()
347
    {
348
        return $this->documentCollections;
349
    }
350
351
    /**
352
     * Create a new Query instance for a class.
353
     *
354
     * @param string $documentName The document class name.
355
     * @return Query\Builder
356
     */
357 178
    public function createQueryBuilder($documentName = null)
358
    {
359 178
        return new Query\Builder($this, $documentName);
360
    }
361
362
    /**
363
     * Creates a new aggregation builder instance for a class.
364
     *
365
     * @param string $documentName The document class name.
366
     * @return Aggregation\Builder
367
     */
368 41
    public function createAggregationBuilder($documentName)
369
    {
370 41
        return new Aggregation\Builder($this, $documentName);
371
    }
372
373
    /**
374
     * Tells the DocumentManager to make an instance managed and persistent.
375
     *
376
     * The document will be entered into the database at or before transaction
377
     * commit or as a result of the flush operation.
378
     *
379
     * NOTE: The persist operation always considers documents that are not yet known to
380
     * this DocumentManager as NEW. Do not pass detached documents to the persist operation.
381
     *
382
     * @param object $document The instance to make managed and persistent.
383
     * @throws \InvalidArgumentException When the given $document param is not an object
384
     */
385 589 View Code Duplication
    public function persist($document)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
386
    {
387 589
        if ( ! is_object($document)) {
388 1
            throw new \InvalidArgumentException(gettype($document));
389
        }
390 588
        $this->errorIfClosed();
391 587
        $this->unitOfWork->persist($document);
392 583
    }
393
394
    /**
395
     * Removes a document instance.
396
     *
397
     * A removed document will be removed from the database at or before transaction commit
398
     * or as a result of the flush operation.
399
     *
400
     * @param object $document The document instance to remove.
401
     * @throws \InvalidArgumentException when the $document param is not an object
402
     */
403 24 View Code Duplication
    public function remove($document)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
404
    {
405 24
        if ( ! is_object($document)) {
406 1
            throw new \InvalidArgumentException(gettype($document));
407
        }
408 23
        $this->errorIfClosed();
409 22
        $this->unitOfWork->remove($document);
410 22
    }
411
412
    /**
413
     * Refreshes the persistent state of a document from the database,
414
     * overriding any local changes that have not yet been persisted.
415
     *
416
     * @param object $document The document to refresh.
417
     * @throws \InvalidArgumentException When the given $document param is not an object
418
     */
419 23 View Code Duplication
    public function refresh($document)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
420
    {
421 23
        if ( ! is_object($document)) {
422 1
            throw new \InvalidArgumentException(gettype($document));
423
        }
424 22
        $this->errorIfClosed();
425 21
        $this->unitOfWork->refresh($document);
426 20
    }
427
428
    /**
429
     * Detaches a document from the DocumentManager, causing a managed document to
430
     * become detached.  Unflushed changes made to the document if any
431
     * (including removal of the document), will not be synchronized to the database.
432
     * Documents which previously referenced the detached document will continue to
433
     * reference it.
434
     *
435
     * @param object $document The document to detach.
436
     * @throws \InvalidArgumentException when the $document param is not an object
437
     */
438 11
    public function detach($document)
439
    {
440 11
        if ( ! is_object($document)) {
441 1
            throw new \InvalidArgumentException(gettype($document));
442
        }
443 10
        $this->unitOfWork->detach($document);
444 10
    }
445
446
    /**
447
     * Merges the state of a detached document into the persistence context
448
     * of this DocumentManager and returns the managed copy of the document.
449
     * The document passed to merge will not become associated/managed with this DocumentManager.
450
     *
451
     * @param object $document The detached document to merge into the persistence context.
452
     * @throws LockException
453
     * @throws \InvalidArgumentException if the $document param is not an object
454
     * @return object The managed copy of the document.
455
     */
456 14 View Code Duplication
    public function merge($document)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
457
    {
458 14
        if ( ! is_object($document)) {
459 1
            throw new \InvalidArgumentException(gettype($document));
460
        }
461 13
        $this->errorIfClosed();
462 12
        return $this->unitOfWork->merge($document);
463
    }
464
465
    /**
466
     * Acquire a lock on the given document.
467
     *
468
     * @param object $document
469
     * @param int $lockMode
470
     * @param int $lockVersion
471
     * @throws \InvalidArgumentException
472
     */
473 8
    public function lock($document, $lockMode, $lockVersion = null)
474
    {
475 8
        if ( ! is_object($document)) {
476
            throw new \InvalidArgumentException(gettype($document));
477
        }
478 8
        $this->unitOfWork->lock($document, $lockMode, $lockVersion);
479 5
    }
480
481
    /**
482
     * Releases a lock on the given document.
483
     *
484
     * @param object $document
485
     * @throws \InvalidArgumentException if the $document param is not an object
486
     */
487 1
    public function unlock($document)
488
    {
489 1
        if ( ! is_object($document)) {
490
            throw new \InvalidArgumentException(gettype($document));
491
        }
492 1
        $this->unitOfWork->unlock($document);
493 1
    }
494
495
    /**
496
     * Gets the repository for a document class.
497
     *
498
     * @param string $documentName  The name of the Document.
499
     * @return ObjectRepository  The repository.
500
     */
501 330
    public function getRepository($documentName)
502
    {
503 330
        return $this->repositoryFactory->getRepository($this, $documentName);
504
    }
505
506
    /**
507
     * Flushes all changes to objects that have been queued up to now to the database.
508
     * This effectively synchronizes the in-memory state of managed objects with the
509
     * database.
510
     *
511
     * @param object $document
512
     * @param array $options Array of options to be used with batchInsert(), update() and remove()
513
     * @throws \InvalidArgumentException
514
     */
515 561
    public function flush($document = null, array $options = array())
516
    {
517 561
        if (null !== $document && ! is_object($document) && ! is_array($document)) {
518
            throw new \InvalidArgumentException(gettype($document));
519
        }
520 561
        $this->errorIfClosed();
521 560
        $this->unitOfWork->commit($document, $options);
0 ignored issues
show
Bug introduced by
It seems like $document can also be of type array; however, Doctrine\ODM\MongoDB\UnitOfWork::commit() does only seem to accept object|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
522 556
    }
523
524
    /**
525
     * Gets a reference to the document identified by the given type and identifier
526
     * without actually loading it.
527
     *
528
     * If partial objects are allowed, this method will return a partial object that only
529
     * has its identifier populated. Otherwise a proxy is returned that automatically
530
     * loads itself on first access.
531
     *
532
     * @param string $documentName
533
     * @param string|object $identifier
534
     * @return mixed|object The document reference.
535
     */
536 127
    public function getReference($documentName, $identifier)
537
    {
538
        /* @var $class \Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo */
539 127
        $class = $this->metadataFactory->getMetadataFor(ltrim($documentName, '\\'));
540
541
        // Check identity map first, if its already in there just return it.
542 127
        if ($document = $this->unitOfWork->tryGetById($identifier, $class)) {
0 ignored issues
show
Compatibility introduced by
$class of type object<Doctrine\ODM\Mong...ping\ClassMetadataInfo> is not a sub-type of object<Doctrine\ODM\Mong...\Mapping\ClassMetadata>. It seems like you assume a child class of the class Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo 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...
543 55
            return $document;
544
        }
545
546 98
        $document = $this->proxyFactory->getProxy($class->name, array($class->identifier => $identifier));
547 98
        $this->unitOfWork->registerManaged($document, $identifier, array());
0 ignored issues
show
Documentation introduced by
$identifier is of type string|object, but the function expects a array.

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...
548
549 98
        return $document;
550
    }
551
552
    /**
553
     * Gets a partial reference to the document identified by the given type and identifier
554
     * without actually loading it, if the document is not yet loaded.
555
     *
556
     * The returned reference may be a partial object if the document is not yet loaded/managed.
557
     * If it is a partial object it will not initialize the rest of the document state on access.
558
     * Thus you can only ever safely access the identifier of a document obtained through
559
     * this method.
560
     *
561
     * The use-cases for partial references involve maintaining bidirectional associations
562
     * without loading one side of the association or to update a document without loading it.
563
     * Note, however, that in the latter case the original (persistent) document data will
564
     * never be visible to the application (especially not event listeners) as it will
565
     * never be loaded in the first place.
566
     *
567
     * @param string $documentName The name of the document type.
568
     * @param mixed $identifier The document identifier.
569
     * @return object The (partial) document reference.
570
     */
571 1
    public function getPartialReference($documentName, $identifier)
572
    {
573 1
        $class = $this->metadataFactory->getMetadataFor(ltrim($documentName, '\\'));
574
575
        // Check identity map first, if its already in there just return it.
576 1
        if ($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...
577
            return $document;
578
        }
579 1
        $document = $class->newInstance();
580 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...
581 1
        $this->unitOfWork->registerManaged($document, $identifier, array());
582
583 1
        return $document;
584
    }
585
586
    /**
587
     * Finds a Document by its identifier.
588
     *
589
     * This is just a convenient shortcut for getRepository($documentName)->find($id).
590
     *
591
     * @param string $documentName
592
     * @param mixed $identifier
593
     * @param int $lockMode
594
     * @param int $lockVersion
595
     * @return object $document
596
     */
597 184
    public function find($documentName, $identifier, $lockMode = LockMode::NONE, $lockVersion = null)
598
    {
599 184
        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...
600
    }
601
602
    /**
603
     * Clears the DocumentManager.
604
     *
605
     * All documents that are currently managed by this DocumentManager become
606
     * detached.
607
     *
608
     * @param string|null $documentName if given, only documents of this type will get detached
609
     */
610 376
    public function clear($documentName = null)
611
    {
612 376
        $this->unitOfWork->clear($documentName);
613 376
    }
614
615
    /**
616
     * Closes the DocumentManager. All documents that are currently managed
617
     * by this DocumentManager become detached. The DocumentManager may no longer
618
     * be used after it is closed.
619
     */
620 6
    public function close()
621
    {
622 6
        $this->clear();
623 6
        $this->closed = true;
624 6
    }
625
626
    /**
627
     * Determines whether a document instance is managed in this DocumentManager.
628
     *
629
     * @param object $document
630
     * @throws \InvalidArgumentException When the $document param is not an object
631
     * @return boolean TRUE if this DocumentManager currently manages the given document, FALSE otherwise.
632
     */
633 6
    public function contains($document)
634
    {
635 6
        if ( ! is_object($document)) {
636
            throw new \InvalidArgumentException(gettype($document));
637
        }
638 6
        return $this->unitOfWork->isScheduledForInsert($document) ||
639 6
            $this->unitOfWork->isInIdentityMap($document) &&
640 6
            ! $this->unitOfWork->isScheduledForDelete($document);
641
    }
642
643
    /**
644
     * Gets the Configuration used by the DocumentManager.
645
     *
646
     * @return Configuration
647
     */
648 727
    public function getConfiguration()
649
    {
650 727
        return $this->config;
651
    }
652
653
    /**
654
     * Returns a reference to the supplied document.
655
     *
656
     * @param object $document A document object
657
     * @param array $referenceMapping Mapping for the field that references the document
658
     *
659
     * @throws \InvalidArgumentException
660
     * @throws MappingException
661
     * @return mixed The reference for the document in question, according to the desired mapping
662
     */
663 222
    public function createReference($document, array $referenceMapping)
664
    {
665 222
        if ( ! is_object($document)) {
666
            throw new \InvalidArgumentException('Cannot create a DBRef, the document is not an object');
667
        }
668
669 222
        $class = $this->getClassMetadata(get_class($document));
670 222
        $id = $this->unitOfWork->getDocumentIdentifier($document);
671
672 222
        if ($id === null) {
673 1
            throw new \RuntimeException(
674 1
                sprintf('Cannot create a DBRef for class %s without an identifier. Have you forgotten to persist/merge the document first?', $class->name)
675
            );
676
        }
677
678 221
        $storeAs = $referenceMapping['storeAs'] ?? null;
679 221
        switch ($storeAs) {
680
            case ClassMetadataInfo::REFERENCE_STORE_AS_ID:
681 45
                if ($class->inheritanceType === ClassMetadataInfo::INHERITANCE_TYPE_SINGLE_COLLECTION) {
682 1
                    throw MappingException::simpleReferenceMustNotTargetDiscriminatedDocument($referenceMapping['targetDocument']);
683
                }
684
685 44
                return $class->getDatabaseIdentifierValue($id);
686
                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...
687
688
689
            case ClassMetadataInfo::REFERENCE_STORE_AS_REF:
690 19
                $reference = ['id' => $class->getDatabaseIdentifierValue($id)];
691 19
                break;
692
693
            case ClassMetadataInfo::REFERENCE_STORE_AS_DB_REF:
694
                $reference = [
695 178
                    '$ref' => $class->getCollection(),
696 178
                    '$id'  => $class->getDatabaseIdentifierValue($id),
697
                ];
698 178
                break;
699
700
            case ClassMetadataInfo::REFERENCE_STORE_AS_DB_REF_WITH_DB:
701
                $reference = [
702 17
                    '$ref' => $class->getCollection(),
703 17
                    '$id'  => $class->getDatabaseIdentifierValue($id),
704 17
                    '$db'  => $this->getDocumentDatabase($class->name)->getDatabaseName(),
705
                ];
706 17
                break;
707
708
            default:
709
                throw new \InvalidArgumentException("Reference type {$storeAs} is invalid.");
710
        }
711
712
        /* If the class has a discriminator (field and value), use it. A child
713
         * class that is not defined in the discriminator map may only have a
714
         * discriminator field and no value, so default to the full class name.
715
         */
716 199
        if (isset($class->discriminatorField)) {
717 18
            $reference[$class->discriminatorField] = $class->discriminatorValue ?? $class->name;
718
        }
719
720
        /* Add a discriminator value if the referenced document is not mapped
721
         * explicitly to a targetDocument class.
722
         */
723 199 View Code Duplication
        if (! isset($referenceMapping['targetDocument'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
724 33
            $discriminatorField = $referenceMapping['discriminatorField'];
725 33
            $discriminatorValue = isset($referenceMapping['discriminatorMap'])
726 8
                ? array_search($class->name, $referenceMapping['discriminatorMap'])
727 33
                : $class->name;
728
729
            /* If the discriminator value was not found in the map, use the full
730
             * class name. In the future, it may be preferable to throw an
731
             * exception here (perhaps based on some strictness option).
732
             *
733
             * @see PersistenceBuilder::prepareEmbeddedDocumentValue()
734
             */
735 33
            if ($discriminatorValue === false) {
736 2
                $discriminatorValue = $class->name;
737
            }
738
739 33
            $reference[$discriminatorField] = $discriminatorValue;
740
        }
741
742 199
        return $reference;
743
    }
744
745
    /**
746
     * Throws an exception if the DocumentManager is closed or currently not active.
747
     *
748
     * @throws MongoDBException If the DocumentManager is closed.
749
     */
750 594
    private function errorIfClosed()
751
    {
752 594
        if ($this->closed) {
753 5
            throw MongoDBException::documentManagerClosed();
754
        }
755 589
    }
756
757
    /**
758
     * Check if the Document manager is open or closed.
759
     *
760
     * @return bool
761
     */
762 1
    public function isOpen()
763
    {
764 1
        return ( ! $this->closed);
765
    }
766
767
    /**
768
     * Gets the filter collection.
769
     *
770
     * @return \Doctrine\ODM\MongoDB\Query\FilterCollection The active filter collection.
771
     */
772 508
    public function getFilterCollection()
773
    {
774 508
        if (null === $this->filterCollection) {
775 508
            $this->filterCollection = new FilterCollection($this);
776
        }
777
778 508
        return $this->filterCollection;
779
    }
780
}
781