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 | } | ||
