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

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