vendor/doctrine/mongodb-odm/lib/Doctrine/ODM/MongoDB/DocumentManager.php line 292

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