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

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