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

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