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

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