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

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