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

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