vendor/doctrine/orm/src/AbstractQuery.php line 959

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Doctrine\ORM;
  4. use BackedEnum;
  5. use Countable;
  6. use Doctrine\Common\Cache\Psr6\CacheAdapter;
  7. use Doctrine\Common\Cache\Psr6\DoctrineProvider;
  8. use Doctrine\Common\Collections\ArrayCollection;
  9. use Doctrine\Common\Collections\Collection;
  10. use Doctrine\DBAL\Cache\QueryCacheProfile;
  11. use Doctrine\DBAL\Result;
  12. use Doctrine\Deprecations\Deprecation;
  13. use Doctrine\ORM\Cache\Exception\InvalidResultCacheDriver;
  14. use Doctrine\ORM\Cache\Logging\CacheLogger;
  15. use Doctrine\ORM\Cache\QueryCacheKey;
  16. use Doctrine\ORM\Cache\TimestampCacheKey;
  17. use Doctrine\ORM\Internal\Hydration\IterableResult;
  18. use Doctrine\ORM\Mapping\MappingException as ORMMappingException;
  19. use Doctrine\ORM\Proxy\DefaultProxyClassNameResolver;
  20. use Doctrine\ORM\Query\Parameter;
  21. use Doctrine\ORM\Query\QueryException;
  22. use Doctrine\ORM\Query\ResultSetMapping;
  23. use Doctrine\Persistence\Mapping\MappingException;
  24. use LogicException;
  25. use Psr\Cache\CacheItemPoolInterface;
  26. use Traversable;
  27. use function array_map;
  28. use function array_shift;
  29. use function assert;
  30. use function count;
  31. use function func_num_args;
  32. use function in_array;
  33. use function is_array;
  34. use function is_numeric;
  35. use function is_object;
  36. use function is_scalar;
  37. use function is_string;
  38. use function iterator_count;
  39. use function iterator_to_array;
  40. use function ksort;
  41. use function method_exists;
  42. use function reset;
  43. use function serialize;
  44. use function sha1;
  45. /**
  46.  * Base contract for ORM queries. Base class for Query and NativeQuery.
  47.  *
  48.  * @link    www.doctrine-project.org
  49.  */
  50. abstract class AbstractQuery
  51. {
  52.     /* Hydration mode constants */
  53.     /**
  54.      * Hydrates an object graph. This is the default behavior.
  55.      */
  56.     public const HYDRATE_OBJECT 1;
  57.     /**
  58.      * Hydrates an array graph.
  59.      */
  60.     public const HYDRATE_ARRAY 2;
  61.     /**
  62.      * Hydrates a flat, rectangular result set with scalar values.
  63.      */
  64.     public const HYDRATE_SCALAR 3;
  65.     /**
  66.      * Hydrates a single scalar value.
  67.      */
  68.     public const HYDRATE_SINGLE_SCALAR 4;
  69.     /**
  70.      * Very simple object hydrator (optimized for performance).
  71.      */
  72.     public const HYDRATE_SIMPLEOBJECT 5;
  73.     /**
  74.      * Hydrates scalar column value.
  75.      */
  76.     public const HYDRATE_SCALAR_COLUMN 6;
  77.     /**
  78.      * The parameter map of this query.
  79.      *
  80.      * @var ArrayCollection|Parameter[]
  81.      * @psalm-var ArrayCollection<int, Parameter>
  82.      */
  83.     protected $parameters;
  84.     /**
  85.      * The user-specified ResultSetMapping to use.
  86.      *
  87.      * @var ResultSetMapping|null
  88.      */
  89.     protected $_resultSetMapping;
  90.     /**
  91.      * The entity manager used by this query object.
  92.      *
  93.      * @var EntityManagerInterface
  94.      */
  95.     protected $_em;
  96.     /**
  97.      * The map of query hints.
  98.      *
  99.      * @psalm-var array<string, mixed>
  100.      */
  101.     protected $_hints = [];
  102.     /**
  103.      * The hydration mode.
  104.      *
  105.      * @var string|int
  106.      * @psalm-var string|AbstractQuery::HYDRATE_*
  107.      */
  108.     protected $_hydrationMode self::HYDRATE_OBJECT;
  109.     /** @var QueryCacheProfile|null */
  110.     protected $_queryCacheProfile;
  111.     /**
  112.      * Whether or not expire the result cache.
  113.      *
  114.      * @var bool
  115.      */
  116.     protected $_expireResultCache false;
  117.     /** @var QueryCacheProfile|null */
  118.     protected $_hydrationCacheProfile;
  119.     /**
  120.      * Whether to use second level cache, if available.
  121.      *
  122.      * @var bool
  123.      */
  124.     protected $cacheable false;
  125.     /** @var bool */
  126.     protected $hasCache false;
  127.     /**
  128.      * Second level cache region name.
  129.      *
  130.      * @var string|null
  131.      */
  132.     protected $cacheRegion;
  133.     /**
  134.      * Second level query cache mode.
  135.      *
  136.      * @var int|null
  137.      * @psalm-var Cache::MODE_*|null
  138.      */
  139.     protected $cacheMode;
  140.     /** @var CacheLogger|null */
  141.     protected $cacheLogger;
  142.     /** @var int */
  143.     protected $lifetime 0;
  144.     /**
  145.      * Initializes a new instance of a class derived from <tt>AbstractQuery</tt>.
  146.      */
  147.     public function __construct(EntityManagerInterface $em)
  148.     {
  149.         $this->_em        $em;
  150.         $this->parameters = new ArrayCollection();
  151.         $this->_hints     $em->getConfiguration()->getDefaultQueryHints();
  152.         $this->hasCache   $this->_em->getConfiguration()->isSecondLevelCacheEnabled();
  153.         if ($this->hasCache) {
  154.             $this->cacheLogger $em->getConfiguration()
  155.                 ->getSecondLevelCacheConfiguration()
  156.                 ->getCacheLogger();
  157.         }
  158.     }
  159.     /**
  160.      * Enable/disable second level query (result) caching for this query.
  161.      *
  162.      * @param bool $cacheable
  163.      *
  164.      * @return $this
  165.      */
  166.     public function setCacheable($cacheable)
  167.     {
  168.         $this->cacheable = (bool) $cacheable;
  169.         return $this;
  170.     }
  171.     /** @return bool TRUE if the query results are enabled for second level cache, FALSE otherwise. */
  172.     public function isCacheable()
  173.     {
  174.         return $this->cacheable;
  175.     }
  176.     /**
  177.      * @param string $cacheRegion
  178.      *
  179.      * @return $this
  180.      */
  181.     public function setCacheRegion($cacheRegion)
  182.     {
  183.         $this->cacheRegion = (string) $cacheRegion;
  184.         return $this;
  185.     }
  186.     /**
  187.      * Obtain the name of the second level query cache region in which query results will be stored
  188.      *
  189.      * @return string|null The cache region name; NULL indicates the default region.
  190.      */
  191.     public function getCacheRegion()
  192.     {
  193.         return $this->cacheRegion;
  194.     }
  195.     /** @return bool TRUE if the query cache and second level cache are enabled, FALSE otherwise. */
  196.     protected function isCacheEnabled()
  197.     {
  198.         return $this->cacheable && $this->hasCache;
  199.     }
  200.     /** @return int */
  201.     public function getLifetime()
  202.     {
  203.         return $this->lifetime;
  204.     }
  205.     /**
  206.      * Sets the life-time for this query into second level cache.
  207.      *
  208.      * @param int $lifetime
  209.      *
  210.      * @return $this
  211.      */
  212.     public function setLifetime($lifetime)
  213.     {
  214.         $this->lifetime = (int) $lifetime;
  215.         return $this;
  216.     }
  217.     /**
  218.      * @return int|null
  219.      * @psalm-return Cache::MODE_*|null
  220.      */
  221.     public function getCacheMode()
  222.     {
  223.         return $this->cacheMode;
  224.     }
  225.     /**
  226.      * @param int $cacheMode
  227.      * @psalm-param Cache::MODE_* $cacheMode
  228.      *
  229.      * @return $this
  230.      */
  231.     public function setCacheMode($cacheMode)
  232.     {
  233.         $this->cacheMode = (int) $cacheMode;
  234.         return $this;
  235.     }
  236.     /**
  237.      * Gets the SQL query that corresponds to this query object.
  238.      * The returned SQL syntax depends on the connection driver that is used
  239.      * by this query object at the time of this method call.
  240.      *
  241.      * @return list<string>|string SQL query
  242.      */
  243.     abstract public function getSQL();
  244.     /**
  245.      * Retrieves the associated EntityManager of this Query instance.
  246.      *
  247.      * @return EntityManagerInterface
  248.      */
  249.     public function getEntityManager()
  250.     {
  251.         return $this->_em;
  252.     }
  253.     /**
  254.      * Frees the resources used by the query object.
  255.      *
  256.      * Resets Parameters, Parameter Types and Query Hints.
  257.      *
  258.      * @return void
  259.      */
  260.     public function free()
  261.     {
  262.         $this->parameters = new ArrayCollection();
  263.         $this->_hints $this->_em->getConfiguration()->getDefaultQueryHints();
  264.     }
  265.     /**
  266.      * Get all defined parameters.
  267.      *
  268.      * @return ArrayCollection The defined query parameters.
  269.      * @psalm-return ArrayCollection<int, Parameter>
  270.      */
  271.     public function getParameters()
  272.     {
  273.         return $this->parameters;
  274.     }
  275.     /**
  276.      * Gets a query parameter.
  277.      *
  278.      * @param int|string $key The key (index or name) of the bound parameter.
  279.      *
  280.      * @return Parameter|null The value of the bound parameter, or NULL if not available.
  281.      */
  282.     public function getParameter($key)
  283.     {
  284.         $key Query\Parameter::normalizeName($key);
  285.         $filteredParameters $this->parameters->filter(
  286.             static function (Query\Parameter $parameter) use ($key): bool {
  287.                 $parameterName $parameter->getName();
  288.                 return $key === $parameterName;
  289.             }
  290.         );
  291.         return ! $filteredParameters->isEmpty() ? $filteredParameters->first() : null;
  292.     }
  293.     /**
  294.      * Sets a collection of query parameters.
  295.      *
  296.      * @param ArrayCollection|mixed[] $parameters
  297.      * @psalm-param ArrayCollection<int, Parameter>|mixed[] $parameters
  298.      *
  299.      * @return $this
  300.      */
  301.     public function setParameters($parameters)
  302.     {
  303.         if (is_array($parameters)) {
  304.             /** @psalm-var ArrayCollection<int, Parameter> $parameterCollection */
  305.             $parameterCollection = new ArrayCollection();
  306.             foreach ($parameters as $key => $value) {
  307.                 $parameterCollection->add(new Parameter($key$value));
  308.             }
  309.             $parameters $parameterCollection;
  310.         }
  311.         $this->parameters $parameters;
  312.         return $this;
  313.     }
  314.     /**
  315.      * Sets a query parameter.
  316.      *
  317.      * @param string|int      $key   The parameter position or name.
  318.      * @param mixed           $value The parameter value.
  319.      * @param string|int|null $type  The parameter type. If specified, the given value will be run through
  320.      *                               the type conversion of this type. This is usually not needed for
  321.      *                               strings and numeric types.
  322.      *
  323.      * @return $this
  324.      */
  325.     public function setParameter($key$value$type null)
  326.     {
  327.         $existingParameter $this->getParameter($key);
  328.         if ($existingParameter !== null) {
  329.             $existingParameter->setValue($value$type);
  330.             return $this;
  331.         }
  332.         $this->parameters->add(new Parameter($key$value$type));
  333.         return $this;
  334.     }
  335.     /**
  336.      * Processes an individual parameter value.
  337.      *
  338.      * @param mixed $value
  339.      *
  340.      * @return mixed
  341.      *
  342.      * @throws ORMInvalidArgumentException
  343.      */
  344.     public function processParameterValue($value)
  345.     {
  346.         if (is_scalar($value)) {
  347.             return $value;
  348.         }
  349.         if ($value instanceof Collection) {
  350.             $value iterator_to_array($value);
  351.         }
  352.         if (is_array($value)) {
  353.             $value $this->processArrayParameterValue($value);
  354.             return $value;
  355.         }
  356.         if ($value instanceof Mapping\ClassMetadata) {
  357.             return $value->name;
  358.         }
  359.         if ($value instanceof BackedEnum) {
  360.             return $value->value;
  361.         }
  362.         if (! is_object($value)) {
  363.             return $value;
  364.         }
  365.         try {
  366.             $class DefaultProxyClassNameResolver::getClass($value);
  367.             $value $this->_em->getUnitOfWork()->getSingleIdentifierValue($value);
  368.             if ($value === null) {
  369.                 throw ORMInvalidArgumentException::invalidIdentifierBindingEntity($class);
  370.             }
  371.         } catch (MappingException ORMMappingException $e) {
  372.             /* Silence any mapping exceptions. These can occur if the object in
  373.                question is not a mapped entity, in which case we just don't do
  374.                any preparation on the value.
  375.                Depending on MappingDriver, either MappingException or
  376.                ORMMappingException is thrown. */
  377.             $value $this->potentiallyProcessIterable($value);
  378.         }
  379.         return $value;
  380.     }
  381.     /**
  382.      * If no mapping is detected, trying to resolve the value as a Traversable
  383.      *
  384.      * @param mixed $value
  385.      *
  386.      * @return mixed
  387.      */
  388.     private function potentiallyProcessIterable($value)
  389.     {
  390.         if ($value instanceof Traversable) {
  391.             $value iterator_to_array($value);
  392.             $value $this->processArrayParameterValue($value);
  393.         }
  394.         return $value;
  395.     }
  396.     /**
  397.      * Process a parameter value which was previously identified as an array
  398.      *
  399.      * @param mixed[] $value
  400.      *
  401.      * @return mixed[]
  402.      */
  403.     private function processArrayParameterValue(array $value): array
  404.     {
  405.         foreach ($value as $key => $paramValue) {
  406.             $paramValue  $this->processParameterValue($paramValue);
  407.             $value[$key] = is_array($paramValue) ? reset($paramValue) : $paramValue;
  408.         }
  409.         return $value;
  410.     }
  411.     /**
  412.      * Sets the ResultSetMapping that should be used for hydration.
  413.      *
  414.      * @return $this
  415.      */
  416.     public function setResultSetMapping(Query\ResultSetMapping $rsm)
  417.     {
  418.         $this->translateNamespaces($rsm);
  419.         $this->_resultSetMapping $rsm;
  420.         return $this;
  421.     }
  422.     /**
  423.      * Gets the ResultSetMapping used for hydration.
  424.      *
  425.      * @return ResultSetMapping|null
  426.      */
  427.     protected function getResultSetMapping()
  428.     {
  429.         return $this->_resultSetMapping;
  430.     }
  431.     /**
  432.      * Allows to translate entity namespaces to full qualified names.
  433.      */
  434.     private function translateNamespaces(Query\ResultSetMapping $rsm): void
  435.     {
  436.         $translate = function ($alias): string {
  437.             return $this->_em->getClassMetadata($alias)->getName();
  438.         };
  439.         $rsm->aliasMap         array_map($translate$rsm->aliasMap);
  440.         $rsm->declaringClasses array_map($translate$rsm->declaringClasses);
  441.     }
  442.     /**
  443.      * Set a cache profile for hydration caching.
  444.      *
  445.      * If no result cache driver is set in the QueryCacheProfile, the default
  446.      * result cache driver is used from the configuration.
  447.      *
  448.      * Important: Hydration caching does NOT register entities in the
  449.      * UnitOfWork when retrieved from the cache. Never use result cached
  450.      * entities for requests that also flush the EntityManager. If you want
  451.      * some form of caching with UnitOfWork registration you should use
  452.      * {@see AbstractQuery::setResultCacheProfile()}.
  453.      *
  454.      * @return $this
  455.      *
  456.      * @example
  457.      * $lifetime = 100;
  458.      * $resultKey = "abc";
  459.      * $query->setHydrationCacheProfile(new QueryCacheProfile());
  460.      * $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey));
  461.      */
  462.     public function setHydrationCacheProfile(?QueryCacheProfile $profile null)
  463.     {
  464.         if ($profile === null) {
  465.             if (func_num_args() < 1) {
  466.                 Deprecation::trigger(
  467.                     'doctrine/orm',
  468.                     'https://github.com/doctrine/orm/pull/9791',
  469.                     'Calling %s without arguments is deprecated, pass null instead.',
  470.                     __METHOD__
  471.                 );
  472.             }
  473.             $this->_hydrationCacheProfile null;
  474.             return $this;
  475.         }
  476.         // DBAL 2
  477.         if (! method_exists(QueryCacheProfile::class, 'setResultCache')) {
  478.             // @phpstan-ignore method.deprecated
  479.             if (! $profile->getResultCacheDriver()) {
  480.                 $defaultHydrationCacheImpl $this->_em->getConfiguration()->getHydrationCache();
  481.                 if ($defaultHydrationCacheImpl) {
  482.                     // @phpstan-ignore method.deprecated
  483.                     $profile $profile->setResultCacheDriver(DoctrineProvider::wrap($defaultHydrationCacheImpl));
  484.                 }
  485.             }
  486.         } elseif (! $profile->getResultCache()) {
  487.             $defaultHydrationCacheImpl $this->_em->getConfiguration()->getHydrationCache();
  488.             if ($defaultHydrationCacheImpl) {
  489.                 $profile $profile->setResultCache($defaultHydrationCacheImpl);
  490.             }
  491.         }
  492.         $this->_hydrationCacheProfile $profile;
  493.         return $this;
  494.     }
  495.     /** @return QueryCacheProfile|null */
  496.     public function getHydrationCacheProfile()
  497.     {
  498.         return $this->_hydrationCacheProfile;
  499.     }
  500.     /**
  501.      * Set a cache profile for the result cache.
  502.      *
  503.      * If no result cache driver is set in the QueryCacheProfile, the default
  504.      * result cache driver is used from the configuration.
  505.      *
  506.      * @return $this
  507.      */
  508.     public function setResultCacheProfile(?QueryCacheProfile $profile null)
  509.     {
  510.         if ($profile === null) {
  511.             if (func_num_args() < 1) {
  512.                 Deprecation::trigger(
  513.                     'doctrine/orm',
  514.                     'https://github.com/doctrine/orm/pull/9791',
  515.                     'Calling %s without arguments is deprecated, pass null instead.',
  516.                     __METHOD__
  517.                 );
  518.             }
  519.             $this->_queryCacheProfile null;
  520.             return $this;
  521.         }
  522.         // DBAL 2
  523.         if (! method_exists(QueryCacheProfile::class, 'setResultCache')) {
  524.             // @phpstan-ignore method.deprecated
  525.             if (! $profile->getResultCacheDriver()) {
  526.                 $defaultResultCacheDriver $this->_em->getConfiguration()->getResultCache();
  527.                 if ($defaultResultCacheDriver) {
  528.                     // @phpstan-ignore method.deprecated
  529.                     $profile $profile->setResultCacheDriver(DoctrineProvider::wrap($defaultResultCacheDriver));
  530.                 }
  531.             }
  532.         } elseif (! $profile->getResultCache()) {
  533.             $defaultResultCache $this->_em->getConfiguration()->getResultCache();
  534.             if ($defaultResultCache) {
  535.                 $profile $profile->setResultCache($defaultResultCache);
  536.             }
  537.         }
  538.         $this->_queryCacheProfile $profile;
  539.         return $this;
  540.     }
  541.     /**
  542.      * Defines a cache driver to be used for caching result sets and implicitly enables caching.
  543.      *
  544.      * @deprecated Use {@see setResultCache()} instead.
  545.      *
  546.      * @param \Doctrine\Common\Cache\Cache|null $resultCacheDriver Cache driver
  547.      *
  548.      * @return $this
  549.      *
  550.      * @throws InvalidResultCacheDriver
  551.      */
  552.     public function setResultCacheDriver($resultCacheDriver null)
  553.     {
  554.         /** @phpstan-ignore-next-line */
  555.         if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) {
  556.             throw InvalidResultCacheDriver::create();
  557.         }
  558.         return $this->setResultCache($resultCacheDriver CacheAdapter::wrap($resultCacheDriver) : null);
  559.     }
  560.     /**
  561.      * Defines a cache driver to be used for caching result sets and implicitly enables caching.
  562.      *
  563.      * @return $this
  564.      */
  565.     public function setResultCache(?CacheItemPoolInterface $resultCache null)
  566.     {
  567.         if ($resultCache === null) {
  568.             if (func_num_args() < 1) {
  569.                 Deprecation::trigger(
  570.                     'doctrine/orm',
  571.                     'https://github.com/doctrine/orm/pull/9791',
  572.                     'Calling %s without arguments is deprecated, pass null instead.',
  573.                     __METHOD__
  574.                 );
  575.             }
  576.             if ($this->_queryCacheProfile) {
  577.                 $this->_queryCacheProfile = new QueryCacheProfile($this->_queryCacheProfile->getLifetime(), $this->_queryCacheProfile->getCacheKey());
  578.             }
  579.             return $this;
  580.         }
  581.         // DBAL 2
  582.         if (! method_exists(QueryCacheProfile::class, 'setResultCache')) {
  583.             $resultCacheDriver DoctrineProvider::wrap($resultCache);
  584.             $this->_queryCacheProfile $this->_queryCacheProfile
  585.                 // @phpstan-ignore method.deprecated
  586.                 $this->_queryCacheProfile->setResultCacheDriver($resultCacheDriver)
  587.                 : new QueryCacheProfile(0null$resultCacheDriver);
  588.             return $this;
  589.         }
  590.         $this->_queryCacheProfile $this->_queryCacheProfile
  591.             $this->_queryCacheProfile->setResultCache($resultCache)
  592.             : new QueryCacheProfile(0null$resultCache);
  593.         return $this;
  594.     }
  595.     /**
  596.      * Returns the cache driver used for caching result sets.
  597.      *
  598.      * @deprecated
  599.      *
  600.      * @return \Doctrine\Common\Cache\Cache Cache driver
  601.      */
  602.     public function getResultCacheDriver()
  603.     {
  604.         if ($this->_queryCacheProfile && $this->_queryCacheProfile->getResultCacheDriver()) {
  605.             return $this->_queryCacheProfile->getResultCacheDriver();
  606.         }
  607.         return $this->_em->getConfiguration()->getResultCacheImpl();
  608.     }
  609.     /**
  610.      * Set whether or not to cache the results of this query and if so, for
  611.      * how long and which ID to use for the cache entry.
  612.      *
  613.      * @deprecated 2.7 Use {@see enableResultCache} and {@see disableResultCache} instead.
  614.      *
  615.      * @param bool   $useCache      Whether or not to cache the results of this query.
  616.      * @param int    $lifetime      How long the cache entry is valid, in seconds.
  617.      * @param string $resultCacheId ID to use for the cache entry.
  618.      *
  619.      * @return $this
  620.      */
  621.     public function useResultCache($useCache$lifetime null$resultCacheId null)
  622.     {
  623.         return $useCache
  624.             $this->enableResultCache($lifetime$resultCacheId)
  625.             : $this->disableResultCache();
  626.     }
  627.     /**
  628.      * Enables caching of the results of this query, for given or default amount of seconds
  629.      * and optionally specifies which ID to use for the cache entry.
  630.      *
  631.      * @param int|null    $lifetime      How long the cache entry is valid, in seconds.
  632.      * @param string|null $resultCacheId ID to use for the cache entry.
  633.      *
  634.      * @return $this
  635.      */
  636.     public function enableResultCache(?int $lifetime null, ?string $resultCacheId null): self
  637.     {
  638.         $this->setResultCacheLifetime($lifetime);
  639.         $this->setResultCacheId($resultCacheId);
  640.         return $this;
  641.     }
  642.     /**
  643.      * Disables caching of the results of this query.
  644.      *
  645.      * @return $this
  646.      */
  647.     public function disableResultCache(): self
  648.     {
  649.         $this->_queryCacheProfile null;
  650.         return $this;
  651.     }
  652.     /**
  653.      * Defines how long the result cache will be active before expire.
  654.      *
  655.      * @param int|null $lifetime How long the cache entry is valid, in seconds.
  656.      *
  657.      * @return $this
  658.      */
  659.     public function setResultCacheLifetime($lifetime)
  660.     {
  661.         $lifetime = (int) $lifetime;
  662.         if ($this->_queryCacheProfile) {
  663.             $this->_queryCacheProfile $this->_queryCacheProfile->setLifetime($lifetime);
  664.             return $this;
  665.         }
  666.         $this->_queryCacheProfile = new QueryCacheProfile($lifetime);
  667.         $cache $this->_em->getConfiguration()->getResultCache();
  668.         if (! $cache) {
  669.             return $this;
  670.         }
  671.         // Compatibility for DBAL 2
  672.         if (! method_exists($this->_queryCacheProfile'setResultCache')) {
  673.             // @phpstan-ignore method.deprecated
  674.             $this->_queryCacheProfile $this->_queryCacheProfile->setResultCacheDriver(DoctrineProvider::wrap($cache));
  675.             return $this;
  676.         }
  677.         $this->_queryCacheProfile $this->_queryCacheProfile->setResultCache($cache);
  678.         return $this;
  679.     }
  680.     /**
  681.      * Retrieves the lifetime of resultset cache.
  682.      *
  683.      * @deprecated
  684.      *
  685.      * @return int
  686.      */
  687.     public function getResultCacheLifetime()
  688.     {
  689.         return $this->_queryCacheProfile $this->_queryCacheProfile->getLifetime() : 0;
  690.     }
  691.     /**
  692.      * Defines if the result cache is active or not.
  693.      *
  694.      * @param bool $expire Whether or not to force resultset cache expiration.
  695.      *
  696.      * @return $this
  697.      */
  698.     public function expireResultCache($expire true)
  699.     {
  700.         $this->_expireResultCache $expire;
  701.         return $this;
  702.     }
  703.     /**
  704.      * Retrieves if the resultset cache is active or not.
  705.      *
  706.      * @return bool
  707.      */
  708.     public function getExpireResultCache()
  709.     {
  710.         return $this->_expireResultCache;
  711.     }
  712.     /** @return QueryCacheProfile|null */
  713.     public function getQueryCacheProfile()
  714.     {
  715.         return $this->_queryCacheProfile;
  716.     }
  717.     /**
  718.      * Change the default fetch mode of an association for this query.
  719.      *
  720.      * @param class-string $class
  721.      * @param string       $assocName
  722.      * @param int          $fetchMode
  723.      * @psalm-param Mapping\ClassMetadata::FETCH_EAGER|Mapping\ClassMetadata::FETCH_LAZY $fetchMode
  724.      *
  725.      * @return $this
  726.      */
  727.     public function setFetchMode($class$assocName$fetchMode)
  728.     {
  729.         if (! in_array($fetchMode, [Mapping\ClassMetadata::FETCH_EAGERMapping\ClassMetadata::FETCH_LAZY], true)) {
  730.             Deprecation::trigger(
  731.                 'doctrine/orm',
  732.                 'https://github.com/doctrine/orm/pull/9777',
  733.                 'Calling %s() with something else than ClassMetadata::FETCH_EAGER or ClassMetadata::FETCH_LAZY is deprecated.',
  734.                 __METHOD__
  735.             );
  736.             $fetchMode Mapping\ClassMetadata::FETCH_LAZY;
  737.         }
  738.         $this->_hints['fetchMode'][$class][$assocName] = $fetchMode;
  739.         return $this;
  740.     }
  741.     /**
  742.      * Defines the processing mode to be used during hydration / result set transformation.
  743.      *
  744.      * @param string|int $hydrationMode Doctrine processing mode to be used during hydration process.
  745.      *                                  One of the Query::HYDRATE_* constants.
  746.      * @psalm-param string|AbstractQuery::HYDRATE_* $hydrationMode
  747.      *
  748.      * @return $this
  749.      */
  750.     public function setHydrationMode($hydrationMode)
  751.     {
  752.         $this->_hydrationMode $hydrationMode;
  753.         return $this;
  754.     }
  755.     /**
  756.      * Gets the hydration mode currently used by the query.
  757.      *
  758.      * @return string|int
  759.      * @psalm-return string|AbstractQuery::HYDRATE_*
  760.      */
  761.     public function getHydrationMode()
  762.     {
  763.         return $this->_hydrationMode;
  764.     }
  765.     /**
  766.      * Gets the list of results for the query.
  767.      *
  768.      * Alias for execute(null, $hydrationMode = HYDRATE_OBJECT).
  769.      *
  770.      * @param string|int $hydrationMode
  771.      * @psalm-param string|AbstractQuery::HYDRATE_* $hydrationMode
  772.      *
  773.      * @return mixed
  774.      */
  775.     public function getResult($hydrationMode self::HYDRATE_OBJECT)
  776.     {
  777.         return $this->execute(null$hydrationMode);
  778.     }
  779.     /**
  780.      * Gets the array of results for the query.
  781.      *
  782.      * Alias for execute(null, HYDRATE_ARRAY).
  783.      *
  784.      * @return mixed[]
  785.      */
  786.     public function getArrayResult()
  787.     {
  788.         return $this->execute(nullself::HYDRATE_ARRAY);
  789.     }
  790.     /**
  791.      * Gets one-dimensional array of results for the query.
  792.      *
  793.      * Alias for execute(null, HYDRATE_SCALAR_COLUMN).
  794.      *
  795.      * @return mixed[]
  796.      */
  797.     public function getSingleColumnResult()
  798.     {
  799.         return $this->execute(nullself::HYDRATE_SCALAR_COLUMN);
  800.     }
  801.     /**
  802.      * Gets the scalar results for the query.
  803.      *
  804.      * Alias for execute(null, HYDRATE_SCALAR).
  805.      *
  806.      * @return mixed[]
  807.      */
  808.     public function getScalarResult()
  809.     {
  810.         return $this->execute(nullself::HYDRATE_SCALAR);
  811.     }
  812.     /**
  813.      * Get exactly one result or null.
  814.      *
  815.      * @param string|int|null $hydrationMode
  816.      * @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode
  817.      *
  818.      * @return mixed
  819.      *
  820.      * @throws NonUniqueResultException
  821.      */
  822.     public function getOneOrNullResult($hydrationMode null)
  823.     {
  824.         try {
  825.             $result $this->execute(null$hydrationMode);
  826.         } catch (NoResultException $e) {
  827.             return null;
  828.         }
  829.         if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
  830.             return null;
  831.         }
  832.         if (! is_array($result)) {
  833.             return $result;
  834.         }
  835.         if (count($result) > 1) {
  836.             throw new NonUniqueResultException();
  837.         }
  838.         return array_shift($result);
  839.     }
  840.     /**
  841.      * Gets the single result of the query.
  842.      *
  843.      * Enforces the presence as well as the uniqueness of the result.
  844.      *
  845.      * If the result is not unique, a NonUniqueResultException is thrown.
  846.      * If there is no result, a NoResultException is thrown.
  847.      *
  848.      * @param string|int|null $hydrationMode
  849.      * @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode
  850.      *
  851.      * @return mixed
  852.      *
  853.      * @throws NonUniqueResultException If the query result is not unique.
  854.      * @throws NoResultException        If the query returned no result.
  855.      */
  856.     public function getSingleResult($hydrationMode null)
  857.     {
  858.         $result $this->execute(null$hydrationMode);
  859.         if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
  860.             throw new NoResultException();
  861.         }
  862.         if (! is_array($result)) {
  863.             return $result;
  864.         }
  865.         if (count($result) > 1) {
  866.             throw new NonUniqueResultException();
  867.         }
  868.         return array_shift($result);
  869.     }
  870.     /**
  871.      * Gets the single scalar result of the query.
  872.      *
  873.      * Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
  874.      *
  875.      * @return bool|float|int|string|null The scalar result.
  876.      *
  877.      * @throws NoResultException        If the query returned no result.
  878.      * @throws NonUniqueResultException If the query result is not unique.
  879.      */
  880.     public function getSingleScalarResult()
  881.     {
  882.         return $this->getSingleResult(self::HYDRATE_SINGLE_SCALAR);
  883.     }
  884.     /**
  885.      * Sets a query hint. If the hint name is not recognized, it is silently ignored.
  886.      *
  887.      * @param string $name  The name of the hint.
  888.      * @param mixed  $value The value of the hint.
  889.      *
  890.      * @return $this
  891.      */
  892.     public function setHint($name$value)
  893.     {
  894.         $this->_hints[$name] = $value;
  895.         return $this;
  896.     }
  897.     /**
  898.      * Gets the value of a query hint. If the hint name is not recognized, FALSE is returned.
  899.      *
  900.      * @param string $name The name of the hint.
  901.      *
  902.      * @return mixed The value of the hint or FALSE, if the hint name is not recognized.
  903.      */
  904.     public function getHint($name)
  905.     {
  906.         return $this->_hints[$name] ?? false;
  907.     }
  908.     /**
  909.      * Check if the query has a hint
  910.      *
  911.      * @param string $name The name of the hint
  912.      *
  913.      * @return bool False if the query does not have any hint
  914.      */
  915.     public function hasHint($name)
  916.     {
  917.         return isset($this->_hints[$name]);
  918.     }
  919.     /**
  920.      * Return the key value map of query hints that are currently set.
  921.      *
  922.      * @return array<string,mixed>
  923.      */
  924.     public function getHints()
  925.     {
  926.         return $this->_hints;
  927.     }
  928.     /**
  929.      * Executes the query and returns an IterableResult that can be used to incrementally
  930.      * iterate over the result.
  931.      *
  932.      * @deprecated 2.8 Use {@see toIterable} instead. See https://github.com/doctrine/orm/issues/8463
  933.      *
  934.      * @param ArrayCollection|mixed[]|null $parameters    The query parameters.
  935.      * @param string|int|null              $hydrationMode The hydration mode to use.
  936.      * @psalm-param ArrayCollection<int, Parameter>|array<string, mixed>|null $parameters
  937.      * @psalm-param string|AbstractQuery::HYDRATE_*|null                      $hydrationMode The hydration mode to use.
  938.      *
  939.      * @return IterableResult
  940.      */
  941.     public function iterate($parameters null$hydrationMode null)
  942.     {
  943.         Deprecation::trigger(
  944.             'doctrine/orm',
  945.             'https://github.com/doctrine/orm/issues/8463',
  946.             'Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use toIterable() instead.',
  947.             __METHOD__
  948.         );
  949.         if ($hydrationMode !== null) {
  950.             $this->setHydrationMode($hydrationMode);
  951.         }
  952.         if (! empty($parameters)) {
  953.             $this->setParameters($parameters);
  954.         }
  955.         $rsm $this->getResultSetMapping();
  956.         if ($rsm === null) {
  957.             throw new LogicException('Uninitialized result set mapping.');
  958.         }
  959.         $stmt $this->_doExecute();
  960.         return $this->_em->newHydrator($this->_hydrationMode)->iterate($stmt$rsm$this->_hints);
  961.     }
  962.     /**
  963.      * Executes the query and returns an iterable that can be used to incrementally
  964.      * iterate over the result.
  965.      *
  966.      * @param ArrayCollection|array|mixed[] $parameters    The query parameters.
  967.      * @param string|int|null               $hydrationMode The hydration mode to use.
  968.      * @psalm-param ArrayCollection<int, Parameter>|mixed[] $parameters
  969.      * @psalm-param string|AbstractQuery::HYDRATE_*|null    $hydrationMode
  970.      *
  971.      * @return iterable<mixed>
  972.      */
  973.     public function toIterable(iterable $parameters = [], $hydrationMode null): iterable
  974.     {
  975.         if ($hydrationMode !== null) {
  976.             $this->setHydrationMode($hydrationMode);
  977.         }
  978.         if (
  979.             ($this->isCountable($parameters) && count($parameters) !== 0)
  980.             || ($parameters instanceof Traversable && iterator_count($parameters) !== 0)
  981.         ) {
  982.             $this->setParameters($parameters);
  983.         }
  984.         $rsm $this->getResultSetMapping();
  985.         if ($rsm === null) {
  986.             throw new LogicException('Uninitialized result set mapping.');
  987.         }
  988.         if ($rsm->isMixed && count($rsm->scalarMappings) > 0) {
  989.             throw QueryException::iterateWithMixedResultNotAllowed();
  990.         }
  991.         $stmt $this->_doExecute();
  992.         return $this->_em->newHydrator($this->_hydrationMode)->toIterable($stmt$rsm$this->_hints);
  993.     }
  994.     /**
  995.      * Executes the query.
  996.      *
  997.      * @param ArrayCollection|mixed[]|null $parameters    Query parameters.
  998.      * @param string|int|null              $hydrationMode Processing mode to be used during the hydration process.
  999.      * @psalm-param ArrayCollection<int, Parameter>|mixed[]|null $parameters
  1000.      * @psalm-param string|AbstractQuery::HYDRATE_*|null         $hydrationMode
  1001.      *
  1002.      * @return mixed
  1003.      */
  1004.     public function execute($parameters null$hydrationMode null)
  1005.     {
  1006.         if ($this->cacheable && $this->isCacheEnabled()) {
  1007.             return $this->executeUsingQueryCache($parameters$hydrationMode);
  1008.         }
  1009.         return $this->executeIgnoreQueryCache($parameters$hydrationMode);
  1010.     }
  1011.     /**
  1012.      * Execute query ignoring second level cache.
  1013.      *
  1014.      * @param ArrayCollection|mixed[]|null $parameters
  1015.      * @param string|int|null              $hydrationMode
  1016.      * @psalm-param ArrayCollection<int, Parameter>|mixed[]|null $parameters
  1017.      * @psalm-param string|AbstractQuery::HYDRATE_*|null         $hydrationMode
  1018.      *
  1019.      * @return mixed
  1020.      */
  1021.     private function executeIgnoreQueryCache($parameters null$hydrationMode null)
  1022.     {
  1023.         if ($hydrationMode !== null) {
  1024.             $this->setHydrationMode($hydrationMode);
  1025.         }
  1026.         if (! empty($parameters)) {
  1027.             $this->setParameters($parameters);
  1028.         }
  1029.         $setCacheEntry = static function ($data): void {
  1030.         };
  1031.         if ($this->_hydrationCacheProfile !== null) {
  1032.             [$cacheKey$realCacheKey] = $this->getHydrationCacheId();
  1033.             $cache     $this->getHydrationCache();
  1034.             $cacheItem $cache->getItem($cacheKey);
  1035.             $result    $cacheItem->isHit() ? $cacheItem->get() : [];
  1036.             if (isset($result[$realCacheKey])) {
  1037.                 return $result[$realCacheKey];
  1038.             }
  1039.             if (! $result) {
  1040.                 $result = [];
  1041.             }
  1042.             $setCacheEntry = static function ($data) use ($cache$result$cacheItem$realCacheKey): void {
  1043.                 $cache->save($cacheItem->set($result + [$realCacheKey => $data]));
  1044.             };
  1045.         }
  1046.         $stmt $this->_doExecute();
  1047.         if (is_numeric($stmt)) {
  1048.             $setCacheEntry($stmt);
  1049.             return $stmt;
  1050.         }
  1051.         $rsm $this->getResultSetMapping();
  1052.         if ($rsm === null) {
  1053.             throw new LogicException('Uninitialized result set mapping.');
  1054.         }
  1055.         $data $this->_em->newHydrator($this->_hydrationMode)->hydrateAll($stmt$rsm$this->_hints);
  1056.         $setCacheEntry($data);
  1057.         return $data;
  1058.     }
  1059.     private function getHydrationCache(): CacheItemPoolInterface
  1060.     {
  1061.         assert($this->_hydrationCacheProfile !== null);
  1062.         // Support for DBAL 2
  1063.         if (! method_exists($this->_hydrationCacheProfile'getResultCache')) {
  1064.             // @phpstan-ignore method.deprecated
  1065.             $cacheDriver $this->_hydrationCacheProfile->getResultCacheDriver();
  1066.             assert($cacheDriver !== null);
  1067.             return CacheAdapter::wrap($cacheDriver);
  1068.         }
  1069.         $cache $this->_hydrationCacheProfile->getResultCache();
  1070.         assert($cache !== null);
  1071.         return $cache;
  1072.     }
  1073.     /**
  1074.      * Load from second level cache or executes the query and put into cache.
  1075.      *
  1076.      * @param ArrayCollection|mixed[]|null $parameters
  1077.      * @param string|int|null              $hydrationMode
  1078.      * @psalm-param ArrayCollection<int, Parameter>|mixed[]|null $parameters
  1079.      * @psalm-param string|AbstractQuery::HYDRATE_*|null         $hydrationMode
  1080.      *
  1081.      * @return mixed
  1082.      */
  1083.     private function executeUsingQueryCache($parameters null$hydrationMode null)
  1084.     {
  1085.         $rsm $this->getResultSetMapping();
  1086.         if ($rsm === null) {
  1087.             throw new LogicException('Uninitialized result set mapping.');
  1088.         }
  1089.         $queryCache $this->_em->getCache()->getQueryCache($this->cacheRegion);
  1090.         $queryKey   = new QueryCacheKey(
  1091.             $this->getHash(),
  1092.             $this->lifetime,
  1093.             $this->cacheMode ?: Cache::MODE_NORMAL,
  1094.             $this->getTimestampKey()
  1095.         );
  1096.         $result $queryCache->get($queryKey$rsm$this->_hints);
  1097.         if ($result !== null) {
  1098.             if ($this->cacheLogger) {
  1099.                 $this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $queryKey);
  1100.             }
  1101.             return $result;
  1102.         }
  1103.         $result $this->executeIgnoreQueryCache($parameters$hydrationMode);
  1104.         $cached $queryCache->put($queryKey$rsm$result$this->_hints);
  1105.         if ($this->cacheLogger) {
  1106.             $this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $queryKey);
  1107.             if ($cached) {
  1108.                 $this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $queryKey);
  1109.             }
  1110.         }
  1111.         return $result;
  1112.     }
  1113.     private function getTimestampKey(): ?TimestampCacheKey
  1114.     {
  1115.         assert($this->_resultSetMapping !== null);
  1116.         $entityName reset($this->_resultSetMapping->aliasMap);
  1117.         if (empty($entityName)) {
  1118.             return null;
  1119.         }
  1120.         $metadata $this->_em->getClassMetadata($entityName);
  1121.         return new Cache\TimestampCacheKey($metadata->rootEntityName);
  1122.     }
  1123.     /**
  1124.      * Get the result cache id to use to store the result set cache entry.
  1125.      * Will return the configured id if it exists otherwise a hash will be
  1126.      * automatically generated for you.
  1127.      *
  1128.      * @return string[] ($key, $hash)
  1129.      * @psalm-return array{string, string} ($key, $hash)
  1130.      */
  1131.     protected function getHydrationCacheId()
  1132.     {
  1133.         $parameters = [];
  1134.         $types      = [];
  1135.         foreach ($this->getParameters() as $parameter) {
  1136.             $parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
  1137.             $types[$parameter->getName()]      = $parameter->getType();
  1138.         }
  1139.         $sql $this->getSQL();
  1140.         assert(is_string($sql));
  1141.         $queryCacheProfile      $this->getHydrationCacheProfile();
  1142.         $hints                  $this->getHints();
  1143.         $hints['hydrationMode'] = $this->getHydrationMode();
  1144.         ksort($hints);
  1145.         assert($queryCacheProfile !== null);
  1146.         return $queryCacheProfile->generateCacheKeys($sql$parameters$types$hints);
  1147.     }
  1148.     /**
  1149.      * Set the result cache id to use to store the result set cache entry.
  1150.      * If this is not explicitly set by the developer then a hash is automatically
  1151.      * generated for you.
  1152.      *
  1153.      * @param string|null $id
  1154.      *
  1155.      * @return $this
  1156.      */
  1157.     public function setResultCacheId($id)
  1158.     {
  1159.         if (! $this->_queryCacheProfile) {
  1160.             return $this->setResultCacheProfile(new QueryCacheProfile(0$id));
  1161.         }
  1162.         $this->_queryCacheProfile $this->_queryCacheProfile->setCacheKey($id);
  1163.         return $this;
  1164.     }
  1165.     /**
  1166.      * Get the result cache id to use to store the result set cache entry if set.
  1167.      *
  1168.      * @deprecated
  1169.      *
  1170.      * @return string|null
  1171.      */
  1172.     public function getResultCacheId()
  1173.     {
  1174.         return $this->_queryCacheProfile $this->_queryCacheProfile->getCacheKey() : null;
  1175.     }
  1176.     /**
  1177.      * Executes the query and returns a the resulting Statement object.
  1178.      *
  1179.      * @return Result|int The executed database statement that holds
  1180.      *                    the results, or an integer indicating how
  1181.      *                    many rows were affected.
  1182.      */
  1183.     abstract protected function _doExecute();
  1184.     /**
  1185.      * Cleanup Query resource when clone is called.
  1186.      *
  1187.      * @return void
  1188.      */
  1189.     public function __clone()
  1190.     {
  1191.         $this->parameters = new ArrayCollection();
  1192.         $this->_hints = [];
  1193.         $this->_hints $this->_em->getConfiguration()->getDefaultQueryHints();
  1194.     }
  1195.     /**
  1196.      * Generates a string of currently query to use for the cache second level cache.
  1197.      *
  1198.      * @return string
  1199.      */
  1200.     protected function getHash()
  1201.     {
  1202.         $query $this->getSQL();
  1203.         assert(is_string($query));
  1204.         $hints  $this->getHints();
  1205.         $params array_map(function (Parameter $parameter) {
  1206.             $value $parameter->getValue();
  1207.             // Small optimization
  1208.             // Does not invoke processParameterValue for scalar value
  1209.             if (is_scalar($value)) {
  1210.                 return $value;
  1211.             }
  1212.             return $this->processParameterValue($value);
  1213.         }, $this->parameters->getValues());
  1214.         ksort($hints);
  1215.         return sha1($query '-' serialize($params) . '-' serialize($hints));
  1216.     }
  1217.     /** @param iterable<mixed> $subject */
  1218.     private function isCountable(iterable $subject): bool
  1219.     {
  1220.         return $subject instanceof Countable || is_array($subject);
  1221.     }
  1222. }