1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace Doctrine\ORM; |
6
|
|
|
|
7
|
|
|
use Doctrine\Common\Cache\Cache as CommonCache; |
8
|
|
|
use Doctrine\Common\Collections\ArrayCollection; |
9
|
|
|
use Doctrine\Common\Collections\Collection; |
10
|
|
|
use Doctrine\DBAL\Cache\QueryCacheProfile; |
11
|
|
|
use Doctrine\DBAL\Driver\ResultStatement; |
12
|
|
|
use Doctrine\ORM\Cache\Logging\CacheLogger; |
13
|
|
|
use Doctrine\ORM\Cache\QueryCacheKey; |
14
|
|
|
use Doctrine\ORM\Cache\TimestampCacheKey; |
15
|
|
|
use Doctrine\ORM\Internal\Hydration\IterableResult; |
16
|
|
|
use Doctrine\ORM\Query\Parameter; |
17
|
|
|
use Doctrine\ORM\Query\ResultSetMapping; |
18
|
|
|
use Doctrine\ORM\Utility\StaticClassNameConverter; |
19
|
|
|
use function array_map; |
20
|
|
|
use function array_shift; |
21
|
|
|
use function count; |
22
|
|
|
use function is_array; |
23
|
|
|
use function is_numeric; |
24
|
|
|
use function is_object; |
25
|
|
|
use function is_scalar; |
26
|
|
|
use function ksort; |
27
|
|
|
use function reset; |
28
|
|
|
use function serialize; |
29
|
|
|
use function sha1; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* Base contract for ORM queries. Base class for Query and NativeQuery. |
33
|
|
|
*/ |
34
|
|
|
abstract class AbstractQuery |
35
|
|
|
{ |
36
|
|
|
/* Hydration mode constants */ |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* Hydrates an object graph. This is the default behavior. |
40
|
|
|
*/ |
41
|
|
|
public const HYDRATE_OBJECT = 1; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* Hydrates an array graph. |
45
|
|
|
*/ |
46
|
|
|
public const HYDRATE_ARRAY = 2; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* Hydrates a flat, rectangular result set with scalar values. |
50
|
|
|
*/ |
51
|
|
|
public const HYDRATE_SCALAR = 3; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* Hydrates a single scalar value. |
55
|
|
|
*/ |
56
|
|
|
public const HYDRATE_SINGLE_SCALAR = 4; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* Very simple object hydrator (optimized for performance). |
60
|
|
|
*/ |
61
|
|
|
public const HYDRATE_SIMPLEOBJECT = 5; |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* The parameter map of this query. |
65
|
|
|
* |
66
|
|
|
* @var ArrayCollection |
67
|
|
|
*/ |
68
|
|
|
protected $parameters; |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* The user-specified ResultSetMapping to use. |
72
|
|
|
* |
73
|
|
|
* @var ResultSetMapping |
74
|
|
|
*/ |
75
|
|
|
protected $resultSetMapping; |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* The entity manager used by this query object. |
79
|
|
|
* |
80
|
|
|
* @var EntityManagerInterface |
81
|
|
|
*/ |
82
|
|
|
protected $em; |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* The map of query hints. |
86
|
|
|
* |
87
|
|
|
* @var mixed[] |
88
|
|
|
*/ |
89
|
|
|
protected $hints = []; |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* The hydration mode. |
93
|
|
|
* |
94
|
|
|
* @var int|string |
95
|
|
|
*/ |
96
|
|
|
protected $hydrationMode = self::HYDRATE_OBJECT; |
97
|
|
|
|
98
|
|
|
/** @var QueryCacheProfile */ |
99
|
|
|
protected $queryCacheProfile; |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Whether or not expire the result cache. |
103
|
|
|
* |
104
|
|
|
* @var bool |
105
|
|
|
*/ |
106
|
|
|
protected $expireResultCache = false; |
107
|
|
|
|
108
|
|
|
/** @var QueryCacheProfile */ |
109
|
|
|
protected $hydrationCacheProfile; |
110
|
|
|
|
111
|
|
|
/** |
112
|
|
|
* Whether to use second level cache, if available. |
113
|
|
|
* |
114
|
|
|
* @var bool |
115
|
|
|
*/ |
116
|
|
|
protected $cacheable = false; |
117
|
|
|
|
118
|
|
|
/** @var bool */ |
119
|
|
|
protected $hasCache = false; |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* Second level cache region name. |
123
|
|
|
* |
124
|
|
|
* @var string|null |
125
|
|
|
*/ |
126
|
|
|
protected $cacheRegion; |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* Second level query cache mode. |
130
|
|
|
* |
131
|
|
|
* @var int|null |
132
|
|
|
*/ |
133
|
|
|
protected $cacheMode; |
134
|
|
|
|
135
|
|
|
/** @var CacheLogger|null */ |
136
|
|
|
protected $cacheLogger; |
137
|
|
|
|
138
|
|
|
/** @var int */ |
139
|
|
|
protected $lifetime = 0; |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* Initializes a new instance of a class derived from <tt>AbstractQuery</tt>. |
143
|
|
|
*/ |
144
|
967 |
|
public function __construct(EntityManagerInterface $em) |
145
|
|
|
{ |
146
|
967 |
|
$this->em = $em; |
147
|
967 |
|
$this->parameters = new ArrayCollection(); |
148
|
967 |
|
$this->hints = $em->getConfiguration()->getDefaultQueryHints(); |
149
|
967 |
|
$this->hasCache = $this->em->getConfiguration()->isSecondLevelCacheEnabled(); |
150
|
|
|
|
151
|
967 |
|
if ($this->hasCache) { |
152
|
32 |
|
$this->cacheLogger = $em->getConfiguration() |
153
|
32 |
|
->getSecondLevelCacheConfiguration() |
154
|
32 |
|
->getCacheLogger(); |
155
|
|
|
} |
156
|
967 |
|
} |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* Enable/disable second level query (result) caching for this query. |
160
|
|
|
* |
161
|
|
|
* @return static This query instance. |
162
|
|
|
*/ |
163
|
133 |
|
public function setCacheable(bool $cacheable) : self |
164
|
|
|
{ |
165
|
133 |
|
$this->cacheable = (bool) $cacheable; |
166
|
|
|
|
167
|
133 |
|
return $this; |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
/** |
171
|
|
|
* @return bool TRUE if the query results are enable for second level cache, FALSE otherwise. |
172
|
|
|
*/ |
173
|
90 |
|
public function isCacheable() : bool |
174
|
|
|
{ |
175
|
90 |
|
return $this->cacheable; |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* @return static This query instance. |
180
|
|
|
*/ |
181
|
2 |
|
public function setCacheRegion(string $cacheRegion) : self |
182
|
|
|
{ |
183
|
2 |
|
$this->cacheRegion = $cacheRegion; |
184
|
|
|
|
185
|
2 |
|
return $this; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* Obtain the name of the second level query cache region in which query results will be stored |
190
|
|
|
* |
191
|
|
|
* @return string|null The cache region name; NULL indicates the default region. |
192
|
|
|
*/ |
193
|
1 |
|
public function getCacheRegion() : ?string |
194
|
|
|
{ |
195
|
1 |
|
return $this->cacheRegion; |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* @return bool TRUE if the query cache and second level cache are enabled, FALSE otherwise. |
200
|
|
|
*/ |
201
|
465 |
|
protected function isCacheEnabled() : bool |
202
|
|
|
{ |
203
|
465 |
|
return $this->cacheable && $this->hasCache; |
204
|
|
|
} |
205
|
|
|
|
206
|
1 |
|
public function getLifetime() : int |
207
|
|
|
{ |
208
|
1 |
|
return $this->lifetime; |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
/** |
212
|
|
|
* Sets the life-time for this query into second level cache. |
213
|
|
|
* |
214
|
|
|
* @return AbstractQuery This query instance. |
215
|
|
|
*/ |
216
|
2 |
|
public function setLifetime(int $lifetime) : self |
217
|
|
|
{ |
218
|
2 |
|
$this->lifetime = $lifetime; |
219
|
|
|
|
220
|
2 |
|
return $this; |
221
|
|
|
} |
222
|
|
|
|
223
|
1 |
|
public function getCacheMode() : ?int |
224
|
|
|
{ |
225
|
1 |
|
return $this->cacheMode; |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
/** |
229
|
|
|
* @return AbstractQuery This query instance. |
230
|
|
|
*/ |
231
|
4 |
|
public function setCacheMode(int $cacheMode) : self |
232
|
|
|
{ |
233
|
4 |
|
$this->cacheMode = (int) $cacheMode; |
234
|
|
|
|
235
|
4 |
|
return $this; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* Gets the SQL query that corresponds to this query object. |
240
|
|
|
* The returned SQL syntax depends on the connection driver that is used |
241
|
|
|
* by this query object at the time of this method call. |
242
|
|
|
* |
243
|
|
|
* @return string|string[] SQL query |
244
|
|
|
*/ |
245
|
|
|
abstract public function getSQL(); |
246
|
|
|
|
247
|
|
|
/** |
248
|
|
|
* Retrieves the associated EntityManager of this Query instance. |
249
|
|
|
*/ |
250
|
890 |
|
public function getEntityManager() : EntityManagerInterface |
251
|
|
|
{ |
252
|
890 |
|
return $this->em; |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
/** |
256
|
|
|
* Frees the resources used by the query object. |
257
|
|
|
* |
258
|
|
|
* Resets Parameters, Parameter Types and Query Hints. |
259
|
|
|
*/ |
260
|
220 |
|
public function free() : void |
261
|
|
|
{ |
262
|
220 |
|
$this->parameters = new ArrayCollection(); |
263
|
|
|
|
264
|
220 |
|
$this->hints = $this->em->getConfiguration()->getDefaultQueryHints(); |
265
|
220 |
|
} |
266
|
|
|
|
267
|
|
|
/** |
268
|
|
|
* Get all defined parameters. |
269
|
|
|
* |
270
|
|
|
* @return ArrayCollection The defined query parameters. |
271
|
|
|
*/ |
272
|
135 |
|
public function getParameters() : ArrayCollection |
273
|
|
|
{ |
274
|
135 |
|
return $this->parameters; |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
/** |
278
|
|
|
* Gets a query parameter. |
279
|
|
|
* |
280
|
|
|
* @param mixed $key The key (index or name) of the bound parameter. |
281
|
|
|
* |
282
|
|
|
* @return Parameter|null The value of the bound parameter, or NULL if not available. |
283
|
|
|
*/ |
284
|
266 |
|
public function getParameter($key) : ?Parameter |
285
|
|
|
{ |
286
|
266 |
|
$filteredParameters = $this->parameters->filter( |
287
|
266 |
|
function (Parameter $parameter) use ($key) : bool { |
288
|
150 |
|
$parameterName = $parameter->getName(); |
289
|
|
|
|
290
|
150 |
|
return $key === $parameterName || (string) $key === (string) $parameterName; |
291
|
266 |
|
} |
292
|
|
|
); |
293
|
|
|
|
294
|
266 |
|
return $filteredParameters->isEmpty() ? null : $filteredParameters->first(); |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
/** |
298
|
|
|
* Sets a collection of query parameters. |
299
|
|
|
* |
300
|
|
|
* @param ArrayCollection|array|Parameter[]|mixed[] $parameters |
301
|
|
|
* |
302
|
|
|
* @return static This query instance. |
303
|
|
|
*/ |
304
|
190 |
|
public function setParameters(iterable $parameters) : self |
305
|
|
|
{ |
306
|
|
|
// BC compatibility with 2.3- |
307
|
190 |
|
if (is_array($parameters)) { |
308
|
1 |
|
$parameterCollection = new ArrayCollection(); |
309
|
|
|
|
310
|
1 |
|
foreach ($parameters as $key => $value) { |
311
|
1 |
|
$parameterCollection->add(new Parameter($key, $value)); |
312
|
|
|
} |
313
|
|
|
|
314
|
1 |
|
$parameters = $parameterCollection; |
315
|
|
|
} |
316
|
|
|
|
317
|
190 |
|
$this->parameters = $parameters; |
|
|
|
|
318
|
|
|
|
319
|
190 |
|
return $this; |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
/** |
323
|
|
|
* Sets a query parameter. |
324
|
|
|
* |
325
|
|
|
* @param string|int $key The parameter position or name. |
326
|
|
|
* @param mixed $value The parameter value. |
327
|
|
|
* @param string|int|null $type The parameter type. If specified, the given value will be run through |
328
|
|
|
* the type conversion of this type. This is usually not needed for |
329
|
|
|
* strings and numeric types. |
330
|
|
|
* |
331
|
|
|
* @return static This query instance. |
332
|
|
|
*/ |
333
|
176 |
|
public function setParameter($key, $value, $type = null) : self |
334
|
|
|
{ |
335
|
176 |
|
$existingParameter = $this->getParameter($key); |
336
|
|
|
|
337
|
176 |
|
if ($existingParameter !== null) { |
338
|
5 |
|
$existingParameter->setValue($value, $type); |
339
|
|
|
|
340
|
5 |
|
return $this; |
341
|
|
|
} |
342
|
|
|
|
343
|
174 |
|
$this->parameters->add(new Parameter($key, $value, $type)); |
344
|
|
|
|
345
|
174 |
|
return $this; |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
/** |
349
|
|
|
* Processes an individual parameter value. |
350
|
|
|
* |
351
|
|
|
* @param mixed $value |
352
|
|
|
* |
353
|
|
|
* @return string|mixed[] |
354
|
|
|
* |
355
|
|
|
* @throws ORMInvalidArgumentException |
356
|
|
|
*/ |
357
|
173 |
|
public function processParameterValue($value) |
358
|
|
|
{ |
359
|
173 |
|
if (is_scalar($value)) { |
360
|
161 |
|
return $value; |
361
|
|
|
} |
362
|
|
|
|
363
|
88 |
|
if ($value instanceof Mapping\ClassMetadata) { |
364
|
1 |
|
return $value->discriminatorValue ?: $value->getClassName(); |
365
|
|
|
} |
366
|
|
|
|
367
|
87 |
|
if ($value instanceof Collection) { |
368
|
1 |
|
$value = $value->toArray(); |
369
|
|
|
} |
370
|
|
|
|
371
|
87 |
|
if (is_array($value)) { |
372
|
72 |
|
foreach ($value as $key => $paramValue) { |
373
|
72 |
|
$paramValue = $this->processParameterValue($paramValue); |
374
|
72 |
|
$value[$key] = is_array($paramValue) ? reset($paramValue) : $paramValue; |
375
|
|
|
} |
376
|
|
|
|
377
|
72 |
|
return $value; |
378
|
|
|
} |
379
|
|
|
|
380
|
18 |
|
if (is_object($value) && $this->em->getMetadataFactory()->hasMetadataFor(StaticClassNameConverter::getClass($value))) { |
381
|
13 |
|
$value = $this->em->getUnitOfWork()->getSingleIdentifierValue($value); |
382
|
|
|
|
383
|
13 |
|
if ($value === null) { |
384
|
|
|
throw ORMInvalidArgumentException::invalidIdentifierBindingEntity(); |
385
|
|
|
} |
386
|
|
|
} |
387
|
|
|
|
388
|
18 |
|
return $value; |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
/** |
392
|
|
|
* Sets the ResultSetMapping that should be used for hydration. |
393
|
|
|
* |
394
|
|
|
* @return static This query instance. |
395
|
|
|
*/ |
396
|
22 |
|
public function setResultSetMapping(ResultSetMapping $rsm) : self |
397
|
|
|
{ |
398
|
22 |
|
$this->resultSetMapping = $rsm; |
399
|
|
|
|
400
|
22 |
|
return $this; |
401
|
|
|
} |
402
|
|
|
|
403
|
|
|
/** |
404
|
|
|
* Gets the ResultSetMapping used for hydration. |
405
|
|
|
*/ |
406
|
14 |
|
protected function getResultSetMapping() : ResultSetMapping |
407
|
|
|
{ |
408
|
14 |
|
return $this->resultSetMapping; |
409
|
|
|
} |
410
|
|
|
|
411
|
|
|
/** |
412
|
|
|
* Set a cache profile for hydration caching. |
413
|
|
|
* |
414
|
|
|
* If no result cache driver is set in the QueryCacheProfile, the default |
415
|
|
|
* result cache driver is used from the configuration. |
416
|
|
|
* |
417
|
|
|
* Important: Hydration caching does NOT register entities in the |
418
|
|
|
* UnitOfWork when retrieved from the cache. Never use result cached |
419
|
|
|
* entities for requests that also flush the EntityManager. If you want |
420
|
|
|
* some form of caching with UnitOfWork registration you should use |
421
|
|
|
* {@see AbstractQuery::setResultCacheProfile()}. |
422
|
|
|
* |
423
|
|
|
* @example |
424
|
|
|
* $lifetime = 100; |
425
|
|
|
* $resultKey = "abc"; |
426
|
|
|
* $query->setHydrationCacheProfile(new QueryCacheProfile()); |
427
|
|
|
* $query->setHydrationCacheProfile(new QueryCacheProfile($lifetime, $resultKey)); |
428
|
|
|
* |
429
|
|
|
* @return static This query instance. |
430
|
|
|
*/ |
431
|
3 |
|
public function setHydrationCacheProfile(?QueryCacheProfile $profile = null) : self |
432
|
|
|
{ |
433
|
3 |
|
if ($profile !== null && ! $profile->getResultCacheDriver()) { |
434
|
|
|
$resultCacheDriver = $this->em->getConfiguration()->getHydrationCacheImpl(); |
435
|
|
|
$profile = $profile->setResultCacheDriver($resultCacheDriver); |
436
|
|
|
} |
437
|
|
|
|
438
|
3 |
|
$this->hydrationCacheProfile = $profile; |
439
|
|
|
|
440
|
3 |
|
return $this; |
441
|
|
|
} |
442
|
|
|
|
443
|
3 |
|
public function getHydrationCacheProfile() : ?QueryCacheProfile |
444
|
|
|
{ |
445
|
3 |
|
return $this->hydrationCacheProfile; |
446
|
|
|
} |
447
|
|
|
|
448
|
|
|
/** |
449
|
|
|
* Set a cache profile for the result cache. |
450
|
|
|
* |
451
|
|
|
* If no result cache driver is set in the QueryCacheProfile, the default |
452
|
|
|
* result cache driver is used from the configuration. |
453
|
|
|
* |
454
|
|
|
* @return static This query instance. |
455
|
|
|
*/ |
456
|
1 |
|
public function setResultCacheProfile(?QueryCacheProfile $profile = null) : self |
457
|
|
|
{ |
458
|
1 |
|
if ($profile !== null && ! $profile->getResultCacheDriver()) { |
459
|
|
|
$resultCacheDriver = $this->em->getConfiguration()->getResultCacheImpl(); |
460
|
|
|
$profile = $profile->setResultCacheDriver($resultCacheDriver); |
461
|
|
|
} |
462
|
|
|
|
463
|
1 |
|
$this->queryCacheProfile = $profile; |
464
|
|
|
|
465
|
1 |
|
return $this; |
466
|
|
|
} |
467
|
|
|
|
468
|
|
|
/** |
469
|
|
|
* Defines a cache driver to be used for caching result sets and implicitly enables caching. |
470
|
|
|
* |
471
|
|
|
* @param CommonCache|null $resultCacheDriver Cache driver |
472
|
|
|
* |
473
|
|
|
* @return static This query instance. |
474
|
|
|
* |
475
|
|
|
* @throws ORMException |
476
|
|
|
*/ |
477
|
8 |
|
public function setResultCacheDriver(?CommonCache $resultCacheDriver = null) : self |
478
|
|
|
{ |
479
|
8 |
|
$this->queryCacheProfile = $this->queryCacheProfile |
480
|
1 |
|
? $this->queryCacheProfile->setResultCacheDriver($resultCacheDriver) |
|
|
|
|
481
|
7 |
|
: new QueryCacheProfile(0, null, $resultCacheDriver); |
482
|
|
|
|
483
|
8 |
|
return $this; |
484
|
|
|
} |
485
|
|
|
|
486
|
|
|
/** |
487
|
|
|
* Returns the cache driver used for caching result sets. |
488
|
|
|
* |
489
|
|
|
* @deprecated |
490
|
|
|
* |
491
|
|
|
* @return CommonCache Cache driver |
492
|
|
|
*/ |
493
|
3 |
|
public function getResultCacheDriver() : CommonCache |
494
|
|
|
{ |
495
|
3 |
|
if ($this->queryCacheProfile && $this->queryCacheProfile->getResultCacheDriver()) { |
496
|
3 |
|
return $this->queryCacheProfile->getResultCacheDriver(); |
497
|
|
|
} |
498
|
|
|
|
499
|
|
|
return $this->em->getConfiguration()->getResultCacheImpl(); |
|
|
|
|
500
|
|
|
} |
501
|
|
|
|
502
|
|
|
/** |
503
|
|
|
* Set whether or not to cache the results of this query and if so, for |
504
|
|
|
* how long and which ID to use for the cache entry. |
505
|
|
|
* |
506
|
|
|
* @return static This query instance. |
507
|
|
|
*/ |
508
|
9 |
|
public function useResultCache(bool $bool, ?int $lifetime = null, ?string $resultCacheId = null) : self |
509
|
|
|
{ |
510
|
9 |
|
if ($bool) { |
511
|
9 |
|
$this->setResultCacheLifetime($lifetime); |
512
|
9 |
|
$this->setResultCacheId($resultCacheId); |
513
|
|
|
|
514
|
9 |
|
return $this; |
515
|
|
|
} |
516
|
|
|
|
517
|
1 |
|
$this->queryCacheProfile = null; |
518
|
|
|
|
519
|
1 |
|
return $this; |
520
|
|
|
} |
521
|
|
|
|
522
|
|
|
/** |
523
|
|
|
* Defines how long the result cache will be active before expire. |
524
|
|
|
* |
525
|
|
|
* @param int|null $lifetime How long the cache entry is valid. |
526
|
|
|
* |
527
|
|
|
* @return static This query instance. |
528
|
|
|
*/ |
529
|
10 |
|
public function setResultCacheLifetime(?int $lifetime) : self |
530
|
|
|
{ |
531
|
10 |
|
$lifetime = ($lifetime !== null) ? (int) $lifetime : 0; |
532
|
|
|
|
533
|
10 |
|
$this->queryCacheProfile = $this->queryCacheProfile |
534
|
4 |
|
? $this->queryCacheProfile->setLifetime($lifetime) |
535
|
6 |
|
: new QueryCacheProfile($lifetime, null, $this->em->getConfiguration()->getResultCacheImpl()); |
536
|
|
|
|
537
|
10 |
|
return $this; |
538
|
|
|
} |
539
|
|
|
|
540
|
|
|
/** |
541
|
|
|
* Retrieves the lifetime of resultset cache. |
542
|
|
|
* |
543
|
|
|
* @deprecated |
544
|
|
|
*/ |
545
|
|
|
public function getResultCacheLifetime() : int |
546
|
|
|
{ |
547
|
|
|
return $this->queryCacheProfile ? $this->queryCacheProfile->getLifetime() : 0; |
548
|
|
|
} |
549
|
|
|
|
550
|
|
|
/** |
551
|
|
|
* Defines if the result cache is active or not. |
552
|
|
|
* |
553
|
|
|
* @param bool $expire Whether or not to force resultset cache expiration. |
554
|
|
|
* |
555
|
|
|
* @return static This query instance. |
556
|
|
|
*/ |
557
|
4 |
|
public function expireResultCache(bool $expire = true) : self |
558
|
|
|
{ |
559
|
4 |
|
$this->expireResultCache = $expire; |
560
|
|
|
|
561
|
4 |
|
return $this; |
562
|
|
|
} |
563
|
|
|
|
564
|
|
|
/** |
565
|
|
|
* Retrieves if the resultset cache is active or not. |
566
|
|
|
*/ |
567
|
8 |
|
public function getExpireResultCache() : bool |
568
|
|
|
{ |
569
|
8 |
|
return $this->expireResultCache; |
570
|
|
|
} |
571
|
|
|
|
572
|
1 |
|
public function getQueryCacheProfile() : QueryCacheProfile |
573
|
|
|
{ |
574
|
1 |
|
return $this->queryCacheProfile; |
575
|
|
|
} |
576
|
|
|
|
577
|
|
|
/** |
578
|
|
|
* Change the default fetch mode of an association for this query. |
579
|
|
|
* |
580
|
|
|
* @param string $fetchMode can be one of FetchMode::EAGER, FetchMode::LAZY or FetchMode::EXTRA_LAZY |
581
|
|
|
* |
582
|
|
|
* @return static This query instance. |
583
|
|
|
*/ |
584
|
3 |
|
public function setFetchMode(string $class, string $assocName, string $fetchMode) : self |
585
|
|
|
{ |
586
|
3 |
|
if ($fetchMode !== Mapping\FetchMode::EAGER) { |
587
|
|
|
$fetchMode = Mapping\FetchMode::LAZY; |
588
|
|
|
} |
589
|
|
|
|
590
|
3 |
|
$this->hints['fetchMode'][$class][$assocName] = $fetchMode; |
591
|
|
|
|
592
|
3 |
|
return $this; |
593
|
|
|
} |
594
|
|
|
|
595
|
|
|
/** |
596
|
|
|
* Defines the processing mode to be used during hydration / result set transformation. |
597
|
|
|
* |
598
|
|
|
* @param int|string $hydrationMode Doctrine processing mode to be used during hydration process. |
599
|
|
|
* One of the Query::HYDRATE_* constants. |
600
|
|
|
* |
601
|
|
|
* @return static This query instance. |
602
|
|
|
*/ |
603
|
372 |
|
public function setHydrationMode($hydrationMode) : self |
604
|
|
|
{ |
605
|
372 |
|
$this->hydrationMode = $hydrationMode; |
606
|
|
|
|
607
|
372 |
|
return $this; |
608
|
|
|
} |
609
|
|
|
|
610
|
|
|
/** |
611
|
|
|
* Gets the hydration mode currently used by the query. |
612
|
|
|
* |
613
|
|
|
* @return int|string |
614
|
|
|
*/ |
615
|
653 |
|
public function getHydrationMode() |
616
|
|
|
{ |
617
|
653 |
|
return $this->hydrationMode; |
618
|
|
|
} |
619
|
|
|
|
620
|
|
|
/** |
621
|
|
|
* Gets the list of results for the query. |
622
|
|
|
* |
623
|
|
|
* Alias for execute(null, $hydrationMode = HYDRATE_OBJECT). |
624
|
|
|
* |
625
|
|
|
* @param int|string $hydrationMode |
626
|
|
|
* |
627
|
|
|
* @return mixed |
628
|
|
|
*/ |
629
|
293 |
|
public function getResult($hydrationMode = self::HYDRATE_OBJECT) |
630
|
|
|
{ |
631
|
293 |
|
return $this->execute(null, $hydrationMode); |
632
|
|
|
} |
633
|
|
|
|
634
|
|
|
/** |
635
|
|
|
* Gets the array of results for the query. |
636
|
|
|
* |
637
|
|
|
* Alias for execute(null, HYDRATE_ARRAY). |
638
|
|
|
* |
639
|
|
|
* @return mixed[] |
640
|
|
|
*/ |
641
|
24 |
|
public function getArrayResult() : array |
642
|
|
|
{ |
643
|
24 |
|
return $this->execute(null, self::HYDRATE_ARRAY); |
|
|
|
|
644
|
|
|
} |
645
|
|
|
|
646
|
|
|
/** |
647
|
|
|
* Gets the scalar results for the query. |
648
|
|
|
* |
649
|
|
|
* Alias for execute(null, HYDRATE_SCALAR). |
650
|
|
|
* |
651
|
|
|
* @return mixed[] |
652
|
|
|
*/ |
653
|
87 |
|
public function getScalarResult() : array |
654
|
|
|
{ |
655
|
87 |
|
return $this->execute(null, self::HYDRATE_SCALAR); |
|
|
|
|
656
|
|
|
} |
657
|
|
|
|
658
|
|
|
/** |
659
|
|
|
* Get exactly one result or null. |
660
|
|
|
* |
661
|
|
|
* @param int|string|null $hydrationMode |
662
|
|
|
* |
663
|
|
|
* @return mixed |
664
|
|
|
* |
665
|
|
|
* @throws NonUniqueResultException |
666
|
|
|
*/ |
667
|
16 |
|
public function getOneOrNullResult($hydrationMode = null) |
668
|
|
|
{ |
669
|
|
|
try { |
670
|
16 |
|
$result = $this->execute(null, $hydrationMode); |
671
|
1 |
|
} catch (NoResultException $e) { |
672
|
1 |
|
return null; |
673
|
|
|
} |
674
|
|
|
|
675
|
15 |
|
if ($this->hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) { |
676
|
1 |
|
return null; |
677
|
|
|
} |
678
|
|
|
|
679
|
14 |
|
if (! is_array($result)) { |
680
|
1 |
|
return $result; |
681
|
|
|
} |
682
|
|
|
|
683
|
14 |
|
if (count($result) > 1) { |
684
|
1 |
|
throw new NonUniqueResultException(); |
685
|
|
|
} |
686
|
|
|
|
687
|
13 |
|
return array_shift($result); |
688
|
|
|
} |
689
|
|
|
|
690
|
|
|
/** |
691
|
|
|
* Gets the single result of the query. |
692
|
|
|
* |
693
|
|
|
* Enforces the presence as well as the uniqueness of the result. |
694
|
|
|
* |
695
|
|
|
* If the result is not unique, a NonUniqueResultException is thrown. |
696
|
|
|
* If there is no result, a NoResultException is thrown. |
697
|
|
|
* |
698
|
|
|
* @param int|string|null $hydrationMode |
699
|
|
|
* |
700
|
|
|
* @return mixed |
701
|
|
|
* |
702
|
|
|
* @throws NonUniqueResultException If the query result is not unique. |
703
|
|
|
* @throws NoResultException If the query returned no result. |
704
|
|
|
*/ |
705
|
102 |
|
public function getSingleResult($hydrationMode = null) |
706
|
|
|
{ |
707
|
102 |
|
$result = $this->execute(null, $hydrationMode); |
708
|
|
|
|
709
|
96 |
|
if ($this->hydrationMode !== self::HYDRATE_SINGLE_SCALAR && ! $result) { |
710
|
2 |
|
throw new NoResultException(); |
711
|
|
|
} |
712
|
|
|
|
713
|
95 |
|
if (! is_array($result)) { |
714
|
9 |
|
return $result; |
715
|
|
|
} |
716
|
|
|
|
717
|
87 |
|
if (count($result) > 1) { |
718
|
1 |
|
throw new NonUniqueResultException(); |
719
|
|
|
} |
720
|
|
|
|
721
|
86 |
|
return array_shift($result); |
722
|
|
|
} |
723
|
|
|
|
724
|
|
|
/** |
725
|
|
|
* Gets the single scalar result of the query. |
726
|
|
|
* |
727
|
|
|
* Alias for getSingleResult(HYDRATE_SINGLE_SCALAR). |
728
|
|
|
* |
729
|
|
|
* @return mixed The scalar result, or NULL if the query returned no result. |
730
|
|
|
* |
731
|
|
|
* @throws NonUniqueResultException If the query result is not unique. |
732
|
|
|
* @throws NoResultException If the query returned no result. |
733
|
|
|
*/ |
734
|
11 |
|
public function getSingleScalarResult() |
735
|
|
|
{ |
736
|
11 |
|
return $this->getSingleResult(self::HYDRATE_SINGLE_SCALAR); |
737
|
|
|
} |
738
|
|
|
|
739
|
|
|
/** |
740
|
|
|
* Sets a query hint. If the hint name is not recognized, it is silently ignored. |
741
|
|
|
* |
742
|
|
|
* @param string $name The name of the hint. |
743
|
|
|
* @param mixed $value The value of the hint. |
744
|
|
|
* |
745
|
|
|
* @return static This query instance. |
746
|
|
|
*/ |
747
|
464 |
|
public function setHint(string $name, $value) : self |
748
|
|
|
{ |
749
|
464 |
|
$this->hints[$name] = $value; |
750
|
|
|
|
751
|
464 |
|
return $this; |
752
|
|
|
} |
753
|
|
|
|
754
|
|
|
/** |
755
|
|
|
* Gets the value of a query hint. If the hint name is not recognized, FALSE is returned. |
756
|
|
|
* |
757
|
|
|
* @param string $name The name of the hint. |
758
|
|
|
* |
759
|
|
|
* @return mixed The value of the hint or FALSE, if the hint name is not recognized. |
760
|
|
|
*/ |
761
|
797 |
|
public function getHint(string $name) |
762
|
|
|
{ |
763
|
797 |
|
return $this->hints[$name] ?? false; |
764
|
|
|
} |
765
|
|
|
|
766
|
|
|
/** |
767
|
|
|
* Check if the query has a hint |
768
|
|
|
* |
769
|
|
|
* @param string $name The name of the hint |
770
|
|
|
* |
771
|
|
|
* @return bool False if the query does not have any hint |
772
|
|
|
*/ |
773
|
17 |
|
public function hasHint(string $name) : bool |
774
|
|
|
{ |
775
|
17 |
|
return isset($this->hints[$name]); |
776
|
|
|
} |
777
|
|
|
|
778
|
|
|
/** |
779
|
|
|
* Return the key value map of query hints that are currently set. |
780
|
|
|
* |
781
|
|
|
* @return mixed[] |
782
|
|
|
*/ |
783
|
136 |
|
public function getHints() : array |
784
|
|
|
{ |
785
|
136 |
|
return $this->hints; |
786
|
|
|
} |
787
|
|
|
|
788
|
|
|
/** |
789
|
|
|
* Executes the query and returns an IterableResult that can be used to incrementally |
790
|
|
|
* iterate over the result. |
791
|
|
|
* |
792
|
|
|
* @param ArrayCollection|array|Parameter[]|mixed[]|null $parameters The query parameters. |
793
|
|
|
* @param int|string|null $hydrationMode The hydration mode to use. |
794
|
|
|
*/ |
795
|
10 |
|
public function iterate(?iterable $parameters = null, $hydrationMode = null) : IterableResult |
796
|
|
|
{ |
797
|
10 |
|
if ($hydrationMode !== null) { |
798
|
10 |
|
$this->setHydrationMode($hydrationMode); |
799
|
|
|
} |
800
|
|
|
|
801
|
10 |
|
if (! empty($parameters)) { |
802
|
1 |
|
$this->setParameters($parameters); |
803
|
|
|
} |
804
|
|
|
|
805
|
10 |
|
$rsm = $this->getResultSetMapping(); |
806
|
7 |
|
$stmt = $this->doExecute(); |
807
|
|
|
|
808
|
7 |
|
return $this->em->newHydrator($this->hydrationMode)->iterate($stmt, $rsm, $this->hints); |
|
|
|
|
809
|
|
|
} |
810
|
|
|
|
811
|
|
|
/** |
812
|
|
|
* Executes the query. |
813
|
|
|
* |
814
|
|
|
* @param ArrayCollection|array|Parameter[]|mixed[]|null $parameters Query parameters. |
815
|
|
|
* @param int|string|null $hydrationMode Processing mode to be used during the hydration process. |
816
|
|
|
* |
817
|
|
|
* @return mixed |
818
|
|
|
*/ |
819
|
465 |
|
public function execute(?iterable $parameters = null, $hydrationMode = null) |
820
|
|
|
{ |
821
|
465 |
|
return $this->isCacheEnabled() |
822
|
29 |
|
? $this->executeUsingQueryCache($parameters, $hydrationMode) |
823
|
462 |
|
: $this->executeIgnoreQueryCache($parameters, $hydrationMode); |
824
|
|
|
} |
825
|
|
|
|
826
|
|
|
/** |
827
|
|
|
* Execute query ignoring second level cache. |
828
|
|
|
* |
829
|
|
|
* @param ArrayCollection|array|Parameter[]|mixed[]|null $parameters |
830
|
|
|
* @param int|string|null $hydrationMode |
831
|
|
|
* |
832
|
|
|
* @return mixed |
833
|
|
|
*/ |
834
|
465 |
|
private function executeIgnoreQueryCache(?iterable $parameters = null, $hydrationMode = null) |
835
|
|
|
{ |
836
|
465 |
|
if ($hydrationMode !== null) { |
837
|
362 |
|
$this->setHydrationMode($hydrationMode); |
838
|
|
|
} |
839
|
|
|
|
840
|
465 |
|
if (! empty($parameters)) { |
841
|
|
|
$this->setParameters($parameters); |
842
|
|
|
} |
843
|
|
|
|
844
|
465 |
|
$setCacheEntry = function () : void { |
845
|
465 |
|
}; |
846
|
|
|
|
847
|
465 |
|
if ($this->hydrationCacheProfile !== null) { |
848
|
2 |
|
list($cacheKey, $realCacheKey) = $this->getHydrationCacheId(); |
849
|
|
|
|
850
|
2 |
|
$queryCacheProfile = $this->getHydrationCacheProfile(); |
851
|
2 |
|
$cache = $queryCacheProfile->getResultCacheDriver(); |
852
|
2 |
|
$result = $cache->fetch($cacheKey); |
853
|
|
|
|
854
|
2 |
|
if (isset($result[$realCacheKey])) { |
855
|
2 |
|
return $result[$realCacheKey]; |
856
|
|
|
} |
857
|
|
|
|
858
|
2 |
|
if (! $result) { |
859
|
2 |
|
$result = []; |
860
|
|
|
} |
861
|
|
|
|
862
|
2 |
|
$setCacheEntry = function ($data) use ($cache, $result, $cacheKey, $realCacheKey, $queryCacheProfile) : void { |
863
|
2 |
|
$result[$realCacheKey] = $data; |
864
|
|
|
|
865
|
2 |
|
$cache->save($cacheKey, $result, $queryCacheProfile->getLifetime()); |
866
|
2 |
|
}; |
867
|
|
|
} |
868
|
|
|
|
869
|
465 |
|
$stmt = $this->doExecute(); |
870
|
|
|
|
871
|
455 |
|
if (is_numeric($stmt)) { |
872
|
27 |
|
$setCacheEntry($stmt); |
873
|
|
|
|
874
|
27 |
|
return $stmt; |
875
|
|
|
} |
876
|
|
|
|
877
|
440 |
|
$rsm = $this->getResultSetMapping(); |
878
|
440 |
|
$data = $this->em->newHydrator($this->hydrationMode)->hydrateAll($stmt, $rsm, $this->hints); |
879
|
|
|
|
880
|
436 |
|
$setCacheEntry($data); |
881
|
|
|
|
882
|
436 |
|
return $data; |
883
|
|
|
} |
884
|
|
|
|
885
|
|
|
/** |
886
|
|
|
* Load from second level cache or executes the query and put into cache. |
887
|
|
|
* |
888
|
|
|
* @param ArrayCollection|array|Parameter[]|mixed[]|null $parameters |
889
|
|
|
* @param int|string|null $hydrationMode |
890
|
|
|
* |
891
|
|
|
* @return mixed |
892
|
|
|
*/ |
893
|
29 |
|
private function executeUsingQueryCache(?iterable $parameters = null, $hydrationMode = null) |
894
|
|
|
{ |
895
|
29 |
|
$rsm = $this->getResultSetMapping(); |
896
|
29 |
|
$queryCache = $this->em->getCache()->getQueryCache($this->cacheRegion); |
897
|
29 |
|
$queryKey = new QueryCacheKey( |
898
|
29 |
|
$this->getHash(), |
899
|
29 |
|
$this->lifetime, |
900
|
29 |
|
$this->cacheMode ?: Cache::MODE_NORMAL, |
901
|
29 |
|
$this->getTimestampKey() |
902
|
|
|
); |
903
|
|
|
|
904
|
29 |
|
$result = $queryCache->get($queryKey, $rsm, $this->hints); |
905
|
|
|
|
906
|
29 |
|
if ($result !== null) { |
907
|
16 |
|
if ($this->cacheLogger) { |
908
|
16 |
|
$this->cacheLogger->queryCacheHit($queryCache->getRegion()->getName(), $queryKey); |
909
|
|
|
} |
910
|
|
|
|
911
|
16 |
|
return $result; |
912
|
|
|
} |
913
|
|
|
|
914
|
29 |
|
$result = $this->executeIgnoreQueryCache($parameters, $hydrationMode); |
915
|
29 |
|
$cached = $queryCache->put($queryKey, $rsm, $result, $this->hints); |
916
|
|
|
|
917
|
26 |
|
if ($this->cacheLogger) { |
918
|
26 |
|
$this->cacheLogger->queryCacheMiss($queryCache->getRegion()->getName(), $queryKey); |
919
|
|
|
|
920
|
26 |
|
if ($cached) { |
921
|
26 |
|
$this->cacheLogger->queryCachePut($queryCache->getRegion()->getName(), $queryKey); |
922
|
|
|
} |
923
|
|
|
} |
924
|
|
|
|
925
|
26 |
|
return $result; |
926
|
|
|
} |
927
|
|
|
|
928
|
29 |
|
private function getTimestampKey() : ?TimestampCacheKey |
929
|
|
|
{ |
930
|
29 |
|
$entityName = reset($this->resultSetMapping->aliasMap); |
931
|
|
|
|
932
|
29 |
|
if (empty($entityName)) { |
933
|
2 |
|
return null; |
934
|
|
|
} |
935
|
|
|
|
936
|
27 |
|
$metadata = $this->em->getClassMetadata($entityName); |
937
|
|
|
|
938
|
27 |
|
return new Cache\TimestampCacheKey($metadata->getRootClassName()); |
939
|
|
|
} |
940
|
|
|
|
941
|
|
|
/** |
942
|
|
|
* Get the result cache id to use to store the result set cache entry. |
943
|
|
|
* Will return the configured id if it exists otherwise a hash will be |
944
|
|
|
* automatically generated for you. |
945
|
|
|
* |
946
|
|
|
* @return string[] ($key, $hash) |
947
|
|
|
*/ |
948
|
2 |
|
protected function getHydrationCacheId() : array |
949
|
|
|
{ |
950
|
2 |
|
$parameters = []; |
951
|
|
|
|
952
|
2 |
|
foreach ($this->getParameters() as $parameter) { |
953
|
1 |
|
$parameters[$parameter->getName()] = $this->processParameterValue($parameter->getValue()); |
954
|
|
|
} |
955
|
|
|
|
956
|
2 |
|
$sql = $this->getSQL(); |
957
|
2 |
|
$queryCacheProfile = $this->getHydrationCacheProfile(); |
958
|
2 |
|
$hints = $this->getHints(); |
959
|
2 |
|
$hints['hydrationMode'] = $this->getHydrationMode(); |
960
|
|
|
|
961
|
2 |
|
ksort($hints); |
962
|
|
|
|
963
|
2 |
|
return $queryCacheProfile->generateCacheKeys($sql, $parameters, $hints); |
|
|
|
|
964
|
|
|
} |
965
|
|
|
|
966
|
|
|
/** |
967
|
|
|
* Set the result cache id to use to store the result set cache entry. |
968
|
|
|
* If this is not explicitly set by the developer then a hash is automatically |
969
|
|
|
* generated for you. |
970
|
|
|
* |
971
|
|
|
* @return static This query instance. |
972
|
|
|
*/ |
973
|
12 |
|
public function setResultCacheId(?string $id) : self |
974
|
|
|
{ |
975
|
12 |
|
$this->queryCacheProfile = $this->queryCacheProfile |
976
|
12 |
|
? $this->queryCacheProfile->setCacheKey($id) |
977
|
|
|
: new QueryCacheProfile(0, $id, $this->em->getConfiguration()->getResultCacheImpl()); |
978
|
|
|
|
979
|
12 |
|
return $this; |
980
|
|
|
} |
981
|
|
|
|
982
|
|
|
/** |
983
|
|
|
* Get the result cache id to use to store the result set cache entry if set. |
984
|
|
|
* |
985
|
|
|
* @deprecated |
986
|
|
|
*/ |
987
|
|
|
public function getResultCacheId() : ?string |
988
|
|
|
{ |
989
|
|
|
return $this->queryCacheProfile ? $this->queryCacheProfile->getCacheKey() : null; |
990
|
|
|
} |
991
|
|
|
|
992
|
|
|
/** |
993
|
|
|
* Executes the query and returns a the resulting Statement object. |
994
|
|
|
* |
995
|
|
|
* @return ResultStatement|int The executed database statement that holds the results. |
996
|
|
|
*/ |
997
|
|
|
abstract protected function doExecute(); |
998
|
|
|
|
999
|
|
|
/** |
1000
|
|
|
* Cleanup Query resource when clone is called. |
1001
|
|
|
*/ |
1002
|
139 |
|
public function __clone() |
1003
|
|
|
{ |
1004
|
139 |
|
$this->parameters = new ArrayCollection(); |
1005
|
139 |
|
$this->hints = $this->em->getConfiguration()->getDefaultQueryHints(); |
1006
|
139 |
|
} |
1007
|
|
|
|
1008
|
|
|
/** |
1009
|
|
|
* Generates a string of currently query to use for the cache second level cache. |
1010
|
|
|
*/ |
1011
|
29 |
|
protected function getHash() : string |
1012
|
|
|
{ |
1013
|
29 |
|
$query = $this->getSQL(); |
1014
|
29 |
|
$hints = $this->getHints(); |
1015
|
29 |
|
$params = array_map(function (Parameter $parameter) { |
1016
|
5 |
|
$value = $parameter->getValue(); |
1017
|
|
|
|
1018
|
|
|
// Small optimization |
1019
|
|
|
// Does not invoke processParameterValue for scalar values |
1020
|
5 |
|
if (is_scalar($value)) { |
1021
|
4 |
|
return $value; |
1022
|
|
|
} |
1023
|
|
|
|
1024
|
1 |
|
return $this->processParameterValue($value); |
1025
|
29 |
|
}, $this->parameters->getValues()); |
1026
|
|
|
|
1027
|
29 |
|
ksort($hints); |
1028
|
|
|
|
1029
|
29 |
|
return sha1($query . '-' . serialize($params) . '-' . serialize($hints)); |
|
|
|
|
1030
|
|
|
} |
1031
|
|
|
} |
1032
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.