<?php 
 
declare(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\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\Proxy\DefaultProxyClassNameResolver; 
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 = DefaultProxyClassNameResolver::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 in 
               question is not a mapped entity, in which case we just don't do 
               any preparation on the value. 
               Depending on MappingDriver, either MappingException or 
               ORMMappingException 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 2 
        if (! 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 2 
        if (! 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 2 
        if (! 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 2 
        if (! 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 bool|float|int|string|null 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 2 
        if (! 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 value 
            if (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); 
    } 
}