These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /* |
||
3 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||
4 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||
5 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||
6 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||
7 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||
8 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||
9 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||
10 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||
11 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||
12 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||
13 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||
14 | * |
||
15 | * This software consists of voluntary contributions made by many individuals |
||
16 | * and is licensed under the MIT license. For more information, see |
||
17 | * <http://www.doctrine-project.org>. |
||
18 | */ |
||
19 | |||
20 | namespace Doctrine\ORM; |
||
21 | |||
22 | use Doctrine\DBAL\LockMode; |
||
23 | use Doctrine\ORM\Query\Exec\AbstractSqlExecutor; |
||
24 | use Doctrine\ORM\Query\Parser; |
||
25 | use Doctrine\ORM\Query\ParserResult; |
||
26 | use Doctrine\ORM\Query\QueryException; |
||
27 | use Doctrine\ORM\Mapping\ClassMetadata; |
||
28 | use Doctrine\ORM\Query\ParameterTypeInferer; |
||
29 | use Doctrine\Common\Collections\ArrayCollection; |
||
30 | use Doctrine\ORM\Utility\HierarchyDiscriminatorResolver; |
||
31 | |||
32 | /** |
||
33 | * A Query object represents a DQL query. |
||
34 | * |
||
35 | * @since 1.0 |
||
36 | * @author Guilherme Blanco <[email protected]> |
||
37 | * @author Konsta Vesterinen <[email protected]> |
||
38 | * @author Roman Borschel <[email protected]> |
||
39 | */ |
||
40 | final class Query extends AbstractQuery |
||
41 | { |
||
42 | /** |
||
43 | * A query object is in CLEAN state when it has NO unparsed/unprocessed DQL parts. |
||
44 | */ |
||
45 | const STATE_CLEAN = 1; |
||
46 | |||
47 | /** |
||
48 | * A query object is in state DIRTY when it has DQL parts that have not yet been |
||
49 | * parsed/processed. This is automatically defined as DIRTY when addDqlQueryPart |
||
50 | * is called. |
||
51 | */ |
||
52 | const STATE_DIRTY = 2; |
||
53 | |||
54 | /* Query HINTS */ |
||
55 | |||
56 | /** |
||
57 | * The refresh hint turns any query into a refresh query with the result that |
||
58 | * any local changes in entities are overridden with the fetched values. |
||
59 | * |
||
60 | * @var string |
||
61 | */ |
||
62 | const HINT_REFRESH = 'doctrine.refresh'; |
||
63 | |||
64 | /** |
||
65 | * @var string |
||
66 | */ |
||
67 | const HINT_CACHE_ENABLED = 'doctrine.cache.enabled'; |
||
68 | |||
69 | /** |
||
70 | * @var string |
||
71 | */ |
||
72 | const HINT_CACHE_EVICT = 'doctrine.cache.evict'; |
||
73 | |||
74 | /** |
||
75 | * Internal hint: is set to the proxy entity that is currently triggered for loading |
||
76 | * |
||
77 | * @var string |
||
78 | */ |
||
79 | const HINT_REFRESH_ENTITY = 'doctrine.refresh.entity'; |
||
80 | |||
81 | /** |
||
82 | * The forcePartialLoad query hint forces a particular query to return |
||
83 | * partial objects. |
||
84 | * |
||
85 | * @var string |
||
86 | * @todo Rename: HINT_OPTIMIZE |
||
87 | */ |
||
88 | const HINT_FORCE_PARTIAL_LOAD = 'doctrine.forcePartialLoad'; |
||
89 | |||
90 | /** |
||
91 | * The includeMetaColumns query hint causes meta columns like foreign keys and |
||
92 | * discriminator columns to be selected and returned as part of the query result. |
||
93 | * |
||
94 | * This hint does only apply to non-object queries. |
||
95 | * |
||
96 | * @var string |
||
97 | */ |
||
98 | const HINT_INCLUDE_META_COLUMNS = 'doctrine.includeMetaColumns'; |
||
99 | |||
100 | /** |
||
101 | * An array of class names that implement \Doctrine\ORM\Query\TreeWalker and |
||
102 | * are iterated and executed after the DQL has been parsed into an AST. |
||
103 | * |
||
104 | * @var string |
||
105 | */ |
||
106 | const HINT_CUSTOM_TREE_WALKERS = 'doctrine.customTreeWalkers'; |
||
107 | |||
108 | /** |
||
109 | * A string with a class name that implements \Doctrine\ORM\Query\TreeWalker |
||
110 | * and is used for generating the target SQL from any DQL AST tree. |
||
111 | * |
||
112 | * @var string |
||
113 | */ |
||
114 | const HINT_CUSTOM_OUTPUT_WALKER = 'doctrine.customOutputWalker'; |
||
115 | |||
116 | //const HINT_READ_ONLY = 'doctrine.readOnly'; |
||
117 | |||
118 | /** |
||
119 | * @var string |
||
120 | */ |
||
121 | const HINT_INTERNAL_ITERATION = 'doctrine.internal.iteration'; |
||
122 | |||
123 | /** |
||
124 | * @var string |
||
125 | */ |
||
126 | const HINT_LOCK_MODE = 'doctrine.lockMode'; |
||
127 | |||
128 | /** |
||
129 | * The current state of this query. |
||
130 | * |
||
131 | * @var integer |
||
132 | */ |
||
133 | private $_state = self::STATE_CLEAN; |
||
134 | |||
135 | /** |
||
136 | * A snapshot of the parameter types the query was parsed with. |
||
137 | * |
||
138 | * @var array |
||
139 | */ |
||
140 | private $_parsedTypes = []; |
||
141 | |||
142 | /** |
||
143 | * Cached DQL query. |
||
144 | * |
||
145 | * @var string |
||
146 | */ |
||
147 | private $_dql = null; |
||
148 | |||
149 | /** |
||
150 | * The parser result that holds DQL => SQL information. |
||
151 | * |
||
152 | * @var \Doctrine\ORM\Query\ParserResult |
||
153 | */ |
||
154 | private $_parserResult; |
||
155 | |||
156 | /** |
||
157 | * The first result to return (the "offset"). |
||
158 | * |
||
159 | * @var integer |
||
160 | */ |
||
161 | private $_firstResult = null; |
||
162 | |||
163 | /** |
||
164 | * The maximum number of results to return (the "limit"). |
||
165 | * |
||
166 | * @var integer|null |
||
167 | */ |
||
168 | private $_maxResults = null; |
||
169 | |||
170 | /** |
||
171 | * The cache driver used for caching queries. |
||
172 | * |
||
173 | * @var \Doctrine\Common\Cache\Cache|null |
||
174 | */ |
||
175 | private $_queryCache; |
||
176 | |||
177 | /** |
||
178 | * Whether or not expire the query cache. |
||
179 | * |
||
180 | * @var boolean |
||
181 | */ |
||
182 | private $_expireQueryCache = false; |
||
183 | |||
184 | /** |
||
185 | * The query cache lifetime. |
||
186 | * |
||
187 | * @var int |
||
188 | */ |
||
189 | private $_queryCacheTTL; |
||
190 | |||
191 | /** |
||
192 | * Whether to use a query cache, if available. Defaults to TRUE. |
||
193 | * |
||
194 | * @var boolean |
||
195 | */ |
||
196 | private $_useQueryCache = true; |
||
197 | |||
198 | /** |
||
199 | * Gets the SQL query/queries that correspond to this DQL query. |
||
200 | * |
||
201 | * @return mixed The built sql query or an array of all sql queries. |
||
202 | * |
||
203 | * @override |
||
204 | */ |
||
205 | 336 | public function getSQL() |
|
206 | { |
||
207 | 336 | return $this->_parse()->getSqlExecutor()->getSqlStatements(); |
|
0 ignored issues
–
show
|
|||
208 | } |
||
209 | |||
210 | /** |
||
211 | * Returns the corresponding AST for this DQL query. |
||
212 | * |
||
213 | * @return \Doctrine\ORM\Query\AST\SelectStatement | |
||
214 | * \Doctrine\ORM\Query\AST\UpdateStatement | |
||
215 | * \Doctrine\ORM\Query\AST\DeleteStatement |
||
216 | */ |
||
217 | 2 | public function getAST() |
|
218 | { |
||
219 | 2 | $parser = new Parser($this); |
|
220 | |||
221 | 2 | return $parser->getAST(); |
|
222 | } |
||
223 | |||
224 | /** |
||
225 | * {@inheritdoc} |
||
226 | */ |
||
227 | 440 | protected function getResultSetMapping() |
|
228 | { |
||
229 | // parse query or load from cache |
||
230 | 440 | if ($this->_resultSetMapping === null) { |
|
231 | 38 | $this->_resultSetMapping = $this->_parse()->getResultSetMapping(); |
|
232 | } |
||
233 | |||
234 | 437 | return $this->_resultSetMapping; |
|
235 | } |
||
236 | |||
237 | /** |
||
238 | * Parses the DQL query, if necessary, and stores the parser result. |
||
239 | * |
||
240 | * Note: Populates $this->_parserResult as a side-effect. |
||
241 | * |
||
242 | * @return \Doctrine\ORM\Query\ParserResult |
||
243 | */ |
||
244 | 761 | private function _parse() |
|
245 | { |
||
246 | 761 | $types = []; |
|
247 | |||
248 | 761 | foreach ($this->parameters as $parameter) { |
|
249 | /** @var Query\Parameter $parameter */ |
||
250 | 174 | $types[$parameter->getName()] = $parameter->getType(); |
|
251 | } |
||
252 | |||
253 | // Return previous parser result if the query and the filter collection are both clean |
||
254 | 761 | if ($this->_state === self::STATE_CLEAN && $this->_parsedTypes === $types && $this->_em->isFiltersStateClean()) { |
|
255 | 39 | return $this->_parserResult; |
|
256 | } |
||
257 | |||
258 | 761 | $this->_state = self::STATE_CLEAN; |
|
259 | 761 | $this->_parsedTypes = $types; |
|
260 | |||
261 | // Check query cache. |
||
262 | 761 | if ( ! ($this->_useQueryCache && ($queryCache = $this->getQueryCacheDriver()))) { |
|
263 | 172 | $parser = new Parser($this); |
|
264 | |||
265 | 172 | $this->_parserResult = $parser->parse(); |
|
266 | |||
267 | 168 | return $this->_parserResult; |
|
268 | } |
||
269 | |||
270 | 589 | $hash = $this->_getQueryCacheId(); |
|
271 | 589 | $cached = $this->_expireQueryCache ? false : $queryCache->fetch($hash); |
|
272 | |||
273 | 589 | if ($cached instanceof ParserResult) { |
|
274 | // Cache hit. |
||
275 | 121 | $this->_parserResult = $cached; |
|
276 | |||
277 | 121 | return $this->_parserResult; |
|
278 | } |
||
279 | |||
280 | // Cache miss. |
||
281 | 538 | $parser = new Parser($this); |
|
282 | |||
283 | 538 | $this->_parserResult = $parser->parse(); |
|
284 | |||
285 | 517 | $queryCache->save($hash, $this->_parserResult, $this->_queryCacheTTL); |
|
286 | |||
287 | 517 | return $this->_parserResult; |
|
288 | } |
||
289 | |||
290 | /** |
||
291 | * {@inheritdoc} |
||
292 | */ |
||
293 | 457 | protected function _doExecute() |
|
294 | { |
||
295 | 457 | $executor = $this->_parse()->getSqlExecutor(); |
|
296 | |||
297 | 448 | if ($this->_queryCacheProfile) { |
|
298 | 8 | $executor->setQueryCacheProfile($this->_queryCacheProfile); |
|
299 | } else { |
||
300 | 442 | $executor->removeQueryCacheProfile(); |
|
301 | } |
||
302 | |||
303 | 448 | if ($this->_resultSetMapping === null) { |
|
304 | 406 | $this->_resultSetMapping = $this->_parserResult->getResultSetMapping(); |
|
305 | } |
||
306 | |||
307 | // Prepare parameters |
||
308 | 448 | $paramMappings = $this->_parserResult->getParameterMappings(); |
|
309 | 448 | $paramCount = count($this->parameters); |
|
310 | 448 | $mappingCount = count($paramMappings); |
|
311 | |||
312 | 448 | if ($paramCount > $mappingCount) { |
|
313 | 1 | throw QueryException::tooManyParameters($mappingCount, $paramCount); |
|
314 | } |
||
315 | |||
316 | 447 | if ($paramCount < $mappingCount) { |
|
317 | 1 | throw QueryException::tooFewParameters($mappingCount, $paramCount); |
|
318 | } |
||
319 | |||
320 | // evict all cache for the entity region |
||
321 | 446 | if ($this->hasCache && isset($this->_hints[self::HINT_CACHE_EVICT]) && $this->_hints[self::HINT_CACHE_EVICT]) { |
|
322 | 2 | $this->evictEntityCacheRegion(); |
|
323 | } |
||
324 | |||
325 | 446 | list($sqlParams, $types) = $this->processParameterMappings($paramMappings); |
|
326 | |||
327 | 445 | $this->evictResultSetCache( |
|
328 | 445 | $executor, |
|
329 | 445 | $sqlParams, |
|
330 | 445 | $types, |
|
331 | 445 | $this->_em->getConnection()->getParams() |
|
332 | ); |
||
333 | |||
334 | 445 | return $executor->execute($this->_em->getConnection(), $sqlParams, $types); |
|
335 | } |
||
336 | |||
337 | 445 | private function evictResultSetCache( |
|
338 | AbstractSqlExecutor $executor, |
||
339 | array $sqlParams, |
||
340 | array $types, |
||
341 | array $connectionParams |
||
342 | ) { |
||
343 | 445 | if (null === $this->_queryCacheProfile || ! $this->getExpireResultCache()) { |
|
344 | 445 | return; |
|
345 | } |
||
346 | |||
347 | 2 | $cacheDriver = $this->_queryCacheProfile->getResultCacheDriver(); |
|
348 | 2 | $statements = (array) $executor->getSqlStatements(); // Type casted since it can either be a string or an array |
|
349 | |||
350 | 2 | foreach ($statements as $statement) { |
|
351 | 2 | $cacheKeys = $this->_queryCacheProfile->generateCacheKeys($statement, $sqlParams, $types, $connectionParams); |
|
352 | |||
353 | 2 | $cacheDriver->delete(reset($cacheKeys)); |
|
354 | } |
||
355 | 2 | } |
|
356 | |||
357 | /** |
||
358 | * Evict entity cache region |
||
359 | */ |
||
360 | 2 | private function evictEntityCacheRegion() |
|
361 | { |
||
362 | 2 | $AST = $this->getAST(); |
|
363 | |||
364 | 2 | if ($AST instanceof \Doctrine\ORM\Query\AST\SelectStatement) { |
|
365 | throw new QueryException('The hint "HINT_CACHE_EVICT" is not valid for select statements.'); |
||
366 | } |
||
367 | |||
368 | 2 | $className = ($AST instanceof \Doctrine\ORM\Query\AST\DeleteStatement) |
|
369 | 1 | ? $AST->deleteClause->abstractSchemaName |
|
370 | 2 | : $AST->updateClause->abstractSchemaName; |
|
371 | |||
372 | 2 | $this->_em->getCache()->evictEntityRegion($className); |
|
373 | 2 | } |
|
374 | |||
375 | /** |
||
376 | * Processes query parameter mappings. |
||
377 | * |
||
378 | * @param array $paramMappings |
||
379 | * |
||
380 | * @return array |
||
381 | * |
||
382 | * @throws Query\QueryException |
||
383 | */ |
||
384 | 446 | private function processParameterMappings($paramMappings) |
|
385 | { |
||
386 | 446 | $sqlParams = []; |
|
387 | 446 | $types = []; |
|
388 | |||
389 | 446 | foreach ($this->parameters as $parameter) { |
|
390 | 162 | $key = $parameter->getName(); |
|
391 | 162 | $value = $parameter->getValue(); |
|
392 | 162 | $rsm = $this->getResultSetMapping(); |
|
393 | |||
394 | 162 | if ( ! isset($paramMappings[$key])) { |
|
395 | 1 | throw QueryException::unknownParameter($key); |
|
396 | } |
||
397 | |||
398 | 161 | if (isset($rsm->metadataParameterMapping[$key]) && $value instanceof ClassMetadata) { |
|
399 | $value = $value->getMetadataValue($rsm->metadataParameterMapping[$key]); |
||
400 | } |
||
401 | |||
402 | 161 | if (isset($rsm->discriminatorParameters[$key]) && $value instanceof ClassMetadata) { |
|
403 | 3 | $value = array_keys(HierarchyDiscriminatorResolver::resolveDiscriminatorsForClass($value, $this->_em)); |
|
404 | } |
||
405 | |||
406 | 161 | $value = $this->processParameterValue($value); |
|
407 | 161 | $type = ($parameter->getValue() === $value) |
|
408 | 149 | ? $parameter->getType() |
|
409 | 161 | : ParameterTypeInferer::inferType($value); |
|
410 | |||
411 | 161 | foreach ($paramMappings[$key] as $position) { |
|
412 | 161 | $types[$position] = $type; |
|
413 | } |
||
414 | |||
415 | 161 | $sqlPositions = $paramMappings[$key]; |
|
416 | |||
417 | // optimized multi value sql positions away for now, |
||
418 | // they are not allowed in DQL anyways. |
||
419 | 161 | $value = [$value]; |
|
420 | 161 | $countValue = count($value); |
|
421 | |||
422 | 161 | for ($i = 0, $l = count($sqlPositions); $i < $l; $i++) { |
|
423 | 161 | $sqlParams[$sqlPositions[$i]] = $value[($i % $countValue)]; |
|
424 | } |
||
425 | } |
||
426 | |||
427 | 445 | if (count($sqlParams) != count($types)) { |
|
428 | throw QueryException::parameterTypeMismatch(); |
||
429 | } |
||
430 | |||
431 | 445 | if ($sqlParams) { |
|
432 | 161 | ksort($sqlParams); |
|
433 | 161 | $sqlParams = array_values($sqlParams); |
|
434 | |||
435 | 161 | ksort($types); |
|
436 | 161 | $types = array_values($types); |
|
437 | } |
||
438 | |||
439 | 445 | return [$sqlParams, $types]; |
|
440 | } |
||
441 | |||
442 | /** |
||
443 | * Defines a cache driver to be used for caching queries. |
||
444 | * |
||
445 | * @param \Doctrine\Common\Cache\Cache|null $queryCache Cache driver. |
||
446 | * |
||
447 | * @return Query This query instance. |
||
448 | */ |
||
449 | 6 | public function setQueryCacheDriver($queryCache) |
|
450 | { |
||
451 | 6 | $this->_queryCache = $queryCache; |
|
452 | |||
453 | 6 | return $this; |
|
454 | } |
||
455 | |||
456 | /** |
||
457 | * Defines whether the query should make use of a query cache, if available. |
||
458 | * |
||
459 | * @param boolean $bool |
||
460 | * |
||
461 | * @return Query This query instance. |
||
462 | */ |
||
463 | 175 | public function useQueryCache($bool) |
|
464 | { |
||
465 | 175 | $this->_useQueryCache = $bool; |
|
466 | |||
467 | 175 | return $this; |
|
468 | } |
||
469 | |||
470 | /** |
||
471 | * Returns the cache driver used for query caching. |
||
472 | * |
||
473 | * @return \Doctrine\Common\Cache\Cache|null The cache driver used for query caching or NULL, if |
||
474 | * this Query does not use query caching. |
||
475 | */ |
||
476 | 589 | public function getQueryCacheDriver() |
|
477 | { |
||
478 | 589 | if ($this->_queryCache) { |
|
479 | 9 | return $this->_queryCache; |
|
480 | } |
||
481 | |||
482 | 580 | return $this->_em->getConfiguration()->getQueryCacheImpl(); |
|
483 | } |
||
484 | |||
485 | /** |
||
486 | * Defines how long the query cache will be active before expire. |
||
487 | * |
||
488 | * @param integer $timeToLive How long the cache entry is valid. |
||
489 | * |
||
490 | * @return Query This query instance. |
||
491 | */ |
||
492 | 1 | public function setQueryCacheLifetime($timeToLive) |
|
493 | { |
||
494 | 1 | if ($timeToLive !== null) { |
|
495 | 1 | $timeToLive = (int) $timeToLive; |
|
496 | } |
||
497 | |||
498 | 1 | $this->_queryCacheTTL = $timeToLive; |
|
499 | |||
500 | 1 | return $this; |
|
501 | } |
||
502 | |||
503 | /** |
||
504 | * Retrieves the lifetime of resultset cache. |
||
505 | * |
||
506 | * @return int |
||
507 | */ |
||
508 | public function getQueryCacheLifetime() |
||
509 | { |
||
510 | return $this->_queryCacheTTL; |
||
511 | } |
||
512 | |||
513 | /** |
||
514 | * Defines if the query cache is active or not. |
||
515 | * |
||
516 | * @param boolean $expire Whether or not to force query cache expiration. |
||
517 | * |
||
518 | * @return Query This query instance. |
||
519 | */ |
||
520 | 7 | public function expireQueryCache($expire = true) |
|
521 | { |
||
522 | 7 | $this->_expireQueryCache = $expire; |
|
523 | |||
524 | 7 | return $this; |
|
525 | } |
||
526 | |||
527 | /** |
||
528 | * Retrieves if the query cache is active or not. |
||
529 | * |
||
530 | * @return bool |
||
531 | */ |
||
532 | public function getExpireQueryCache() |
||
533 | { |
||
534 | return $this->_expireQueryCache; |
||
535 | } |
||
536 | |||
537 | /** |
||
538 | * @override |
||
539 | */ |
||
540 | 211 | public function free() |
|
541 | { |
||
542 | 211 | parent::free(); |
|
543 | |||
544 | 211 | $this->_dql = null; |
|
545 | 211 | $this->_state = self::STATE_CLEAN; |
|
546 | 211 | } |
|
547 | |||
548 | /** |
||
549 | * Sets a DQL query string. |
||
550 | * |
||
551 | * @param string $dqlQuery DQL Query. |
||
552 | * |
||
553 | * @return \Doctrine\ORM\AbstractQuery |
||
554 | */ |
||
555 | 944 | public function setDQL($dqlQuery) |
|
556 | { |
||
557 | 944 | if ($dqlQuery !== null) { |
|
558 | 944 | $this->_dql = $dqlQuery; |
|
559 | 944 | $this->_state = self::STATE_DIRTY; |
|
560 | } |
||
561 | |||
562 | 944 | return $this; |
|
563 | } |
||
564 | |||
565 | /** |
||
566 | * Returns the DQL query that is represented by this query object. |
||
567 | * |
||
568 | * @return string DQL query. |
||
569 | */ |
||
570 | 896 | public function getDQL() |
|
571 | { |
||
572 | 896 | return $this->_dql; |
|
573 | } |
||
574 | |||
575 | /** |
||
576 | * Returns the state of this query object |
||
577 | * By default the type is Doctrine_ORM_Query_Abstract::STATE_CLEAN but if it appears any unprocessed DQL |
||
578 | * part, it is switched to Doctrine_ORM_Query_Abstract::STATE_DIRTY. |
||
579 | * |
||
580 | * @see AbstractQuery::STATE_CLEAN |
||
581 | * @see AbstractQuery::STATE_DIRTY |
||
582 | * |
||
583 | * @return integer The query state. |
||
584 | */ |
||
585 | public function getState() |
||
586 | { |
||
587 | return $this->_state; |
||
588 | } |
||
589 | |||
590 | /** |
||
591 | * Method to check if an arbitrary piece of DQL exists |
||
592 | * |
||
593 | * @param string $dql Arbitrary piece of DQL to check for. |
||
594 | * |
||
595 | * @return boolean |
||
596 | */ |
||
597 | public function contains($dql) |
||
598 | { |
||
599 | return stripos($this->getDQL(), $dql) !== false; |
||
600 | } |
||
601 | |||
602 | /** |
||
603 | * Sets the position of the first result to retrieve (the "offset"). |
||
604 | * |
||
605 | * @param integer $firstResult The first result to return. |
||
606 | * |
||
607 | * @return Query This query object. |
||
608 | */ |
||
609 | 220 | public function setFirstResult($firstResult) |
|
610 | { |
||
611 | 220 | $this->_firstResult = $firstResult; |
|
612 | 220 | $this->_state = self::STATE_DIRTY; |
|
613 | |||
614 | 220 | return $this; |
|
615 | } |
||
616 | |||
617 | /** |
||
618 | * Gets the position of the first result the query object was set to retrieve (the "offset"). |
||
619 | * Returns NULL if {@link setFirstResult} was not applied to this query. |
||
620 | * |
||
621 | * @return integer The position of the first result. |
||
622 | */ |
||
623 | 646 | public function getFirstResult() |
|
624 | { |
||
625 | 646 | return $this->_firstResult; |
|
626 | } |
||
627 | |||
628 | /** |
||
629 | * Sets the maximum number of results to retrieve (the "limit"). |
||
630 | * |
||
631 | * @param integer|null $maxResults |
||
632 | * |
||
633 | * @return Query This query object. |
||
634 | */ |
||
635 | 229 | public function setMaxResults($maxResults) |
|
636 | { |
||
637 | 229 | $this->_maxResults = $maxResults; |
|
638 | 229 | $this->_state = self::STATE_DIRTY; |
|
639 | |||
640 | 229 | return $this; |
|
641 | } |
||
642 | |||
643 | /** |
||
644 | * Gets the maximum number of results the query object was set to retrieve (the "limit"). |
||
645 | * Returns NULL if {@link setMaxResults} was not applied to this query. |
||
646 | * |
||
647 | * @return integer|null Maximum number of results. |
||
648 | */ |
||
649 | 646 | public function getMaxResults() |
|
650 | { |
||
651 | 646 | return $this->_maxResults; |
|
652 | } |
||
653 | |||
654 | /** |
||
655 | * Executes the query and returns an IterableResult that can be used to incrementally |
||
656 | * iterated over the result. |
||
657 | * |
||
658 | * @param ArrayCollection|array|null $parameters The query parameters. |
||
659 | * @param integer $hydrationMode The hydration mode to use. |
||
660 | * |
||
661 | * @return \Doctrine\ORM\Internal\Hydration\IterableResult |
||
662 | */ |
||
663 | 10 | public function iterate($parameters = null, $hydrationMode = self::HYDRATE_OBJECT) |
|
664 | { |
||
665 | 10 | $this->setHint(self::HINT_INTERNAL_ITERATION, true); |
|
666 | |||
667 | 10 | return parent::iterate($parameters, $hydrationMode); |
|
668 | } |
||
669 | |||
670 | /** |
||
671 | * {@inheritdoc} |
||
672 | */ |
||
673 | 461 | public function setHint($name, $value) |
|
674 | { |
||
675 | 461 | $this->_state = self::STATE_DIRTY; |
|
676 | |||
677 | 461 | return parent::setHint($name, $value); |
|
678 | } |
||
679 | |||
680 | /** |
||
681 | * {@inheritdoc} |
||
682 | */ |
||
683 | 354 | public function setHydrationMode($hydrationMode) |
|
684 | { |
||
685 | 354 | $this->_state = self::STATE_DIRTY; |
|
686 | |||
687 | 354 | return parent::setHydrationMode($hydrationMode); |
|
688 | } |
||
689 | |||
690 | /** |
||
691 | * Set the lock mode for this Query. |
||
692 | * |
||
693 | * @see \Doctrine\DBAL\LockMode |
||
694 | * |
||
695 | * @param int $lockMode |
||
696 | * |
||
697 | * @return Query |
||
698 | * |
||
699 | * @throws TransactionRequiredException |
||
700 | */ |
||
701 | public function setLockMode($lockMode) |
||
702 | { |
||
703 | if (in_array($lockMode, [LockMode::NONE, LockMode::PESSIMISTIC_READ, LockMode::PESSIMISTIC_WRITE], true)) { |
||
704 | if ( ! $this->_em->getConnection()->isTransactionActive()) { |
||
705 | throw TransactionRequiredException::transactionRequired(); |
||
706 | } |
||
707 | } |
||
708 | |||
709 | $this->setHint(self::HINT_LOCK_MODE, $lockMode); |
||
710 | |||
711 | return $this; |
||
712 | } |
||
713 | |||
714 | /** |
||
715 | * Get the current lock mode for this query. |
||
716 | * |
||
717 | * @return int|null The current lock mode of this query or NULL if no specific lock mode is set. |
||
718 | */ |
||
719 | public function getLockMode() |
||
720 | { |
||
721 | $lockMode = $this->getHint(self::HINT_LOCK_MODE); |
||
722 | |||
723 | if (false === $lockMode) { |
||
724 | return null; |
||
725 | } |
||
726 | |||
727 | return $lockMode; |
||
728 | } |
||
729 | |||
730 | /** |
||
731 | * Generate a cache id for the query cache - reusing the Result-Cache-Id generator. |
||
732 | * |
||
733 | * @return string |
||
734 | */ |
||
735 | 589 | protected function _getQueryCacheId() |
|
736 | { |
||
737 | 589 | ksort($this->_hints); |
|
738 | |||
739 | 589 | $platform = $this->getEntityManager() |
|
740 | 589 | ->getConnection() |
|
741 | 589 | ->getDatabasePlatform() |
|
742 | 589 | ->getName(); |
|
743 | |||
744 | 589 | return md5( |
|
745 | 589 | $this->getDQL() . serialize($this->_hints) . |
|
746 | 589 | '&platform=' . $platform . |
|
747 | 589 | ($this->_em->hasFilters() ? $this->_em->getFilters()->getHash() : '') . |
|
748 | 589 | '&firstResult=' . $this->_firstResult . '&maxResult=' . $this->_maxResults . |
|
749 | 589 | '&hydrationMode=' . $this->_hydrationMode . '&types=' . serialize($this->_parsedTypes) . 'DOCTRINE_QUERY_CACHE_SALT' |
|
750 | ); |
||
751 | } |
||
752 | |||
753 | /** |
||
754 | * {@inheritdoc} |
||
755 | */ |
||
756 | 28 | protected function getHash() |
|
757 | { |
||
758 | 28 | return sha1(parent::getHash(). '-'. $this->_firstResult . '-' . $this->_maxResults); |
|
759 | } |
||
760 | |||
761 | /** |
||
762 | * Cleanup Query resource when clone is called. |
||
763 | * |
||
764 | * @return void |
||
765 | */ |
||
766 | 136 | public function __clone() |
|
767 | { |
||
768 | 136 | parent::__clone(); |
|
769 | |||
770 | 136 | $this->_state = self::STATE_DIRTY; |
|
771 | 136 | } |
|
772 | } |
||
773 |
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.