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

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. * @phpstan-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. * @phpstan-var array<string, mixed>
  100. */
  101. protected $_hints = [];
  102. /**
  103. * The hydration mode.
  104. *
  105. * @var string|int
  106. * @phpstan-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. * @phpstan-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. * @phpstan-return Cache::MODE_*|null
  220. */
  221. public function getCacheMode()
  222. {
  223. return $this->cacheMode;
  224. }
  225. /**
  226. * @param int $cacheMode
  227. * @phpstan-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. * @phpstan-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. * @phpstan-param ArrayCollection<int, Parameter>|mixed[] $parameters
  298. *
  299. * @return $this
  300. */
  301. public function setParameters($parameters)
  302. {
  303. if (is_array($parameters)) {
  304. /** @phpstan-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. // @phpstan-ignore method.deprecated
  479. if (! $profile->getResultCacheDriver()) {
  480. $defaultHydrationCacheImpl = $this->_em->getConfiguration()->getHydrationCache();
  481. if ($defaultHydrationCacheImpl) {
  482. // @phpstan-ignore method.deprecated
  483. $profile = $profile->setResultCacheDriver(DoctrineProvider::wrap($defaultHydrationCacheImpl));
  484. }
  485. }
  486. } elseif (! $profile->getResultCache()) {
  487. $defaultHydrationCacheImpl = $this->_em->getConfiguration()->getHydrationCache();
  488. if ($defaultHydrationCacheImpl) {
  489. $profile = $profile->setResultCache($defaultHydrationCacheImpl);
  490. }
  491. }
  492. $this->_hydrationCacheProfile = $profile;
  493. return $this;
  494. }
  495. /** @return QueryCacheProfile|null */
  496. public function getHydrationCacheProfile()
  497. {
  498. return $this->_hydrationCacheProfile;
  499. }
  500. /**
  501. * Set a cache profile for the result cache.
  502. *
  503. * If no result cache driver is set in the QueryCacheProfile, the default
  504. * result cache driver is used from the configuration.
  505. *
  506. * @return $this
  507. */
  508. public function setResultCacheProfile(?QueryCacheProfile $profile = null)
  509. {
  510. if ($profile === null) {
  511. if (func_num_args() < 1) {
  512. Deprecation::trigger(
  513. 'doctrine/orm',
  514. 'https://github.com/doctrine/orm/pull/9791',
  515. 'Calling %s without arguments is deprecated, pass null instead.',
  516. __METHOD__
  517. );
  518. }
  519. $this->_queryCacheProfile = null;
  520. return $this;
  521. }
  522. // DBAL 2
  523. if (! method_exists(QueryCacheProfile::class, 'setResultCache')) {
  524. // @phpstan-ignore method.deprecated
  525. if (! $profile->getResultCacheDriver()) {
  526. $defaultResultCacheDriver = $this->_em->getConfiguration()->getResultCache();
  527. if ($defaultResultCacheDriver) {
  528. // @phpstan-ignore method.deprecated
  529. $profile = $profile->setResultCacheDriver(DoctrineProvider::wrap($defaultResultCacheDriver));
  530. }
  531. }
  532. } elseif (! $profile->getResultCache()) {
  533. $defaultResultCache = $this->_em->getConfiguration()->getResultCache();
  534. if ($defaultResultCache) {
  535. $profile = $profile->setResultCache($defaultResultCache);
  536. }
  537. }
  538. $this->_queryCacheProfile = $profile;
  539. return $this;
  540. }
  541. /**
  542. * Defines a cache driver to be used for caching result sets and implicitly enables caching.
  543. *
  544. * @deprecated Use {@see setResultCache()} instead.
  545. *
  546. * @param \Doctrine\Common\Cache\Cache|null $resultCacheDriver Cache driver
  547. *
  548. * @return $this
  549. *
  550. * @throws InvalidResultCacheDriver
  551. */
  552. public function setResultCacheDriver($resultCacheDriver = null)
  553. {
  554. /** @phpstan-ignore-next-line */
  555. if ($resultCacheDriver !== null && ! ($resultCacheDriver instanceof \Doctrine\Common\Cache\Cache)) {
  556. throw InvalidResultCacheDriver::create();
  557. }
  558. return $this->setResultCache($resultCacheDriver ? CacheAdapter::wrap($resultCacheDriver) : null);
  559. }
  560. /**
  561. * Defines a cache driver to be used for caching result sets and implicitly enables caching.
  562. *
  563. * @return $this
  564. */
  565. public function setResultCache(?CacheItemPoolInterface $resultCache = null)
  566. {
  567. if ($resultCache === null) {
  568. if (func_num_args() < 1) {
  569. Deprecation::trigger(
  570. 'doctrine/orm',
  571. 'https://github.com/doctrine/orm/pull/9791',
  572. 'Calling %s without arguments is deprecated, pass null instead.',
  573. __METHOD__
  574. );
  575. }
  576. if ($this->_queryCacheProfile) {
  577. $this->_queryCacheProfile = new QueryCacheProfile($this->_queryCacheProfile->getLifetime(), $this->_queryCacheProfile->getCacheKey());
  578. }
  579. return $this;
  580. }
  581. // DBAL 2
  582. if (! method_exists(QueryCacheProfile::class, 'setResultCache')) {
  583. $resultCacheDriver = DoctrineProvider::wrap($resultCache);
  584. $this->_queryCacheProfile = $this->_queryCacheProfile
  585. // @phpstan-ignore method.deprecated
  586. ? $this->_queryCacheProfile->setResultCacheDriver($resultCacheDriver)
  587. : new QueryCacheProfile(0, null, $resultCacheDriver);
  588. return $this;
  589. }
  590. $this->_queryCacheProfile = $this->_queryCacheProfile
  591. ? $this->_queryCacheProfile->setResultCache($resultCache)
  592. : new QueryCacheProfile(0, null, $resultCache);
  593. return $this;
  594. }
  595. /**
  596. * Returns the cache driver used for caching result sets.
  597. *
  598. * @deprecated
  599. *
  600. * @return \Doctrine\Common\Cache\Cache Cache driver
  601. */
  602. public function getResultCacheDriver()
  603. {
  604. if ($this->_queryCacheProfile && $this->_queryCacheProfile->getResultCacheDriver()) {
  605. return $this->_queryCacheProfile->getResultCacheDriver();
  606. }
  607. return $this->_em->getConfiguration()->getResultCacheImpl();
  608. }
  609. /**
  610. * Set whether or not to cache the results of this query and if so, for
  611. * how long and which ID to use for the cache entry.
  612. *
  613. * @deprecated 2.7 Use {@see enableResultCache} and {@see disableResultCache} instead.
  614. *
  615. * @param bool $useCache Whether or not to cache the results of this query.
  616. * @param int $lifetime How long the cache entry is valid, in seconds.
  617. * @param string $resultCacheId ID to use for the cache entry.
  618. *
  619. * @return $this
  620. */
  621. public function useResultCache($useCache, $lifetime = null, $resultCacheId = null)
  622. {
  623. return $useCache
  624. ? $this->enableResultCache($lifetime, $resultCacheId)
  625. : $this->disableResultCache();
  626. }
  627. /**
  628. * Enables caching of the results of this query, for given or default amount of seconds
  629. * and optionally specifies which ID to use for the cache entry.
  630. *
  631. * @param int|null $lifetime How long the cache entry is valid, in seconds.
  632. * @param string|null $resultCacheId ID to use for the cache entry.
  633. *
  634. * @return $this
  635. */
  636. public function enableResultCache(?int $lifetime = null, ?string $resultCacheId = null): self
  637. {
  638. $this->setResultCacheLifetime($lifetime);
  639. $this->setResultCacheId($resultCacheId);
  640. return $this;
  641. }
  642. /**
  643. * Disables caching of the results of this query.
  644. *
  645. * @return $this
  646. */
  647. public function disableResultCache(): self
  648. {
  649. $this->_queryCacheProfile = null;
  650. return $this;
  651. }
  652. /**
  653. * Defines how long the result cache will be active before expire.
  654. *
  655. * @param int|null $lifetime How long the cache entry is valid, in seconds.
  656. *
  657. * @return $this
  658. */
  659. public function setResultCacheLifetime($lifetime)
  660. {
  661. $lifetime = (int) $lifetime;
  662. if ($this->_queryCacheProfile) {
  663. $this->_queryCacheProfile = $this->_queryCacheProfile->setLifetime($lifetime);
  664. return $this;
  665. }
  666. $this->_queryCacheProfile = new QueryCacheProfile($lifetime);
  667. $cache = $this->_em->getConfiguration()->getResultCache();
  668. if (! $cache) {
  669. return $this;
  670. }
  671. // Compatibility for DBAL 2
  672. if (! method_exists($this->_queryCacheProfile, 'setResultCache')) {
  673. // @phpstan-ignore method.deprecated
  674. $this->_queryCacheProfile = $this->_queryCacheProfile->setResultCacheDriver(DoctrineProvider::wrap($cache));
  675. return $this;
  676. }
  677. $this->_queryCacheProfile = $this->_queryCacheProfile->setResultCache($cache);
  678. return $this;
  679. }
  680. /**
  681. * Retrieves the lifetime of resultset cache.
  682. *
  683. * @deprecated
  684. *
  685. * @return int
  686. */
  687. public function getResultCacheLifetime()
  688. {
  689. return $this->_queryCacheProfile ? $this->_queryCacheProfile->getLifetime() : 0;
  690. }
  691. /**
  692. * Defines if the result cache is active or not.
  693. *
  694. * @param bool $expire Whether or not to force resultset cache expiration.
  695. *
  696. * @return $this
  697. */
  698. public function expireResultCache($expire = true)
  699. {
  700. $this->_expireResultCache = $expire;
  701. return $this;
  702. }
  703. /**
  704. * Retrieves if the resultset cache is active or not.
  705. *
  706. * @return bool
  707. */
  708. public function getExpireResultCache()
  709. {
  710. return $this->_expireResultCache;
  711. }
  712. /** @return QueryCacheProfile|null */
  713. public function getQueryCacheProfile()
  714. {
  715. return $this->_queryCacheProfile;
  716. }
  717. /**
  718. * Change the default fetch mode of an association for this query.
  719. *
  720. * @param class-string $class
  721. * @param string $assocName
  722. * @param int $fetchMode
  723. * @phpstan-param Mapping\ClassMetadata::FETCH_EAGER|Mapping\ClassMetadata::FETCH_LAZY $fetchMode
  724. *
  725. * @return $this
  726. */
  727. public function setFetchMode($class, $assocName, $fetchMode)
  728. {
  729. if (! in_array($fetchMode, [Mapping\ClassMetadata::FETCH_EAGER, Mapping\ClassMetadata::FETCH_LAZY], true)) {
  730. Deprecation::trigger(
  731. 'doctrine/orm',
  732. 'https://github.com/doctrine/orm/pull/9777',
  733. 'Calling %s() with something else than ClassMetadata::FETCH_EAGER or ClassMetadata::FETCH_LAZY is deprecated.',
  734. __METHOD__
  735. );
  736. $fetchMode = Mapping\ClassMetadata::FETCH_LAZY;
  737. }
  738. $this->_hints['fetchMode'][$class][$assocName] = $fetchMode;
  739. return $this;
  740. }
  741. /**
  742. * Defines the processing mode to be used during hydration / result set transformation.
  743. *
  744. * @param string|int $hydrationMode Doctrine processing mode to be used during hydration process.
  745. * One of the Query::HYDRATE_* constants.
  746. * @phpstan-param string|AbstractQuery::HYDRATE_* $hydrationMode
  747. *
  748. * @return $this
  749. */
  750. public function setHydrationMode($hydrationMode)
  751. {
  752. $this->_hydrationMode = $hydrationMode;
  753. return $this;
  754. }
  755. /**
  756. * Gets the hydration mode currently used by the query.
  757. *
  758. * @return string|int
  759. * @phpstan-return string|AbstractQuery::HYDRATE_*
  760. */
  761. public function getHydrationMode()
  762. {
  763. return $this->_hydrationMode;
  764. }
  765. /**
  766. * Gets the list of results for the query.
  767. *
  768. * Alias for execute(null, $hydrationMode = HYDRATE_OBJECT).
  769. *
  770. * @param string|int $hydrationMode
  771. * @phpstan-param string|AbstractQuery::HYDRATE_* $hydrationMode
  772. *
  773. * @return mixed
  774. */
  775. public function getResult($hydrationMode = self::HYDRATE_OBJECT)
  776. {
  777. return $this->execute(null, $hydrationMode);
  778. }
  779. /**
  780. * Gets the array of results for the query.
  781. *
  782. * Alias for execute(null, HYDRATE_ARRAY).
  783. *
  784. * @return mixed[]
  785. */
  786. public function getArrayResult()
  787. {
  788. return $this->execute(null, self::HYDRATE_ARRAY);
  789. }
  790. /**
  791. * Gets one-dimensional array of results for the query.
  792. *
  793. * Alias for execute(null, HYDRATE_SCALAR_COLUMN).
  794. *
  795. * @return mixed[]
  796. */
  797. public function getSingleColumnResult()
  798. {
  799. return $this->execute(null, self::HYDRATE_SCALAR_COLUMN);
  800. }
  801. /**
  802. * Gets the scalar results for the query.
  803. *
  804. * Alias for execute(null, HYDRATE_SCALAR).
  805. *
  806. * @return mixed[]
  807. */
  808. public function getScalarResult()
  809. {
  810. return $this->execute(null, self::HYDRATE_SCALAR);
  811. }
  812. /**
  813. * Get exactly one result or null.
  814. *
  815. * @param string|int|null $hydrationMode
  816. * @phpstan-param string|AbstractQuery::HYDRATE_*|null $hydrationMode
  817. *
  818. * @return mixed
  819. *
  820. * @throws NonUniqueResultException
  821. */
  822. public function getOneOrNullResult($hydrationMode = null)
  823. {
  824. try {
  825. $result = $this->execute(null, $hydrationMode);
  826. } catch (NoResultException $e) {
  827. return null;
  828. }
  829. if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
  830. return null;
  831. }
  832. if (! is_array($result)) {
  833. return $result;
  834. }
  835. if (count($result) > 1) {
  836. throw new NonUniqueResultException();
  837. }
  838. return array_shift($result);
  839. }
  840. /**
  841. * Gets the single result of the query.
  842. *
  843. * Enforces the presence as well as the uniqueness of the result.
  844. *
  845. * If the result is not unique, a NonUniqueResultException is thrown.
  846. * If there is no result, a NoResultException is thrown.
  847. *
  848. * @param string|int|null $hydrationMode
  849. * @phpstan-param string|AbstractQuery::HYDRATE_*|null $hydrationMode
  850. *
  851. * @return mixed
  852. *
  853. * @throws NonUniqueResultException If the query result is not unique.
  854. * @throws NoResultException If the query returned no result.
  855. */
  856. public function getSingleResult($hydrationMode = null)
  857. {
  858. $result = $this->execute(null, $hydrationMode);
  859. if ($this->_hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) {
  860. throw new NoResultException();
  861. }
  862. if (! is_array($result)) {
  863. return $result;
  864. }
  865. if (count($result) > 1) {
  866. throw new NonUniqueResultException();
  867. }
  868. return array_shift($result);
  869. }
  870. /**
  871. * Gets the single scalar result of the query.
  872. *
  873. * Alias for getSingleResult(HYDRATE_SINGLE_SCALAR).
  874. *
  875. * @return bool|float|int|string|null The scalar result.
  876. *
  877. * @throws NoResultException If the query returned no result.
  878. * @throws NonUniqueResultException If the query result is not unique.
  879. */
  880. public function getSingleScalarResult()
  881. {
  882. return $this->getSingleResult(self::HYDRATE_SINGLE_SCALAR);
  883. }
  884. /**
  885. * Sets a query hint. If the hint name is not recognized, it is silently ignored.
  886. *
  887. * @param string $name The name of the hint.
  888. * @param mixed $value The value of the hint.
  889. *
  890. * @return $this
  891. */
  892. public function setHint($name, $value)
  893. {
  894. $this->_hints[$name] = $value;
  895. return $this;
  896. }
  897. /**
  898. * Gets the value of a query hint. If the hint name is not recognized, FALSE is returned.
  899. *
  900. * @param string $name The name of the hint.
  901. *
  902. * @return mixed The value of the hint or FALSE, if the hint name is not recognized.
  903. */
  904. public function getHint($name)
  905. {
  906. return $this->_hints[$name] ?? false;
  907. }
  908. /**
  909. * Check if the query has a hint
  910. *
  911. * @param string $name The name of the hint
  912. *
  913. * @return bool False if the query does not have any hint
  914. */
  915. public function hasHint($name)
  916. {
  917. return isset($this->_hints[$name]);
  918. }
  919. /**
  920. * Return the key value map of query hints that are currently set.
  921. *
  922. * @return array<string,mixed>
  923. */
  924. public function getHints()
  925. {
  926. return $this->_hints;
  927. }
  928. /**
  929. * Executes the query and returns an IterableResult that can be used to incrementally
  930. * iterate over the result.
  931. *
  932. * @deprecated 2.8 Use {@see toIterable} instead. See https://github.com/doctrine/orm/issues/8463
  933. *
  934. * @param ArrayCollection|mixed[]|null $parameters The query parameters.
  935. * @param string|int|null $hydrationMode The hydration mode to use.
  936. * @phpstan-param ArrayCollection<int, Parameter>|array<string, mixed>|null $parameters
  937. * @phpstan-param string|AbstractQuery::HYDRATE_*|null $hydrationMode The hydration mode to use.
  938. *
  939. * @return IterableResult
  940. */
  941. public function iterate($parameters = null, $hydrationMode = null)
  942. {
  943. Deprecation::trigger(
  944. 'doctrine/orm',
  945. 'https://github.com/doctrine/orm/issues/8463',
  946. 'Method %s() is deprecated and will be removed in Doctrine ORM 3.0. Use toIterable() instead.',
  947. __METHOD__
  948. );
  949. if ($hydrationMode !== null) {
  950. $this->setHydrationMode($hydrationMode);
  951. }
  952. if (! empty($parameters)) {
  953. $this->setParameters($parameters);
  954. }
  955. $rsm = $this->getResultSetMapping();
  956. if ($rsm === null) {
  957. throw new LogicException('Uninitialized result set mapping.');
  958. }
  959. $stmt = $this->_doExecute();
  960. return $this->_em->newHydrator($this->_hydrationMode)->iterate($stmt, $rsm, $this->_hints);
  961. }
  962. /**
  963. * Executes the query and returns an iterable that can be used to incrementally
  964. * iterate over the result.
  965. *
  966. * @param ArrayCollection|array|mixed[] $parameters The query parameters.
  967. * @param string|int|null $hydrationMode The hydration mode to use.
  968. * @phpstan-param ArrayCollection<int, Parameter>|mixed[] $parameters
  969. * @phpstan-param string|AbstractQuery::HYDRATE_*|null $hydrationMode
  970. *
  971. * @return iterable<mixed>
  972. */
  973. public function toIterable(iterable $parameters = [], $hydrationMode = null): iterable
  974. {
  975. if ($hydrationMode !== null) {
  976. $this->setHydrationMode($hydrationMode);
  977. }
  978. if (
  979. ($this->isCountable($parameters) && count($parameters) !== 0)
  980. || ($parameters instanceof Traversable && iterator_count($parameters) !== 0)
  981. ) {
  982. $this->setParameters($parameters);
  983. }
  984. $rsm = $this->getResultSetMapping();
  985. if ($rsm === null) {
  986. throw new LogicException('Uninitialized result set mapping.');
  987. }
  988. if ($rsm->isMixed && count($rsm->scalarMappings) > 0) {
  989. throw QueryException::iterateWithMixedResultNotAllowed();
  990. }
  991. $stmt = $this->_doExecute();
  992. return $this->_em->newHydrator($this->_hydrationMode)->toIterable($stmt, $rsm, $this->_hints);
  993. }
  994. /**
  995. * Executes the query.
  996. *
  997. * @param ArrayCollection|mixed[]|null $parameters Query parameters.
  998. * @param string|int|null $hydrationMode Processing mode to be used during the hydration process.
  999. * @phpstan-param ArrayCollection<int, Parameter>|mixed[]|null $parameters
  1000. * @phpstan-param string|AbstractQuery::HYDRATE_*|null $hydrationMode
  1001. *
  1002. * @return mixed
  1003. */
  1004. public function execute($parameters = null, $hydrationMode = null)
  1005. {
  1006. if ($this->cacheable && $this->isCacheEnabled()) {
  1007. return $this->executeUsingQueryCache($parameters, $hydrationMode);
  1008. }
  1009. return $this->executeIgnoreQueryCache($parameters, $hydrationMode);
  1010. }
  1011. /**
  1012. * Execute query ignoring second level cache.
  1013. *
  1014. * @param ArrayCollection|mixed[]|null $parameters
  1015. * @param string|int|null $hydrationMode
  1016. * @phpstan-param ArrayCollection<int, Parameter>|mixed[]|null $parameters
  1017. * @phpstan-param string|AbstractQuery::HYDRATE_*|null $hydrationMode
  1018. *
  1019. * @return mixed
  1020. */
  1021. private function executeIgnoreQueryCache($parameters = null, $hydrationMode = null)
  1022. {
  1023. if ($hydrationMode !== null) {
  1024. $this->setHydrationMode($hydrationMode);
  1025. }
  1026. if (! empty($parameters)) {
  1027. $this->setParameters($parameters);
  1028. }
  1029. $setCacheEntry = static function ($data): void {
  1030. };
  1031. if ($this->_hydrationCacheProfile !== null) {
  1032. [$cacheKey, $realCacheKey] = $this->getHydrationCacheId();
  1033. $cache = $this->getHydrationCache();
  1034. $cacheItem = $cache->getItem($cacheKey);
  1035. $result = $cacheItem->isHit() ? $cacheItem->get() : [];
  1036. if (isset($result[$realCacheKey])) {
  1037. return $result[$realCacheKey];
  1038. }
  1039. if (! $result) {
  1040. $result = [];
  1041. }
  1042. $setCacheEntry = static function ($data) use ($cache, $result, $cacheItem, $realCacheKey): void {
  1043. $cache->save($cacheItem->set($result + [$realCacheKey => $data]));
  1044. };
  1045. }
  1046. $stmt = $this->_doExecute();
  1047. if (is_numeric($stmt)) {
  1048. $setCacheEntry($stmt);
  1049. return $stmt;
  1050. }
  1051. $rsm = $this->getResultSetMapping();
  1052. if ($rsm === null) {
  1053. throw new LogicException('Uninitialized result set mapping.');
  1054. }
  1055. $data = $this->_em->newHydrator($this->_hydrationMode)->hydrateAll($stmt, $rsm, $this->_hints);
  1056. $setCacheEntry($data);
  1057. return $data;
  1058. }
  1059. private function getHydrationCache(): CacheItemPoolInterface
  1060. {
  1061. assert($this->_hydrationCacheProfile !== null);
  1062. // Support for DBAL 2
  1063. if (! method_exists($this->_hydrationCacheProfile, 'getResultCache')) {
  1064. // @phpstan-ignore method.deprecated
  1065. $cacheDriver = $this->_hydrationCacheProfile->getResultCacheDriver();
  1066. assert($cacheDriver !== null);
  1067. return CacheAdapter::wrap($cacheDriver);
  1068. }
  1069. $cache = $this->_hydrationCacheProfile->getResultCache();
  1070. assert($cache !== null);
  1071. return $cache;
  1072. }
  1073. /**
  1074. * Load from second level cache or executes the query and put into cache.
  1075. *
  1076. * @param ArrayCollection|mixed[]|null $parameters
  1077. * @param string|int|null $hydrationMode
  1078. * @phpstan-param ArrayCollection<int, Parameter>|mixed[]|null $parameters
  1079. * @phpstan-param string|AbstractQuery::HYDRATE_*|null $hydrationMode
  1080. *
  1081. * @return mixed
  1082. */
  1083. private function executeUsingQueryCache($parameters = null, $hydrationMode = null)
  1084. {
  1085. $rsm = $this->getResultSetMapping();
  1086. if ($rsm === null) {
  1087. throw new LogicException('Uninitialized result set mapping.');
  1088. }
  1089. $queryCache = $this->_em->getCache()->getQueryCache($this->cacheRegion);
  1090. $queryKey = new QueryCacheKey(
  1091. $this->getHash(),
  1092. $this->lifetime,
  1093. $this->cacheMode ?: Cache::MODE_NORMAL,
  1094. $this->getTimestampKey()
  1095. );
  1096. $result = $queryCache->get($queryKey, $rsm, $this->_hints);
  1097. if ($result !== null) {
  1098. if ($this->cacheLogger) {
  1099. $this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $queryKey);
  1100. }
  1101. return $result;
  1102. }
  1103. $result = $this->executeIgnoreQueryCache($parameters, $hydrationMode);
  1104. $cached = $queryCache->put($queryKey, $rsm, $result, $this->_hints);
  1105. if ($this->cacheLogger) {
  1106. $this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $queryKey);
  1107. if ($cached) {
  1108. $this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $queryKey);
  1109. }
  1110. }
  1111. return $result;
  1112. }
  1113. private function getTimestampKey(): ?TimestampCacheKey
  1114. {
  1115. assert($this->_resultSetMapping !== null);
  1116. $entityName = reset($this->_resultSetMapping->aliasMap);
  1117. if (empty($entityName)) {
  1118. return null;
  1119. }
  1120. $metadata = $this->_em->getClassMetadata($entityName);
  1121. return new Cache\TimestampCacheKey($metadata->rootEntityName);
  1122. }
  1123. /**
  1124. * Get the result cache id to use to store the result set cache entry.
  1125. * Will return the configured id if it exists otherwise a hash will be
  1126. * automatically generated for you.
  1127. *
  1128. * @return string[] ($key, $hash)
  1129. * @phpstan-return array{string, string} ($key, $hash)
  1130. */
  1131. protected function getHydrationCacheId()
  1132. {
  1133. $parameters = [];
  1134. $types = [];
  1135. foreach ($this->getParameters() as $parameter) {
  1136. $parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue());
  1137. $types[$parameter->getName()] = $parameter->getType();
  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, $types, $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. }