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

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