837 lines
23 KiB
PHP
837 lines
23 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Doctrine\ORM;
|
|
|
|
use Doctrine\Common\Cache\Cache;
|
|
use Doctrine\Common\Cache\Psr6\CacheAdapter;
|
|
use Doctrine\Common\Cache\Psr6\DoctrineProvider;
|
|
use Doctrine\Common\Collections\ArrayCollection;
|
|
use Doctrine\DBAL\Cache\QueryCacheProfile;
|
|
use Doctrine\DBAL\LockMode;
|
|
use Doctrine\DBAL\Types\Type;
|
|
use Doctrine\Deprecations\Deprecation;
|
|
use Doctrine\ORM\Internal\Hydration\IterableResult;
|
|
use Doctrine\ORM\Mapping\ClassMetadata;
|
|
use Doctrine\ORM\Query\AST\DeleteStatement;
|
|
use Doctrine\ORM\Query\AST\SelectStatement;
|
|
use Doctrine\ORM\Query\AST\UpdateStatement;
|
|
use Doctrine\ORM\Query\Exec\AbstractSqlExecutor;
|
|
use Doctrine\ORM\Query\Parameter;
|
|
use Doctrine\ORM\Query\ParameterTypeInferer;
|
|
use Doctrine\ORM\Query\Parser;
|
|
use Doctrine\ORM\Query\ParserResult;
|
|
use Doctrine\ORM\Query\QueryException;
|
|
use Doctrine\ORM\Query\ResultSetMapping;
|
|
use Doctrine\ORM\Utility\HierarchyDiscriminatorResolver;
|
|
use Psr\Cache\CacheItemPoolInterface;
|
|
|
|
use function array_keys;
|
|
use function array_values;
|
|
use function assert;
|
|
use function count;
|
|
use function get_debug_type;
|
|
use function in_array;
|
|
use function is_int;
|
|
use function ksort;
|
|
use function md5;
|
|
use function method_exists;
|
|
use function reset;
|
|
use function serialize;
|
|
use function sha1;
|
|
use function stripos;
|
|
|
|
/**
|
|
* A Query object represents a DQL query.
|
|
*/
|
|
final class Query extends AbstractQuery
|
|
{
|
|
/**
|
|
* A query object is in CLEAN state when it has NO unparsed/unprocessed DQL parts.
|
|
*/
|
|
public const STATE_CLEAN = 1;
|
|
|
|
/**
|
|
* A query object is in state DIRTY when it has DQL parts that have not yet been
|
|
* parsed/processed. This is automatically defined as DIRTY when addDqlQueryPart
|
|
* is called.
|
|
*/
|
|
public const STATE_DIRTY = 2;
|
|
|
|
/* Query HINTS */
|
|
|
|
/**
|
|
* The refresh hint turns any query into a refresh query with the result that
|
|
* any local changes in entities are overridden with the fetched values.
|
|
*/
|
|
public const HINT_REFRESH = 'doctrine.refresh';
|
|
|
|
public const HINT_CACHE_ENABLED = 'doctrine.cache.enabled';
|
|
|
|
public const HINT_CACHE_EVICT = 'doctrine.cache.evict';
|
|
|
|
/**
|
|
* Internal hint: is set to the proxy entity that is currently triggered for loading
|
|
*/
|
|
public const HINT_REFRESH_ENTITY = 'doctrine.refresh.entity';
|
|
|
|
/**
|
|
* The forcePartialLoad query hint forces a particular query to return
|
|
* partial objects.
|
|
*
|
|
* @todo Rename: HINT_OPTIMIZE
|
|
*/
|
|
public const HINT_FORCE_PARTIAL_LOAD = 'doctrine.forcePartialLoad';
|
|
|
|
/**
|
|
* The includeMetaColumns query hint causes meta columns like foreign keys and
|
|
* discriminator columns to be selected and returned as part of the query result.
|
|
*
|
|
* This hint does only apply to non-object queries.
|
|
*/
|
|
public const HINT_INCLUDE_META_COLUMNS = 'doctrine.includeMetaColumns';
|
|
|
|
/**
|
|
* An array of class names that implement \Doctrine\ORM\Query\TreeWalker and
|
|
* are iterated and executed after the DQL has been parsed into an AST.
|
|
*/
|
|
public const HINT_CUSTOM_TREE_WALKERS = 'doctrine.customTreeWalkers';
|
|
|
|
/**
|
|
* A string with a class name that implements \Doctrine\ORM\Query\TreeWalker
|
|
* and is used for generating the target SQL from any DQL AST tree.
|
|
*/
|
|
public const HINT_CUSTOM_OUTPUT_WALKER = 'doctrine.customOutputWalker';
|
|
|
|
/**
|
|
* Marks queries as creating only read only objects.
|
|
*
|
|
* If the object retrieved from the query is already in the identity map
|
|
* then it does not get marked as read only if it wasn't already.
|
|
*/
|
|
public const HINT_READ_ONLY = 'doctrine.readOnly';
|
|
|
|
public const HINT_INTERNAL_ITERATION = 'doctrine.internal.iteration';
|
|
|
|
public const HINT_LOCK_MODE = 'doctrine.lockMode';
|
|
|
|
/**
|
|
* The current state of this query.
|
|
*
|
|
* @var int
|
|
* @psalm-var self::STATE_*
|
|
*/
|
|
private $state = self::STATE_DIRTY;
|
|
|
|
/**
|
|
* A snapshot of the parameter types the query was parsed with.
|
|
*
|
|
* @var array<string,Type>
|
|
*/
|
|
private $parsedTypes = [];
|
|
|
|
/**
|
|
* Cached DQL query.
|
|
*
|
|
* @var string|null
|
|
*/
|
|
private $dql = null;
|
|
|
|
/**
|
|
* The parser result that holds DQL => SQL information.
|
|
*
|
|
* @var ParserResult
|
|
*/
|
|
private $parserResult;
|
|
|
|
/**
|
|
* The first result to return (the "offset").
|
|
*
|
|
* @var int
|
|
*/
|
|
private $firstResult = 0;
|
|
|
|
/**
|
|
* The maximum number of results to return (the "limit").
|
|
*
|
|
* @var int|null
|
|
*/
|
|
private $maxResults = null;
|
|
|
|
/**
|
|
* The cache driver used for caching queries.
|
|
*
|
|
* @var CacheItemPoolInterface|null
|
|
*/
|
|
private $queryCache;
|
|
|
|
/**
|
|
* Whether or not expire the query cache.
|
|
*
|
|
* @var bool
|
|
*/
|
|
private $expireQueryCache = false;
|
|
|
|
/**
|
|
* The query cache lifetime.
|
|
*
|
|
* @var int|null
|
|
*/
|
|
private $queryCacheTTL;
|
|
|
|
/**
|
|
* Whether to use a query cache, if available. Defaults to TRUE.
|
|
*
|
|
* @var bool
|
|
*/
|
|
private $useQueryCache = true;
|
|
|
|
/**
|
|
* Gets the SQL query/queries that correspond to this DQL query.
|
|
*
|
|
* @return list<string>|string The built sql query or an array of all sql queries.
|
|
*/
|
|
public function getSQL()
|
|
{
|
|
return $this->parse()->getSqlExecutor()->getSqlStatements();
|
|
}
|
|
|
|
/**
|
|
* Returns the corresponding AST for this DQL query.
|
|
*
|
|
* @return SelectStatement|UpdateStatement|DeleteStatement
|
|
*/
|
|
public function getAST()
|
|
{
|
|
$parser = new Parser($this);
|
|
|
|
return $parser->getAST();
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*
|
|
* @return ResultSetMapping
|
|
*/
|
|
protected function getResultSetMapping()
|
|
{
|
|
// 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;
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
protected function _doExecute()
|
|
{
|
|
$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<string,mixed> $sqlParams
|
|
* @param array<string,Type> $types
|
|
* @param array<string,mixed> $connectionParams
|
|
*/
|
|
private function evictResultSetCache(
|
|
AbstractSqlExecutor $executor,
|
|
array $sqlParams,
|
|
array $types,
|
|
array $connectionParams
|
|
): void {
|
|
if ($this->_queryCacheProfile === null || ! $this->getExpireResultCache()) {
|
|
return;
|
|
}
|
|
|
|
$cache = method_exists(QueryCacheProfile::class, 'getResultCache')
|
|
? $this->_queryCacheProfile->getResultCache()
|
|
: $this->_queryCacheProfile->getResultCacheDriver();
|
|
|
|
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 instanceof CacheItemPoolInterface
|
|
? $cache->deleteItem(reset($cacheKeys))
|
|
: $cache->delete(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<list<int>> $paramMappings
|
|
*
|
|
* @return mixed[][]
|
|
* @psalm-return array{0: list<mixed>, 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.
|
|
*
|
|
* @deprecated Call {@see setQueryCache()} instead.
|
|
*
|
|
* @param Cache|null $queryCache Cache driver.
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function setQueryCacheDriver($queryCache): self
|
|
{
|
|
Deprecation::trigger(
|
|
'doctrine/orm',
|
|
'https://github.com/doctrine/orm/pull/9004',
|
|
'%s is deprecated and will be removed in Doctrine 3.0. Use setQueryCache() instead.',
|
|
__METHOD__
|
|
);
|
|
|
|
$this->queryCache = $queryCache ? CacheAdapter::wrap($queryCache) : null;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Defines a cache driver to be used for caching queries.
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function setQueryCache(?CacheItemPoolInterface $queryCache): self
|
|
{
|
|
$this->queryCache = $queryCache;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Defines whether the query should make use of a query cache, if available.
|
|
*
|
|
* @param bool $bool
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function useQueryCache($bool): self
|
|
{
|
|
$this->useQueryCache = $bool;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Returns the cache driver used for query caching.
|
|
*
|
|
* @deprecated
|
|
*
|
|
* @return Cache|null The cache driver used for query caching or NULL, if
|
|
* this Query does not use query caching.
|
|
*/
|
|
public function getQueryCacheDriver(): ?Cache
|
|
{
|
|
Deprecation::trigger(
|
|
'doctrine/orm',
|
|
'https://github.com/doctrine/orm/pull/9004',
|
|
'%s is deprecated and will be removed in Doctrine 3.0 without replacement.',
|
|
__METHOD__
|
|
);
|
|
|
|
$queryCache = $this->queryCache ?? $this->_em->getConfiguration()->getQueryCache();
|
|
|
|
return $queryCache ? DoctrineProvider::wrap($queryCache) : null;
|
|
}
|
|
|
|
/**
|
|
* 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($timeToLive): self
|
|
{
|
|
if ($timeToLive !== null) {
|
|
$timeToLive = (int) $timeToLive;
|
|
}
|
|
|
|
$this->queryCacheTTL = $timeToLive;
|
|
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the lifetime of resultset cache.
|
|
*/
|
|
public function getQueryCacheLifetime(): ?int
|
|
{
|
|
return $this->queryCacheTTL;
|
|
}
|
|
|
|
/**
|
|
* Defines if the query cache is active or not.
|
|
*
|
|
* @param bool $expire Whether or not to force query cache expiration.
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function expireQueryCache($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.
|
|
*
|
|
* @param string|null $dqlQuery DQL Query.
|
|
*/
|
|
public function setDQL($dqlQuery): self
|
|
{
|
|
if ($dqlQuery === null) {
|
|
Deprecation::trigger(
|
|
'doctrine/orm',
|
|
'https://github.com/doctrine/orm/pull/9784',
|
|
'Calling %s with null is deprecated and will result in a TypeError in Doctrine 3.0',
|
|
__METHOD__
|
|
);
|
|
|
|
return $this;
|
|
}
|
|
|
|
$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
|
|
{
|
|
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($dql): bool
|
|
{
|
|
return stripos($this->getDQL(), $dql) !== false;
|
|
}
|
|
|
|
/**
|
|
* Sets the position of the first result to retrieve (the "offset").
|
|
*
|
|
* @param int|null $firstResult The first result to return.
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function setFirstResult($firstResult): self
|
|
{
|
|
if (! is_int($firstResult)) {
|
|
Deprecation::trigger(
|
|
'doctrine/orm',
|
|
'https://github.com/doctrine/orm/pull/9809',
|
|
'Calling %s with %s is deprecated and will result in a TypeError in Doctrine 3.0. Pass an integer.',
|
|
__METHOD__,
|
|
get_debug_type($firstResult)
|
|
);
|
|
|
|
$firstResult = (int) $firstResult;
|
|
}
|
|
|
|
$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").
|
|
*
|
|
* @param int|null $maxResults
|
|
*
|
|
* @return $this
|
|
*/
|
|
public function setMaxResults($maxResults): self
|
|
{
|
|
if ($maxResults !== null) {
|
|
$maxResults = (int) $maxResults;
|
|
}
|
|
|
|
$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
|
|
{
|
|
return $this->maxResults;
|
|
}
|
|
|
|
/**
|
|
* Executes the query and returns an IterableResult that can be used to incrementally
|
|
* iterated over the result.
|
|
*
|
|
* @deprecated
|
|
*
|
|
* @param ArrayCollection|mixed[]|null $parameters The query parameters.
|
|
* @param string|int $hydrationMode The hydration mode to use.
|
|
* @psalm-param ArrayCollection<int, Parameter>|array<string, mixed>|null $parameters
|
|
* @psalm-param string|AbstractQuery::HYDRATE_*|null $hydrationMode
|
|
*/
|
|
public function iterate($parameters = null, $hydrationMode = self::HYDRATE_OBJECT): IterableResult
|
|
{
|
|
$this->setHint(self::HINT_INTERNAL_ITERATION, true);
|
|
|
|
return parent::iterate($parameters, $hydrationMode);
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public function toIterable(iterable $parameters = [], $hydrationMode = self::HYDRATE_OBJECT): iterable
|
|
{
|
|
$this->setHint(self::HINT_INTERNAL_ITERATION, true);
|
|
|
|
return parent::toIterable($parameters, $hydrationMode);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function setHint($name, $value): self
|
|
{
|
|
$this->state = self::STATE_DIRTY;
|
|
|
|
return parent::setHint($name, $value);
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
public function setHydrationMode($hydrationMode): self
|
|
{
|
|
$this->state = self::STATE_DIRTY;
|
|
|
|
return parent::setHydrationMode($hydrationMode);
|
|
}
|
|
|
|
/**
|
|
* Set the lock mode for this Query.
|
|
*
|
|
* @see \Doctrine\DBAL\LockMode
|
|
*
|
|
* @param int $lockMode
|
|
* @psalm-param LockMode::* $lockMode
|
|
*
|
|
* @return $this
|
|
*
|
|
* @throws TransactionRequiredException
|
|
*/
|
|
public function setLockMode($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 int|null The current lock mode of this query or NULL if no specific lock mode is set.
|
|
*/
|
|
public function getLockMode(): ?int
|
|
{
|
|
$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;
|
|
}
|
|
}
|