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

Open in your IDE?
  1. <?php
  2. /*
  3.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  4.  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  5.  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  6.  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  7.  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  8.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  9.  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  10.  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  11.  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  12.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  13.  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  14.  *
  15.  * This software consists of voluntary contributions made by many individuals
  16.  * and is licensed under the MIT license. For more information, see
  17.  * <http://www.doctrine-project.org>.
  18.  */
  19. namespace Doctrine\ORM;
  20. use Countable;
  21. use Doctrine\Common\Collections\ArrayCollection;
  22. use Doctrine\Common\Collections\Collection;
  23. use Doctrine\DBAL\Cache\QueryCacheProfile;
  24. use Doctrine\DBAL\Driver\ResultStatement;
  25. use Doctrine\Deprecations\Deprecation;
  26. use Doctrine\ORM\Cache\Logging\CacheLogger;
  27. use Doctrine\ORM\Cache\QueryCacheKey;
  28. use Doctrine\ORM\Cache\TimestampCacheKey;
  29. use Doctrine\ORM\Internal\Hydration\IterableResult;
  30. use Doctrine\ORM\Mapping\MappingException as ORMMappingException;
  31. use Doctrine\ORM\Query\Parameter;
  32. use Doctrine\ORM\Query\QueryException;
  33. use Doctrine\ORM\Query\ResultSetMapping;
  34. use Doctrine\Persistence\Mapping\MappingException;
  35. use Traversable;
  36. use function array_map;
  37. use function array_shift;
  38. use function count;
  39. use function is_array;
  40. use function is_numeric;
  41. use function is_object;
  42. use function is_scalar;
  43. use function iterator_count;
  44. use function iterator_to_array;
  45. use function ksort;
  46. use function reset;
  47. use function serialize;
  48. use function sha1;
  49. /**
  50.  * Base contract for ORM queries. Base class for Query and NativeQuery.
  51.  *
  52.  * @link    www.doctrine-project.org
  53.  */
  54. abstract class AbstractQuery
  55. {
  56.     /* Hydration mode constants */
  57.     /**
  58.      * Hydrates an object graph. This is the default behavior.
  59.      */
  60.     public const HYDRATE_OBJECT 1;
  61.     /**
  62.      * Hydrates an array graph.
  63.      */
  64.     public const HYDRATE_ARRAY 2;
  65.     /**
  66.      * Hydrates a flat, rectangular result set with scalar values.
  67.      */
  68.     public const HYDRATE_SCALAR 3;
  69.     /**
  70.      * Hydrates a single scalar value.
  71.      */
  72.     public const HYDRATE_SINGLE_SCALAR 4;
  73.     /**
  74.      * Very simple object hydrator (optimized for performance).
  75.      */
  76.     public const HYDRATE_SIMPLEOBJECT 5;
  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
  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.      */
  107.     protected $_hydrationMode self::HYDRATE_OBJECT;
  108.     /** @var QueryCacheProfile|null */
  109.     protected $_queryCacheProfile;
  110.     /**
  111.      * Whether or not expire the result cache.
  112.      *
  113.      * @var bool
  114.      */
  115.     protected $_expireResultCache false;
  116.     /** @var QueryCacheProfile */
  117.     protected $_hydrationCacheProfile;
  118.     /**
  119.      * Whether to use second level cache, if available.
  120.      *
  121.      * @var bool
  122.      */
  123.     protected $cacheable false;
  124.     /** @var bool */
  125.     protected $hasCache false;
  126.     /**
  127.      * Second level cache region name.
  128.      *
  129.      * @var string|null
  130.      */
  131.     protected $cacheRegion;
  132.     /**
  133.      * Second level query cache mode.
  134.      *
  135.      * @var int|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 static This query instance.
  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 static This query instance.
  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 static This query instance.
  215.      */
  216.     public function setLifetime($lifetime)
  217.     {
  218.         $this->lifetime = (int) $lifetime;
  219.         return $this;
  220.     }
  221.     /**
  222.      * @return int
  223.      */
  224.     public function getCacheMode()
  225.     {
  226.         return $this->cacheMode;
  227.     }
  228.     /**
  229.      * @param int $cacheMode
  230.      *
  231.      * @return static This query instance.
  232.      */
  233.     public function setCacheMode($cacheMode)
  234.     {
  235.         $this->cacheMode = (int) $cacheMode;
  236.         return $this;
  237.     }
  238.     /**
  239.      * Gets the SQL query that corresponds to this query object.
  240.      * The returned SQL syntax depends on the connection driver that is used
  241.      * by this query object at the time of this method call.
  242.      *
  243.      * @return string SQL query
  244.      */
  245.     abstract public function getSQL();
  246.     /**
  247.      * Retrieves the associated EntityManager of this Query instance.
  248.      *
  249.      * @return EntityManagerInterface
  250.      */
  251.     public function getEntityManager()
  252.     {
  253.         return $this->_em;
  254.     }
  255.     /**
  256.      * Frees the resources used by the query object.
  257.      *
  258.      * Resets Parameters, Parameter Types and Query Hints.
  259.      *
  260.      * @return void
  261.      */
  262.     public function free()
  263.     {
  264.         $this->parameters = new ArrayCollection();
  265.         $this->_hints $this->_em->getConfiguration()->getDefaultQueryHints();
  266.     }
  267.     /**
  268.      * Get all defined parameters.
  269.      *
  270.      * @return ArrayCollection The defined query parameters.
  271.      * @psalm-return ArrayCollection<int, Parameter>
  272.      */
  273.     public function getParameters()
  274.     {
  275.         return $this->parameters;
  276.     }
  277.     /**
  278.      * Gets a query parameter.
  279.      *
  280.      * @param mixed $key The key (index or name) of the bound parameter.
  281.      *
  282.      * @return Parameter|null The value of the bound parameter, or NULL if not available.
  283.      */
  284.     public function getParameter($key)
  285.     {
  286.         $key Query\Parameter::normalizeName($key);
  287.         $filteredParameters $this->parameters->filter(
  288.             static function (Query\Parameter $parameter) use ($key): bool {
  289.                 $parameterName $parameter->getName();
  290.                 return $key === $parameterName;
  291.             }
  292.         );
  293.         return ! $filteredParameters->isEmpty() ? $filteredParameters->first() : null;
  294.     }
  295.     /**
  296.      * Sets a collection of query parameters.
  297.      *
  298.      * @param ArrayCollection|mixed[] $parameters
  299.      * @psalm-param ArrayCollection<int, Parameter>|mixed[] $parameters
  300.      *
  301.      * @return static This query instance.
  302.      */
  303.     public function setParameters($parameters)
  304.     {
  305.         // BC compatibility with 2.3-
  306.         if (is_array($parameters)) {
  307.             /** @psalm-var ArrayCollection<int, Parameter> $parameterCollection */
  308.             $parameterCollection = new ArrayCollection();
  309.             foreach ($parameters as $key => $value) {
  310.                 $parameterCollection->add(new Parameter($key$value));
  311.             }
  312.             $parameters $parameterCollection;
  313.         }
  314.         $this->parameters $parameters;
  315.         return $this;
  316.     }
  317.     /**
  318.      * Sets a query parameter.
  319.      *
  320.      * @param string|int  $key   The parameter position or name.
  321.      * @param mixed       $value The parameter value.
  322.      * @param string|null $type  The parameter type. If specified, the given value will be run through
  323.      *                           the type conversion of this type. This is usually not needed for
  324.      *                           strings and numeric types.
  325.      *
  326.      * @return static This query instance.
  327.      */
  328.     public function setParameter($key$value$type null)
  329.     {
  330.         $existingParameter $this->getParameter($key);
  331.         if ($existingParameter !== null) {
  332.             $existingParameter->setValue($value$type);
  333.             return $this;
  334.         }
  335.         $this->parameters->add(new Parameter($key$value$type));
  336.         return $this;
  337.     }
  338.     /**
  339.      * Processes an individual parameter value.
  340.      *
  341.      * @param mixed $value
  342.      *
  343.      * @return mixed[]|string|int|float|bool
  344.      * @psalm-return array|scalar
  345.      *
  346.      * @throws ORMInvalidArgumentException
  347.      */
  348.     public function processParameterValue($value)
  349.     {
  350.         if (is_scalar($value)) {
  351.             return $value;
  352.         }
  353.         if ($value instanceof Collection) {
  354.             $value iterator_to_array($value);
  355.         }
  356.         if (is_array($value)) {
  357.             $value $this->processArrayParameterValue($value);
  358.             return $value;
  359.         }
  360.         if ($value instanceof Mapping\ClassMetadata) {
  361.             return $value->name;
  362.         }
  363.         if (! is_object($value)) {
  364.             return $value;
  365.         }
  366.         try {
  367.             $value $this->_em->getUnitOfWork()->getSingleIdentifierValue($value);
  368.             if ($value === null) {
  369.                 throw ORMInvalidArgumentException::invalidIdentifierBindingEntity();
  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 static This query instance.
  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
  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 static This query instance.
  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 && ! $profile->getResultCacheDriver()) {
  465.             $resultCacheDriver $this->_em->getConfiguration()->getHydrationCacheImpl();
  466.             $profile           $profile->setResultCacheDriver($resultCacheDriver);
  467.         }
  468.         $this->_hydrationCacheProfile $profile;
  469.         return $this;
  470.     }
  471.     /**
  472.      * @return QueryCacheProfile
  473.      */
  474.     public function getHydrationCacheProfile()
  475.     {
  476.         return $this->_hydrationCacheProfile;
  477.     }
  478.     /**
  479.      * Set a cache profile for the result cache.
  480.      *
  481.      * If no result cache driver is set in the QueryCacheProfile, the default
  482.      * result cache driver is used from the configuration.
  483.      *
  484.      * @return static This query instance.
  485.      */
  486.     public function setResultCacheProfile(?QueryCacheProfile $profile null)
  487.     {
  488.         if ($profile !== null && ! $profile->getResultCacheDriver()) {
  489.             $resultCacheDriver $this->_em->getConfiguration()->getResultCacheImpl();
  490.             $profile           $profile->setResultCacheDriver($resultCacheDriver);
  491.         }
  492.         $this->_queryCacheProfile $profile;
  493.         return $this;
  494.     }
  495.     /**
  496.      * Defines a cache driver to be used for caching result sets and implicitly enables caching.
  497.      *
  498.      * @param \Doctrine\Common\Cache\Cache|null $resultCacheDriver Cache driver
  499.      *
  500.      * @return static This query instance.
  501.      *
  502.      * @throws ORMException
  503.      */
  504.     public function setResultCacheDriver($resultCacheDriver null)
  505.     {
  506.         /** @phpstan-ignore-next-line */
  507.         if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) {
  508.             throw ORMException::invalidResultCacheDriver();
  509.         }
  510.         $this->_queryCacheProfile $this->_queryCacheProfile
  511.             $this->_queryCacheProfile->setResultCacheDriver($resultCacheDriver)
  512.             : new QueryCacheProfile(0null$resultCacheDriver);
  513.         return $this;
  514.     }
  515.     /**
  516.      * Returns the cache driver used for caching result sets.
  517.      *
  518.      * @deprecated
  519.      *
  520.      * @return \Doctrine\Common\Cache\Cache Cache driver
  521.      */
  522.     public function getResultCacheDriver()
  523.     {
  524.         if ($this->_queryCacheProfile && $this->_queryCacheProfile->getResultCacheDriver()) {
  525.             return $this->_queryCacheProfile->getResultCacheDriver();
  526.         }
  527.         return $this->_em->getConfiguration()->getResultCacheImpl();
  528.     }
  529.     /**
  530.      * Set whether or not to cache the results of this query and if so, for
  531.      * how long and which ID to use for the cache entry.
  532.      *
  533.      * @deprecated 2.7 Use {@see enableResultCache} and {@see disableResultCache} instead.
  534.      *
  535.      * @param bool   $useCache
  536.      * @param int    $lifetime
  537.      * @param string $resultCacheId
  538.      *
  539.      * @return static This query instance.
  540.      */
  541.     public function useResultCache($useCache$lifetime null$resultCacheId null)
  542.     {
  543.         return $useCache
  544.             $this->enableResultCache($lifetime$resultCacheId)
  545.             : $this->disableResultCache();
  546.     }
  547.     /**
  548.      * Enables caching of the results of this query, for given or default amount of seconds
  549.      * and optionally specifies which ID to use for the cache entry.
  550.      *
  551.      * @param int|null    $lifetime      How long the cache entry is valid, in seconds.
  552.      * @param string|null $resultCacheId ID to use for the cache entry.
  553.      *
  554.      * @return static This query instance.
  555.      */
  556.     public function enableResultCache(?int $lifetime null, ?string $resultCacheId null): self
  557.     {
  558.         $this->setResultCacheLifetime($lifetime);
  559.         $this->setResultCacheId($resultCacheId);
  560.         return $this;
  561.     }
  562.     /**
  563.      * Disables caching of the results of this query.
  564.      *
  565.      * @return static This query instance.
  566.      */
  567.     public function disableResultCache(): self
  568.     {
  569.         $this->_queryCacheProfile null;
  570.         return $this;
  571.     }
  572.     /**
  573.      * Defines how long the result cache will be active before expire.
  574.      *
  575.      * @param int|null $lifetime How long the cache entry is valid.
  576.      *
  577.      * @return static This query instance.
  578.      */
  579.     public function setResultCacheLifetime($lifetime)
  580.     {
  581.         $lifetime $lifetime !== null ? (int) $lifetime 0;
  582.         $this->_queryCacheProfile $this->_queryCacheProfile
  583.             $this->_queryCacheProfile->setLifetime($lifetime)
  584.             : new QueryCacheProfile($lifetimenull$this->_em->getConfiguration()->getResultCacheImpl());
  585.         return $this;
  586.     }
  587.     /**
  588.      * Retrieves the lifetime of resultset cache.
  589.      *
  590.      * @deprecated
  591.      *
  592.      * @return int
  593.      */
  594.     public function getResultCacheLifetime()
  595.     {
  596.         return $this->_queryCacheProfile $this->_queryCacheProfile->getLifetime() : 0;
  597.     }
  598.     /**
  599.      * Defines if the result cache is active or not.
  600.      *
  601.      * @param bool $expire Whether or not to force resultset cache expiration.
  602.      *
  603.      * @return static This query instance.
  604.      */
  605.     public function expireResultCache($expire true)
  606.     {
  607.         $this->_expireResultCache $expire;
  608.         return $this;
  609.     }
  610.     /**
  611.      * Retrieves if the resultset cache is active or not.
  612.      *
  613.      * @return bool
  614.      */
  615.     public function getExpireResultCache()
  616.     {
  617.         return $this->_expireResultCache;
  618.     }
  619.     /**
  620.      * @return QueryCacheProfile|null
  621.      */
  622.     public function getQueryCacheProfile()
  623.     {
  624.         return $this->_queryCacheProfile;
  625.     }
  626.     /**
  627.      * Change the default fetch mode of an association for this query.
  628.      *
  629.      * $fetchMode can be one of ClassMetadata::FETCH_EAGER or ClassMetadata::FETCH_LAZY
  630.      *
  631.      * @param string $class
  632.      * @param string $assocName
  633.      * @param int    $fetchMode
  634.      *
  635.      * @return static This query instance.
  636.      */
  637.     public function setFetchMode($class$assocName$fetchMode)
  638.     {
  639.         if ($fetchMode !== Mapping\ClassMetadata::FETCH_EAGER) {
  640.             $fetchMode Mapping\ClassMetadata::FETCH_LAZY;
  641.         }
  642.         $this->_hints['fetchMode'][$class][$assocName] = $fetchMode;
  643.         return $this;
  644.     }
  645.     /**
  646.      * Defines the processing mode to be used during hydration / result set transformation.
  647.      *
  648.      * @param string|int $hydrationMode Doctrine processing mode to be used during hydration process.
  649.      *                                  One of the Query::HYDRATE_* constants.
  650.      *
  651.      * @return static This query instance.
  652.      */
  653.     public function setHydrationMode($hydrationMode)
  654.     {
  655.         $this->_hydrationMode $hydrationMode;
  656.         return $this;
  657.     }
  658.     /**
  659.      * Gets the hydration mode currently used by the query.
  660.      *
  661.      * @return string|int
  662.      */
  663.     public function getHydrationMode()
  664.     {
  665.         return $this->_hydrationMode;
  666.     }
  667.     /**
  668.      * Gets the list of results for the query.
  669.      *
  670.      * Alias for execute(null, $hydrationMode = HYDRATE_OBJECT).
  671.      *
  672.      * @param string|int $hydrationMode
  673.      *
  674.      * @return mixed
  675.      */
  676.     public function getResult($hydrationMode self::HYDRATE_OBJECT)
  677.     {
  678.         return $this->execute(null$hydrationMode);
  679.     }
  680.     /**
  681.      * Gets the array of results for the query.
  682.      *
  683.      * Alias for execute(null, HYDRATE_ARRAY).
  684.      *
  685.      * @return mixed[]
  686.      */
  687.     public function getArrayResult()
  688.     {
  689.         return $this->execute(nullself::HYDRATE_ARRAY);
  690.     }
  691.     /**
  692.      * Gets the scalar results for the query.
  693.      *
  694.      * Alias for execute(null, HYDRATE_SCALAR).
  695.      *
  696.      * @return mixed[]
  697.      */
  698.     public function getScalarResult()
  699.     {
  700.         return $this->execute(nullself::HYDRATE_SCALAR);
  701.     }
  702.     /**
  703.      * Get exactly one result or null.
  704.      *
  705.      * @param string|int $hydrationMode
  706.      *
  707.      * @return mixed
  708.      *
  709.      * @throws NonUniqueResultException
  710.      */
  711.     public function getOneOrNullResult($hydrationMode null)
  712.     {
  713.         try {
  714.             $result $this->execute(null$hydrationMode);
  715.         } catch (NoResultException $e) {
  716.             return null;
  717.         }
  718.         if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
  719.             return null;
  720.         }
  721.         if (! is_array($result)) {
  722.             return $result;
  723.         }
  724.         if (count($result) > 1) {
  725.             throw new NonUniqueResultException();
  726.         }
  727.         return array_shift($result);
  728.     }
  729.     /**
  730.      * Gets the single result of the query.
  731.      *
  732.      * Enforces the presence as well as the uniqueness of the result.
  733.      *
  734.      * If the result is not unique, a NonUniqueResultException is thrown.
  735.      * If there is no result, a NoResultException is thrown.
  736.      *
  737.      * @param string|int $hydrationMode
  738.      *
  739.      * @return mixed
  740.      *
  741.      * @throws NonUniqueResultException If the query result is not unique.
  742.      * @throws NoResultException        If the query returned no result and hydration mode is not HYDRATE_SINGLE_SCALAR.
  743.      */
  744.     public function getSingleResult($hydrationMode null)
  745.     {
  746.         $result $this->execute(null$hydrationMode);
  747.         if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
  748.             throw new NoResultException();
  749.         }
  750.         if (! is_array($result)) {
  751.             return $result;
  752.         }
  753.         if (count($result) > 1) {
  754.             throw new NonUniqueResultException();
  755.         }
  756.         return array_shift($result);
  757.     }
  758.     /**
  759.      * Gets the single scalar result of the query.
  760.      *
  761.      * Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
  762.      *
  763.      * @return mixed The scalar result.
  764.      *
  765.      * @throws NoResultException        If the query returned no result.
  766.      * @throws NonUniqueResultException If the query result is not unique.
  767.      */
  768.     public function getSingleScalarResult()
  769.     {
  770.         return $this->getSingleResult(self::HYDRATE_SINGLE_SCALAR);
  771.     }
  772.     /**
  773.      * Sets a query hint. If the hint name is not recognized, it is silently ignored.
  774.      *
  775.      * @param string $name  The name of the hint.
  776.      * @param mixed  $value The value of the hint.
  777.      *
  778.      * @return static This query instance.
  779.      */
  780.     public function setHint($name$value)
  781.     {
  782.         $this->_hints[$name] = $value;
  783.         return $this;
  784.     }
  785.     /**
  786.      * Gets the value of a query hint. If the hint name is not recognized, FALSE is returned.
  787.      *
  788.      * @param string $name The name of the hint.
  789.      *
  790.      * @return mixed The value of the hint or FALSE, if the hint name is not recognized.
  791.      */
  792.     public function getHint($name)
  793.     {
  794.         return $this->_hints[$name] ?? false;
  795.     }
  796.     /**
  797.      * Check if the query has a hint
  798.      *
  799.      * @param string $name The name of the hint
  800.      *
  801.      * @return bool False if the query does not have any hint
  802.      */
  803.     public function hasHint($name)
  804.     {
  805.         return isset($this->_hints[$name]);
  806.     }
  807.     /**
  808.      * Return the key value map of query hints that are currently set.
  809.      *
  810.      * @return array<string,mixed>
  811.      */
  812.     public function getHints()
  813.     {
  814.         return $this->_hints;
  815.     }
  816.     /**
  817.      * Executes the query and returns an IterableResult that can be used to incrementally
  818.      * iterate over the result.
  819.      *
  820.      * @deprecated 2.8 Use {@see toIterable} instead. See https://github.com/doctrine/orm/issues/8463
  821.      *
  822.      * @param ArrayCollection|mixed[]|null $parameters    The query parameters.
  823.      * @param string|int|null              $hydrationMode The hydration mode to use.
  824.      *
  825.      * @return IterableResult
  826.      */
  827.     public function iterate($parameters null$hydrationMode null)
  828.     {
  829.         Deprecation::trigger(
  830.             'doctrine/orm',
  831.             'https://github.com/doctrine/orm/issues/8463',
  832.             'Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use toIterable() instead.',
  833.             __METHOD__
  834.         );
  835.         if ($hydrationMode !== null) {
  836.             $this->setHydrationMode($hydrationMode);
  837.         }
  838.         if (! empty($parameters)) {
  839.             $this->setParameters($parameters);
  840.         }
  841.         $rsm  $this->getResultSetMapping();
  842.         $stmt $this->_doExecute();
  843.         return $this->_em->newHydrator($this->_hydrationMode)->iterate($stmt$rsm$this->_hints);
  844.     }
  845.     /**
  846.      * Executes the query and returns an iterable that can be used to incrementally
  847.      * iterate over the result.
  848.      *
  849.      * @param ArrayCollection|array|mixed[] $parameters    The query parameters.
  850.      * @param string|int|null               $hydrationMode The hydration mode to use.
  851.      * @psalm-param ArrayCollection<int, Parameter>|mixed[] $parameters
  852.      *
  853.      * @return iterable<mixed>
  854.      */
  855.     public function toIterable(iterable $parameters = [], $hydrationMode null): iterable
  856.     {
  857.         if ($hydrationMode !== null) {
  858.             $this->setHydrationMode($hydrationMode);
  859.         }
  860.         if (
  861.             ($this->isCountable($parameters) && count($parameters) !== 0)
  862.             || ($parameters instanceof Traversable && iterator_count($parameters) !== 0)
  863.         ) {
  864.             $this->setParameters($parameters);
  865.         }
  866.         $rsm $this->getResultSetMapping();
  867.         if ($rsm->isMixed && count($rsm->scalarMappings) > 0) {
  868.             throw QueryException::iterateWithMixedResultNotAllowed();
  869.         }
  870.         $stmt $this->_doExecute();
  871.         return $this->_em->newHydrator($this->_hydrationMode)->toIterable($stmt$rsm$this->_hints);
  872.     }
  873.     /**
  874.      * Executes the query.
  875.      *
  876.      * @param ArrayCollection|mixed[]|null $parameters    Query parameters.
  877.      * @param string|int|null              $hydrationMode Processing mode to be used during the hydration process.
  878.      * @psalm-param ArrayCollection<int, Parameter>|mixed[]|null $parameters
  879.      *
  880.      * @return mixed
  881.      */
  882.     public function execute($parameters null$hydrationMode null)
  883.     {
  884.         if ($this->cacheable && $this->isCacheEnabled()) {
  885.             return $this->executeUsingQueryCache($parameters$hydrationMode);
  886.         }
  887.         return $this->executeIgnoreQueryCache($parameters$hydrationMode);
  888.     }
  889.     /**
  890.      * Execute query ignoring second level cache.
  891.      *
  892.      * @param ArrayCollection|mixed[]|null $parameters
  893.      * @param string|int|null              $hydrationMode
  894.      * @psalm-param ArrayCollection<int, Parameter>|mixed[]|null $parameters
  895.      *
  896.      * @return mixed
  897.      */
  898.     private function executeIgnoreQueryCache($parameters null$hydrationMode null)
  899.     {
  900.         if ($hydrationMode !== null) {
  901.             $this->setHydrationMode($hydrationMode);
  902.         }
  903.         if (! empty($parameters)) {
  904.             $this->setParameters($parameters);
  905.         }
  906.         $setCacheEntry = static function ($data): void {
  907.         };
  908.         if ($this->_hydrationCacheProfile !== null) {
  909.             [$cacheKey$realCacheKey] = $this->getHydrationCacheId();
  910.             $queryCacheProfile $this->getHydrationCacheProfile();
  911.             $cache             $queryCacheProfile->getResultCacheDriver();
  912.             $result            $cache->fetch($cacheKey);
  913.             if (isset($result[$realCacheKey])) {
  914.                 return $result[$realCacheKey];
  915.             }
  916.             if (! $result) {
  917.                 $result = [];
  918.             }
  919.             $setCacheEntry = static function ($data) use ($cache$result$cacheKey$realCacheKey$queryCacheProfile): void {
  920.                 $result[$realCacheKey] = $data;
  921.                 $cache->save($cacheKey$result$queryCacheProfile->getLifetime());
  922.             };
  923.         }
  924.         $stmt $this->_doExecute();
  925.         if (is_numeric($stmt)) {
  926.             $setCacheEntry($stmt);
  927.             return $stmt;
  928.         }
  929.         $rsm  $this->getResultSetMapping();
  930.         $data $this->_em->newHydrator($this->_hydrationMode)->hydrateAll($stmt$rsm$this->_hints);
  931.         $setCacheEntry($data);
  932.         return $data;
  933.     }
  934.     /**
  935.      * Load from second level cache or executes the query and put into cache.
  936.      *
  937.      * @param ArrayCollection|mixed[]|null $parameters
  938.      * @param string|int|null              $hydrationMode
  939.      * @psalm-param ArrayCollection<int, Parameter>|mixed[]|null $parameters
  940.      *
  941.      * @return mixed
  942.      */
  943.     private function executeUsingQueryCache($parameters null$hydrationMode null)
  944.     {
  945.         $rsm        $this->getResultSetMapping();
  946.         $queryCache $this->_em->getCache()->getQueryCache($this->cacheRegion);
  947.         $queryKey   = new QueryCacheKey(
  948.             $this->getHash(),
  949.             $this->lifetime,
  950.             $this->cacheMode ?: Cache::MODE_NORMAL,
  951.             $this->getTimestampKey()
  952.         );
  953.         $result $queryCache->get($queryKey$rsm$this->_hints);
  954.         if ($result !== null) {
  955.             if ($this->cacheLogger) {
  956.                 $this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $queryKey);
  957.             }
  958.             return $result;
  959.         }
  960.         $result $this->executeIgnoreQueryCache($parameters$hydrationMode);
  961.         $cached $queryCache->put($queryKey$rsm$result$this->_hints);
  962.         if ($this->cacheLogger) {
  963.             $this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $queryKey);
  964.             if ($cached) {
  965.                 $this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $queryKey);
  966.             }
  967.         }
  968.         return $result;
  969.     }
  970.     private function getTimestampKey(): ?TimestampCacheKey
  971.     {
  972.         $entityName reset($this->_resultSetMapping->aliasMap);
  973.         if (empty($entityName)) {
  974.             return null;
  975.         }
  976.         $metadata $this->_em->getClassMetadata($entityName);
  977.         return new Cache\TimestampCacheKey($metadata->rootEntityName);
  978.     }
  979.     /**
  980.      * Get the result cache id to use to store the result set cache entry.
  981.      * Will return the configured id if it exists otherwise a hash will be
  982.      * automatically generated for you.
  983.      *
  984.      * @return array<string, string> ($key, $hash)
  985.      */
  986.     protected function getHydrationCacheId()
  987.     {
  988.         $parameters = [];
  989.         foreach ($this->getParameters() as $parameter) {
  990.             $parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
  991.         }
  992.         $sql                    $this->getSQL();
  993.         $queryCacheProfile      $this->getHydrationCacheProfile();
  994.         $hints                  $this->getHints();
  995.         $hints['hydrationMode'] = $this->getHydrationMode();
  996.         ksort($hints);
  997.         return $queryCacheProfile->generateCacheKeys($sql$parameters$hints);
  998.     }
  999.     /**
  1000.      * Set the result cache id to use to store the result set cache entry.
  1001.      * If this is not explicitly set by the developer then a hash is automatically
  1002.      * generated for you.
  1003.      *
  1004.      * @param string $id
  1005.      *
  1006.      * @return static This query instance.
  1007.      */
  1008.     public function setResultCacheId($id)
  1009.     {
  1010.         $this->_queryCacheProfile $this->_queryCacheProfile
  1011.             $this->_queryCacheProfile->setCacheKey($id)
  1012.             : new QueryCacheProfile(0$id$this->_em->getConfiguration()->getResultCacheImpl());
  1013.         return $this;
  1014.     }
  1015.     /**
  1016.      * Get the result cache id to use to store the result set cache entry if set.
  1017.      *
  1018.      * @deprecated
  1019.      *
  1020.      * @return string
  1021.      */
  1022.     public function getResultCacheId()
  1023.     {
  1024.         return $this->_queryCacheProfile $this->_queryCacheProfile->getCacheKey() : null;
  1025.     }
  1026.     /**
  1027.      * Executes the query and returns a the resulting Statement object.
  1028.      *
  1029.      * @return ResultStatement|int The executed database statement that holds
  1030.      *                             the results, or an integer indicating how
  1031.      *                             many rows were affected.
  1032.      */
  1033.     abstract protected function _doExecute();
  1034.     /**
  1035.      * Cleanup Query resource when clone is called.
  1036.      *
  1037.      * @return void
  1038.      */
  1039.     public function __clone()
  1040.     {
  1041.         $this->parameters = new ArrayCollection();
  1042.         $this->_hints = [];
  1043.         $this->_hints $this->_em->getConfiguration()->getDefaultQueryHints();
  1044.     }
  1045.     /**
  1046.      * Generates a string of currently query to use for the cache second level cache.
  1047.      *
  1048.      * @return string
  1049.      */
  1050.     protected function getHash()
  1051.     {
  1052.         $query  $this->getSQL();
  1053.         $hints  $this->getHints();
  1054.         $params array_map(function (Parameter $parameter) {
  1055.             $value $parameter->getValue();
  1056.             // Small optimization
  1057.             // Does not invoke processParameterValue for scalar value
  1058.             if (is_scalar($value)) {
  1059.                 return $value;
  1060.             }
  1061.             return $this->processParameterValue($value);
  1062.         }, $this->parameters->getValues());
  1063.         ksort($hints);
  1064.         return sha1($query '-' serialize($params) . '-' serialize($hints));
  1065.     }
  1066.     /** @param iterable<mixed> $subject */
  1067.     private function isCountable(iterable $subject): bool
  1068.     {
  1069.         return $subject instanceof Countable || is_array($subject);
  1070.     }
  1071. }