vendor/doctrine/orm/lib/Doctrine/ORM/AbstractQuery.php line 968

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