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

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