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

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