Completed
Pull Request — master (#1790)
by Andreas
17:36
created

DocumentManager::errorIfClosed()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
crap 2
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\ProxyFactory;
15
use Doctrine\ODM\MongoDB\Query\FilterCollection;
16
use Doctrine\ODM\MongoDB\Repository\RepositoryFactory;
17
use MongoDB\Client;
18
use MongoDB\Collection;
19
use MongoDB\Database;
20
use MongoDB\Driver\ReadPreference;
21
use MongoDB\GridFS\Bucket;
22
use function array_search;
23
use function get_class;
24
use function gettype;
25
use function is_object;
26
use function ltrim;
27
use function sprintf;
28
29
/**
30
 * The DocumentManager class is the central access point for managing the
31
 * persistence of documents.
32
 *
33
 *     <?php
34
 *
35
 *     $config = new Configuration();
36
 *     $dm = DocumentManager::create(new Connection(), $config);
37
 *
38
 */
39
class DocumentManager implements ObjectManager
40
{
41
    /**
42
     * The Doctrine MongoDB connection instance.
43
     *
44
     * @var Client
45
     */
46
    private $client;
47
48
    /**
49
     * The used Configuration.
50
     *
51
     * @var Configuration
52
     */
53
    private $config;
54
55
    /**
56
     * The metadata factory, used to retrieve the ODM metadata of document classes.
57
     *
58
     * @var ClassMetadataFactory
59
     */
60
    private $metadataFactory;
61
62
    /**
63
     * The UnitOfWork used to coordinate object-level transactions.
64
     *
65
     * @var UnitOfWork
66
     */
67
    private $unitOfWork;
68
69
    /**
70
     * The event manager that is the central point of the event system.
71
     *
72
     * @var EventManager
73
     */
74
    private $eventManager;
75
76
    /**
77
     * The Hydrator factory instance.
78
     *
79
     * @var HydratorFactory
80
     */
81
    private $hydratorFactory;
82
83
    /**
84
     * The Proxy factory instance.
85
     *
86
     * @var ProxyFactory
87
     */
88
    private $proxyFactory;
89
90
    /**
91
     * The repository factory used to create dynamic repositories.
92
     *
93
     * @var RepositoryFactory
94
     */
95
    private $repositoryFactory;
96
97
    /**
98
     * SchemaManager instance
99
     *
100
     * @var SchemaManager
101
     */
102
    private $schemaManager;
103
104
    /**
105
     * Array of cached document database instances that are lazily loaded.
106
     *
107
     * @var Database[]
108
     */
109
    private $documentDatabases = [];
110
111
    /**
112
     * Array of cached document collection instances that are lazily loaded.
113
     *
114
     * @var Collection[]
115
     */
116
    private $documentCollections = [];
117
118
    /**
119
     * Array of cached document bucket instances that are lazily loaded.
120
     *
121
     * @var Bucket[]
122
     */
123
    private $documentBuckets = [];
124
125
    /**
126
     * Whether the DocumentManager is closed or not.
127
     *
128
     * @var bool
129
     */
130
    private $closed = false;
131
132
    /**
133
     * Collection of query filters.
134
     *
135
     * @var FilterCollection
136
     */
137
    private $filterCollection;
138
139
    /**
140
     * Creates a new Document that operates on the given Mongo connection
141
     * and uses the given Configuration.
142
     *
143
     */
144 1601
    protected function __construct(?Client $client = null, ?Configuration $config = null, ?EventManager $eventManager = null)
145
    {
146 1601
        $this->config = $config ?: new Configuration();
147 1601
        $this->eventManager = $eventManager ?: new EventManager();
148 1601
        $this->client = $client ?: new Client('mongodb://127.0.0.1', [], ['typeMap' => ['root' => 'array', 'document' => 'array']]);
149
150 1601
        $metadataFactoryClassName = $this->config->getClassMetadataFactoryName();
151 1601
        $this->metadataFactory = new $metadataFactoryClassName();
152 1601
        $this->metadataFactory->setDocumentManager($this);
153 1601
        $this->metadataFactory->setConfiguration($this->config);
154
155 1601
        $cacheDriver = $this->config->getMetadataCacheImpl();
156 1601
        if ($cacheDriver) {
157
            $this->metadataFactory->setCacheDriver($cacheDriver);
158
        }
159
160 1601
        $hydratorDir = $this->config->getHydratorDir();
161 1601
        $hydratorNs = $this->config->getHydratorNamespace();
162 1601
        $this->hydratorFactory = new HydratorFactory(
163 1601
            $this,
164 1601
            $this->eventManager,
165 1601
            $hydratorDir,
166 1601
            $hydratorNs,
167 1601
            $this->config->getAutoGenerateHydratorClasses()
168
        );
169
170 1601
        $this->unitOfWork = new UnitOfWork($this, $this->eventManager, $this->hydratorFactory);
171 1601
        $this->hydratorFactory->setUnitOfWork($this->unitOfWork);
172 1601
        $this->schemaManager = new SchemaManager($this, $this->metadataFactory);
173 1601
        $this->proxyFactory = new ProxyFactory(
174 1601
            $this,
175 1601
            $this->config->getProxyDir(),
176 1601
            $this->config->getProxyNamespace(),
177 1601
            $this->config->getAutoGenerateProxyClasses()
178
        );
179 1601
        $this->repositoryFactory = $this->config->getRepositoryFactory();
180 1601
    }
181
182
    /**
183
     * Gets the proxy factory used by the DocumentManager to create document proxies.
184
     *
185
     * @return ProxyFactory
186
     */
187 1
    public function getProxyFactory()
188
    {
189 1
        return $this->proxyFactory;
190
    }
191
192
    /**
193
     * Creates a new Document that operates on the given Mongo connection
194
     * and uses the given Configuration.
195
     *
196
     * @static
197
     * @return DocumentManager
198
     */
199 1601
    public static function create(?Client $client = null, ?Configuration $config = null, ?EventManager $eventManager = null)
200
    {
201 1601
        return new static($client, $config, $eventManager);
202
    }
203
204
    /**
205
     * Gets the EventManager used by the DocumentManager.
206
     *
207
     * @return EventManager
208
     */
209 1664
    public function getEventManager()
210
    {
211 1664
        return $this->eventManager;
212
    }
213
214
    /**
215
     * Gets the MongoDB client instance that this DocumentManager wraps.
216
     *
217
     * @return Client
218
     */
219 1601
    public function getClient()
220
    {
221 1601
        return $this->client;
222
    }
223
224
    /**
225
     * Gets the metadata factory used to gather the metadata of classes.
226
     *
227
     * @return ClassMetadataFactory
228
     */
229 1601
    public function getMetadataFactory()
230
    {
231 1601
        return $this->metadataFactory;
232
    }
233
234
    /**
235
     * Helper method to initialize a lazy loading proxy or persistent collection.
236
     *
237
     * This method is a no-op for other objects.
238
     *
239
     * @param object $obj
240
     */
241
    public function initializeObject($obj)
242
    {
243
        $this->unitOfWork->initializeObject($obj);
244
    }
245
246
    /**
247
     * Gets the UnitOfWork used by the DocumentManager to coordinate operations.
248
     *
249
     * @return UnitOfWork
250
     */
251 1608
    public function getUnitOfWork()
252
    {
253 1608
        return $this->unitOfWork;
254
    }
255
256
    /**
257
     * Gets the Hydrator factory used by the DocumentManager to generate and get hydrators
258
     * for each type of document.
259
     *
260
     * @return HydratorFactory
261
     */
262 69
    public function getHydratorFactory()
263
    {
264 69
        return $this->hydratorFactory;
265
    }
266
267
    /**
268
     * Returns SchemaManager, used to create/drop indexes/collections/databases.
269
     *
270
     * @return SchemaManager
271
     */
272 19
    public function getSchemaManager()
273
    {
274 19
        return $this->schemaManager;
275
    }
276
277
    /**
278
     * Returns the metadata for a class.
279
     *
280
     * @param string $className The class name.
281
     * @return ClassMetadata
282
     * @internal Performance-sensitive method.
283
     */
284 1339
    public function getClassMetadata($className)
285
    {
286 1339
        return $this->metadataFactory->getMetadataFor(ltrim($className, '\\'));
287
    }
288
289
    /**
290
     * Returns the MongoDB instance for a class.
291
     *
292
     * @param string $className The class name.
293
     * @return Database
294
     */
295 1268
    public function getDocumentDatabase($className)
296
    {
297 1268
        $className = ltrim($className, '\\');
298
299 1268
        if (isset($this->documentDatabases[$className])) {
300 37
            return $this->documentDatabases[$className];
301
        }
302
303 1263
        $metadata = $this->metadataFactory->getMetadataFor($className);
304 1263
        $db = $metadata->getDatabase();
305 1263
        $db = $db ?: $this->config->getDefaultDB();
306 1263
        $db = $db ?: 'doctrine';
307 1263
        $this->documentDatabases[$className] = $this->client->selectDatabase($db);
308
309 1263
        return $this->documentDatabases[$className];
310
    }
311
312
    /**
313
     * Gets the array of instantiated document database instances.
314
     *
315
     * @return Database[]
316
     */
317
    public function getDocumentDatabases()
318
    {
319
        return $this->documentDatabases;
320
    }
321
322
    /**
323
     * Returns the collection instance for a class.
324
     *
325
     * @param string $className The class name.
326
     * @throws MongoDBException When the $className param is not mapped to a collection.
327
     * @return Collection
328
     */
329 1271
    public function getDocumentCollection($className)
330
    {
331 1271
        $className = ltrim($className, '\\');
332
333
        /** @var ClassMetadata $metadata */
334 1271
        $metadata = $this->metadataFactory->getMetadataFor($className);
335 1271
        if ($metadata->isFile) {
336 15
            return $this->getDocumentBucket($className)->getFilesCollection();
337
        }
338
339 1261
        $collectionName = $metadata->getCollection();
340
341 1261
        if (! $collectionName) {
342
            throw MongoDBException::documentNotMappedToCollection($className);
343
        }
344
345 1261
        if (! isset($this->documentCollections[$className])) {
346 1251
            $db = $this->getDocumentDatabase($className);
347
348 1251
            $options = [];
349 1251
            if ($metadata->readPreference !== null) {
350 3
                $options['readPreference'] = new ReadPreference($metadata->readPreference, $metadata->readPreferenceTags);
351
            }
352
353 1251
            $this->documentCollections[$className] = $db->selectCollection($collectionName, $options);
354
        }
355
356 1261
        return $this->documentCollections[$className];
357
    }
358
359
    /**
360
     * Returns the bucket instance for a class.
361
     *
362
     * @param string $className The class name.
363
     * @throws MongoDBException When the $className param is not mapped to a collection.
364
     */
365 15
    public function getDocumentBucket(string $className): Bucket
366
    {
367 15
        $className = ltrim($className, '\\');
368
369
        /** @var ClassMetadata $metadata */
370 15
        $metadata = $this->metadataFactory->getMetadataFor($className);
371 15
        if (! $metadata->isFile) {
372
            throw MongoDBException::documentBucketOnlyAvailableForGridFSFiles($className);
373
        }
374
375 15
        $bucketName = $metadata->getBucketName();
376
377 15
        if (! $bucketName) {
378
            throw MongoDBException::documentNotMappedToCollection($className);
379
        }
380
381 15
        if (! isset($this->documentBuckets[$className])) {
382 10
            $db = $this->getDocumentDatabase($className);
383
384 10
            $options = ['bucketName' => $bucketName];
385 10
            if ($metadata->readPreference !== null) {
386
                $options['readPreference'] = new ReadPreference($metadata->readPreference, $metadata->readPreferenceTags);
387
            }
388
389 10
            $this->documentBuckets[$className] = $db->selectGridFSBucket($options);
390
        }
391
392 15
        return $this->documentBuckets[$className];
393
    }
394
395
    /**
396
     * Gets the array of instantiated document collection instances.
397
     *
398
     * @return Collection[]
399
     */
400
    public function getDocumentCollections()
401
    {
402
        return $this->documentCollections;
403
    }
404
405
    /**
406
     * Create a new Query instance for a class.
407
     *
408
     * @param string $documentName The document class name.
409
     * @return Query\Builder
410
     */
411 182
    public function createQueryBuilder($documentName = null)
412
    {
413 182
        return new Query\Builder($this, $documentName);
414
    }
415
416
    /**
417
     * Creates a new aggregation builder instance for a class.
418
     *
419
     * @param string $documentName The document class name.
420
     * @return Aggregation\Builder
421
     */
422 41
    public function createAggregationBuilder($documentName)
423
    {
424 41
        return new Aggregation\Builder($this, $documentName);
425
    }
426
427
    /**
428
     * Tells the DocumentManager to make an instance managed and persistent.
429
     *
430
     * The document will be entered into the database at or before transaction
431
     * commit or as a result of the flush operation.
432
     *
433
     * NOTE: The persist operation always considers documents that are not yet known to
434
     * this DocumentManager as NEW. Do not pass detached documents to the persist operation.
435
     *
436
     * @param object $document The instance to make managed and persistent.
437
     * @throws \InvalidArgumentException When the given $document param is not an object.
438
     */
439 585
    public function persist($document)
440
    {
441 585
        if (! is_object($document)) {
442 1
            throw new \InvalidArgumentException(gettype($document));
443
        }
444 584
        $this->errorIfClosed();
445 583
        $this->unitOfWork->persist($document);
446 579
    }
447
448
    /**
449
     * Removes a document instance.
450
     *
451
     * A removed document will be removed from the database at or before transaction commit
452
     * or as a result of the flush operation.
453
     *
454
     * @param object $document The document instance to remove.
455
     * @throws \InvalidArgumentException When the $document param is not an object.
456
     */
457 26
    public function remove($document)
458
    {
459 26
        if (! is_object($document)) {
460 1
            throw new \InvalidArgumentException(gettype($document));
461
        }
462 25
        $this->errorIfClosed();
463 24
        $this->unitOfWork->remove($document);
464 24
    }
465
466
    /**
467
     * Refreshes the persistent state of a document from the database,
468
     * overriding any local changes that have not yet been persisted.
469
     *
470
     * @param object $document The document to refresh.
471
     * @throws \InvalidArgumentException When the given $document param is not an object.
472
     */
473 25
    public function refresh($document)
474
    {
475 25
        if (! is_object($document)) {
476 1
            throw new \InvalidArgumentException(gettype($document));
477
        }
478 24
        $this->errorIfClosed();
479 23
        $this->unitOfWork->refresh($document);
480 22
    }
481
482
    /**
483
     * Detaches a document from the DocumentManager, causing a managed document to
484
     * become detached.  Unflushed changes made to the document if any
485
     * (including removal of the document), will not be synchronized to the database.
486
     * Documents which previously referenced the detached document will continue to
487
     * reference it.
488
     *
489
     * @param object $document The document to detach.
490
     * @throws \InvalidArgumentException When the $document param is not an object.
491
     */
492 11
    public function detach($document)
493
    {
494 11
        if (! is_object($document)) {
495 1
            throw new \InvalidArgumentException(gettype($document));
496
        }
497 10
        $this->unitOfWork->detach($document);
498 10
    }
499
500
    /**
501
     * Merges the state of a detached document into the persistence context
502
     * of this DocumentManager and returns the managed copy of the document.
503
     * The document passed to merge will not become associated/managed with this DocumentManager.
504
     *
505
     * @param object $document The detached document to merge into the persistence context.
506
     * @throws LockException
507
     * @throws \InvalidArgumentException If the $document param is not an object.
508
     * @return object The managed copy of the document.
509
     */
510 14
    public function merge($document)
511
    {
512 14
        if (! is_object($document)) {
513 1
            throw new \InvalidArgumentException(gettype($document));
514
        }
515 13
        $this->errorIfClosed();
516 12
        return $this->unitOfWork->merge($document);
517
    }
518
519
    /**
520
     * Acquire a lock on the given document.
521
     *
522
     * @param object $document
523
     * @param int    $lockMode
524
     * @param int    $lockVersion
525
     * @throws \InvalidArgumentException
526
     */
527 8
    public function lock($document, $lockMode, $lockVersion = null)
528
    {
529 8
        if (! is_object($document)) {
530
            throw new \InvalidArgumentException(gettype($document));
531
        }
532 8
        $this->unitOfWork->lock($document, $lockMode, $lockVersion);
533 5
    }
534
535
    /**
536
     * Releases a lock on the given document.
537
     *
538
     * @param object $document
539
     * @throws \InvalidArgumentException If the $document param is not an object.
540
     */
541 1
    public function unlock($document)
542
    {
543 1
        if (! is_object($document)) {
544
            throw new \InvalidArgumentException(gettype($document));
545
        }
546 1
        $this->unitOfWork->unlock($document);
547 1
    }
548
549
    /**
550
     * Gets the repository for a document class.
551
     *
552
     * @param string $documentName The name of the Document.
553
     * @return ObjectRepository  The repository.
554
     */
555 336
    public function getRepository($documentName)
556
    {
557 336
        return $this->repositoryFactory->getRepository($this, $documentName);
558
    }
559
560
    /**
561
     * Flushes all changes to objects that have been queued up to now to the database.
562
     * This effectively synchronizes the in-memory state of managed objects with the
563
     * database.
564
     *
565
     * @param array $options Array of options to be used with batchInsert(), update() and remove()
566
     * @throws \InvalidArgumentException
567
     */
568 557
    public function flush(array $options = [])
569
    {
570 557
        $this->errorIfClosed();
571 556
        $this->unitOfWork->commit($options);
572 553
    }
573
574
    /**
575
     * Gets a reference to the document identified by the given type and identifier
576
     * without actually loading it.
577
     *
578
     * If partial objects are allowed, this method will return a partial object that only
579
     * has its identifier populated. Otherwise a proxy is returned that automatically
580
     * loads itself on first access.
581
     *
582
     * @param string        $documentName
583
     * @param string|object $identifier
584
     * @return mixed|object The document reference.
585
     */
586 132
    public function getReference($documentName, $identifier)
587
    {
588
        /** @var ClassMetadata $class */
589 132
        $class = $this->metadataFactory->getMetadataFor(ltrim($documentName, '\\'));
590 132
        $document = $this->unitOfWork->tryGetById($identifier, $class);
591
592
        // Check identity map first, if its already in there just return it.
593 132
        if ($document) {
594 56
            return $document;
595
        }
596
597 103
        $document = $this->proxyFactory->getProxy($class->name, [$class->identifier => $identifier]);
598 103
        $this->unitOfWork->registerManaged($document, $identifier, []);
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...
599
600 103
        return $document;
601
    }
602
603
    /**
604
     * Gets a partial reference to the document identified by the given type and identifier
605
     * without actually loading it, if the document is not yet loaded.
606
     *
607
     * The returned reference may be a partial object if the document is not yet loaded/managed.
608
     * If it is a partial object it will not initialize the rest of the document state on access.
609
     * Thus you can only ever safely access the identifier of a document obtained through
610
     * this method.
611
     *
612
     * The use-cases for partial references involve maintaining bidirectional associations
613
     * without loading one side of the association or to update a document without loading it.
614
     * Note, however, that in the latter case the original (persistent) document data will
615
     * never be visible to the application (especially not event listeners) as it will
616
     * never be loaded in the first place.
617
     *
618
     * @param string $documentName The name of the document type.
619
     * @param mixed  $identifier   The document identifier.
620
     * @return object The (partial) document reference.
621
     */
622 1
    public function getPartialReference($documentName, $identifier)
623
    {
624 1
        $class = $this->metadataFactory->getMetadataFor(ltrim($documentName, '\\'));
625 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...
626
627
        // Check identity map first, if its already in there just return it.
628 1
        if ($document) {
629
            return $document;
630
        }
631 1
        $document = $class->newInstance();
632 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...
633 1
        $this->unitOfWork->registerManaged($document, $identifier, []);
634
635 1
        return $document;
636
    }
637
638
    /**
639
     * Finds a Document by its identifier.
640
     *
641
     * This is just a convenient shortcut for getRepository($documentName)->find($id).
642
     *
643
     * @param string $documentName
644
     * @param mixed  $identifier
645
     * @param int    $lockMode
646
     * @param int    $lockVersion
647
     * @return object $document
648
     */
649 182
    public function find($documentName, $identifier, $lockMode = LockMode::NONE, $lockVersion = null)
650
    {
651 182
        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...
652
    }
653
654
    /**
655
     * Clears the DocumentManager.
656
     *
657
     * All documents that are currently managed by this DocumentManager become
658
     * detached.
659
     *
660
     * @param string|null $documentName if given, only documents of this type will get detached
661
     */
662 372
    public function clear($documentName = null)
663
    {
664 372
        $this->unitOfWork->clear($documentName);
665 372
    }
666
667
    /**
668
     * Closes the DocumentManager. All documents that are currently managed
669
     * by this DocumentManager become detached. The DocumentManager may no longer
670
     * be used after it is closed.
671
     */
672 6
    public function close()
673
    {
674 6
        $this->clear();
675 6
        $this->closed = true;
676 6
    }
677
678
    /**
679
     * Determines whether a document instance is managed in this DocumentManager.
680
     *
681
     * @param object $document
682
     * @throws \InvalidArgumentException When the $document param is not an object.
683
     * @return bool TRUE if this DocumentManager currently manages the given document, FALSE otherwise.
684
     */
685 3
    public function contains($document)
686
    {
687 3
        if (! is_object($document)) {
688
            throw new \InvalidArgumentException(gettype($document));
689
        }
690 3
        return $this->unitOfWork->isScheduledForInsert($document) ||
691 3
            $this->unitOfWork->isInIdentityMap($document) &&
692 3
            ! $this->unitOfWork->isScheduledForDelete($document);
693
    }
694
695
    /**
696
     * Gets the Configuration used by the DocumentManager.
697
     *
698
     * @return Configuration
699
     */
700 752
    public function getConfiguration()
701
    {
702 752
        return $this->config;
703
    }
704
705
    /**
706
     * Returns a reference to the supplied document.
707
     *
708
     * @param object $document         A document object
709
     * @param array  $referenceMapping Mapping for the field that references the document
710
     *
711
     * @throws \InvalidArgumentException
712
     * @throws MappingException
713
     * @return mixed The reference for the document in question, according to the desired mapping
714
     */
715 225
    public function createReference($document, array $referenceMapping)
716
    {
717 225
        if (! is_object($document)) {
718
            throw new \InvalidArgumentException('Cannot create a DBRef, the document is not an object');
719
        }
720
721 225
        $class = $this->getClassMetadata(get_class($document));
722 225
        $id = $this->unitOfWork->getDocumentIdentifier($document);
723
724 225
        if ($id === null) {
725 1
            throw new \RuntimeException(
726 1
                sprintf('Cannot create a DBRef for class %s without an identifier. Have you forgotten to persist/merge the document first?', $class->name)
727
            );
728
        }
729
730 224
        $storeAs = $referenceMapping['storeAs'] ?? null;
731 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...
732 224
        switch ($storeAs) {
733
            case ClassMetadata::REFERENCE_STORE_AS_ID:
734 46
                if ($class->inheritanceType === ClassMetadata::INHERITANCE_TYPE_SINGLE_COLLECTION) {
735 1
                    throw MappingException::simpleReferenceMustNotTargetDiscriminatedDocument($referenceMapping['targetDocument']);
736
                }
737
738 45
                return $class->getDatabaseIdentifierValue($id);
739
                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...
740
741
            case ClassMetadata::REFERENCE_STORE_AS_REF:
742 20
                $reference = ['id' => $class->getDatabaseIdentifierValue($id)];
743 20
                break;
744
745
            case ClassMetadata::REFERENCE_STORE_AS_DB_REF:
746
                $reference = [
747 180
                    '$ref' => $class->getCollection(),
748 180
                    '$id'  => $class->getDatabaseIdentifierValue($id),
749
                ];
750 180
                break;
751
752
            case ClassMetadata::REFERENCE_STORE_AS_DB_REF_WITH_DB:
753
                $reference = [
754 17
                    '$ref' => $class->getCollection(),
755 17
                    '$id'  => $class->getDatabaseIdentifierValue($id),
756 17
                    '$db'  => $this->getDocumentDatabase($class->name)->getDatabaseName(),
757
                ];
758 17
                break;
759
760
            default:
761
                throw new \InvalidArgumentException(sprintf('Reference type %s is invalid.', $storeAs));
762
        }
763
764
        /* If the class has a discriminator (field and value), use it. A child
765
         * class that is not defined in the discriminator map may only have a
766
         * discriminator field and no value, so default to the full class name.
767
         */
768 202
        if (isset($class->discriminatorField)) {
769 18
            $reference[$class->discriminatorField] = $class->discriminatorValue ?? $class->name;
770
        }
771
772
        /* Add a discriminator value if the referenced document is not mapped
773
         * explicitly to a targetDocument class.
774
         */
775 202
        if (! isset($referenceMapping['targetDocument'])) {
776 33
            $discriminatorField = $referenceMapping['discriminatorField'];
777 33
            $discriminatorValue = isset($referenceMapping['discriminatorMap'])
778 8
                ? array_search($class->name, $referenceMapping['discriminatorMap'])
779 33
                : $class->name;
780
781
            /* If the discriminator value was not found in the map, use the full
782
             * class name. In the future, it may be preferable to throw an
783
             * exception here (perhaps based on some strictness option).
784
             *
785
             * @see PersistenceBuilder::prepareEmbeddedDocumentValue()
786
             */
787 33
            if ($discriminatorValue === false) {
788 3
                $discriminatorValue = $class->name;
789
            }
790
791 33
            $reference[$discriminatorField] = $discriminatorValue;
792
        }
793
794 202
        return $reference;
795
    }
796
797
    /**
798
     * Throws an exception if the DocumentManager is closed or currently not active.
799
     *
800
     * @throws MongoDBException If the DocumentManager is closed.
801
     */
802 590
    private function errorIfClosed()
803
    {
804 590
        if ($this->closed) {
805 5
            throw MongoDBException::documentManagerClosed();
806
        }
807 585
    }
808
809
    /**
810
     * Check if the Document manager is open or closed.
811
     *
812
     * @return bool
813
     */
814 1
    public function isOpen()
815
    {
816 1
        return ! $this->closed;
817
    }
818
819
    /**
820
     * Gets the filter collection.
821
     *
822
     * @return FilterCollection The active filter collection.
823
     */
824 517
    public function getFilterCollection()
825
    {
826 517
        if ($this->filterCollection === null) {
827 517
            $this->filterCollection = new FilterCollection($this);
828
        }
829
830 517
        return $this->filterCollection;
831
    }
832
}
833