Query.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797
  1. <?php
  2. /*
  3. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  4. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  5. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  6. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  7. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  8. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  9. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  10. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  11. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  12. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  13. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  14. *
  15. * This software consists of voluntary contributions made by many individuals
  16. * and is licensed under the MIT license. For more information, see
  17. * <http://www.doctrine-project.org>.
  18. */
  19. namespace Doctrine\ORM;
  20. use Doctrine\Common\Cache\Cache;
  21. use Doctrine\Common\Collections\ArrayCollection;
  22. use Doctrine\DBAL\LockMode;
  23. use Doctrine\DBAL\Types\Type;
  24. use Doctrine\ORM\Internal\Hydration\IterableResult;
  25. use Doctrine\ORM\Mapping\ClassMetadata;
  26. use Doctrine\ORM\Query\AST\DeleteStatement;
  27. use Doctrine\ORM\Query\AST\SelectStatement;
  28. use Doctrine\ORM\Query\AST\UpdateStatement;
  29. use Doctrine\ORM\Query\Exec\AbstractSqlExecutor;
  30. use Doctrine\ORM\Query\Parameter;
  31. use Doctrine\ORM\Query\ParameterTypeInferer;
  32. use Doctrine\ORM\Query\Parser;
  33. use Doctrine\ORM\Query\ParserResult;
  34. use Doctrine\ORM\Query\QueryException;
  35. use Doctrine\ORM\Utility\HierarchyDiscriminatorResolver;
  36. use function array_keys;
  37. use function array_values;
  38. use function assert;
  39. use function count;
  40. use function in_array;
  41. use function ksort;
  42. use function md5;
  43. use function reset;
  44. use function serialize;
  45. use function sha1;
  46. use function stripos;
  47. /**
  48. * A Query object represents a DQL query.
  49. */
  50. final class Query extends AbstractQuery
  51. {
  52. /**
  53. * A query object is in CLEAN state when it has NO unparsed/unprocessed DQL parts.
  54. */
  55. public const STATE_CLEAN = 1;
  56. /**
  57. * A query object is in state DIRTY when it has DQL parts that have not yet been
  58. * parsed/processed. This is automatically defined as DIRTY when addDqlQueryPart
  59. * is called.
  60. */
  61. public const STATE_DIRTY = 2;
  62. /* Query HINTS */
  63. /**
  64. * The refresh hint turns any query into a refresh query with the result that
  65. * any local changes in entities are overridden with the fetched values.
  66. */
  67. public const HINT_REFRESH = 'doctrine.refresh';
  68. public const HINT_CACHE_ENABLED = 'doctrine.cache.enabled';
  69. public const HINT_CACHE_EVICT = 'doctrine.cache.evict';
  70. /**
  71. * Internal hint: is set to the proxy entity that is currently triggered for loading
  72. */
  73. public const HINT_REFRESH_ENTITY = 'doctrine.refresh.entity';
  74. /**
  75. * The forcePartialLoad query hint forces a particular query to return
  76. * partial objects.
  77. *
  78. * @todo Rename: HINT_OPTIMIZE
  79. */
  80. public const HINT_FORCE_PARTIAL_LOAD = 'doctrine.forcePartialLoad';
  81. /**
  82. * The includeMetaColumns query hint causes meta columns like foreign keys and
  83. * discriminator columns to be selected and returned as part of the query result.
  84. *
  85. * This hint does only apply to non-object queries.
  86. */
  87. public const HINT_INCLUDE_META_COLUMNS = 'doctrine.includeMetaColumns';
  88. /**
  89. * An array of class names that implement \Doctrine\ORM\Query\TreeWalker and
  90. * are iterated and executed after the DQL has been parsed into an AST.
  91. */
  92. public const HINT_CUSTOM_TREE_WALKERS = 'doctrine.customTreeWalkers';
  93. /**
  94. * A string with a class name that implements \Doctrine\ORM\Query\TreeWalker
  95. * and is used for generating the target SQL from any DQL AST tree.
  96. */
  97. public const HINT_CUSTOM_OUTPUT_WALKER = 'doctrine.customOutputWalker';
  98. //const HINT_READ_ONLY = 'doctrine.readOnly';
  99. public const HINT_INTERNAL_ITERATION = 'doctrine.internal.iteration';
  100. public const HINT_LOCK_MODE = 'doctrine.lockMode';
  101. /**
  102. * The current state of this query.
  103. *
  104. * @var int
  105. */
  106. private $_state = self::STATE_DIRTY;
  107. /**
  108. * A snapshot of the parameter types the query was parsed with.
  109. *
  110. * @var array<string,Type>
  111. */
  112. private $parsedTypes = [];
  113. /**
  114. * Cached DQL query.
  115. *
  116. * @var string|null
  117. */
  118. private $dql = null;
  119. /**
  120. * The parser result that holds DQL => SQL information.
  121. *
  122. * @var ParserResult
  123. */
  124. private $parserResult;
  125. /**
  126. * The first result to return (the "offset").
  127. *
  128. * @var int|null
  129. */
  130. private $firstResult = null;
  131. /**
  132. * The maximum number of results to return (the "limit").
  133. *
  134. * @var int|null
  135. */
  136. private $maxResults = null;
  137. /**
  138. * The cache driver used for caching queries.
  139. *
  140. * @var Cache|null
  141. */
  142. private $queryCache;
  143. /**
  144. * Whether or not expire the query cache.
  145. *
  146. * @var bool
  147. */
  148. private $expireQueryCache = false;
  149. /**
  150. * The query cache lifetime.
  151. *
  152. * @var int
  153. */
  154. private $queryCacheTTL;
  155. /**
  156. * Whether to use a query cache, if available. Defaults to TRUE.
  157. *
  158. * @var bool
  159. */
  160. private $useQueryCache = true;
  161. /**
  162. * Gets the SQL query/queries that correspond to this DQL query.
  163. *
  164. * @return mixed The built sql query or an array of all sql queries.
  165. *
  166. * @override
  167. */
  168. public function getSQL()
  169. {
  170. return $this->parse()->getSqlExecutor()->getSqlStatements();
  171. }
  172. /**
  173. * Returns the corresponding AST for this DQL query.
  174. *
  175. * @return SelectStatement|UpdateStatement|DeleteStatement
  176. */
  177. public function getAST()
  178. {
  179. $parser = new Parser($this);
  180. return $parser->getAST();
  181. }
  182. /**
  183. * {@inheritdoc}
  184. */
  185. protected function getResultSetMapping()
  186. {
  187. // parse query or load from cache
  188. if ($this->_resultSetMapping === null) {
  189. $this->_resultSetMapping = $this->parse()->getResultSetMapping();
  190. }
  191. return $this->_resultSetMapping;
  192. }
  193. /**
  194. * Parses the DQL query, if necessary, and stores the parser result.
  195. *
  196. * Note: Populates $this->_parserResult as a side-effect.
  197. *
  198. * @return ParserResult
  199. */
  200. private function parse()
  201. {
  202. $types = [];
  203. foreach ($this->parameters as $parameter) {
  204. /** @var Query\Parameter $parameter */
  205. $types[$parameter->getName()] = $parameter->getType();
  206. }
  207. // Return previous parser result if the query and the filter collection are both clean
  208. if ($this->_state === self::STATE_CLEAN && $this->parsedTypes === $types && $this->_em->isFiltersStateClean()) {
  209. return $this->parserResult;
  210. }
  211. $this->_state = self::STATE_CLEAN;
  212. $this->parsedTypes = $types;
  213. $queryCache = $this->getQueryCacheDriver();
  214. // Check query cache.
  215. if (! ($this->useQueryCache && $queryCache)) {
  216. $parser = new Parser($this);
  217. $this->parserResult = $parser->parse();
  218. return $this->parserResult;
  219. }
  220. $hash = $this->getQueryCacheId();
  221. $cached = $this->expireQueryCache ? false : $queryCache->fetch($hash);
  222. if ($cached instanceof ParserResult) {
  223. // Cache hit.
  224. $this->parserResult = $cached;
  225. return $this->parserResult;
  226. }
  227. // Cache miss.
  228. $parser = new Parser($this);
  229. $this->parserResult = $parser->parse();
  230. $queryCache->save($hash, $this->parserResult, $this->queryCacheTTL);
  231. return $this->parserResult;
  232. }
  233. /**
  234. * {@inheritdoc}
  235. */
  236. protected function _doExecute()
  237. {
  238. $executor = $this->parse()->getSqlExecutor();
  239. if ($this->_queryCacheProfile) {
  240. $executor->setQueryCacheProfile($this->_queryCacheProfile);
  241. } else {
  242. $executor->removeQueryCacheProfile();
  243. }
  244. if ($this->_resultSetMapping === null) {
  245. $this->_resultSetMapping = $this->parserResult->getResultSetMapping();
  246. }
  247. // Prepare parameters
  248. $paramMappings = $this->parserResult->getParameterMappings();
  249. $paramCount = count($this->parameters);
  250. $mappingCount = count($paramMappings);
  251. if ($paramCount > $mappingCount) {
  252. throw QueryException::tooManyParameters($mappingCount, $paramCount);
  253. }
  254. if ($paramCount < $mappingCount) {
  255. throw QueryException::tooFewParameters($mappingCount, $paramCount);
  256. }
  257. // evict all cache for the entity region
  258. if ($this->hasCache && isset($this->_hints[self::HINT_CACHE_EVICT]) && $this->_hints[self::HINT_CACHE_EVICT]) {
  259. $this->evictEntityCacheRegion();
  260. }
  261. [$sqlParams, $types] = $this->processParameterMappings($paramMappings);
  262. $this->evictResultSetCache(
  263. $executor,
  264. $sqlParams,
  265. $types,
  266. $this->_em->getConnection()->getParams()
  267. );
  268. return $executor->execute($this->_em->getConnection(), $sqlParams, $types);
  269. }
  270. /**
  271. * @param array<string,mixed> $sqlParams
  272. * @param array<string,Type> $types
  273. * @param array<string,mixed> $connectionParams
  274. */
  275. private function evictResultSetCache(
  276. AbstractSqlExecutor $executor,
  277. array $sqlParams,
  278. array $types,
  279. array $connectionParams
  280. ) {
  281. if ($this->_queryCacheProfile === null || ! $this->getExpireResultCache()) {
  282. return;
  283. }
  284. $cacheDriver = $this->_queryCacheProfile->getResultCacheDriver();
  285. $statements = (array) $executor->getSqlStatements(); // Type casted since it can either be a string or an array
  286. foreach ($statements as $statement) {
  287. $cacheKeys = $this->_queryCacheProfile->generateCacheKeys($statement, $sqlParams, $types, $connectionParams);
  288. $cacheDriver->delete(reset($cacheKeys));
  289. }
  290. }
  291. /**
  292. * Evict entity cache region
  293. */
  294. private function evictEntityCacheRegion()
  295. {
  296. $AST = $this->getAST();
  297. if ($AST instanceof SelectStatement) {
  298. throw new QueryException('The hint "HINT_CACHE_EVICT" is not valid for select statements.');
  299. }
  300. $className = $AST instanceof DeleteStatement
  301. ? $AST->deleteClause->abstractSchemaName
  302. : $AST->updateClause->abstractSchemaName;
  303. $this->_em->getCache()->evictEntityRegion($className);
  304. }
  305. /**
  306. * Processes query parameter mappings.
  307. *
  308. * @param Parameter[] $paramMappings
  309. *
  310. * @return mixed[][]
  311. *
  312. * @throws Query\QueryException
  313. *
  314. * @psalm-return array{0: list<mixed>, 1: array}
  315. */
  316. private function processParameterMappings(array $paramMappings): array
  317. {
  318. $sqlParams = [];
  319. $types = [];
  320. foreach ($this->parameters as $parameter) {
  321. $key = $parameter->getName();
  322. if (! isset($paramMappings[$key])) {
  323. throw QueryException::unknownParameter($key);
  324. }
  325. [$value, $type] = $this->resolveParameterValue($parameter);
  326. foreach ($paramMappings[$key] as $position) {
  327. $types[$position] = $type;
  328. }
  329. $sqlPositions = $paramMappings[$key];
  330. // optimized multi value sql positions away for now,
  331. // they are not allowed in DQL anyways.
  332. $value = [$value];
  333. $countValue = count($value);
  334. for ($i = 0, $l = count($sqlPositions); $i < $l; $i++) {
  335. $sqlParams[$sqlPositions[$i]] = $value[$i % $countValue];
  336. }
  337. }
  338. if (count($sqlParams) !== count($types)) {
  339. throw QueryException::parameterTypeMismatch();
  340. }
  341. if ($sqlParams) {
  342. ksort($sqlParams);
  343. $sqlParams = array_values($sqlParams);
  344. ksort($types);
  345. $types = array_values($types);
  346. }
  347. return [$sqlParams, $types];
  348. }
  349. /**
  350. * @return mixed[] tuple of (value, type)
  351. *
  352. * @psalm-return array{0: mixed, 1: mixed}
  353. */
  354. private function resolveParameterValue(Parameter $parameter): array
  355. {
  356. if ($parameter->typeWasSpecified()) {
  357. return [$parameter->getValue(), $parameter->getType()];
  358. }
  359. $key = $parameter->getName();
  360. $originalValue = $parameter->getValue();
  361. $value = $originalValue;
  362. $rsm = $this->getResultSetMapping();
  363. assert($rsm !== null);
  364. if ($value instanceof ClassMetadata && isset($rsm->metadataParameterMapping[$key])) {
  365. $value = $value->getMetadataValue($rsm->metadataParameterMapping[$key]);
  366. }
  367. if ($value instanceof ClassMetadata && isset($rsm->discriminatorParameters[$key])) {
  368. $value = array_keys(HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($value, $this->_em));
  369. }
  370. $processedValue = $this->processParameterValue($value);
  371. return [
  372. $processedValue,
  373. $originalValue === $processedValue
  374. ? $parameter->getType()
  375. : ParameterTypeInferer::inferType($processedValue),
  376. ];
  377. }
  378. /**
  379. * Defines a cache driver to be used for caching queries.
  380. *
  381. * @param Cache|null $queryCache Cache driver.
  382. *
  383. * @return self This query instance.
  384. */
  385. public function setQueryCacheDriver($queryCache): self
  386. {
  387. $this->queryCache = $queryCache;
  388. return $this;
  389. }
  390. /**
  391. * Defines whether the query should make use of a query cache, if available.
  392. *
  393. * @param bool $bool
  394. *
  395. * @return self This query instance.
  396. */
  397. public function useQueryCache($bool): self
  398. {
  399. $this->useQueryCache = $bool;
  400. return $this;
  401. }
  402. /**
  403. * Returns the cache driver used for query caching.
  404. *
  405. * @return Cache|null The cache driver used for query caching or NULL, if
  406. * this Query does not use query caching.
  407. */
  408. public function getQueryCacheDriver()
  409. {
  410. if ($this->queryCache) {
  411. return $this->queryCache;
  412. }
  413. return $this->_em->getConfiguration()->getQueryCacheImpl();
  414. }
  415. /**
  416. * Defines how long the query cache will be active before expire.
  417. *
  418. * @param int $timeToLive How long the cache entry is valid.
  419. *
  420. * @return self This query instance.
  421. */
  422. public function setQueryCacheLifetime($timeToLive): self
  423. {
  424. if ($timeToLive !== null) {
  425. $timeToLive = (int) $timeToLive;
  426. }
  427. $this->queryCacheTTL = $timeToLive;
  428. return $this;
  429. }
  430. /**
  431. * Retrieves the lifetime of resultset cache.
  432. *
  433. * @return int
  434. */
  435. public function getQueryCacheLifetime()
  436. {
  437. return $this->queryCacheTTL;
  438. }
  439. /**
  440. * Defines if the query cache is active or not.
  441. *
  442. * @param bool $expire Whether or not to force query cache expiration.
  443. *
  444. * @return self This query instance.
  445. */
  446. public function expireQueryCache($expire = true): self
  447. {
  448. $this->expireQueryCache = $expire;
  449. return $this;
  450. }
  451. /**
  452. * Retrieves if the query cache is active or not.
  453. *
  454. * @return bool
  455. */
  456. public function getExpireQueryCache()
  457. {
  458. return $this->expireQueryCache;
  459. }
  460. /**
  461. * @override
  462. */
  463. public function free()
  464. {
  465. parent::free();
  466. $this->dql = null;
  467. $this->_state = self::STATE_CLEAN;
  468. }
  469. /**
  470. * Sets a DQL query string.
  471. *
  472. * @param string $dqlQuery DQL Query.
  473. */
  474. public function setDQL($dqlQuery): self
  475. {
  476. if ($dqlQuery !== null) {
  477. $this->dql = $dqlQuery;
  478. $this->_state = self::STATE_DIRTY;
  479. }
  480. return $this;
  481. }
  482. /**
  483. * Returns the DQL query that is represented by this query object.
  484. *
  485. * @return string|null
  486. */
  487. public function getDQL()
  488. {
  489. return $this->dql;
  490. }
  491. /**
  492. * Returns the state of this query object
  493. * By default the type is Doctrine_ORM_Query_Abstract::STATE_CLEAN but if it appears any unprocessed DQL
  494. * part, it is switched to Doctrine_ORM_Query_Abstract::STATE_DIRTY.
  495. *
  496. * @see AbstractQuery::STATE_CLEAN
  497. * @see AbstractQuery::STATE_DIRTY
  498. *
  499. * @return int The query state.
  500. */
  501. public function getState()
  502. {
  503. return $this->_state;
  504. }
  505. /**
  506. * Method to check if an arbitrary piece of DQL exists
  507. *
  508. * @param string $dql Arbitrary piece of DQL to check for.
  509. *
  510. * @return bool
  511. */
  512. public function contains($dql)
  513. {
  514. return stripos($this->getDQL(), $dql) !== false;
  515. }
  516. /**
  517. * Sets the position of the first result to retrieve (the "offset").
  518. *
  519. * @param int|null $firstResult The first result to return.
  520. *
  521. * @return self This query object.
  522. */
  523. public function setFirstResult($firstResult): self
  524. {
  525. $this->firstResult = $firstResult;
  526. $this->_state = self::STATE_DIRTY;
  527. return $this;
  528. }
  529. /**
  530. * Gets the position of the first result the query object was set to retrieve (the "offset").
  531. * Returns NULL if {@link setFirstResult} was not applied to this query.
  532. *
  533. * @return int|null The position of the first result.
  534. */
  535. public function getFirstResult()
  536. {
  537. return $this->firstResult;
  538. }
  539. /**
  540. * Sets the maximum number of results to retrieve (the "limit").
  541. *
  542. * @param int|null $maxResults
  543. *
  544. * @return self This query object.
  545. */
  546. public function setMaxResults($maxResults): self
  547. {
  548. $this->maxResults = $maxResults;
  549. $this->_state = self::STATE_DIRTY;
  550. return $this;
  551. }
  552. /**
  553. * Gets the maximum number of results the query object was set to retrieve (the "limit").
  554. * Returns NULL if {@link setMaxResults} was not applied to this query.
  555. *
  556. * @return int|null Maximum number of results.
  557. */
  558. public function getMaxResults()
  559. {
  560. return $this->maxResults;
  561. }
  562. /**
  563. * Executes the query and returns an IterableResult that can be used to incrementally
  564. * iterated over the result.
  565. *
  566. * @deprecated
  567. *
  568. * @param ArrayCollection|mixed[]|null $parameters The query parameters.
  569. * @param string|int $hydrationMode The hydration mode to use.
  570. *
  571. * @return IterableResult
  572. */
  573. public function iterate($parameters = null, $hydrationMode = self::HYDRATE_OBJECT)
  574. {
  575. $this->setHint(self::HINT_INTERNAL_ITERATION, true);
  576. return parent::iterate($parameters, $hydrationMode);
  577. }
  578. /** {@inheritDoc} */
  579. public function toIterable(iterable $parameters = [], $hydrationMode = self::HYDRATE_OBJECT): iterable
  580. {
  581. $this->setHint(self::HINT_INTERNAL_ITERATION, true);
  582. return parent::toIterable($parameters, $hydrationMode);
  583. }
  584. /**
  585. * {@inheritdoc}
  586. */
  587. public function setHint($name, $value)
  588. {
  589. $this->_state = self::STATE_DIRTY;
  590. return parent::setHint($name, $value);
  591. }
  592. /**
  593. * {@inheritdoc}
  594. */
  595. public function setHydrationMode($hydrationMode)
  596. {
  597. $this->_state = self::STATE_DIRTY;
  598. return parent::setHydrationMode($hydrationMode);
  599. }
  600. /**
  601. * Set the lock mode for this Query.
  602. *
  603. * @see \Doctrine\DBAL\LockMode
  604. *
  605. * @param int $lockMode
  606. *
  607. * @throws TransactionRequiredException
  608. */
  609. public function setLockMode($lockMode): self
  610. {
  611. if (in_array($lockMode, [LockMode::NONE, LockMode::PESSIMISTIC_READ, LockMode::PESSIMISTIC_WRITE], true)) {
  612. if (! $this->_em->getConnection()->isTransactionActive()) {
  613. throw TransactionRequiredException::transactionRequired();
  614. }
  615. }
  616. $this->setHint(self::HINT_LOCK_MODE, $lockMode);
  617. return $this;
  618. }
  619. /**
  620. * Get the current lock mode for this query.
  621. *
  622. * @return int|null The current lock mode of this query or NULL if no specific lock mode is set.
  623. */
  624. public function getLockMode()
  625. {
  626. $lockMode = $this->getHint(self::HINT_LOCK_MODE);
  627. if ($lockMode === false) {
  628. return null;
  629. }
  630. return $lockMode;
  631. }
  632. /**
  633. * Generate a cache id for the query cache - reusing the Result-Cache-Id generator.
  634. */
  635. protected function getQueryCacheId(): string
  636. {
  637. ksort($this->_hints);
  638. $platform = $this->getEntityManager()
  639. ->getConnection()
  640. ->getDatabasePlatform()
  641. ->getName();
  642. return md5(
  643. $this->getDQL() . serialize($this->_hints) .
  644. '&platform=' . $platform .
  645. ($this->_em->hasFilters() ? $this->_em->getFilters()->getHash() : '') .
  646. '&firstResult=' . $this->firstResult . '&maxResult=' . $this->maxResults .
  647. '&hydrationMode=' . $this->_hydrationMode . '&types=' . serialize($this->parsedTypes) . 'DOCTRINE_QUERY_CACHE_SALT'
  648. );
  649. }
  650. /**
  651. * {@inheritdoc}
  652. */
  653. protected function getHash()
  654. {
  655. return sha1(parent::getHash() . '-' . $this->firstResult . '-' . $this->maxResults);
  656. }
  657. /**
  658. * Cleanup Query resource when clone is called.
  659. *
  660. * @return void
  661. */
  662. public function __clone()
  663. {
  664. parent::__clone();
  665. $this->_state = self::STATE_DIRTY;
  666. }
  667. }