vendor/doctrine/orm/lib/Doctrine/ORM/AbstractQuery.php line 1160
<?phpdeclare(strict_types=1);namespace Doctrine\ORM;use BackedEnum;use Countable;use Doctrine\Common\Cache\Psr6\CacheAdapter;use Doctrine\Common\Cache\Psr6\DoctrineProvider;use Doctrine\Common\Collections\ArrayCollection;use Doctrine\Common\Collections\Collection;use Doctrine\Common\Util\ClassUtils;use Doctrine\DBAL\Cache\QueryCacheProfile;use Doctrine\DBAL\Result;use Doctrine\Deprecations\Deprecation;use Doctrine\ORM\Cache\Exception\InvalidResultCacheDriver;use Doctrine\ORM\Cache\Logging\CacheLogger;use Doctrine\ORM\Cache\QueryCacheKey;use Doctrine\ORM\Cache\TimestampCacheKey;use Doctrine\ORM\Internal\Hydration\IterableResult;use Doctrine\ORM\Mapping\MappingException as ORMMappingException;use Doctrine\ORM\Query\Parameter;use Doctrine\ORM\Query\QueryException;use Doctrine\ORM\Query\ResultSetMapping;use Doctrine\Persistence\Mapping\MappingException;use LogicException;use Psr\Cache\CacheItemPoolInterface;use Traversable;use function array_map;use function array_shift;use function assert;use function count;use function func_num_args;use function in_array;use function is_array;use function is_numeric;use function is_object;use function is_scalar;use function is_string;use function iterator_count;use function iterator_to_array;use function ksort;use function method_exists;use function reset;use function serialize;use function sha1;/*** Base contract for ORM queries. Base class for Query and NativeQuery.** @link www.doctrine-project.org*/abstract class AbstractQuery{/* Hydration mode constants *//*** Hydrates an object graph. This is the default behavior.*/public const HYDRATE_OBJECT = 1;/*** Hydrates an array graph.*/public const HYDRATE_ARRAY = 2;/*** Hydrates a flat, rectangular result set with scalar values.*/public const HYDRATE_SCALAR = 3;/*** Hydrates a single scalar value.*/public const HYDRATE_SINGLE_SCALAR = 4;/*** Very simple object hydrator (optimized for performance).*/public const HYDRATE_SIMPLEOBJECT = 5;/*** Hydrates scalar column value.*/public const HYDRATE_SCALAR_COLUMN = 6;/*** The parameter map of this query.** @var ArrayCollection|Parameter[]* @psalm-var ArrayCollection<int, Parameter>*/protected $parameters;/*** The user-specified ResultSetMapping to use.** @var ResultSetMapping|null*/protected $_resultSetMapping;/*** The entity manager used by this query object.** @var EntityManagerInterface*/protected $_em;/*** The map of query hints.** @psalm-var array<string, mixed>*/protected $_hints = [];/*** The hydration mode.** @var string|int* @psalm-var string|AbstractQuery::HYDRATE_**/protected $_hydrationMode = self::HYDRATE_OBJECT;/** @var QueryCacheProfile|null */protected $_queryCacheProfile;/*** Whether or not expire the result cache.** @var bool*/protected $_expireResultCache = false;/** @var QueryCacheProfile|null */protected $_hydrationCacheProfile;/*** Whether to use second level cache, if available.** @var bool*/protected $cacheable = false;/** @var bool */protected $hasCache = false;/*** Second level cache region name.** @var string|null*/protected $cacheRegion;/*** Second level query cache mode.** @var int|null* @psalm-var Cache::MODE_*|null*/protected $cacheMode;/** @var CacheLogger|null */protected $cacheLogger;/** @var int */protected $lifetime = 0;/*** Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.*/public function __construct(EntityManagerInterface $em){$this->_em = $em;$this->parameters = new ArrayCollection();$this->_hints = $em->getConfiguration()->getDefaultQueryHints();$this->hasCache = $this->_em->getConfiguration()->isSecondLevelCacheEnabled();if ($this->hasCache) {$this->cacheLogger = $em->getConfiguration()->getSecondLevelCacheConfiguration()->getCacheLogger();}}/*** Enable/disable second level query (result) caching for this query.** @param bool $cacheable** @return $this*/public function setCacheable($cacheable){$this->cacheable = (bool) $cacheable;return $this;}/** @return bool TRUE if the query results are enabled for second level cache, FALSE otherwise. */public function isCacheable(){return $this->cacheable;}/*** @param string $cacheRegion** @return $this*/public function setCacheRegion($cacheRegion){$this->cacheRegion = (string) $cacheRegion;return $this;}/*** Obtain the name of the second level query cache region in which query results will be stored** @return string|null The cache region name; NULL indicates the default region.*/public function getCacheRegion(){return $this->cacheRegion;}/** @return bool TRUE if the query cache and second level cache are enabled, FALSE otherwise. */protected function isCacheEnabled(){return $this->cacheable && $this->hasCache;}/** @return int */public function getLifetime(){return $this->lifetime;}/*** Sets the life-time for this query into second level cache.** @param int $lifetime** @return $this*/public function setLifetime($lifetime){$this->lifetime = (int) $lifetime;return $this;}/*** @return int|null* @psalm-return Cache::MODE_*|null*/public function getCacheMode(){return $this->cacheMode;}/*** @param int $cacheMode* @psalm-param Cache::MODE_* $cacheMode** @return $this*/public function setCacheMode($cacheMode){$this->cacheMode = (int) $cacheMode;return $this;}/*** Gets the SQL query that corresponds to this query object.* The returned SQL syntax depends on the connection driver that is used* by this query object at the time of this method call.** @return list<string>|string SQL query*/abstract public function getSQL();/*** Retrieves the associated EntityManager of this Query instance.** @return EntityManagerInterface*/public function getEntityManager(){return $this->_em;}/*** Frees the resources used by the query object.** Resets Parameters, Parameter Types and Query Hints.** @return void*/public function free(){$this->parameters = new ArrayCollection();$this->_hints = $this->_em->getConfiguration()->getDefaultQueryHints();}/*** Get all defined parameters.** @return ArrayCollection The defined query parameters.* @psalm-return ArrayCollection<int, Parameter>*/public function getParameters(){return $this->parameters;}/*** Gets a query parameter.** @param int|string $key The key (index or name) of the bound parameter.** @return Parameter|null The value of the bound parameter, or NULL if not available.*/public function getParameter($key){$key = Query\Parameter::normalizeName($key);$filteredParameters = $this->parameters->filter(static function (Query\Parameter $parameter) use ($key): bool {$parameterName = $parameter->getName();return $key === $parameterName;});return ! $filteredParameters->isEmpty() ? $filteredParameters->first() : null;}/*** Sets a collection of query parameters.** @param ArrayCollection|mixed[] $parameters* @psalm-param ArrayCollection<int, Parameter>|mixed[] $parameters** @return $this*/public function setParameters($parameters){if (is_array($parameters)) {/** @psalm-var ArrayCollection<int, Parameter> $parameterCollection */$parameterCollection = new ArrayCollection();foreach ($parameters as $key => $value) {$parameterCollection->add(new Parameter($key, $value));}$parameters = $parameterCollection;}$this->parameters = $parameters;return $this;}/*** Sets a query parameter.** @param string|int $key The parameter position or name.* @param mixed $value The parameter value.* @param string|int|null $type The parameter type. If specified, the given value will be run through* the type conversion of this type. This is usually not needed for* strings and numeric types.** @return $this*/public function setParameter($key, $value, $type = null){$existingParameter = $this->getParameter($key);if ($existingParameter !== null) {$existingParameter->setValue($value, $type);return $this;}$this->parameters->add(new Parameter($key, $value, $type));return $this;}/*** Processes an individual parameter value.** @param mixed $value** @return mixed** @throws ORMInvalidArgumentException*/public function processParameterValue($value){if (is_scalar($value)) {return $value;}if ($value instanceof Collection) {$value = iterator_to_array($value);}if (is_array($value)) {$value = $this->processArrayParameterValue($value);return $value;}if ($value instanceof Mapping\ClassMetadata) {return $value->name;}if ($value instanceof BackedEnum) {return $value->value;}if (! is_object($value)) {return $value;}try {$class = ClassUtils::getClass($value);$value = $this->_em->getUnitOfWork()->getSingleIdentifierValue($value);if ($value === null) {throw ORMInvalidArgumentException::invalidIdentifierBindingEntity($class);}} catch (MappingException | ORMMappingException $e) {/* Silence any mapping exceptions. These can occur if the object inquestion is not a mapped entity, in which case we just don't doany preparation on the value.Depending on MappingDriver, either MappingException orORMMappingException is thrown. */$value = $this->potentiallyProcessIterable($value);}return $value;}/*** If no mapping is detected, trying to resolve the value as a Traversable** @param mixed $value** @return mixed*/private function potentiallyProcessIterable($value){if ($value instanceof Traversable) {$value = iterator_to_array($value);$value = $this->processArrayParameterValue($value);}return $value;}/*** Process a parameter value which was previously identified as an array** @param mixed[] $value** @return mixed[]*/private function processArrayParameterValue(array $value): array{foreach ($value as $key => $paramValue) {$paramValue = $this->processParameterValue($paramValue);$value[$key] = is_array($paramValue) ? reset($paramValue) : $paramValue;}return $value;}/*** Sets the ResultSetMapping that should be used for hydration.** @return $this*/public function setResultSetMapping(Query\ResultSetMapping $rsm){$this->translateNamespaces($rsm);$this->_resultSetMapping = $rsm;return $this;}/*** Gets the ResultSetMapping used for hydration.** @return ResultSetMapping|null*/protected function getResultSetMapping(){return $this->_resultSetMapping;}/*** Allows to translate entity namespaces to full qualified names.*/private function translateNamespaces(Query\ResultSetMapping $rsm): void{$translate = function ($alias): string {return $this->_em->getClassMetadata($alias)->getName();};$rsm->aliasMap = array_map($translate, $rsm->aliasMap);$rsm->declaringClasses = array_map($translate, $rsm->declaringClasses);}/*** Set a cache profile for hydration caching.** If no result cache driver is set in the QueryCacheProfile, the default* result cache driver is used from the configuration.** Important: Hydration caching does NOT register entities in the* UnitOfWork when retrieved from the cache. Never use result cached* entities for requests that also flush the EntityManager. If you want* some form of caching with UnitOfWork registration you should use* {@see AbstractQuery::setResultCacheProfile()}.** @return $this** @example* $lifetime = 100;* $resultKey = "abc";* $query->setHydrationCacheProfile(new QueryCacheProfile());* $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey));*/public function setHydrationCacheProfile(?QueryCacheProfile $profile = null){if ($profile === null) {if (func_num_args() < 1) {Deprecation::trigger('doctrine/orm','https://github.com/doctrine/orm/pull/9791','Calling %s without arguments is deprecated, pass null instead.',__METHOD__);}$this->_hydrationCacheProfile = null;return $this;}// DBAL 2if (! method_exists(QueryCacheProfile::class, 'setResultCache')) {if (! $profile->getResultCacheDriver()) {$defaultHydrationCacheImpl = $this->_em->getConfiguration()->getHydrationCache();if ($defaultHydrationCacheImpl) {$profile = $profile->setResultCacheDriver(DoctrineProvider::wrap($defaultHydrationCacheImpl));}}} elseif (! $profile->getResultCache()) {$defaultHydrationCacheImpl = $this->_em->getConfiguration()->getHydrationCache();if ($defaultHydrationCacheImpl) {$profile = $profile->setResultCache($defaultHydrationCacheImpl);}}$this->_hydrationCacheProfile = $profile;return $this;}/** @return QueryCacheProfile|null */public function getHydrationCacheProfile(){return $this->_hydrationCacheProfile;}/*** Set a cache profile for the result cache.** If no result cache driver is set in the QueryCacheProfile, the default* result cache driver is used from the configuration.** @return $this*/public function setResultCacheProfile(?QueryCacheProfile $profile = null){if ($profile === null) {if (func_num_args() < 1) {Deprecation::trigger('doctrine/orm','https://github.com/doctrine/orm/pull/9791','Calling %s without arguments is deprecated, pass null instead.',__METHOD__);}$this->_queryCacheProfile = null;return $this;}// DBAL 2if (! method_exists(QueryCacheProfile::class, 'setResultCache')) {if (! $profile->getResultCacheDriver()) {$defaultResultCacheDriver = $this->_em->getConfiguration()->getResultCache();if ($defaultResultCacheDriver) {$profile = $profile->setResultCacheDriver(DoctrineProvider::wrap($defaultResultCacheDriver));}}} elseif (! $profile->getResultCache()) {$defaultResultCache = $this->_em->getConfiguration()->getResultCache();if ($defaultResultCache) {$profile = $profile->setResultCache($defaultResultCache);}}$this->_queryCacheProfile = $profile;return $this;}/*** Defines a cache driver to be used for caching result sets and implicitly enables caching.** @deprecated Use {@see setResultCache()} instead.** @param \Doctrine\Common\Cache\Cache|null $resultCacheDriver Cache driver** @return $this** @throws InvalidResultCacheDriver*/public function setResultCacheDriver($resultCacheDriver = null){/** @phpstan-ignore-next-line */if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) {throw InvalidResultCacheDriver::create();}return $this->setResultCache($resultCacheDriver ? CacheAdapter::wrap($resultCacheDriver) : null);}/*** Defines a cache driver to be used for caching result sets and implicitly enables caching.** @return $this*/public function setResultCache(?CacheItemPoolInterface $resultCache = null){if ($resultCache === null) {if (func_num_args() < 1) {Deprecation::trigger('doctrine/orm','https://github.com/doctrine/orm/pull/9791','Calling %s without arguments is deprecated, pass null instead.',__METHOD__);}if ($this->_queryCacheProfile) {$this->_queryCacheProfile = new QueryCacheProfile($this->_queryCacheProfile->getLifetime(), $this->_queryCacheProfile->getCacheKey());}return $this;}// DBAL 2if (! method_exists(QueryCacheProfile::class, 'setResultCache')) {$resultCacheDriver = DoctrineProvider::wrap($resultCache);$this->_queryCacheProfile = $this->_queryCacheProfile? $this->_queryCacheProfile->setResultCacheDriver($resultCacheDriver): new QueryCacheProfile(0, null, $resultCacheDriver);return $this;}$this->_queryCacheProfile = $this->_queryCacheProfile? $this->_queryCacheProfile->setResultCache($resultCache): new QueryCacheProfile(0, null, $resultCache);return $this;}/*** Returns the cache driver used for caching result sets.** @deprecated** @return \Doctrine\Common\Cache\Cache Cache driver*/public function getResultCacheDriver(){if ($this->_queryCacheProfile && $this->_queryCacheProfile->getResultCacheDriver()) {return $this->_queryCacheProfile->getResultCacheDriver();}return $this->_em->getConfiguration()->getResultCacheImpl();}/*** Set whether or not to cache the results of this query and if so, for* how long and which ID to use for the cache entry.** @deprecated 2.7 Use {@see enableResultCache} and {@see disableResultCache} instead.** @param bool $useCache Whether or not to cache the results of this query.* @param int $lifetime How long the cache entry is valid, in seconds.* @param string $resultCacheId ID to use for the cache entry.** @return $this*/public function useResultCache($useCache, $lifetime = null, $resultCacheId = null){return $useCache? $this->enableResultCache($lifetime, $resultCacheId): $this->disableResultCache();}/*** Enables caching of the results of this query, for given or default amount of seconds* and optionally specifies which ID to use for the cache entry.** @param int|null $lifetime How long the cache entry is valid, in seconds.* @param string|null $resultCacheId ID to use for the cache entry.** @return $this*/public function enableResultCache(?int $lifetime = null, ?string $resultCacheId = null): self{$this->setResultCacheLifetime($lifetime);$this->setResultCacheId($resultCacheId);return $this;}/*** Disables caching of the results of this query.** @return $this*/public function disableResultCache(): self{$this->_queryCacheProfile = null;return $this;}/*** Defines how long the result cache will be active before expire.** @param int|null $lifetime How long the cache entry is valid, in seconds.** @return $this*/public function setResultCacheLifetime($lifetime){$lifetime = (int) $lifetime;if ($this->_queryCacheProfile) {$this->_queryCacheProfile = $this->_queryCacheProfile->setLifetime($lifetime);return $this;}$this->_queryCacheProfile = new QueryCacheProfile($lifetime);$cache = $this->_em->getConfiguration()->getResultCache();if (! $cache) {return $this;}// Compatibility for DBAL 2if (! method_exists($this->_queryCacheProfile, 'setResultCache')) {$this->_queryCacheProfile = $this->_queryCacheProfile->setResultCacheDriver(DoctrineProvider::wrap($cache));return $this;}$this->_queryCacheProfile = $this->_queryCacheProfile->setResultCache($cache);return $this;}/*** Retrieves the lifetime of resultset cache.** @deprecated** @return int*/public function getResultCacheLifetime(){return $this->_queryCacheProfile ? $this->_queryCacheProfile->getLifetime() : 0;}/*** Defines if the result cache is active or not.** @param bool $expire Whether or not to force resultset cache expiration.** @return $this*/public function expireResultCache($expire = true){$this->_expireResultCache = $expire;return $this;}/*** Retrieves if the resultset cache is active or not.** @return bool*/public function getExpireResultCache(){return $this->_expireResultCache;}/** @return QueryCacheProfile|null */public function getQueryCacheProfile(){return $this->_queryCacheProfile;}/*** Change the default fetch mode of an association for this query.** @param class-string $class* @param string $assocName* @param int $fetchMode* @psalm-param Mapping\ClassMetadata::FETCH_EAGER|Mapping\ClassMetadata::FETCH_LAZY $fetchMode** @return $this*/public function setFetchMode($class, $assocName, $fetchMode){if (! in_array($fetchMode, [Mapping\ClassMetadata::FETCH_EAGER, Mapping\ClassMetadata::FETCH_LAZY], true)) {Deprecation::trigger('doctrine/orm','https://github.com/doctrine/orm/pull/9777','Calling %s() with something else than ClassMetadata::FETCH_EAGER or ClassMetadata::FETCH_LAZY is deprecated.',__METHOD__);$fetchMode = Mapping\ClassMetadata::FETCH_LAZY;}$this->_hints['fetchMode'][$class][$assocName] = $fetchMode;return $this;}/*** Defines the processing mode to be used during hydration / result set transformation.** @param string|int $hydrationMode Doctrine processing mode to be used during hydration process.* One of the Query::HYDRATE_* constants.* @psalm-param string|AbstractQuery::HYDRATE_* $hydrationMode** @return $this*/public function setHydrationMode($hydrationMode){$this->_hydrationMode = $hydrationMode;return $this;}/*** Gets the hydration mode currently used by the query.** @return string|int* @psalm-return string|AbstractQuery::HYDRATE_**/public function getHydrationMode(){return $this->_hydrationMode;}/*** Gets the list of results for the query.** Alias for execute(null, $hydrationMode = HYDRATE_OBJECT).** @param string|int $hydrationMode* @psalm-param string|AbstractQuery::HYDRATE_* $hydrationMode** @return mixed*/public function getResult($hydrationMode = self::HYDRATE_OBJECT){return $this->execute(null, $hydrationMode);}/*** Gets the array of results for the query.** Alias for execute(null, HYDRATE_ARRAY).** @return mixed[]*/public function getArrayResult(){return $this->execute(null, self::HYDRATE_ARRAY);}/*** Gets one-dimensional array of results for the query.** Alias for execute(null, HYDRATE_SCALAR_COLUMN).** @return mixed[]*/public function getSingleColumnResult(){return $this->execute(null, self::HYDRATE_SCALAR_COLUMN);}/*** Gets the scalar results for the query.** Alias for execute(null, HYDRATE_SCALAR).** @return mixed[]*/public function getScalarResult(){return $this->execute(null, self::HYDRATE_SCALAR);}/*** Get exactly one result or null.** @param string|int|null $hydrationMode* @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode** @return mixed** @throws NonUniqueResultException*/public function getOneOrNullResult($hydrationMode = null){try {$result = $this->execute(null, $hydrationMode);} catch (NoResultException $e) {return null;}if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {return null;}if (! is_array($result)) {return $result;}if (count($result) > 1) {throw new NonUniqueResultException();}return array_shift($result);}/*** Gets the single result of the query.** Enforces the presence as well as the uniqueness of the result.** If the result is not unique, a NonUniqueResultException is thrown.* If there is no result, a NoResultException is thrown.** @param string|int|null $hydrationMode* @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode** @return mixed** @throws NonUniqueResultException If the query result is not unique.* @throws NoResultException If the query returned no result.*/public function getSingleResult($hydrationMode = null){$result = $this->execute(null, $hydrationMode);if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {throw new NoResultException();}if (! is_array($result)) {return $result;}if (count($result) > 1) {throw new NonUniqueResultException();}return array_shift($result);}/*** Gets the single scalar result of the query.** Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).** @return mixed The scalar result.** @throws NoResultException If the query returned no result.* @throws NonUniqueResultException If the query result is not unique.*/public function getSingleScalarResult(){return $this->getSingleResult(self::HYDRATE_SINGLE_SCALAR);}/*** Sets a query hint. If the hint name is not recognized, it is silently ignored.** @param string $name The name of the hint.* @param mixed $value The value of the hint.** @return $this*/public function setHint($name, $value){$this->_hints[$name] = $value;return $this;}/*** Gets the value of a query hint. If the hint name is not recognized, FALSE is returned.** @param string $name The name of the hint.** @return mixed The value of the hint or FALSE, if the hint name is not recognized.*/public function getHint($name){return $this->_hints[$name] ?? false;}/*** Check if the query has a hint** @param string $name The name of the hint** @return bool False if the query does not have any hint*/public function hasHint($name){return isset($this->_hints[$name]);}/*** Return the key value map of query hints that are currently set.** @return array<string,mixed>*/public function getHints(){return $this->_hints;}/*** Executes the query and returns an IterableResult that can be used to incrementally* iterate over the result.** @deprecated 2.8 Use {@see toIterable} instead. See https://github.com/doctrine/orm/issues/8463** @param ArrayCollection|mixed[]|null $parameters The query parameters.* @param string|int|null $hydrationMode The hydration mode to use.* @psalm-param ArrayCollection<int, Parameter>|array<string, mixed>|null $parameters* @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode The hydration mode to use.** @return IterableResult*/public function iterate($parameters = null, $hydrationMode = null){Deprecation::trigger('doctrine/orm','https://github.com/doctrine/orm/issues/8463','Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use toIterable() instead.',__METHOD__);if ($hydrationMode !== null) {$this->setHydrationMode($hydrationMode);}if (! empty($parameters)) {$this->setParameters($parameters);}$rsm = $this->getResultSetMapping();if ($rsm === null) {throw new LogicException('Uninitialized result set mapping.');}$stmt = $this->_doExecute();return $this->_em->newHydrator($this->_hydrationMode)->iterate($stmt, $rsm, $this->_hints);}/*** Executes the query and returns an iterable that can be used to incrementally* iterate over the result.** @param ArrayCollection|array|mixed[] $parameters The query parameters.* @param string|int|null $hydrationMode The hydration mode to use.* @psalm-param ArrayCollection<int, Parameter>|mixed[] $parameters* @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode** @return iterable<mixed>*/public function toIterable(iterable $parameters = [], $hydrationMode = null): iterable{if ($hydrationMode !== null) {$this->setHydrationMode($hydrationMode);}if (($this->isCountable($parameters) && count($parameters) !== 0)|| ($parameters instanceof Traversable && iterator_count($parameters) !== 0)) {$this->setParameters($parameters);}$rsm = $this->getResultSetMapping();if ($rsm === null) {throw new LogicException('Uninitialized result set mapping.');}if ($rsm->isMixed && count($rsm->scalarMappings) > 0) {throw QueryException::iterateWithMixedResultNotAllowed();}$stmt = $this->_doExecute();return $this->_em->newHydrator($this->_hydrationMode)->toIterable($stmt, $rsm, $this->_hints);}/*** Executes the query.** @param ArrayCollection|mixed[]|null $parameters Query parameters.* @param string|int|null $hydrationMode Processing mode to be used during the hydration process.* @psalm-param ArrayCollection<int, Parameter>|mixed[]|null $parameters* @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode** @return mixed*/public function execute($parameters = null, $hydrationMode = null){if ($this->cacheable && $this->isCacheEnabled()) {return $this->executeUsingQueryCache($parameters, $hydrationMode);}return $this->executeIgnoreQueryCache($parameters, $hydrationMode);}/*** Execute query ignoring second level cache.** @param ArrayCollection|mixed[]|null $parameters* @param string|int|null $hydrationMode* @psalm-param ArrayCollection<int, Parameter>|mixed[]|null $parameters* @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode** @return mixed*/private function executeIgnoreQueryCache($parameters = null, $hydrationMode = null){if ($hydrationMode !== null) {$this->setHydrationMode($hydrationMode);}if (! empty($parameters)) {$this->setParameters($parameters);}$setCacheEntry = static function ($data): void {};if ($this->_hydrationCacheProfile !== null) {[$cacheKey, $realCacheKey] = $this->getHydrationCacheId();$cache = $this->getHydrationCache();$cacheItem = $cache->getItem($cacheKey);$result = $cacheItem->isHit() ? $cacheItem->get() : [];if (isset($result[$realCacheKey])) {return $result[$realCacheKey];}if (! $result) {$result = [];}$setCacheEntry = static function ($data) use ($cache, $result, $cacheItem, $realCacheKey): void {$cache->save($cacheItem->set($result + [$realCacheKey => $data]));};}$stmt = $this->_doExecute();if (is_numeric($stmt)) {$setCacheEntry($stmt);return $stmt;}$rsm = $this->getResultSetMapping();if ($rsm === null) {throw new LogicException('Uninitialized result set mapping.');}$data = $this->_em->newHydrator($this->_hydrationMode)->hydrateAll($stmt, $rsm, $this->_hints);$setCacheEntry($data);return $data;}private function getHydrationCache(): CacheItemPoolInterface{assert($this->_hydrationCacheProfile !== null);// Support for DBAL 2if (! method_exists($this->_hydrationCacheProfile, 'getResultCache')) {$cacheDriver = $this->_hydrationCacheProfile->getResultCacheDriver();assert($cacheDriver !== null);return CacheAdapter::wrap($cacheDriver);}$cache = $this->_hydrationCacheProfile->getResultCache();assert($cache !== null);return $cache;}/*** Load from second level cache or executes the query and put into cache.** @param ArrayCollection|mixed[]|null $parameters* @param string|int|null $hydrationMode* @psalm-param ArrayCollection<int, Parameter>|mixed[]|null $parameters* @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode** @return mixed*/private function executeUsingQueryCache($parameters = null, $hydrationMode = null){$rsm = $this->getResultSetMapping();if ($rsm === null) {throw new LogicException('Uninitialized result set mapping.');}$queryCache = $this->_em->getCache()->getQueryCache($this->cacheRegion);$queryKey = new QueryCacheKey($this->getHash(),$this->lifetime,$this->cacheMode ?: Cache::MODE_NORMAL,$this->getTimestampKey());$result = $queryCache->get($queryKey, $rsm, $this->_hints);if ($result !== null) {if ($this->cacheLogger) {$this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $queryKey);}return $result;}$result = $this->executeIgnoreQueryCache($parameters, $hydrationMode);$cached = $queryCache->put($queryKey, $rsm, $result, $this->_hints);if ($this->cacheLogger) {$this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $queryKey);if ($cached) {$this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $queryKey);}}return $result;}private function getTimestampKey(): ?TimestampCacheKey{assert($this->_resultSetMapping !== null);$entityName = reset($this->_resultSetMapping->aliasMap);if (empty($entityName)) {return null;}$metadata = $this->_em->getClassMetadata($entityName);return new Cache\TimestampCacheKey($metadata->rootEntityName);}/*** Get the result cache id to use to store the result set cache entry.* Will return the configured id if it exists otherwise a hash will be* automatically generated for you.** @return string[] ($key, $hash)* @psalm-return array{string, string} ($key, $hash)*/protected function getHydrationCacheId(){$parameters = [];$types = [];foreach ($this->getParameters() as $parameter) {$parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());$types[$parameter->getName()] = $parameter->getType();}$sql = $this->getSQL();assert(is_string($sql));$queryCacheProfile = $this->getHydrationCacheProfile();$hints = $this->getHints();$hints['hydrationMode'] = $this->getHydrationMode();ksort($hints);assert($queryCacheProfile !== null);return $queryCacheProfile->generateCacheKeys($sql, $parameters, $types, $hints);}/*** Set the result cache id to use to store the result set cache entry.* If this is not explicitly set by the developer then a hash is automatically* generated for you.** @param string|null $id** @return $this*/public function setResultCacheId($id){if (! $this->_queryCacheProfile) {return $this->setResultCacheProfile(new QueryCacheProfile(0, $id));}$this->_queryCacheProfile = $this->_queryCacheProfile->setCacheKey($id);return $this;}/*** Get the result cache id to use to store the result set cache entry if set.** @deprecated** @return string|null*/public function getResultCacheId(){return $this->_queryCacheProfile ? $this->_queryCacheProfile->getCacheKey() : null;}/*** Executes the query and returns a the resulting Statement object.** @return Result|int The executed database statement that holds* the results, or an integer indicating how* many rows were affected.*/abstract protected function _doExecute();/*** Cleanup Query resource when clone is called.** @return void*/public function __clone(){$this->parameters = new ArrayCollection();$this->_hints = [];$this->_hints = $this->_em->getConfiguration()->getDefaultQueryHints();}/*** Generates a string of currently query to use for the cache second level cache.** @return string*/protected function getHash(){$query = $this->getSQL();assert(is_string($query));$hints = $this->getHints();$params = array_map(function (Parameter $parameter) {$value = $parameter->getValue();// Small optimization// Does not invoke processParameterValue for scalar valueif (is_scalar($value)) {return $value;}return $this->processParameterValue($value);}, $this->parameters->getValues());ksort($hints);return sha1($query . '-' . serialize($params) . '-' . serialize($hints));}/** @param iterable<mixed> $subject */private function isCountable(iterable $subject): bool{return $subject instanceof Countable || is_array($subject);}}