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