From bf6655a534a6775d30cafa67bd801276bda1d98d Mon Sep 17 00:00:00 2001 From: polo Date: Tue, 13 Aug 2024 23:45:21 +0200 Subject: =?UTF-8?q?VERSION=200.2=20doctrine=20ORM=20et=20entit=C3=A9s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vendor/doctrine/orm/src/Query.php | 682 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 682 insertions(+) create mode 100644 vendor/doctrine/orm/src/Query.php (limited to 'vendor/doctrine/orm/src/Query.php') 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 @@ + + */ + private array $parsedTypes = []; + + /** + * Cached DQL query. + */ + private string|null $dql = null; + + /** + * The parser result that holds DQL => SQL information. + */ + private ParserResult $parserResult; + + /** + * The first result to return (the "offset"). + */ + private int $firstResult = 0; + + /** + * The maximum number of results to return (the "limit"). + */ + private int|null $maxResults = null; + + /** + * The cache driver used for caching queries. + */ + private CacheItemPoolInterface|null $queryCache = null; + + /** + * Whether or not expire the query cache. + */ + private bool $expireQueryCache = false; + + /** + * The query cache lifetime. + */ + private int|null $queryCacheTTL = null; + + /** + * Whether to use a query cache, if available. Defaults to TRUE. + */ + private bool $useQueryCache = true; + + /** + * Gets the SQL query/queries that correspond to this DQL query. + * + * @return list|string The built sql query or an array of all sql queries. + */ + public function getSQL(): string|array + { + return $this->parse()->getSqlExecutor()->getSqlStatements(); + } + + /** + * Returns the corresponding AST for this DQL query. + */ + public function getAST(): SelectStatement|UpdateStatement|DeleteStatement + { + $parser = new Parser($this); + + return $parser->getAST(); + } + + protected function getResultSetMapping(): ResultSetMapping + { + // parse query or load from cache + if ($this->resultSetMapping === null) { + $this->resultSetMapping = $this->parse()->getResultSetMapping(); + } + + return $this->resultSetMapping; + } + + /** + * Parses the DQL query, if necessary, and stores the parser result. + * + * Note: Populates $this->_parserResult as a side-effect. + */ + private function parse(): ParserResult + { + $types = []; + + foreach ($this->parameters as $parameter) { + /** @var Query\Parameter $parameter */ + $types[$parameter->getName()] = $parameter->getType(); + } + + // Return previous parser result if the query and the filter collection are both clean + if ($this->state === self::STATE_CLEAN && $this->parsedTypes === $types && $this->em->isFiltersStateClean()) { + return $this->parserResult; + } + + $this->state = self::STATE_CLEAN; + $this->parsedTypes = $types; + + $queryCache = $this->queryCache ?? $this->em->getConfiguration()->getQueryCache(); + // Check query cache. + if (! ($this->useQueryCache && $queryCache)) { + $parser = new Parser($this); + + $this->parserResult = $parser->parse(); + + return $this->parserResult; + } + + $cacheItem = $queryCache->getItem($this->getQueryCacheId()); + + if (! $this->expireQueryCache && $cacheItem->isHit()) { + $cached = $cacheItem->get(); + if ($cached instanceof ParserResult) { + // Cache hit. + $this->parserResult = $cached; + + return $this->parserResult; + } + } + + // Cache miss. + $parser = new Parser($this); + + $this->parserResult = $parser->parse(); + + $queryCache->save($cacheItem->set($this->parserResult)->expiresAfter($this->queryCacheTTL)); + + return $this->parserResult; + } + + protected function _doExecute(): Result|int + { + $executor = $this->parse()->getSqlExecutor(); + + if ($this->queryCacheProfile) { + $executor->setQueryCacheProfile($this->queryCacheProfile); + } else { + $executor->removeQueryCacheProfile(); + } + + if ($this->resultSetMapping === null) { + $this->resultSetMapping = $this->parserResult->getResultSetMapping(); + } + + // Prepare parameters + $paramMappings = $this->parserResult->getParameterMappings(); + $paramCount = count($this->parameters); + $mappingCount = count($paramMappings); + + if ($paramCount > $mappingCount) { + throw QueryException::tooManyParameters($mappingCount, $paramCount); + } + + if ($paramCount < $mappingCount) { + throw QueryException::tooFewParameters($mappingCount, $paramCount); + } + + // evict all cache for the entity region + if ($this->hasCache && isset($this->hints[self::HINT_CACHE_EVICT]) && $this->hints[self::HINT_CACHE_EVICT]) { + $this->evictEntityCacheRegion(); + } + + [$sqlParams, $types] = $this->processParameterMappings($paramMappings); + + $this->evictResultSetCache( + $executor, + $sqlParams, + $types, + $this->em->getConnection()->getParams(), + ); + + return $executor->execute($this->em->getConnection(), $sqlParams, $types); + } + + /** + * @param array $sqlParams + * @param array $types + * @param array $connectionParams + */ + private function evictResultSetCache( + AbstractSqlExecutor $executor, + array $sqlParams, + array $types, + array $connectionParams, + ): void { + if ($this->queryCacheProfile === null || ! $this->getExpireResultCache()) { + return; + } + + $cache = $this->queryCacheProfile->getResultCache(); + + assert($cache !== null); + + $statements = (array) $executor->getSqlStatements(); // Type casted since it can either be a string or an array + + foreach ($statements as $statement) { + $cacheKeys = $this->queryCacheProfile->generateCacheKeys($statement, $sqlParams, $types, $connectionParams); + $cache->deleteItem(reset($cacheKeys)); + } + } + + /** + * Evict entity cache region + */ + private function evictEntityCacheRegion(): void + { + $AST = $this->getAST(); + + if ($AST instanceof SelectStatement) { + throw new QueryException('The hint "HINT_CACHE_EVICT" is not valid for select statements.'); + } + + $className = $AST instanceof DeleteStatement + ? $AST->deleteClause->abstractSchemaName + : $AST->updateClause->abstractSchemaName; + + $this->em->getCache()->evictEntityRegion($className); + } + + /** + * Processes query parameter mappings. + * + * @param array> $paramMappings + * + * @return mixed[][] + * @psalm-return array{0: list, 1: array} + * + * @throws Query\QueryException + */ + private function processParameterMappings(array $paramMappings): array + { + $sqlParams = []; + $types = []; + + foreach ($this->parameters as $parameter) { + $key = $parameter->getName(); + + if (! isset($paramMappings[$key])) { + throw QueryException::unknownParameter($key); + } + + [$value, $type] = $this->resolveParameterValue($parameter); + + foreach ($paramMappings[$key] as $position) { + $types[$position] = $type; + } + + $sqlPositions = $paramMappings[$key]; + + // optimized multi value sql positions away for now, + // they are not allowed in DQL anyways. + $value = [$value]; + $countValue = count($value); + + for ($i = 0, $l = count($sqlPositions); $i < $l; $i++) { + $sqlParams[$sqlPositions[$i]] = $value[$i % $countValue]; + } + } + + if (count($sqlParams) !== count($types)) { + throw QueryException::parameterTypeMismatch(); + } + + if ($sqlParams) { + ksort($sqlParams); + $sqlParams = array_values($sqlParams); + + ksort($types); + $types = array_values($types); + } + + return [$sqlParams, $types]; + } + + /** + * @return mixed[] tuple of (value, type) + * @psalm-return array{0: mixed, 1: mixed} + */ + private function resolveParameterValue(Parameter $parameter): array + { + if ($parameter->typeWasSpecified()) { + return [$parameter->getValue(), $parameter->getType()]; + } + + $key = $parameter->getName(); + $originalValue = $parameter->getValue(); + $value = $originalValue; + $rsm = $this->getResultSetMapping(); + + if ($value instanceof ClassMetadata && isset($rsm->metadataParameterMapping[$key])) { + $value = $value->getMetadataValue($rsm->metadataParameterMapping[$key]); + } + + if ($value instanceof ClassMetadata && isset($rsm->discriminatorParameters[$key])) { + $value = array_keys(HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($value, $this->em)); + } + + $processedValue = $this->processParameterValue($value); + + return [ + $processedValue, + $originalValue === $processedValue + ? $parameter->getType() + : ParameterTypeInferer::inferType($processedValue), + ]; + } + + /** + * Defines a cache driver to be used for caching queries. + * + * @return $this + */ + public function setQueryCache(CacheItemPoolInterface|null $queryCache): self + { + $this->queryCache = $queryCache; + + return $this; + } + + /** + * Defines whether the query should make use of a query cache, if available. + * + * @return $this + */ + public function useQueryCache(bool $bool): self + { + $this->useQueryCache = $bool; + + return $this; + } + + /** + * Defines how long the query cache will be active before expire. + * + * @param int|null $timeToLive How long the cache entry is valid. + * + * @return $this + */ + public function setQueryCacheLifetime(int|null $timeToLive): self + { + $this->queryCacheTTL = $timeToLive; + + return $this; + } + + /** + * Retrieves the lifetime of resultset cache. + */ + public function getQueryCacheLifetime(): int|null + { + return $this->queryCacheTTL; + } + + /** + * Defines if the query cache is active or not. + * + * @return $this + */ + public function expireQueryCache(bool $expire = true): self + { + $this->expireQueryCache = $expire; + + return $this; + } + + /** + * Retrieves if the query cache is active or not. + */ + public function getExpireQueryCache(): bool + { + return $this->expireQueryCache; + } + + public function free(): void + { + parent::free(); + + $this->dql = null; + $this->state = self::STATE_CLEAN; + } + + /** + * Sets a DQL query string. + */ + public function setDQL(string $dqlQuery): self + { + $this->dql = $dqlQuery; + $this->state = self::STATE_DIRTY; + + return $this; + } + + /** + * Returns the DQL query that is represented by this query object. + */ + public function getDQL(): string|null + { + return $this->dql; + } + + /** + * Returns the state of this query object + * By default the type is Doctrine_ORM_Query_Abstract::STATE_CLEAN but if it appears any unprocessed DQL + * part, it is switched to Doctrine_ORM_Query_Abstract::STATE_DIRTY. + * + * @see AbstractQuery::STATE_CLEAN + * @see AbstractQuery::STATE_DIRTY + * + * @return int The query state. + * @psalm-return self::STATE_* The query state. + */ + public function getState(): int + { + return $this->state; + } + + /** + * Method to check if an arbitrary piece of DQL exists + * + * @param string $dql Arbitrary piece of DQL to check for. + */ + public function contains(string $dql): bool + { + return stripos($this->getDQL(), $dql) !== false; + } + + /** + * Sets the position of the first result to retrieve (the "offset"). + * + * @param int $firstResult The first result to return. + * + * @return $this + */ + public function setFirstResult(int $firstResult): self + { + $this->firstResult = $firstResult; + $this->state = self::STATE_DIRTY; + + return $this; + } + + /** + * Gets the position of the first result the query object was set to retrieve (the "offset"). + * Returns 0 if {@link setFirstResult} was not applied to this query. + * + * @return int The position of the first result. + */ + public function getFirstResult(): int + { + return $this->firstResult; + } + + /** + * Sets the maximum number of results to retrieve (the "limit"). + * + * @return $this + */ + public function setMaxResults(int|null $maxResults): self + { + $this->maxResults = $maxResults; + $this->state = self::STATE_DIRTY; + + return $this; + } + + /** + * Gets the maximum number of results the query object was set to retrieve (the "limit"). + * Returns NULL if {@link setMaxResults} was not applied to this query. + * + * @return int|null Maximum number of results. + */ + public function getMaxResults(): int|null + { + return $this->maxResults; + } + + /** {@inheritDoc} */ + public function toIterable(iterable $parameters = [], $hydrationMode = self::HYDRATE_OBJECT): iterable + { + $this->setHint(self::HINT_INTERNAL_ITERATION, true); + + return parent::toIterable($parameters, $hydrationMode); + } + + public function setHint(string $name, mixed $value): static + { + $this->state = self::STATE_DIRTY; + + return parent::setHint($name, $value); + } + + public function setHydrationMode(string|int $hydrationMode): static + { + $this->state = self::STATE_DIRTY; + + return parent::setHydrationMode($hydrationMode); + } + + /** + * Set the lock mode for this Query. + * + * @see \Doctrine\DBAL\LockMode + * + * @psalm-param LockMode::* $lockMode + * + * @return $this + * + * @throws TransactionRequiredException + */ + public function setLockMode(LockMode|int $lockMode): self + { + if (in_array($lockMode, [LockMode::NONE, LockMode::PESSIMISTIC_READ, LockMode::PESSIMISTIC_WRITE], true)) { + if (! $this->em->getConnection()->isTransactionActive()) { + throw TransactionRequiredException::transactionRequired(); + } + } + + $this->setHint(self::HINT_LOCK_MODE, $lockMode); + + return $this; + } + + /** + * Get the current lock mode for this query. + * + * @return LockMode|int|null The current lock mode of this query or NULL if no specific lock mode is set. + * @psalm-return LockMode::*|null + */ + public function getLockMode(): LockMode|int|null + { + $lockMode = $this->getHint(self::HINT_LOCK_MODE); + + if ($lockMode === false) { + return null; + } + + return $lockMode; + } + + /** + * Generate a cache id for the query cache - reusing the Result-Cache-Id generator. + */ + protected function getQueryCacheId(): string + { + ksort($this->hints); + + return md5( + $this->getDQL() . serialize($this->hints) . + '&platform=' . get_debug_type($this->getEntityManager()->getConnection()->getDatabasePlatform()) . + ($this->em->hasFilters() ? $this->em->getFilters()->getHash() : '') . + '&firstResult=' . $this->firstResult . '&maxResult=' . $this->maxResults . + '&hydrationMode=' . $this->hydrationMode . '&types=' . serialize($this->parsedTypes) . 'DOCTRINE_QUERY_CACHE_SALT', + ); + } + + protected function getHash(): string + { + return sha1(parent::getHash() . '-' . $this->firstResult . '-' . $this->maxResults); + } + + /** + * Cleanup Query resource when clone is called. + */ + public function __clone() + { + parent::__clone(); + + $this->state = self::STATE_DIRTY; + } +} -- cgit v1.2.3