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

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