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

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