Cache
9 months ago
Decorator
9 months ago
Event
9 months ago
Exception
9 months ago
Id
9 months ago
Internal
9 months ago
Mapping
9 months ago
Persisters
8 months ago
Proxy
9 months ago
Query
9 months ago
Repository
9 months ago
Tools
9 months ago
Utility
9 months ago
AbstractQuery.php
9 months ago
Cache.php
9 months ago
Configuration.php
9 months ago
EntityManager.php
9 months ago
EntityManagerInterface.php
9 months ago
EntityNotFoundException.php
9 months ago
EntityRepository.php
9 months ago
Events.php
9 months ago
LazyCriteriaCollection.php
9 months ago
NativeQuery.php
9 months ago
NoResultException.php
9 months ago
NonUniqueResultException.php
9 months ago
ORMException.php
9 months ago
ORMInvalidArgumentException.php
9 months ago
ORMSetup.php
9 months ago
OptimisticLockException.php
9 months ago
PersistentCollection.php
9 months ago
PessimisticLockException.php
9 months ago
Query.php
9 months ago
QueryBuilder.php
9 months ago
TransactionRequiredException.php
9 months ago
UnexpectedResultException.php
9 months ago
UnitOfWork.php
9 months ago
Version.php
9 months ago
index.php
9 months ago
Query.php
390 lines
| 1 | <?php |
| 2 | declare (strict_types=1); |
| 3 | namespace MailPoetVendor\Doctrine\ORM; |
| 4 | if (!defined('ABSPATH')) exit; |
| 5 | use MailPoetVendor\Doctrine\Common\Cache\Cache; |
| 6 | use MailPoetVendor\Doctrine\Common\Cache\Psr6\CacheAdapter; |
| 7 | use MailPoetVendor\Doctrine\Common\Cache\Psr6\DoctrineProvider; |
| 8 | use MailPoetVendor\Doctrine\Common\Collections\ArrayCollection; |
| 9 | use MailPoetVendor\Doctrine\DBAL\Cache\QueryCacheProfile; |
| 10 | use MailPoetVendor\Doctrine\DBAL\LockMode; |
| 11 | use MailPoetVendor\Doctrine\DBAL\Types\Type; |
| 12 | use MailPoetVendor\Doctrine\Deprecations\Deprecation; |
| 13 | use MailPoetVendor\Doctrine\ORM\Internal\Hydration\IterableResult; |
| 14 | use MailPoetVendor\Doctrine\ORM\Mapping\ClassMetadata; |
| 15 | use MailPoetVendor\Doctrine\ORM\Query\AST\DeleteStatement; |
| 16 | use MailPoetVendor\Doctrine\ORM\Query\AST\SelectStatement; |
| 17 | use MailPoetVendor\Doctrine\ORM\Query\AST\UpdateStatement; |
| 18 | use MailPoetVendor\Doctrine\ORM\Query\Exec\AbstractSqlExecutor; |
| 19 | use MailPoetVendor\Doctrine\ORM\Query\Exec\SqlFinalizer; |
| 20 | use MailPoetVendor\Doctrine\ORM\Query\OutputWalker; |
| 21 | use MailPoetVendor\Doctrine\ORM\Query\Parameter; |
| 22 | use MailPoetVendor\Doctrine\ORM\Query\ParameterTypeInferer; |
| 23 | use MailPoetVendor\Doctrine\ORM\Query\Parser; |
| 24 | use MailPoetVendor\Doctrine\ORM\Query\ParserResult; |
| 25 | use MailPoetVendor\Doctrine\ORM\Query\QueryException; |
| 26 | use MailPoetVendor\Doctrine\ORM\Query\ResultSetMapping; |
| 27 | use MailPoetVendor\Doctrine\ORM\Utility\HierarchyDiscriminatorResolver; |
| 28 | use MailPoetVendor\Psr\Cache\CacheItemPoolInterface; |
| 29 | use function array_keys; |
| 30 | use function array_values; |
| 31 | use function assert; |
| 32 | use function count; |
| 33 | use function get_debug_type; |
| 34 | use function in_array; |
| 35 | use function is_a; |
| 36 | use function is_int; |
| 37 | use function ksort; |
| 38 | use function md5; |
| 39 | use function method_exists; |
| 40 | use function reset; |
| 41 | use function serialize; |
| 42 | use function sha1; |
| 43 | use function stripos; |
| 44 | class Query extends AbstractQuery |
| 45 | { |
| 46 | public const STATE_CLEAN = 1; |
| 47 | public const STATE_DIRTY = 2; |
| 48 | public const HINT_REFRESH = 'doctrine.refresh'; |
| 49 | public const HINT_CACHE_ENABLED = 'doctrine.cache.enabled'; |
| 50 | public const HINT_CACHE_EVICT = 'doctrine.cache.evict'; |
| 51 | public const HINT_REFRESH_ENTITY = 'doctrine.refresh.entity'; |
| 52 | public const HINT_FORCE_PARTIAL_LOAD = 'doctrine.forcePartialLoad'; |
| 53 | public const HINT_INCLUDE_META_COLUMNS = 'doctrine.includeMetaColumns'; |
| 54 | public const HINT_CUSTOM_TREE_WALKERS = 'doctrine.customTreeWalkers'; |
| 55 | public const HINT_CUSTOM_OUTPUT_WALKER = 'doctrine.customOutputWalker'; |
| 56 | public const HINT_READ_ONLY = 'doctrine.readOnly'; |
| 57 | public const HINT_INTERNAL_ITERATION = 'doctrine.internal.iteration'; |
| 58 | public const HINT_LOCK_MODE = 'doctrine.lockMode'; |
| 59 | private $state = self::STATE_DIRTY; |
| 60 | private $parsedTypes = []; |
| 61 | private $dql = null; |
| 62 | private $parserResult; |
| 63 | private $firstResult = 0; |
| 64 | private $maxResults = null; |
| 65 | private $queryCache; |
| 66 | private $expireQueryCache = \false; |
| 67 | private $queryCacheTTL; |
| 68 | private $useQueryCache = \true; |
| 69 | public function getSQL() |
| 70 | { |
| 71 | return $this->getSqlExecutor()->getSqlStatements(); |
| 72 | } |
| 73 | public function getAST() |
| 74 | { |
| 75 | $parser = new Parser($this); |
| 76 | return $parser->getAST(); |
| 77 | } |
| 78 | protected function getResultSetMapping() |
| 79 | { |
| 80 | // parse query or load from cache |
| 81 | if ($this->_resultSetMapping === null) { |
| 82 | $this->_resultSetMapping = $this->parse()->getResultSetMapping(); |
| 83 | } |
| 84 | return $this->_resultSetMapping; |
| 85 | } |
| 86 | private function parse() : ParserResult |
| 87 | { |
| 88 | $types = []; |
| 89 | foreach ($this->parameters as $parameter) { |
| 90 | $types[$parameter->getName()] = $parameter->getType(); |
| 91 | } |
| 92 | // Return previous parser result if the query and the filter collection are both clean |
| 93 | if ($this->state === self::STATE_CLEAN && $this->parsedTypes === $types && $this->_em->isFiltersStateClean()) { |
| 94 | return $this->parserResult; |
| 95 | } |
| 96 | $this->state = self::STATE_CLEAN; |
| 97 | $this->parsedTypes = $types; |
| 98 | $queryCache = $this->queryCache ?? $this->_em->getConfiguration()->getQueryCache(); |
| 99 | // Check query cache. |
| 100 | if (!($this->useQueryCache && $queryCache)) { |
| 101 | $parser = new Parser($this); |
| 102 | $this->parserResult = $parser->parse(); |
| 103 | return $this->parserResult; |
| 104 | } |
| 105 | $cacheItem = $queryCache->getItem($this->getQueryCacheId()); |
| 106 | if (!$this->expireQueryCache && $cacheItem->isHit()) { |
| 107 | $cached = $cacheItem->get(); |
| 108 | if ($cached instanceof ParserResult) { |
| 109 | // Cache hit. |
| 110 | $this->parserResult = $cached; |
| 111 | return $this->parserResult; |
| 112 | } |
| 113 | } |
| 114 | // Cache miss. |
| 115 | $parser = new Parser($this); |
| 116 | $this->parserResult = $parser->parse(); |
| 117 | $queryCache->save($cacheItem->set($this->parserResult)->expiresAfter($this->queryCacheTTL)); |
| 118 | return $this->parserResult; |
| 119 | } |
| 120 | protected function _doExecute() |
| 121 | { |
| 122 | $executor = $this->getSqlExecutor(); |
| 123 | if ($this->_queryCacheProfile) { |
| 124 | $executor->setQueryCacheProfile($this->_queryCacheProfile); |
| 125 | } else { |
| 126 | $executor->removeQueryCacheProfile(); |
| 127 | } |
| 128 | if ($this->_resultSetMapping === null) { |
| 129 | $this->_resultSetMapping = $this->parserResult->getResultSetMapping(); |
| 130 | } |
| 131 | // Prepare parameters |
| 132 | $paramMappings = $this->parserResult->getParameterMappings(); |
| 133 | $paramCount = count($this->parameters); |
| 134 | $mappingCount = count($paramMappings); |
| 135 | if ($paramCount > $mappingCount) { |
| 136 | throw QueryException::tooManyParameters($mappingCount, $paramCount); |
| 137 | } |
| 138 | if ($paramCount < $mappingCount) { |
| 139 | throw QueryException::tooFewParameters($mappingCount, $paramCount); |
| 140 | } |
| 141 | // evict all cache for the entity region |
| 142 | if ($this->hasCache && isset($this->_hints[self::HINT_CACHE_EVICT]) && $this->_hints[self::HINT_CACHE_EVICT]) { |
| 143 | $this->evictEntityCacheRegion(); |
| 144 | } |
| 145 | [$sqlParams, $types] = $this->processParameterMappings($paramMappings); |
| 146 | $this->evictResultSetCache($executor, $sqlParams, $types, $this->_em->getConnection()->getParams()); |
| 147 | return $executor->execute($this->_em->getConnection(), $sqlParams, $types); |
| 148 | } |
| 149 | private function evictResultSetCache(AbstractSqlExecutor $executor, array $sqlParams, array $types, array $connectionParams) : void |
| 150 | { |
| 151 | if ($this->_queryCacheProfile === null || !$this->getExpireResultCache()) { |
| 152 | return; |
| 153 | } |
| 154 | $cache = method_exists(QueryCacheProfile::class, 'getResultCache') ? $this->_queryCacheProfile->getResultCache() : $this->_queryCacheProfile->getResultCacheDriver(); |
| 155 | assert($cache !== null); |
| 156 | $statements = (array) $executor->getSqlStatements(); |
| 157 | // Type casted since it can either be a string or an array |
| 158 | foreach ($statements as $statement) { |
| 159 | $cacheKeys = $this->_queryCacheProfile->generateCacheKeys($statement, $sqlParams, $types, $connectionParams); |
| 160 | $cache instanceof CacheItemPoolInterface ? $cache->deleteItem(reset($cacheKeys)) : $cache->delete(reset($cacheKeys)); |
| 161 | } |
| 162 | } |
| 163 | private function evictEntityCacheRegion() : void |
| 164 | { |
| 165 | $AST = $this->getAST(); |
| 166 | if ($AST instanceof SelectStatement) { |
| 167 | throw new QueryException('The hint "HINT_CACHE_EVICT" is not valid for select statements.'); |
| 168 | } |
| 169 | $className = $AST instanceof DeleteStatement ? $AST->deleteClause->abstractSchemaName : $AST->updateClause->abstractSchemaName; |
| 170 | $this->_em->getCache()->evictEntityRegion($className); |
| 171 | } |
| 172 | private function processParameterMappings(array $paramMappings) : array |
| 173 | { |
| 174 | $sqlParams = []; |
| 175 | $types = []; |
| 176 | foreach ($this->parameters as $parameter) { |
| 177 | $key = $parameter->getName(); |
| 178 | if (!isset($paramMappings[$key])) { |
| 179 | throw QueryException::unknownParameter($key); |
| 180 | } |
| 181 | [$value, $type] = $this->resolveParameterValue($parameter); |
| 182 | foreach ($paramMappings[$key] as $position) { |
| 183 | $types[$position] = $type; |
| 184 | } |
| 185 | $sqlPositions = $paramMappings[$key]; |
| 186 | // optimized multi value sql positions away for now, |
| 187 | // they are not allowed in DQL anyways. |
| 188 | $value = [$value]; |
| 189 | $countValue = count($value); |
| 190 | for ($i = 0, $l = count($sqlPositions); $i < $l; $i++) { |
| 191 | $sqlParams[$sqlPositions[$i]] = $value[$i % $countValue]; |
| 192 | } |
| 193 | } |
| 194 | if (count($sqlParams) !== count($types)) { |
| 195 | throw QueryException::parameterTypeMismatch(); |
| 196 | } |
| 197 | if ($sqlParams) { |
| 198 | ksort($sqlParams); |
| 199 | $sqlParams = array_values($sqlParams); |
| 200 | ksort($types); |
| 201 | $types = array_values($types); |
| 202 | } |
| 203 | return [$sqlParams, $types]; |
| 204 | } |
| 205 | private function resolveParameterValue(Parameter $parameter) : array |
| 206 | { |
| 207 | if ($parameter->typeWasSpecified()) { |
| 208 | return [$parameter->getValue(), $parameter->getType()]; |
| 209 | } |
| 210 | $key = $parameter->getName(); |
| 211 | $originalValue = $parameter->getValue(); |
| 212 | $value = $originalValue; |
| 213 | $rsm = $this->getResultSetMapping(); |
| 214 | if ($value instanceof ClassMetadata && isset($rsm->metadataParameterMapping[$key])) { |
| 215 | $value = $value->getMetadataValue($rsm->metadataParameterMapping[$key]); |
| 216 | } |
| 217 | if ($value instanceof ClassMetadata && isset($rsm->discriminatorParameters[$key])) { |
| 218 | $value = array_keys(HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($value, $this->_em)); |
| 219 | } |
| 220 | $processedValue = $this->processParameterValue($value); |
| 221 | return [$processedValue, $originalValue === $processedValue ? $parameter->getType() : ParameterTypeInferer::inferType($processedValue)]; |
| 222 | } |
| 223 | public function setQueryCacheDriver($queryCache) : self |
| 224 | { |
| 225 | 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__); |
| 226 | $this->queryCache = $queryCache ? CacheAdapter::wrap($queryCache) : null; |
| 227 | return $this; |
| 228 | } |
| 229 | public function setQueryCache(?CacheItemPoolInterface $queryCache) : self |
| 230 | { |
| 231 | $this->queryCache = $queryCache; |
| 232 | return $this; |
| 233 | } |
| 234 | public function useQueryCache($bool) : self |
| 235 | { |
| 236 | $this->useQueryCache = $bool; |
| 237 | return $this; |
| 238 | } |
| 239 | public function getQueryCacheDriver() : ?Cache |
| 240 | { |
| 241 | 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__); |
| 242 | $queryCache = $this->queryCache ?? $this->_em->getConfiguration()->getQueryCache(); |
| 243 | return $queryCache ? DoctrineProvider::wrap($queryCache) : null; |
| 244 | } |
| 245 | public function setQueryCacheLifetime($timeToLive) : self |
| 246 | { |
| 247 | if ($timeToLive !== null) { |
| 248 | $timeToLive = (int) $timeToLive; |
| 249 | } |
| 250 | $this->queryCacheTTL = $timeToLive; |
| 251 | return $this; |
| 252 | } |
| 253 | public function getQueryCacheLifetime() : ?int |
| 254 | { |
| 255 | return $this->queryCacheTTL; |
| 256 | } |
| 257 | public function expireQueryCache($expire = \true) : self |
| 258 | { |
| 259 | $this->expireQueryCache = $expire; |
| 260 | return $this; |
| 261 | } |
| 262 | public function getExpireQueryCache() : bool |
| 263 | { |
| 264 | return $this->expireQueryCache; |
| 265 | } |
| 266 | public function free() : void |
| 267 | { |
| 268 | parent::free(); |
| 269 | $this->dql = null; |
| 270 | $this->state = self::STATE_CLEAN; |
| 271 | } |
| 272 | public function setDQL($dqlQuery) : self |
| 273 | { |
| 274 | if ($dqlQuery === null) { |
| 275 | 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__); |
| 276 | return $this; |
| 277 | } |
| 278 | $this->dql = $dqlQuery; |
| 279 | $this->state = self::STATE_DIRTY; |
| 280 | return $this; |
| 281 | } |
| 282 | public function getDQL() : ?string |
| 283 | { |
| 284 | return $this->dql; |
| 285 | } |
| 286 | public function getState() : int |
| 287 | { |
| 288 | return $this->state; |
| 289 | } |
| 290 | public function contains($dql) : bool |
| 291 | { |
| 292 | return stripos($this->getDQL(), $dql) !== \false; |
| 293 | } |
| 294 | public function setFirstResult($firstResult) : self |
| 295 | { |
| 296 | if (!is_int($firstResult)) { |
| 297 | 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)); |
| 298 | $firstResult = (int) $firstResult; |
| 299 | } |
| 300 | $this->firstResult = $firstResult; |
| 301 | $this->state = self::STATE_DIRTY; |
| 302 | return $this; |
| 303 | } |
| 304 | public function getFirstResult() : int |
| 305 | { |
| 306 | return $this->firstResult; |
| 307 | } |
| 308 | public function setMaxResults($maxResults) : self |
| 309 | { |
| 310 | if ($maxResults !== null) { |
| 311 | $maxResults = (int) $maxResults; |
| 312 | } |
| 313 | $this->maxResults = $maxResults; |
| 314 | $this->state = self::STATE_DIRTY; |
| 315 | return $this; |
| 316 | } |
| 317 | public function getMaxResults() : ?int |
| 318 | { |
| 319 | return $this->maxResults; |
| 320 | } |
| 321 | public function iterate($parameters = null, $hydrationMode = self::HYDRATE_OBJECT) : IterableResult |
| 322 | { |
| 323 | $this->setHint(self::HINT_INTERNAL_ITERATION, \true); |
| 324 | return parent::iterate($parameters, $hydrationMode); |
| 325 | } |
| 326 | public function toIterable(iterable $parameters = [], $hydrationMode = self::HYDRATE_OBJECT) : iterable |
| 327 | { |
| 328 | $this->setHint(self::HINT_INTERNAL_ITERATION, \true); |
| 329 | return parent::toIterable($parameters, $hydrationMode); |
| 330 | } |
| 331 | public function setHint($name, $value) : self |
| 332 | { |
| 333 | $this->state = self::STATE_DIRTY; |
| 334 | return parent::setHint($name, $value); |
| 335 | } |
| 336 | public function setHydrationMode($hydrationMode) : self |
| 337 | { |
| 338 | $this->state = self::STATE_DIRTY; |
| 339 | return parent::setHydrationMode($hydrationMode); |
| 340 | } |
| 341 | public function setLockMode($lockMode) : self |
| 342 | { |
| 343 | if (in_array($lockMode, [LockMode::NONE, LockMode::PESSIMISTIC_READ, LockMode::PESSIMISTIC_WRITE], \true)) { |
| 344 | if (!$this->_em->getConnection()->isTransactionActive()) { |
| 345 | throw TransactionRequiredException::transactionRequired(); |
| 346 | } |
| 347 | } |
| 348 | $this->setHint(self::HINT_LOCK_MODE, $lockMode); |
| 349 | return $this; |
| 350 | } |
| 351 | public function getLockMode() : ?int |
| 352 | { |
| 353 | $lockMode = $this->getHint(self::HINT_LOCK_MODE); |
| 354 | if ($lockMode === \false) { |
| 355 | return null; |
| 356 | } |
| 357 | return $lockMode; |
| 358 | } |
| 359 | protected function getQueryCacheId() : string |
| 360 | { |
| 361 | ksort($this->_hints); |
| 362 | if (!$this->hasHint(self::HINT_CUSTOM_OUTPUT_WALKER)) { |
| 363 | // Assume Parser will create the SqlOutputWalker; save is_a call, which might trigger a class load |
| 364 | $firstAndMaxResult = ''; |
| 365 | } else { |
| 366 | $outputWalkerClass = $this->getHint(self::HINT_CUSTOM_OUTPUT_WALKER); |
| 367 | if (is_a($outputWalkerClass, OutputWalker::class, \true)) { |
| 368 | $firstAndMaxResult = ''; |
| 369 | } else { |
| 370 | Deprecation::trigger('doctrine/orm', 'https://github.com/doctrine/orm/pull/11188/', 'Your output walker class %s should implement %s in order to provide a %s. This also means the output walker should not use the query firstResult/maxResult values, which should be read from the query by the SqlFinalizer only.', $outputWalkerClass, OutputWalker::class, SqlFinalizer::class); |
| 371 | $firstAndMaxResult = '&firstResult=' . $this->firstResult . '&maxResult=' . $this->maxResults; |
| 372 | } |
| 373 | } |
| 374 | return md5($this->getDQL() . serialize($this->_hints) . '&platform=' . get_debug_type($this->getEntityManager()->getConnection()->getDatabasePlatform()) . ($this->_em->hasFilters() ? $this->_em->getFilters()->getHash() : '') . $firstAndMaxResult . '&hydrationMode=' . $this->_hydrationMode . '&types=' . serialize($this->parsedTypes) . 'DOCTRINE_QUERY_CACHE_SALT'); |
| 375 | } |
| 376 | protected function getHash() : string |
| 377 | { |
| 378 | return sha1(parent::getHash() . '-' . $this->firstResult . '-' . $this->maxResults); |
| 379 | } |
| 380 | public function __clone() |
| 381 | { |
| 382 | parent::__clone(); |
| 383 | $this->state = self::STATE_DIRTY; |
| 384 | } |
| 385 | private function getSqlExecutor() : AbstractSqlExecutor |
| 386 | { |
| 387 | return $this->parse()->prepareSqlExecutor($this); |
| 388 | } |
| 389 | } |
| 390 |