These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | /* |
||
4 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||
5 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||
6 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||
7 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||
8 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||
9 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||
10 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||
11 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||
12 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||
13 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||
14 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||
15 | * |
||
16 | * This software consists of voluntary contributions made by many individuals |
||
17 | * and is licensed under the MIT license. For more information, see |
||
18 | * <http://www.doctrine-project.org>. |
||
19 | */ |
||
20 | |||
21 | namespace Doctrine\ORM\Cache; |
||
22 | |||
23 | use Doctrine\Common\Collections\ArrayCollection; |
||
24 | use Doctrine\ORM\Cache\Persister\CachedPersister; |
||
25 | use Doctrine\ORM\EntityManagerInterface; |
||
26 | use Doctrine\ORM\Query\ResultSetMapping; |
||
27 | use Doctrine\ORM\Mapping\ClassMetadata; |
||
28 | use Doctrine\ORM\PersistentCollection; |
||
29 | use Doctrine\Common\Proxy\Proxy; |
||
30 | use Doctrine\ORM\Cache; |
||
31 | use Doctrine\ORM\Query; |
||
32 | |||
33 | /** |
||
34 | * Default query cache implementation. |
||
35 | * |
||
36 | * @since 2.5 |
||
37 | * @author Fabio B. Silva <[email protected]> |
||
38 | */ |
||
39 | class DefaultQueryCache implements QueryCache |
||
40 | { |
||
41 | /** |
||
42 | * @var \Doctrine\ORM\EntityManagerInterface |
||
43 | */ |
||
44 | private $em; |
||
45 | |||
46 | /** |
||
47 | * @var \Doctrine\ORM\UnitOfWork |
||
48 | */ |
||
49 | private $uow; |
||
50 | |||
51 | /** |
||
52 | * @var \Doctrine\ORM\Cache\Region |
||
53 | */ |
||
54 | private $region; |
||
55 | |||
56 | /** |
||
57 | * @var \Doctrine\ORM\Cache\QueryCacheValidator |
||
58 | */ |
||
59 | private $validator; |
||
60 | |||
61 | /** |
||
62 | * @var \Doctrine\ORM\Cache\Logging\CacheLogger |
||
63 | */ |
||
64 | protected $cacheLogger; |
||
65 | |||
66 | /** |
||
67 | * @var array |
||
68 | */ |
||
69 | private static $hints = [Query::HINT_CACHE_ENABLED => true]; |
||
70 | |||
71 | /** |
||
72 | * @param \Doctrine\ORM\EntityManagerInterface $em The entity manager. |
||
73 | * @param \Doctrine\ORM\Cache\Region $region The query region. |
||
74 | */ |
||
75 | 84 | public function __construct(EntityManagerInterface $em, Region $region) |
|
76 | { |
||
77 | 84 | $cacheConfig = $em->getConfiguration()->getSecondLevelCacheConfiguration(); |
|
78 | |||
79 | 84 | $this->em = $em; |
|
80 | 84 | $this->region = $region; |
|
81 | 84 | $this->uow = $em->getUnitOfWork(); |
|
82 | 84 | $this->cacheLogger = $cacheConfig->getCacheLogger(); |
|
83 | 84 | $this->validator = $cacheConfig->getQueryValidator(); |
|
84 | 84 | } |
|
85 | |||
86 | /** |
||
87 | * {@inheritdoc} |
||
88 | */ |
||
89 | 50 | public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = []) |
|
90 | { |
||
91 | 50 | if ( ! ($key->cacheMode & Cache::MODE_GET)) { |
|
92 | 3 | return null; |
|
93 | } |
||
94 | |||
95 | 49 | $entry = $this->region->get($key); |
|
96 | |||
97 | 49 | if ( ! $entry instanceof QueryCacheEntry) { |
|
98 | 46 | return null; |
|
99 | } |
||
100 | |||
101 | 36 | if ( ! $this->validator->isValid($key, $entry)) { |
|
102 | 8 | $this->region->evict($key); |
|
103 | |||
104 | 8 | return null; |
|
105 | } |
||
106 | |||
107 | 31 | $result = []; |
|
108 | 31 | $entityName = reset($rsm->aliasMap); |
|
109 | 31 | $hasRelation = ( ! empty($rsm->relationMap)); |
|
110 | 31 | $persister = $this->uow->getEntityPersister($entityName); |
|
111 | 31 | $region = $persister->getCacheRegion(); |
|
112 | 31 | $regionName = $region->getName(); |
|
113 | |||
114 | 31 | $cm = $this->em->getClassMetadata($entityName); |
|
115 | |||
116 | 31 | $generateKeys = function (array $entry) use ($cm): EntityCacheKey { |
|
117 | 31 | return new EntityCacheKey($cm->rootEntityName, $entry['identifier']); |
|
0 ignored issues
–
show
|
|||
118 | 31 | }; |
|
119 | |||
120 | 31 | $cacheKeys = new CollectionCacheEntry(array_map($generateKeys, $entry->result)); |
|
121 | 31 | $entries = $region->getMultiple($cacheKeys); |
|
122 | |||
123 | // @TODO - move to cache hydration component |
||
124 | 31 | foreach ($entry->result as $index => $entry) { |
|
125 | |||
126 | 31 | $entityEntry = is_array($entries) && array_key_exists($index, $entries) ? $entries[$index] : null; |
|
127 | |||
128 | 31 | if ($entityEntry === null) { |
|
129 | |||
130 | 2 | if ($this->cacheLogger !== null) { |
|
131 | 1 | $this->cacheLogger->entityCacheMiss($regionName, $cacheKeys->identifiers[$index]); |
|
132 | } |
||
133 | |||
134 | 2 | return null; |
|
135 | } |
||
136 | |||
137 | 29 | if ($this->cacheLogger !== null) { |
|
138 | 28 | $this->cacheLogger->entityCacheHit($regionName, $cacheKeys->identifiers[$index]); |
|
139 | } |
||
140 | |||
141 | 29 | if ( ! $hasRelation) { |
|
142 | |||
143 | 21 | $result[$index] = $this->uow->createEntity($entityEntry->class, $entityEntry->resolveAssociationEntries($this->em), self::$hints); |
|
144 | |||
145 | 21 | continue; |
|
146 | } |
||
147 | |||
148 | 8 | $data = $entityEntry->data; |
|
149 | |||
150 | 8 | foreach ($entry['associations'] as $name => $assoc) { |
|
151 | 8 | $assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']); |
|
152 | 8 | $assocRegion = $assocPersister->getCacheRegion(); |
|
153 | 8 | $assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']); |
|
154 | |||
155 | 8 | if ($assoc['type'] & ClassMetadata::TO_ONE) { |
|
156 | |||
157 | 4 | if (($assocEntry = $assocRegion->get($assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assoc['identifier']))) === null) { |
|
0 ignored issues
–
show
Accessing
rootEntityName on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
If you access a property on an interface, you most likely code against a concrete implementation of the interface. Available Fixes
![]() |
|||
158 | |||
159 | 1 | if ($this->cacheLogger !== null) { |
|
160 | 1 | $this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey); |
|
161 | } |
||
162 | |||
163 | 1 | $this->uow->hydrationComplete(); |
|
164 | |||
165 | 1 | return null; |
|
166 | } |
||
167 | |||
168 | 3 | $data[$name] = $this->uow->createEntity($assocEntry->class, $assocEntry->resolveAssociationEntries($this->em), self::$hints); |
|
169 | |||
170 | 3 | if ($this->cacheLogger !== null) { |
|
171 | 3 | $this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKey); |
|
172 | } |
||
173 | |||
174 | 3 | continue; |
|
175 | } |
||
176 | |||
177 | 4 | if ( ! isset($assoc['list']) || empty($assoc['list'])) { |
|
178 | continue; |
||
179 | } |
||
180 | |||
181 | 4 | $collection = new PersistentCollection($this->em, $assocMetadata, new ArrayCollection()); |
|
182 | |||
183 | 4 | foreach ($assoc['list'] as $assocIndex => $assocId) { |
|
184 | |||
185 | 4 | if (($assocEntry = $assocRegion->get($assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocId))) === null) { |
|
0 ignored issues
–
show
Accessing
rootEntityName on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
If you access a property on an interface, you most likely code against a concrete implementation of the interface. Available Fixes
![]() |
|||
186 | |||
187 | 1 | if ($this->cacheLogger !== null) { |
|
188 | 1 | $this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey); |
|
189 | } |
||
190 | |||
191 | 1 | $this->uow->hydrationComplete(); |
|
192 | |||
193 | 1 | return null; |
|
194 | } |
||
195 | |||
196 | 3 | $element = $this->uow->createEntity($assocEntry->class, $assocEntry->resolveAssociationEntries($this->em), self::$hints); |
|
197 | |||
198 | 3 | $collection->hydrateSet($assocIndex, $element); |
|
199 | |||
200 | 3 | if ($this->cacheLogger !== null) { |
|
201 | 3 | $this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKey); |
|
202 | } |
||
203 | } |
||
204 | |||
205 | 3 | $data[$name] = $collection; |
|
206 | |||
207 | 3 | $collection->setInitialized(true); |
|
208 | } |
||
209 | |||
210 | 6 | $result[$index] = $this->uow->createEntity($entityEntry->class, $data, self::$hints); |
|
211 | } |
||
212 | |||
213 | 27 | $this->uow->hydrationComplete(); |
|
214 | |||
215 | 27 | return $result; |
|
216 | } |
||
217 | |||
218 | /** |
||
219 | * {@inheritdoc} |
||
220 | */ |
||
221 | 57 | public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = []) |
|
222 | { |
||
223 | 57 | if ($rsm->scalarMappings) { |
|
224 | 1 | throw new CacheException("Second level cache does not support scalar results."); |
|
225 | } |
||
226 | |||
227 | 56 | if (count($rsm->entityMappings) > 1) { |
|
228 | 1 | throw new CacheException("Second level cache does not support multiple root entities."); |
|
229 | } |
||
230 | |||
231 | 55 | if ( ! $rsm->isSelect) { |
|
232 | 2 | throw new CacheException("Second-level cache query supports only select statements."); |
|
233 | } |
||
234 | |||
235 | 53 | if (isset($hints[Query::HINT_FORCE_PARTIAL_LOAD]) && $hints[Query::HINT_FORCE_PARTIAL_LOAD]) { |
|
236 | 1 | throw new CacheException("Second level cache does not support partial entities."); |
|
237 | } |
||
238 | |||
239 | 52 | if ( ! ($key->cacheMode & Cache::MODE_PUT)) { |
|
240 | 3 | return false; |
|
241 | } |
||
242 | |||
243 | 51 | $data = []; |
|
244 | 51 | $entityName = reset($rsm->aliasMap); |
|
245 | 51 | $rootAlias = key($rsm->aliasMap); |
|
246 | 51 | $hasRelation = ( ! empty($rsm->relationMap)); |
|
247 | 51 | $persister = $this->uow->getEntityPersister($entityName); |
|
248 | |||
249 | 51 | if ( ! ($persister instanceof CachedPersister)) { |
|
250 | 1 | throw CacheException::nonCacheableEntity($entityName); |
|
251 | } |
||
252 | |||
253 | 50 | $region = $persister->getCacheRegion(); |
|
254 | |||
255 | 50 | foreach ($result as $index => $entity) { |
|
256 | 50 | $identifier = $this->uow->getEntityIdentifier($entity); |
|
257 | 50 | $entityKey = new EntityCacheKey($entityName, $identifier); |
|
258 | 50 | $data[$index]['identifier'] = $identifier; |
|
259 | 50 | $data[$index]['associations'] = []; |
|
260 | |||
261 | 50 | if (($key->cacheMode & Cache::MODE_REFRESH) || ! $region->contains($entityKey)) { |
|
262 | // Cancel put result if entity put fail |
||
263 | 36 | if ( ! $persister->storeEntityCache($entity, $entityKey)) { |
|
264 | 1 | return false; |
|
265 | } |
||
266 | } |
||
267 | |||
268 | 49 | if ( ! $hasRelation) { |
|
269 | 35 | continue; |
|
270 | } |
||
271 | |||
272 | // @TODO - move to cache hydration components |
||
273 | 14 | foreach ($rsm->relationMap as $alias => $name) { |
|
274 | 14 | $parentAlias = $rsm->parentAliasMap[$alias]; |
|
275 | 14 | $parentClass = $rsm->aliasMap[$parentAlias]; |
|
276 | 14 | $metadata = $this->em->getClassMetadata($parentClass); |
|
277 | 14 | $assoc = $metadata->associationMappings[$name]; |
|
0 ignored issues
–
show
Accessing
associationMappings on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
If you access a property on an interface, you most likely code against a concrete implementation of the interface. Available Fixes
![]() |
|||
278 | 14 | $assocValue = $this->getAssociationValue($rsm, $alias, $entity); |
|
279 | |||
280 | 14 | if ($assocValue === null) { |
|
281 | 1 | continue; |
|
282 | } |
||
283 | |||
284 | // root entity association |
||
285 | 13 | if ($rootAlias === $parentAlias) { |
|
286 | // Cancel put result if association put fail |
||
287 | 13 | if ( ($assocInfo = $this->storeAssociationCache($key, $assoc, $assocValue)) === null) { |
|
288 | 2 | return false; |
|
289 | } |
||
290 | |||
291 | 11 | $data[$index]['associations'][$name] = $assocInfo; |
|
292 | |||
293 | 11 | continue; |
|
294 | } |
||
295 | |||
296 | // store single nested association |
||
297 | 2 | if ( ! is_array($assocValue)) { |
|
298 | // Cancel put result if association put fail |
||
299 | 1 | if ($this->storeAssociationCache($key, $assoc, $assocValue) === null) { |
|
300 | return false; |
||
301 | } |
||
302 | |||
303 | 1 | continue; |
|
304 | } |
||
305 | |||
306 | // store array of nested association |
||
307 | 1 | foreach ($assocValue as $aVal) { |
|
308 | // Cancel put result if association put fail |
||
309 | 1 | if ($this->storeAssociationCache($key, $assoc, $aVal) === null) { |
|
310 | 12 | return false; |
|
311 | } |
||
312 | } |
||
313 | } |
||
314 | } |
||
315 | |||
316 | 47 | return $this->region->put($key, new QueryCacheEntry($data)); |
|
317 | } |
||
318 | |||
319 | /** |
||
320 | * @param \Doctrine\ORM\Cache\QueryCacheKey $key |
||
321 | * @param array $assoc |
||
322 | * @param mixed $assocValue |
||
323 | * |
||
324 | * @return array|null |
||
325 | */ |
||
326 | 13 | private function storeAssociationCache(QueryCacheKey $key, array $assoc, $assocValue) |
|
327 | { |
||
328 | 13 | $assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']); |
|
329 | 13 | $assocMetadata = $assocPersister->getClassMetadata(); |
|
330 | 13 | $assocRegion = $assocPersister->getCacheRegion(); |
|
331 | |||
332 | // Handle *-to-one associations |
||
333 | 13 | if ($assoc['type'] & ClassMetadata::TO_ONE) { |
|
334 | 7 | $assocIdentifier = $this->uow->getEntityIdentifier($assocValue); |
|
335 | 7 | $entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier); |
|
336 | |||
337 | 7 | if ( ! $assocValue instanceof Proxy && ($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) { |
|
338 | // Entity put fail |
||
339 | 7 | if ( ! $assocPersister->storeEntityCache($assocValue, $entityKey)) { |
|
340 | 1 | return null; |
|
341 | } |
||
342 | } |
||
343 | |||
344 | return [ |
||
345 | 6 | 'targetEntity' => $assocMetadata->rootEntityName, |
|
346 | 6 | 'identifier' => $assocIdentifier, |
|
347 | 6 | 'type' => $assoc['type'] |
|
348 | ]; |
||
349 | } |
||
350 | |||
351 | // Handle *-to-many associations |
||
352 | 6 | $list = []; |
|
353 | |||
354 | 6 | foreach ($assocValue as $assocItemIndex => $assocItem) { |
|
355 | 6 | $assocIdentifier = $this->uow->getEntityIdentifier($assocItem); |
|
356 | 6 | $entityKey = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier); |
|
357 | |||
358 | 6 | if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) { |
|
359 | // Entity put fail |
||
360 | 6 | if ( ! $assocPersister->storeEntityCache($assocItem, $entityKey)) { |
|
361 | 1 | return null; |
|
362 | } |
||
363 | } |
||
364 | |||
365 | 5 | $list[$assocItemIndex] = $assocIdentifier; |
|
366 | } |
||
367 | |||
368 | return [ |
||
369 | 5 | 'targetEntity' => $assocMetadata->rootEntityName, |
|
370 | 5 | 'type' => $assoc['type'], |
|
371 | 5 | 'list' => $list, |
|
372 | ]; |
||
373 | } |
||
374 | |||
375 | /** |
||
376 | * @param \Doctrine\ORM\Query\ResultSetMapping $rsm |
||
377 | * @param string $assocAlias |
||
378 | * @param object $entity |
||
379 | * |
||
380 | * @return array|object |
||
381 | */ |
||
382 | 15 | private function getAssociationValue(ResultSetMapping $rsm, $assocAlias, $entity) |
|
383 | { |
||
384 | 15 | $path = []; |
|
385 | 15 | $alias = $assocAlias; |
|
386 | |||
387 | 15 | while (isset($rsm->parentAliasMap[$alias])) { |
|
388 | 15 | $parent = $rsm->parentAliasMap[$alias]; |
|
389 | 15 | $field = $rsm->relationMap[$alias]; |
|
390 | 15 | $class = $rsm->aliasMap[$parent]; |
|
391 | |||
392 | 15 | array_unshift($path, [ |
|
393 | 15 | 'field' => $field, |
|
394 | 15 | 'class' => $class |
|
395 | ] |
||
396 | ); |
||
397 | |||
398 | 15 | $alias = $parent; |
|
399 | } |
||
400 | |||
401 | 15 | return $this->getAssociationPathValue($entity, $path); |
|
402 | } |
||
403 | |||
404 | /** |
||
405 | * @param mixed $value |
||
406 | * @param array $path |
||
407 | * |
||
408 | * @return array|object|null |
||
409 | */ |
||
410 | 15 | private function getAssociationPathValue($value, array $path) |
|
411 | { |
||
412 | 15 | $mapping = array_shift($path); |
|
413 | 15 | $metadata = $this->em->getClassMetadata($mapping['class']); |
|
414 | 15 | $assoc = $metadata->associationMappings[$mapping['field']]; |
|
0 ignored issues
–
show
Accessing
associationMappings on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
If you access a property on an interface, you most likely code against a concrete implementation of the interface. Available Fixes
![]() |
|||
415 | 15 | $value = $metadata->getFieldValue($value, $mapping['field']); |
|
416 | |||
417 | 15 | if ($value === null) { |
|
418 | 1 | return null; |
|
419 | } |
||
420 | |||
421 | 14 | if (empty($path)) { |
|
422 | 14 | return $value; |
|
423 | } |
||
424 | |||
425 | // Handle *-to-one associations |
||
426 | 3 | if ($assoc['type'] & ClassMetadata::TO_ONE) { |
|
427 | 1 | return $this->getAssociationPathValue($value, $path); |
|
428 | } |
||
429 | |||
430 | 2 | $values = []; |
|
431 | |||
432 | 2 | foreach ($value as $item) { |
|
433 | 2 | $values[] = $this->getAssociationPathValue($item, $path); |
|
434 | } |
||
435 | |||
436 | 2 | return $values; |
|
437 | } |
||
438 | |||
439 | /** |
||
440 | * {@inheritdoc} |
||
441 | */ |
||
442 | 48 | public function clear() |
|
443 | { |
||
444 | 48 | return $this->region->evictAll(); |
|
445 | } |
||
446 | |||
447 | /** |
||
448 | * {@inheritdoc} |
||
449 | */ |
||
450 | 28 | public function getRegion() |
|
451 | { |
||
452 | 28 | return $this->region; |
|
453 | } |
||
454 | } |
||
455 |
If you access a property on an interface, you most likely code against a concrete implementation of the interface.
Available Fixes
Adding an additional type check:
Changing the type hint: