1 | <?php |
||||||
2 | |||||||
3 | declare(strict_types=1); |
||||||
4 | |||||||
5 | namespace Doctrine\ORM\Cache; |
||||||
6 | |||||||
7 | use Doctrine\ORM\Cache; |
||||||
8 | use Doctrine\ORM\Cache\Persister\CachedPersister; |
||||||
9 | use Doctrine\ORM\EntityManagerInterface; |
||||||
10 | use Doctrine\ORM\Mapping\AssociationMetadata; |
||||||
11 | use Doctrine\ORM\Mapping\ToOneAssociationMetadata; |
||||||
12 | use Doctrine\ORM\Query; |
||||||
13 | use Doctrine\ORM\Query\ResultSetMapping; |
||||||
14 | use ProxyManager\Proxy\GhostObjectInterface; |
||||||
15 | |||||||
16 | /** |
||||||
17 | * Default query cache implementation. |
||||||
18 | */ |
||||||
19 | class DefaultQueryCache implements QueryCache |
||||||
20 | { |
||||||
21 | /** |
||||||
22 | * @var \Doctrine\ORM\EntityManagerInterface |
||||||
23 | */ |
||||||
24 | private $em; |
||||||
25 | |||||||
26 | /** |
||||||
27 | * @var \Doctrine\ORM\Cache\Region |
||||||
28 | */ |
||||||
29 | private $region; |
||||||
30 | |||||||
31 | /** |
||||||
32 | * @var \Doctrine\ORM\Cache\QueryCacheValidator |
||||||
33 | */ |
||||||
34 | private $validator; |
||||||
35 | |||||||
36 | /** |
||||||
37 | * @var \Doctrine\ORM\Cache\Logging\CacheLogger |
||||||
38 | */ |
||||||
39 | protected $cacheLogger; |
||||||
40 | |||||||
41 | /** |
||||||
42 | * @var mixed[] |
||||||
43 | */ |
||||||
44 | private static $hints = [Query::HINT_CACHE_ENABLED => true]; |
||||||
45 | |||||||
46 | /** |
||||||
47 | * @param \Doctrine\ORM\EntityManagerInterface $em The entity manager. |
||||||
48 | * @param \Doctrine\ORM\Cache\Region $region The query region. |
||||||
49 | */ |
||||||
50 | 86 | public function __construct(EntityManagerInterface $em, Region $region) |
|||||
51 | { |
||||||
52 | 86 | $cacheConfig = $em->getConfiguration()->getSecondLevelCacheConfiguration(); |
|||||
53 | |||||||
54 | 86 | $this->em = $em; |
|||||
55 | 86 | $this->region = $region; |
|||||
56 | 86 | $this->cacheLogger = $cacheConfig->getCacheLogger(); |
|||||
57 | 86 | $this->validator = $cacheConfig->getQueryValidator(); |
|||||
58 | 86 | } |
|||||
59 | |||||||
60 | /** |
||||||
61 | * {@inheritdoc} |
||||||
62 | */ |
||||||
63 | 52 | public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = []) |
|||||
64 | { |
||||||
65 | 52 | if (! ($key->cacheMode & Cache::MODE_GET)) { |
|||||
66 | 3 | return null; |
|||||
67 | } |
||||||
68 | |||||||
69 | 51 | $cacheEntry = $this->region->get($key); |
|||||
70 | |||||||
71 | 51 | if (! $cacheEntry instanceof QueryCacheEntry) { |
|||||
72 | 47 | return null; |
|||||
73 | } |
||||||
74 | |||||||
75 | 38 | if (! $this->validator->isValid($key, $cacheEntry)) { |
|||||
76 | 8 | $this->region->evict($key); |
|||||
77 | |||||||
78 | 8 | return null; |
|||||
79 | } |
||||||
80 | |||||||
81 | 33 | $result = []; |
|||||
82 | 33 | $entityName = reset($rsm->aliasMap); |
|||||
83 | 33 | $hasRelation = ( ! empty($rsm->relationMap)); |
|||||
84 | 33 | $unitOfWork = $this->em->getUnitOfWork(); |
|||||
85 | 33 | $persister = $unitOfWork->getEntityPersister($entityName); |
|||||
86 | 33 | $region = $persister->getCacheRegion(); |
|||||
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||||
87 | 33 | $regionName = $region->getName(); |
|||||
88 | |||||||
89 | 33 | $cm = $this->em->getClassMetadata($entityName); |
|||||
90 | |||||||
91 | 33 | $generateKeys = function (array $entry) use ($cm) : EntityCacheKey { |
|||||
92 | 33 | return new EntityCacheKey($cm->getRootClassName(), $entry['identifier']); |
|||||
0 ignored issues
–
show
The method
getRootClassName() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed.
Loading history...
|
|||||||
93 | 33 | }; |
|||||
94 | |||||||
95 | 33 | $cacheKeys = new CollectionCacheEntry(array_map($generateKeys, $cacheEntry->result)); |
|||||
96 | 33 | $entries = $region->getMultiple($cacheKeys); |
|||||
97 | |||||||
98 | // @TODO - move to cache hydration component |
||||||
99 | 33 | foreach ($cacheEntry->result as $index => $entry) { |
|||||
100 | 33 | $entityEntry = is_array($entries) ? ($entries[$index] ?? null) : null; |
|||||
101 | |||||||
102 | 33 | if ($entityEntry === null) { |
|||||
103 | 2 | if ($this->cacheLogger !== null) { |
|||||
104 | 1 | $this->cacheLogger->entityCacheMiss($regionName, $cacheKeys->identifiers[$index]); |
|||||
105 | } |
||||||
106 | |||||||
107 | 2 | return null; |
|||||
108 | } |
||||||
109 | |||||||
110 | 31 | if ($this->cacheLogger !== null) { |
|||||
111 | 29 | $this->cacheLogger->entityCacheHit($regionName, $cacheKeys->identifiers[$index]); |
|||||
112 | } |
||||||
113 | |||||||
114 | 31 | if (! $hasRelation) { |
|||||
115 | 22 | $result[$index] = $unitOfWork->createEntity( |
|||||
116 | 22 | $entityEntry->class, |
|||||
117 | 22 | $entityEntry->resolveAssociationEntries($this->em), |
|||||
118 | 22 | self::$hints |
|||||
119 | ); |
||||||
120 | |||||||
121 | 22 | continue; |
|||||
122 | } |
||||||
123 | |||||||
124 | 9 | $data = $entityEntry->data; |
|||||
125 | |||||||
126 | 9 | foreach ($entry['associations'] as $name => $assoc) { |
|||||
127 | 9 | $assocPersister = $unitOfWork->getEntityPersister($assoc['targetEntity']); |
|||||
128 | 9 | $assocRegion = $assocPersister->getCacheRegion(); |
|||||
129 | 9 | $assocMetadata = $this->em->getClassMetadata($assoc['targetEntity']); |
|||||
130 | |||||||
131 | // *-to-one association |
||||||
132 | 9 | if (isset($assoc['identifier'])) { |
|||||
133 | 5 | $assocKey = new EntityCacheKey($assocMetadata->getRootClassName(), $assoc['identifier']); |
|||||
134 | |||||||
135 | 5 | $assocEntry = $assocRegion->get($assocKey); |
|||||
136 | 5 | if ($assocEntry === null) { |
|||||
137 | 1 | if ($this->cacheLogger !== null) { |
|||||
138 | 1 | $this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey); |
|||||
139 | } |
||||||
140 | |||||||
141 | 1 | $unitOfWork->hydrationComplete(); |
|||||
142 | |||||||
143 | 1 | return null; |
|||||
144 | } |
||||||
145 | |||||||
146 | 4 | $data[$name] = $unitOfWork->createEntity( |
|||||
147 | 4 | $assocEntry->class, |
|||||
148 | 4 | $assocEntry->resolveAssociationEntries($this->em), |
|||||
149 | 4 | self::$hints |
|||||
150 | ); |
||||||
151 | |||||||
152 | 4 | if ($this->cacheLogger !== null) { |
|||||
153 | 4 | $this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKey); |
|||||
154 | } |
||||||
155 | |||||||
156 | 4 | continue; |
|||||
157 | } |
||||||
158 | |||||||
159 | 4 | if (! isset($assoc['list']) || empty($assoc['list'])) { |
|||||
160 | continue; |
||||||
161 | } |
||||||
162 | |||||||
163 | 4 | $generateKeys = function ($id) use ($assocMetadata) : EntityCacheKey { |
|||||
164 | 4 | return new EntityCacheKey($assocMetadata->getRootClassName(), $id); |
|||||
165 | 4 | }; |
|||||
166 | |||||||
167 | 4 | $assocKeys = new CollectionCacheEntry(array_map($generateKeys, $assoc['list'])); |
|||||
168 | 4 | $assocEntries = $assocRegion->getMultiple($assocKeys); |
|||||
169 | |||||||
170 | // *-to-many association |
||||||
171 | 4 | $collection = []; |
|||||
172 | |||||||
173 | 4 | foreach ($assoc['list'] as $assocIndex => $assocId) { |
|||||
174 | 4 | $assocEntry = is_array($assocEntries) ? ($assocEntries[$assocIndex] ?? null) : null; |
|||||
175 | |||||||
176 | 4 | if ($assocEntry === null) { |
|||||
177 | 1 | if ($this->cacheLogger !== null) { |
|||||
178 | 1 | $this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKeys->identifiers[$assocIndex]); |
|||||
179 | } |
||||||
180 | |||||||
181 | 1 | $unitOfWork->hydrationComplete(); |
|||||
182 | |||||||
183 | 1 | return null; |
|||||
184 | } |
||||||
185 | |||||||
186 | 3 | $collection[$assocIndex] = $unitOfWork->createEntity( |
|||||
187 | 3 | $assocEntry->class, |
|||||
188 | 3 | $assocEntry->resolveAssociationEntries($this->em), |
|||||
189 | 3 | self::$hints |
|||||
190 | ); |
||||||
191 | |||||||
192 | 3 | if ($this->cacheLogger !== null) { |
|||||
193 | 3 | $this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKeys->identifiers[$assocIndex]); |
|||||
194 | } |
||||||
195 | } |
||||||
196 | |||||||
197 | 3 | $data[$name] = $collection; |
|||||
198 | } |
||||||
199 | |||||||
200 | 7 | foreach ($data as $fieldName => $unCachedAssociationData) { |
|||||
201 | // In some scenarios, such as EAGER+ASSOCIATION+ID+CACHE, the |
||||||
202 | // cache key information in `$cacheEntry` will not contain details |
||||||
203 | // for fields that are associations. |
||||||
204 | // |
||||||
205 | // This means that `$data` keys for some associations that may |
||||||
206 | // actually not be cached will not be converted to actual association |
||||||
207 | // data, yet they contain L2 cache AssociationCacheEntry objects. |
||||||
208 | // |
||||||
209 | // We need to unwrap those associations into proxy references, |
||||||
210 | // since we don't have actual data for them except for identifiers. |
||||||
211 | 7 | if ($unCachedAssociationData instanceof AssociationCacheEntry) { |
|||||
212 | 4 | $data[$fieldName] = $this->em->getReference( |
|||||
213 | 4 | $unCachedAssociationData->class, |
|||||
214 | 7 | $unCachedAssociationData->identifier |
|||||
215 | ); |
||||||
216 | } |
||||||
217 | } |
||||||
218 | |||||||
219 | 7 | $result[$index] = $unitOfWork->createEntity($entityEntry->class, $data, self::$hints); |
|||||
220 | } |
||||||
221 | |||||||
222 | 29 | $unitOfWork->hydrationComplete(); |
|||||
223 | |||||||
224 | 29 | return $result; |
|||||
225 | } |
||||||
226 | |||||||
227 | /** |
||||||
228 | * {@inheritdoc} |
||||||
229 | * @param mixed[] $hints |
||||||
230 | */ |
||||||
231 | 58 | public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = []) |
|||||
232 | { |
||||||
233 | 58 | if ($rsm->scalarMappings) { |
|||||
0 ignored issues
–
show
The expression
$rsm->scalarMappings of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using
Loading history...
|
|||||||
234 | 1 | throw new CacheException('Second level cache does not support scalar results.'); |
|||||
235 | } |
||||||
236 | |||||||
237 | 57 | if (count($rsm->entityMappings) > 1) { |
|||||
238 | 1 | throw new CacheException('Second level cache does not support multiple root entities.'); |
|||||
239 | } |
||||||
240 | |||||||
241 | 56 | if (! $rsm->isSelect) { |
|||||
242 | 2 | throw new CacheException('Second-level cache query supports only select statements.'); |
|||||
243 | } |
||||||
244 | |||||||
245 | 54 | if (isset($hints[Query::HINT_FORCE_PARTIAL_LOAD]) && $hints[Query::HINT_FORCE_PARTIAL_LOAD]) { |
|||||
246 | 1 | throw new CacheException('Second level cache does not support partial entities.'); |
|||||
247 | } |
||||||
248 | |||||||
249 | 53 | if (! ($key->cacheMode & Cache::MODE_PUT)) { |
|||||
250 | 3 | return false; |
|||||
251 | } |
||||||
252 | |||||||
253 | 52 | $data = []; |
|||||
254 | 52 | $entityName = reset($rsm->aliasMap); |
|||||
255 | 52 | $rootAlias = key($rsm->aliasMap); |
|||||
256 | 52 | $unitOfWork = $this->em->getUnitOfWork(); |
|||||
257 | 52 | $persister = $unitOfWork->getEntityPersister($entityName); |
|||||
258 | |||||||
259 | 52 | if (! ($persister instanceof CachedPersister)) { |
|||||
260 | 1 | throw CacheException::nonCacheableEntity($entityName); |
|||||
261 | } |
||||||
262 | |||||||
263 | 51 | $region = $persister->getCacheRegion(); |
|||||
264 | |||||||
265 | 51 | foreach ($result as $index => $entity) { |
|||||
266 | 51 | $identifier = $unitOfWork->getEntityIdentifier($entity); |
|||||
267 | 51 | $entityKey = new EntityCacheKey($entityName, $identifier); |
|||||
268 | |||||||
269 | 51 | if (($key->cacheMode & Cache::MODE_REFRESH) || ! $region->contains($entityKey)) { |
|||||
270 | // Cancel put result if entity put fail |
||||||
271 | 36 | if (! $persister->storeEntityCache($entity, $entityKey)) { |
|||||
0 ignored issues
–
show
The method
storeEntityCache() does not exist on Doctrine\ORM\Persisters\Entity\EntityPersister . It seems like you code against a sub-type of Doctrine\ORM\Persisters\Entity\EntityPersister such as Doctrine\ORM\Cache\Persi...y\CachedEntityPersister .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
The method
storeEntityCache() does not exist on Doctrine\ORM\Cache\Persister\CachedPersister . It seems like you code against a sub-type of Doctrine\ORM\Cache\Persister\CachedPersister such as Doctrine\ORM\Cache\Persi...y\CachedEntityPersister .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
272 | 1 | return false; |
|||||
273 | } |
||||||
274 | } |
||||||
275 | |||||||
276 | 50 | $data[$index]['identifier'] = $identifier; |
|||||
277 | 50 | $data[$index]['associations'] = []; |
|||||
278 | |||||||
279 | // @TODO - move to cache hydration components |
||||||
280 | 50 | foreach ($rsm->relationMap as $alias => $name) { |
|||||
281 | 15 | $parentAlias = $rsm->parentAliasMap[$alias]; |
|||||
282 | 15 | $parentClass = $rsm->aliasMap[$parentAlias]; |
|||||
283 | 15 | $metadata = $this->em->getClassMetadata($parentClass); |
|||||
284 | 15 | $association = $metadata->getProperty($name); |
|||||
0 ignored issues
–
show
The method
getProperty() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed.
Loading history...
|
|||||||
285 | 15 | $assocValue = $this->getAssociationValue($rsm, $alias, $entity); |
|||||
286 | |||||||
287 | 15 | if ($assocValue === null) { |
|||||
288 | 1 | continue; |
|||||
289 | } |
||||||
290 | |||||||
291 | // root entity association |
||||||
292 | 14 | if ($rootAlias === $parentAlias) { |
|||||
293 | // Cancel put result if association put fail |
||||||
294 | 14 | $assocInfo = $this->storeAssociationCache($key, $association, $assocValue); |
|||||
0 ignored issues
–
show
It seems like
$assocValue can also be of type object ; however, parameter $assocValue of Doctrine\ORM\Cache\Defau...storeAssociationCache() does only seem to accept array<mixed,mixed> , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
295 | 14 | if ($assocInfo === null) { |
|||||
296 | 2 | return false; |
|||||
297 | } |
||||||
298 | |||||||
299 | 12 | $data[$index]['associations'][$name] = $assocInfo; |
|||||
300 | |||||||
301 | 12 | continue; |
|||||
302 | } |
||||||
303 | |||||||
304 | // store single nested association |
||||||
305 | 2 | if (! is_array($assocValue)) { |
|||||
306 | // Cancel put result if association put fail |
||||||
307 | 1 | if ($this->storeAssociationCache($key, $association, $assocValue) === null) { |
|||||
308 | return false; |
||||||
309 | } |
||||||
310 | |||||||
311 | 1 | continue; |
|||||
312 | } |
||||||
313 | |||||||
314 | // store array of nested association |
||||||
315 | 1 | foreach ($assocValue as $aVal) { |
|||||
316 | // Cancel put result if association put fail |
||||||
317 | 1 | if ($this->storeAssociationCache($key, $association, $aVal) === null) { |
|||||
318 | 48 | return false; |
|||||
319 | } |
||||||
320 | } |
||||||
321 | } |
||||||
322 | } |
||||||
323 | |||||||
324 | 48 | return $this->region->put($key, new QueryCacheEntry($data)); |
|||||
325 | } |
||||||
326 | |||||||
327 | /** |
||||||
328 | * @param AssociationMetadata $assoc |
||||||
329 | * @param mixed[] $assocValue |
||||||
330 | * |
||||||
331 | * @return mixed[]|null |
||||||
332 | */ |
||||||
333 | 14 | private function storeAssociationCache(QueryCacheKey $key, AssociationMetadata $association, $assocValue) |
|||||
334 | { |
||||||
335 | 14 | $unitOfWork = $this->em->getUnitOfWork(); |
|||||
336 | 14 | $assocPersister = $unitOfWork->getEntityPersister($association->getTargetEntity()); |
|||||
337 | 14 | $assocMetadata = $assocPersister->getClassMetadata(); |
|||||
338 | 14 | $assocRegion = $assocPersister->getCacheRegion(); |
|||||
339 | |||||||
340 | // Handle *-to-one associations |
||||||
341 | 14 | if ($association instanceof ToOneAssociationMetadata) { |
|||||
342 | 8 | $assocIdentifier = $unitOfWork->getEntityIdentifier($assocValue); |
|||||
0 ignored issues
–
show
$assocValue of type array<mixed,mixed> is incompatible with the type object expected by parameter $entity of Doctrine\ORM\UnitOfWork::getEntityIdentifier() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
343 | 8 | $entityKey = new EntityCacheKey($assocMetadata->getRootClassName(), $assocIdentifier); |
|||||
344 | |||||||
345 | 8 | if ((! $assocValue instanceof GhostObjectInterface && ($key->cacheMode & Cache::MODE_REFRESH)) || ! $assocRegion->contains($entityKey)) { |
|||||
346 | // Entity put fail |
||||||
347 | 7 | if (! $assocPersister->storeEntityCache($assocValue, $entityKey)) { |
|||||
348 | 1 | return null; |
|||||
349 | } |
||||||
350 | } |
||||||
351 | |||||||
352 | return [ |
||||||
353 | 7 | 'targetEntity' => $assocMetadata->getRootClassName(), |
|||||
354 | 7 | 'identifier' => $assocIdentifier, |
|||||
355 | ]; |
||||||
356 | } |
||||||
357 | |||||||
358 | // Handle *-to-many associations |
||||||
359 | 6 | $list = []; |
|||||
360 | |||||||
361 | 6 | foreach ($assocValue as $assocItemIndex => $assocItem) { |
|||||
362 | 6 | $assocIdentifier = $unitOfWork->getEntityIdentifier($assocItem); |
|||||
363 | 6 | $entityKey = new EntityCacheKey($assocMetadata->getRootClassName(), $assocIdentifier); |
|||||
364 | |||||||
365 | 6 | if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) { |
|||||
366 | // Entity put fail |
||||||
367 | 6 | if (! $assocPersister->storeEntityCache($assocItem, $entityKey)) { |
|||||
368 | 1 | return null; |
|||||
369 | } |
||||||
370 | } |
||||||
371 | |||||||
372 | 5 | $list[$assocItemIndex] = $assocIdentifier; |
|||||
373 | } |
||||||
374 | |||||||
375 | return [ |
||||||
376 | 5 | 'targetEntity' => $assocMetadata->getRootClassName(), |
|||||
377 | 5 | 'list' => $list, |
|||||
378 | ]; |
||||||
379 | } |
||||||
380 | |||||||
381 | /** |
||||||
382 | * @param string $assocAlias |
||||||
383 | * @param object $entity |
||||||
384 | * |
||||||
385 | * @return mixed[]|object |
||||||
386 | */ |
||||||
387 | 16 | private function getAssociationValue(ResultSetMapping $rsm, $assocAlias, $entity) |
|||||
388 | { |
||||||
389 | 16 | $path = []; |
|||||
390 | 16 | $alias = $assocAlias; |
|||||
391 | |||||||
392 | 16 | while (isset($rsm->parentAliasMap[$alias])) { |
|||||
393 | 16 | $parent = $rsm->parentAliasMap[$alias]; |
|||||
394 | 16 | $field = $rsm->relationMap[$alias]; |
|||||
395 | 16 | $class = $rsm->aliasMap[$parent]; |
|||||
396 | |||||||
397 | 16 | array_unshift($path, [ |
|||||
398 | 16 | 'field' => $field, |
|||||
399 | 16 | 'class' => $class, |
|||||
400 | ]); |
||||||
401 | |||||||
402 | 16 | $alias = $parent; |
|||||
403 | } |
||||||
404 | |||||||
405 | 16 | return $this->getAssociationPathValue($entity, $path); |
|||||
406 | } |
||||||
407 | |||||||
408 | /** |
||||||
409 | * @param mixed $value |
||||||
410 | * @param mixed[][] $path |
||||||
411 | * |
||||||
412 | * @return mixed[]|object|null |
||||||
413 | */ |
||||||
414 | 16 | private function getAssociationPathValue($value, array $path) |
|||||
415 | { |
||||||
416 | 16 | $mapping = array_shift($path); |
|||||
417 | 16 | $metadata = $this->em->getClassMetadata($mapping['class']); |
|||||
418 | 16 | $association = $metadata->getProperty($mapping['field']); |
|||||
419 | 16 | $value = $association->getValue($value); |
|||||
420 | |||||||
421 | 16 | if ($value === null) { |
|||||
422 | 1 | return null; |
|||||
423 | } |
||||||
424 | |||||||
425 | 15 | if (empty($path)) { |
|||||
426 | 15 | return $value; |
|||||
427 | } |
||||||
428 | |||||||
429 | // Handle *-to-one associations |
||||||
430 | 3 | if ($association instanceof ToOneAssociationMetadata) { |
|||||
431 | 1 | return $this->getAssociationPathValue($value, $path); |
|||||
432 | } |
||||||
433 | |||||||
434 | 2 | $values = []; |
|||||
435 | |||||||
436 | 2 | foreach ($value as $item) { |
|||||
437 | 2 | $values[] = $this->getAssociationPathValue($item, $path); |
|||||
438 | } |
||||||
439 | |||||||
440 | 2 | return $values; |
|||||
441 | } |
||||||
442 | |||||||
443 | /** |
||||||
444 | * {@inheritdoc} |
||||||
445 | */ |
||||||
446 | 48 | public function clear() |
|||||
447 | { |
||||||
448 | 48 | return $this->region->evictAll(); |
|||||
449 | } |
||||||
450 | |||||||
451 | /** |
||||||
452 | * {@inheritdoc} |
||||||
453 | */ |
||||||
454 | 28 | public function getRegion() |
|||||
455 | { |
||||||
456 | 28 | return $this->region; |
|||||
457 | } |
||||||
458 | } |
||||||
459 |