vendor/doctrine/orm/src/EntityManager.php line 499

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Doctrine\ORM;
  4. use BackedEnum;
  5. use DateTimeInterface;
  6. use Doctrine\Common\EventManager;
  7. use Doctrine\DBAL\Connection;
  8. use Doctrine\DBAL\LockMode;
  9. use Doctrine\ORM\Exception\EntityManagerClosed;
  10. use Doctrine\ORM\Exception\InvalidHydrationMode;
  11. use Doctrine\ORM\Exception\MissingIdentifierField;
  12. use Doctrine\ORM\Exception\MissingMappingDriverImplementation;
  13. use Doctrine\ORM\Exception\ORMException;
  14. use Doctrine\ORM\Exception\UnrecognizedIdentifierFields;
  15. use Doctrine\ORM\Internal\Hydration\AbstractHydrator;
  16. use Doctrine\ORM\Mapping\ClassMetadata;
  17. use Doctrine\ORM\Mapping\ClassMetadataFactory;
  18. use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
  19. use Doctrine\ORM\Proxy\ProxyFactory;
  20. use Doctrine\ORM\Query\Expr;
  21. use Doctrine\ORM\Query\FilterCollection;
  22. use Doctrine\ORM\Query\ResultSetMapping;
  23. use Doctrine\ORM\Repository\RepositoryFactory;
  24. use function array_keys;
  25. use function is_array;
  26. use function is_object;
  27. use function ltrim;
  28. use function method_exists;
  29. /**
  30. * The EntityManager is the central access point to ORM functionality.
  31. *
  32. * It is a facade to all different ORM subsystems such as UnitOfWork,
  33. * Query Language and Repository API. The quickest way to obtain a fully
  34. * configured EntityManager is:
  35. *
  36. * use Doctrine\ORM\Tools\ORMSetup;
  37. * use Doctrine\ORM\EntityManager;
  38. *
  39. * $paths = ['/path/to/entity/mapping/files'];
  40. *
  41. * $config = ORMSetup::createAttributeMetadataConfig($paths);
  42. * $connection = DriverManager::getConnection(['driver' => 'pdo_sqlite', 'memory' => true], $config);
  43. * $entityManager = new EntityManager($connection, $config);
  44. *
  45. * For more information see
  46. * {@link http://docs.doctrine-project.org/projects/doctrine-orm/en/stable/reference/configuration.html}
  47. *
  48. * You should never attempt to inherit from the EntityManager: Inheritance
  49. * is not a valid extension point for the EntityManager. Instead you
  50. * should take a look at the {@see \Doctrine\ORM\Decorator\EntityManagerDecorator}
  51. * and wrap your entity manager in a decorator.
  52. *
  53. * @final
  54. */
  55. class EntityManager implements EntityManagerInterface
  56. {
  57. /**
  58. * The metadata factory, used to retrieve the ORM metadata of entity classes.
  59. */
  60. private ClassMetadataFactory $metadataFactory;
  61. /**
  62. * The UnitOfWork used to coordinate object-level transactions.
  63. */
  64. private UnitOfWork $unitOfWork;
  65. /**
  66. * The event manager that is the central point of the event system.
  67. */
  68. private EventManager $eventManager;
  69. /**
  70. * The proxy factory used to create dynamic proxies.
  71. */
  72. private ProxyFactory $proxyFactory;
  73. /**
  74. * The repository factory used to create dynamic repositories.
  75. */
  76. private RepositoryFactory $repositoryFactory;
  77. /**
  78. * The expression builder instance used to generate query expressions.
  79. */
  80. private Expr|null $expressionBuilder = null;
  81. /**
  82. * Whether the EntityManager is closed or not.
  83. */
  84. private bool $closed = false;
  85. /**
  86. * Collection of query filters.
  87. */
  88. private FilterCollection|null $filterCollection = null;
  89. /**
  90. * The second level cache regions API.
  91. */
  92. private Cache|null $cache = null;
  93. /**
  94. * Creates a new EntityManager that operates on the given database connection
  95. * and uses the given Configuration and EventManager implementations.
  96. *
  97. * @param Connection $conn The database connection used by the EntityManager.
  98. */
  99. public function __construct(
  100. private Connection $conn,
  101. private Configuration $config,
  102. EventManager|null $eventManager = null,
  103. ) {
  104. if (! $config->getMetadataDriverImpl()) {
  105. throw MissingMappingDriverImplementation::create();
  106. }
  107. $this->eventManager = $eventManager
  108. ?? (method_exists($conn, 'getEventManager')
  109. ? $conn->getEventManager()
  110. : new EventManager()
  111. );
  112. $metadataFactoryClassName = $config->getClassMetadataFactoryName();
  113. $this->metadataFactory = new $metadataFactoryClassName();
  114. $this->metadataFactory->setEntityManager($this);
  115. $this->configureMetadataCache();
  116. $this->repositoryFactory = $config->getRepositoryFactory();
  117. $this->unitOfWork = new UnitOfWork($this);
  118. if ($config->isNativeLazyObjectsEnabled()) {
  119. $this->proxyFactory = new ProxyFactory($this);
  120. } else {
  121. $this->proxyFactory = new ProxyFactory(
  122. $this,
  123. $config->getProxyDir(),
  124. $config->getProxyNamespace(),
  125. $config->getAutoGenerateProxyClasses(),
  126. );
  127. }
  128. if ($config->isSecondLevelCacheEnabled()) {
  129. $cacheConfig = $config->getSecondLevelCacheConfiguration();
  130. $cacheFactory = $cacheConfig->getCacheFactory();
  131. $this->cache = $cacheFactory->createCache($this);
  132. }
  133. }
  134. public function getConnection(): Connection
  135. {
  136. return $this->conn;
  137. }
  138. public function getMetadataFactory(): ClassMetadataFactory
  139. {
  140. return $this->metadataFactory;
  141. }
  142. public function getExpressionBuilder(): Expr
  143. {
  144. return $this->expressionBuilder ??= new Expr();
  145. }
  146. public function beginTransaction(): void
  147. {
  148. $this->conn->beginTransaction();
  149. }
  150. public function getCache(): Cache|null
  151. {
  152. return $this->cache;
  153. }
  154. public function wrapInTransaction(callable $func): mixed
  155. {
  156. $this->conn->beginTransaction();
  157. $successful = false;
  158. try {
  159. $return = $func($this);
  160. $this->flush();
  161. $this->conn->commit();
  162. $successful = true;
  163. return $return;
  164. } finally {
  165. if (! $successful) {
  166. $this->close();
  167. if ($this->conn->isTransactionActive()) {
  168. $this->conn->rollBack();
  169. }
  170. }
  171. }
  172. }
  173. public function commit(): void
  174. {
  175. $this->conn->commit();
  176. }
  177. public function rollback(): void
  178. {
  179. $this->conn->rollBack();
  180. }
  181. /**
  182. * Returns the ORM metadata descriptor for a class.
  183. *
  184. * Internal note: Performance-sensitive method.
  185. *
  186. * {@inheritDoc}
  187. */
  188. public function getClassMetadata(string $className): Mapping\ClassMetadata
  189. {
  190. return $this->metadataFactory->getMetadataFor($className);
  191. }
  192. public function createQuery(string $dql = ''): Query
  193. {
  194. $query = new Query($this);
  195. if (! empty($dql)) {
  196. $query->setDQL($dql);
  197. }
  198. return $query;
  199. }
  200. public function createNativeQuery(string $sql, ResultSetMapping $rsm): NativeQuery
  201. {
  202. $query = new NativeQuery($this);
  203. $query->setSQL($sql);
  204. $query->setResultSetMapping($rsm);
  205. return $query;
  206. }
  207. public function createQueryBuilder(): QueryBuilder
  208. {
  209. return new QueryBuilder($this);
  210. }
  211. /**
  212. * Flushes all changes to objects that have been queued up to now to the database.
  213. * This effectively synchronizes the in-memory state of managed objects with the
  214. * database.
  215. *
  216. * If an entity is explicitly passed to this method only this entity and
  217. * the cascade-persist semantics + scheduled inserts/removals are synchronized.
  218. *
  219. * @throws OptimisticLockException If a version check on an entity that
  220. * makes use of optimistic locking fails.
  221. * @throws ORMException
  222. */
  223. public function flush(): void
  224. {
  225. $this->errorIfClosed();
  226. $this->unitOfWork->commit();
  227. }
  228. /**
  229. * {@inheritDoc}
  230. */
  231. public function find($className, mixed $id, LockMode|int|null $lockMode = null, int|null $lockVersion = null): object|null
  232. {
  233. $class = $this->metadataFactory->getMetadataFor(ltrim($className, '\\'));
  234. if ($lockMode !== null) {
  235. $this->checkLockRequirements($lockMode, $class);
  236. }
  237. if (! is_array($id)) {
  238. if ($class->isIdentifierComposite) {
  239. throw ORMInvalidArgumentException::invalidCompositeIdentifier();
  240. }
  241. $id = [$class->identifier[0] => $id];
  242. }
  243. foreach ($id as $i => $value) {
  244. if (is_object($value)) {
  245. $className = DefaultProxyClassNameResolver::getClass($value);
  246. if ($this->metadataFactory->hasMetadataFor($className)) {
  247. $id[$i] = $this->unitOfWork->getSingleIdentifierValue($value);
  248. if ($id[$i] === null) {
  249. throw ORMInvalidArgumentException::invalidIdentifierBindingEntity($className);
  250. }
  251. }
  252. }
  253. }
  254. $sortedId = [];
  255. foreach ($class->identifier as $identifier) {
  256. if (! isset($id[$identifier])) {
  257. throw MissingIdentifierField::fromFieldAndClass($identifier, $class->name);
  258. }
  259. if ($id[$identifier] instanceof BackedEnum) {
  260. $sortedId[$identifier] = $id[$identifier]->value;
  261. } else {
  262. $sortedId[$identifier] = $id[$identifier];
  263. }
  264. unset($id[$identifier]);
  265. }
  266. if ($id) {
  267. throw UnrecognizedIdentifierFields::fromClassAndFieldNames($class->name, array_keys($id));
  268. }
  269. $unitOfWork = $this->getUnitOfWork();
  270. $entity = $unitOfWork->tryGetById($sortedId, $class->rootEntityName);
  271. // Check identity map first
  272. if ($entity !== false) {
  273. if (! ($entity instanceof $class->name)) {
  274. return null;
  275. }
  276. switch (true) {
  277. case $lockMode === LockMode::OPTIMISTIC:
  278. $this->lock($entity, $lockMode, $lockVersion);
  279. break;
  280. case $lockMode === LockMode::NONE:
  281. case $lockMode === LockMode::PESSIMISTIC_READ:
  282. case $lockMode === LockMode::PESSIMISTIC_WRITE:
  283. $persister = $unitOfWork->getEntityPersister($class->name);
  284. $persister->refresh($sortedId, $entity, $lockMode);
  285. break;
  286. }
  287. return $entity; // Hit!
  288. }
  289. $persister = $unitOfWork->getEntityPersister($class->name);
  290. switch (true) {
  291. case $lockMode === LockMode::OPTIMISTIC:
  292. $entity = $persister->load($sortedId);
  293. if ($entity !== null) {
  294. $unitOfWork->lock($entity, $lockMode, $lockVersion);
  295. }
  296. return $entity;
  297. case $lockMode === LockMode::PESSIMISTIC_READ:
  298. case $lockMode === LockMode::PESSIMISTIC_WRITE:
  299. return $persister->load($sortedId, null, null, [], $lockMode);
  300. default:
  301. return $persister->loadById($sortedId);
  302. }
  303. }
  304. public function getReference(string $entityName, mixed $id): object|null
  305. {
  306. $class = $this->metadataFactory->getMetadataFor(ltrim($entityName, '\\'));
  307. if (! is_array($id)) {
  308. $id = [$class->identifier[0] => $id];
  309. }
  310. $sortedId = [];
  311. foreach ($class->identifier as $identifier) {
  312. if (! isset($id[$identifier])) {
  313. throw MissingIdentifierField::fromFieldAndClass($identifier, $class->name);
  314. }
  315. $sortedId[$identifier] = $id[$identifier];
  316. unset($id[$identifier]);
  317. }
  318. if ($id) {
  319. throw UnrecognizedIdentifierFields::fromClassAndFieldNames($class->name, array_keys($id));
  320. }
  321. $entity = $this->unitOfWork->tryGetById($sortedId, $class->rootEntityName);
  322. // Check identity map first, if its already in there just return it.
  323. if ($entity !== false) {
  324. return $entity instanceof $class->name ? $entity : null;
  325. }
  326. if ($class->subClasses) {
  327. return $this->find($entityName, $sortedId);
  328. }
  329. $entity = $this->proxyFactory->getProxy($class->name, $sortedId);
  330. $this->unitOfWork->registerManaged($entity, $sortedId, []);
  331. return $entity;
  332. }
  333. /**
  334. * Clears the EntityManager. All entities that are currently managed
  335. * by this EntityManager become detached.
  336. */
  337. public function clear(): void
  338. {
  339. $this->unitOfWork->clear();
  340. }
  341. public function close(): void
  342. {
  343. $this->clear();
  344. $this->closed = true;
  345. }
  346. /**
  347. * Tells the EntityManager to make an instance managed and persistent.
  348. *
  349. * The entity will be entered into the database at or before transaction
  350. * commit or as a result of the flush operation.
  351. *
  352. * NOTE: The persist operation always considers entities that are not yet known to
  353. * this EntityManager as NEW. Do not pass detached entities to the persist operation.
  354. *
  355. * @throws ORMInvalidArgumentException
  356. * @throws ORMException
  357. */
  358. public function persist(object $object): void
  359. {
  360. $this->errorIfClosed();
  361. $this->unitOfWork->persist($object);
  362. }
  363. /**
  364. * Removes an entity instance.
  365. *
  366. * A removed entity will be removed from the database at or before transaction commit
  367. * or as a result of the flush operation.
  368. *
  369. * @throws ORMInvalidArgumentException
  370. * @throws ORMException
  371. */
  372. public function remove(object $object): void
  373. {
  374. $this->errorIfClosed();
  375. $this->unitOfWork->remove($object);
  376. }
  377. public function refresh(object $object, LockMode|int|null $lockMode = null): void
  378. {
  379. $this->errorIfClosed();
  380. $this->unitOfWork->refresh($object, $lockMode);
  381. }
  382. /**
  383. * Detaches an entity from the EntityManager, causing a managed entity to
  384. * become detached. Unflushed changes made to the entity if any
  385. * (including removal of the entity), will not be synchronized to the database.
  386. * Entities which previously referenced the detached entity will continue to
  387. * reference it.
  388. *
  389. * @throws ORMInvalidArgumentException
  390. */
  391. public function detach(object $object): void
  392. {
  393. $this->unitOfWork->detach($object);
  394. }
  395. public function lock(object $entity, LockMode|int $lockMode, DateTimeInterface|int|null $lockVersion = null): void
  396. {
  397. $this->unitOfWork->lock($entity, $lockMode, $lockVersion);
  398. }
  399. /**
  400. * Gets the repository for an entity class.
  401. *
  402. * @param class-string<T> $className The name of the entity.
  403. *
  404. * @return EntityRepository<T> The repository class.
  405. *
  406. * @template T of object
  407. */
  408. public function getRepository(string $className): EntityRepository
  409. {
  410. return $this->repositoryFactory->getRepository($this, $className);
  411. }
  412. /**
  413. * Determines whether an entity instance is managed in this EntityManager.
  414. *
  415. * @return bool TRUE if this EntityManager currently manages the given entity, FALSE otherwise.
  416. */
  417. public function contains(object $object): bool
  418. {
  419. return $this->unitOfWork->isScheduledForInsert($object)
  420. || $this->unitOfWork->isInIdentityMap($object)
  421. && ! $this->unitOfWork->isScheduledForDelete($object);
  422. }
  423. public function getEventManager(): EventManager
  424. {
  425. return $this->eventManager;
  426. }
  427. public function getConfiguration(): Configuration
  428. {
  429. return $this->config;
  430. }
  431. /**
  432. * Throws an exception if the EntityManager is closed or currently not active.
  433. *
  434. * @throws EntityManagerClosed If the EntityManager is closed.
  435. */
  436. private function errorIfClosed(): void
  437. {
  438. if ($this->closed) {
  439. throw EntityManagerClosed::create();
  440. }
  441. }
  442. public function isOpen(): bool
  443. {
  444. return ! $this->closed;
  445. }
  446. public function getUnitOfWork(): UnitOfWork
  447. {
  448. return $this->unitOfWork;
  449. }
  450. public function newHydrator(string|int $hydrationMode): AbstractHydrator
  451. {
  452. return match ($hydrationMode) {
  453. Query::HYDRATE_OBJECT => new Internal\Hydration\ObjectHydrator($this),
  454. Query::HYDRATE_ARRAY => new Internal\Hydration\ArrayHydrator($this),
  455. Query::HYDRATE_SCALAR => new Internal\Hydration\ScalarHydrator($this),
  456. Query::HYDRATE_SINGLE_SCALAR => new Internal\Hydration\SingleScalarHydrator($this),
  457. Query::HYDRATE_SIMPLEOBJECT => new Internal\Hydration\SimpleObjectHydrator($this),
  458. Query::HYDRATE_SCALAR_COLUMN => new Internal\Hydration\ScalarColumnHydrator($this),
  459. default => $this->createCustomHydrator((string) $hydrationMode),
  460. };
  461. }
  462. public function getProxyFactory(): ProxyFactory
  463. {
  464. return $this->proxyFactory;
  465. }
  466. public function initializeObject(object $obj): void
  467. {
  468. $this->unitOfWork->initializeObject($obj);
  469. }
  470. /**
  471. * {@inheritDoc}
  472. */
  473. public function isUninitializedObject($value): bool
  474. {
  475. return $this->unitOfWork->isUninitializedObject($value);
  476. }
  477. public function getFilters(): FilterCollection
  478. {
  479. return $this->filterCollection ??= new FilterCollection($this);
  480. }
  481. public function isFiltersStateClean(): bool
  482. {
  483. return $this->filterCollection === null || $this->filterCollection->isClean();
  484. }
  485. public function hasFilters(): bool
  486. {
  487. return $this->filterCollection !== null;
  488. }
  489. /**
  490. * @phpstan-param LockMode::* $lockMode
  491. *
  492. * @throws OptimisticLockException
  493. * @throws TransactionRequiredException
  494. */
  495. private function checkLockRequirements(LockMode|int $lockMode, ClassMetadata $class): void
  496. {
  497. switch ($lockMode) {
  498. case LockMode::OPTIMISTIC:
  499. if (! $class->isVersioned) {
  500. throw OptimisticLockException::notVersioned($class->name);
  501. }
  502. break;
  503. case LockMode::PESSIMISTIC_READ:
  504. case LockMode::PESSIMISTIC_WRITE:
  505. if (! $this->getConnection()->isTransactionActive()) {
  506. throw TransactionRequiredException::transactionRequired();
  507. }
  508. }
  509. }
  510. private function configureMetadataCache(): void
  511. {
  512. $metadataCache = $this->config->getMetadataCache();
  513. if (! $metadataCache) {
  514. return;
  515. }
  516. $this->metadataFactory->setCache($metadataCache);
  517. }
  518. private function createCustomHydrator(string $hydrationMode): AbstractHydrator
  519. {
  520. $class = $this->config->getCustomHydrationMode($hydrationMode);
  521. if ($class !== null) {
  522. return new $class($this);
  523. }
  524. throw InvalidHydrationMode::fromMode($hydrationMode);
  525. }
  526. }