Failed Conditions
Push — master ( 8149a1...97914c )
by Marco
19s
created

DefaultQueryCache::getAssociationPathValue()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 28
ccs 15
cts 15
cp 1
rs 8.439
c 0
b 0
f 0
cc 5
eloc 15
nc 5
nop 2
crap 5
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 85
    public function __construct(EntityManagerInterface $em, Region $region)
76
    {
77 85
        $cacheConfig = $em->getConfiguration()->getSecondLevelCacheConfiguration();
78
79 85
        $this->em           = $em;
80 85
        $this->region       = $region;
81 85
        $this->uow          = $em->getUnitOfWork();
82 85
        $this->cacheLogger  = $cacheConfig->getCacheLogger();
83 85
        $this->validator    = $cacheConfig->getQueryValidator();
84 85
    }
85
86
    /**
87
     * {@inheritdoc}
88
     */
89 51
    public function get(QueryCacheKey $key, ResultSetMapping $rsm, array $hints = [])
90
    {
91 51
        if ( ! ($key->cacheMode & Cache::MODE_GET)) {
92 3
            return null;
93
        }
94
95 50
        $entry = $this->region->get($key);
96
97 50
        if ( ! $entry instanceof QueryCacheEntry) {
98 46
            return null;
99
        }
100
101 37
        if ( ! $this->validator->isValid($key, $entry)) {
102 8
            $this->region->evict($key);
103
104 8
            return null;
105
        }
106
107 32
        $result      = [];
108 32
        $entityName  = reset($rsm->aliasMap);
109 32
        $hasRelation = ( ! empty($rsm->relationMap));
110 32
        $persister   = $this->uow->getEntityPersister($entityName);
111 32
        $region      = $persister->getCacheRegion();
112 32
        $regionName  = $region->getName();
113
114 32
        $cm = $this->em->getClassMetadata($entityName);
115
116 32
        $generateKeys = function (array $entry) use ($cm): EntityCacheKey {
117 32
            return new EntityCacheKey($cm->rootEntityName, $entry['identifier']);
0 ignored issues
show
Bug introduced by
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

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
118 32
        };
119
120 32
        $cacheKeys = new CollectionCacheEntry(array_map($generateKeys, $entry->result));
121 32
        $entries   = $region->getMultiple($cacheKeys);
122
123
        // @TODO - move to cache hydration component
124 32
        foreach ($entry->result as $index => $entry) {
125 32
            $entityEntry = is_array($entries) && array_key_exists($index, $entries) ? $entries[$index] : null;
126
127 32
            if ($entityEntry === null) {
128 2
                if ($this->cacheLogger !== null) {
129 1
                    $this->cacheLogger->entityCacheMiss($regionName, $cacheKeys->identifiers[$index]);
0 ignored issues
show
Compatibility introduced by
$cacheKeys->identifiers[$index] of type object<Doctrine\ORM\Cache\CacheKey> is not a sub-type of object<Doctrine\ORM\Cache\EntityCacheKey>. It seems like you assume a child class of the class Doctrine\ORM\Cache\CacheKey to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
130
                }
131
132 2
                return null;
133
            }
134
135 30
            if ($this->cacheLogger !== null) {
136 28
                $this->cacheLogger->entityCacheHit($regionName, $cacheKeys->identifiers[$index]);
0 ignored issues
show
Compatibility introduced by
$cacheKeys->identifiers[$index] of type object<Doctrine\ORM\Cache\CacheKey> is not a sub-type of object<Doctrine\ORM\Cache\EntityCacheKey>. It seems like you assume a child class of the class Doctrine\ORM\Cache\CacheKey to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
137
            }
138
139 30
            if ( ! $hasRelation) {
140 22
                $result[$index]  = $this->uow->createEntity($entityEntry->class, $entityEntry->resolveAssociationEntries($this->em), self::$hints);
141
142 22
                continue;
143
            }
144
145 8
            $data = $entityEntry->data;
146
147 8
            foreach ($entry['associations'] as $name => $assoc) {
148 8
                $assocPersister  = $this->uow->getEntityPersister($assoc['targetEntity']);
149 8
                $assocRegion     = $assocPersister->getCacheRegion();
150 8
                $assocMetadata   = $this->em->getClassMetadata($assoc['targetEntity']);
151
152 8
                if ($assoc['type'] & ClassMetadata::TO_ONE) {
153
154 4
                    if (($assocEntry = $assocRegion->get($assocKey = new EntityCacheKey($assocMetadata->rootEntityName, $assoc['identifier']))) === null) {
0 ignored issues
show
Bug introduced by
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

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
155
156 1
                        if ($this->cacheLogger !== null) {
157 1
                            $this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKey);
158
                        }
159
160 1
                        $this->uow->hydrationComplete();
161
162 1
                        return null;
163
                    }
164
165 3
                    $data[$name] = $this->uow->createEntity($assocEntry->class, $assocEntry->resolveAssociationEntries($this->em), self::$hints);
166
167 3
                    if ($this->cacheLogger !== null) {
168 3
                        $this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKey);
169
                    }
170
171 3
                    continue;
172
                }
173
174 4
                if ( ! isset($assoc['list']) || empty($assoc['list'])) {
175
                    continue;
176
                }
177
178 4
                $generateKeys = function ($id) use ($assocMetadata): EntityCacheKey {
179 4
                    return new EntityCacheKey($assocMetadata->rootEntityName, $id);
0 ignored issues
show
Bug introduced by
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

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
180 4
                };
181
182 4
                $collection   = new PersistentCollection($this->em, $assocMetadata, new ArrayCollection());
0 ignored issues
show
Compatibility introduced by
$assocMetadata of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
183 4
                $assocKeys    = new CollectionCacheEntry(array_map($generateKeys, $assoc['list']));
184 4
                $assocEntries = $assocRegion->getMultiple($assocKeys);
185
186 4
                foreach ($assoc['list'] as $assocIndex => $assocId) {
187 4
                    $assocEntry = is_array($assocEntries) && array_key_exists($assocIndex, $assocEntries) ? $assocEntries[$assocIndex] : null;
188
189 4
                    if ($assocEntry === null) {
190 1
                        if ($this->cacheLogger !== null) {
191 1
                            $this->cacheLogger->entityCacheMiss($assocRegion->getName(), $assocKeys->identifiers[$assocIndex]);
0 ignored issues
show
Compatibility introduced by
$assocKeys->identifiers[$assocIndex] of type object<Doctrine\ORM\Cache\CacheKey> is not a sub-type of object<Doctrine\ORM\Cache\EntityCacheKey>. It seems like you assume a child class of the class Doctrine\ORM\Cache\CacheKey to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
192
                        }
193
194 1
                        $this->uow->hydrationComplete();
195
196 1
                        return null;
197
                    }
198
199 3
                    $element = $this->uow->createEntity($assocEntry->class, $assocEntry->resolveAssociationEntries($this->em), self::$hints);
200
201 3
                    $collection->hydrateSet($assocIndex, $element);
202
203 3
                    if ($this->cacheLogger !== null) {
204 3
                        $this->cacheLogger->entityCacheHit($assocRegion->getName(), $assocKeys->identifiers[$assocIndex]);
0 ignored issues
show
Compatibility introduced by
$assocKeys->identifiers[$assocIndex] of type object<Doctrine\ORM\Cache\CacheKey> is not a sub-type of object<Doctrine\ORM\Cache\EntityCacheKey>. It seems like you assume a child class of the class Doctrine\ORM\Cache\CacheKey to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
205
                    }
206
                }
207
208 3
                $data[$name] = $collection;
209
210 3
                $collection->setInitialized(true);
211
            }
212
213 6
            $result[$index] = $this->uow->createEntity($entityEntry->class, $data, self::$hints);
214
        }
215
216 28
        $this->uow->hydrationComplete();
217
218 28
        return $result;
219
    }
220
221
    /**
222
     * {@inheritdoc}
223
     */
224 57
    public function put(QueryCacheKey $key, ResultSetMapping $rsm, $result, array $hints = [])
225
    {
226 57
        if ($rsm->scalarMappings) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $rsm->scalarMappings of type array 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 empty(..) or ! empty(...) instead.

Loading history...
227 1
            throw new CacheException("Second level cache does not support scalar results.");
228
        }
229
230 56
        if (count($rsm->entityMappings) > 1) {
231 1
            throw new CacheException("Second level cache does not support multiple root entities.");
232
        }
233
234 55
        if ( ! $rsm->isSelect) {
235 2
            throw new CacheException("Second-level cache query supports only select statements.");
236
        }
237
238 53
        if (isset($hints[Query::HINT_FORCE_PARTIAL_LOAD]) && $hints[Query::HINT_FORCE_PARTIAL_LOAD]) {
239 1
            throw new CacheException("Second level cache does not support partial entities.");
240
        }
241
242 52
        if ( ! ($key->cacheMode & Cache::MODE_PUT)) {
243 3
            return false;
244
        }
245
246 51
        $data        = [];
247 51
        $entityName  = reset($rsm->aliasMap);
248 51
        $rootAlias   = key($rsm->aliasMap);
249 51
        $hasRelation = ( ! empty($rsm->relationMap));
250 51
        $persister   = $this->uow->getEntityPersister($entityName);
251
252 51
        if ( ! ($persister instanceof CachedPersister)) {
253 1
            throw CacheException::nonCacheableEntity($entityName);
254
        }
255
256 50
        $region = $persister->getCacheRegion();
257
258 50
        foreach ($result as $index => $entity) {
259 50
            $identifier                     = $this->uow->getEntityIdentifier($entity);
260 50
            $entityKey                      = new EntityCacheKey($entityName, $identifier);
261 50
            $data[$index]['identifier']     = $identifier;
262 50
            $data[$index]['associations']   = [];
263
264 50
            if (($key->cacheMode & Cache::MODE_REFRESH) || ! $region->contains($entityKey)) {
265
                // Cancel put result if entity put fail
266 36
                if ( ! $persister->storeEntityCache($entity, $entityKey)) {
267 1
                    return false;
268
                }
269
            }
270
271 49
            if ( ! $hasRelation) {
272 35
                continue;
273
            }
274
275
            // @TODO - move to cache hydration components
276 14
            foreach ($rsm->relationMap as $alias => $name) {
277 14
                $parentAlias  = $rsm->parentAliasMap[$alias];
278 14
                $parentClass  = $rsm->aliasMap[$parentAlias];
279 14
                $metadata     = $this->em->getClassMetadata($parentClass);
280 14
                $assoc        = $metadata->associationMappings[$name];
0 ignored issues
show
Bug introduced by
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

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
281 14
                $assocValue   = $this->getAssociationValue($rsm, $alias, $entity);
282
283 14
                if ($assocValue === null) {
284 1
                    continue;
285
                }
286
287
                // root entity association
288 13
                if ($rootAlias === $parentAlias) {
289
                    // Cancel put result if association put fail
290 13
                    if ( ($assocInfo = $this->storeAssociationCache($key, $assoc, $assocValue)) === null) {
291 2
                        return false;
292
                    }
293
294 11
                    $data[$index]['associations'][$name] = $assocInfo;
295
296 11
                    continue;
297
                }
298
299
                // store single nested association
300 2
                if ( ! is_array($assocValue)) {
301
                    // Cancel put result if association put fail
302 1
                    if ($this->storeAssociationCache($key, $assoc, $assocValue) === null) {
303
                        return false;
304
                    }
305
306 1
                    continue;
307
                }
308
309
                // store array of nested association
310 1
                foreach ($assocValue as $aVal) {
311
                    // Cancel put result if association put fail
312 1
                    if ($this->storeAssociationCache($key, $assoc, $aVal) === null) {
313 12
                        return false;
314
                    }
315
                }
316
            }
317
        }
318
319 47
        return $this->region->put($key, new QueryCacheEntry($data));
320
    }
321
322
    /**
323
     * @param \Doctrine\ORM\Cache\QueryCacheKey $key
324
     * @param array                             $assoc
325
     * @param mixed                             $assocValue
326
     *
327
     * @return array|null
328
     */
329 13
    private function storeAssociationCache(QueryCacheKey $key, array $assoc, $assocValue)
330
    {
331 13
        $assocPersister = $this->uow->getEntityPersister($assoc['targetEntity']);
332 13
        $assocMetadata  = $assocPersister->getClassMetadata();
333 13
        $assocRegion    = $assocPersister->getCacheRegion();
334
335
        // Handle *-to-one associations
336 13
        if ($assoc['type'] & ClassMetadata::TO_ONE) {
337 7
            $assocIdentifier = $this->uow->getEntityIdentifier($assocValue);
338 7
            $entityKey       = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier);
339
340 7
            if ( ! $assocValue instanceof Proxy && ($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) {
341
                // Entity put fail
342 7
                if ( ! $assocPersister->storeEntityCache($assocValue, $entityKey)) {
343 1
                    return null;
344
                }
345
            }
346
347
            return [
348 6
                'targetEntity'  => $assocMetadata->rootEntityName,
349 6
                'identifier'    => $assocIdentifier,
350 6
                'type'          => $assoc['type']
351
            ];
352
        }
353
354
        // Handle *-to-many associations
355 6
        $list = [];
356
357 6
        foreach ($assocValue as $assocItemIndex => $assocItem) {
358 6
            $assocIdentifier = $this->uow->getEntityIdentifier($assocItem);
359 6
            $entityKey       = new EntityCacheKey($assocMetadata->rootEntityName, $assocIdentifier);
360
361 6
            if (($key->cacheMode & Cache::MODE_REFRESH) || ! $assocRegion->contains($entityKey)) {
362
                // Entity put fail
363 6
                if ( ! $assocPersister->storeEntityCache($assocItem, $entityKey)) {
364 1
                    return null;
365
                }
366
            }
367
368 5
            $list[$assocItemIndex] = $assocIdentifier;
369
        }
370
371
        return [
372 5
            'targetEntity'  => $assocMetadata->rootEntityName,
373 5
            'type'          => $assoc['type'],
374 5
            'list'          => $list,
375
        ];
376
    }
377
378
    /**
379
     * @param \Doctrine\ORM\Query\ResultSetMapping $rsm
380
     * @param string                               $assocAlias
381
     * @param object                               $entity
382
     *
383
     * @return array|object
384
     */
385 15
    private function getAssociationValue(ResultSetMapping $rsm, $assocAlias, $entity)
386
    {
387 15
        $path  = [];
388 15
        $alias = $assocAlias;
389
390 15
        while (isset($rsm->parentAliasMap[$alias])) {
391 15
            $parent = $rsm->parentAliasMap[$alias];
392 15
            $field  = $rsm->relationMap[$alias];
393 15
            $class  = $rsm->aliasMap[$parent];
394
395 15
            array_unshift($path, [
396 15
                'field'  => $field,
397 15
                'class'  => $class
398
            ]
399
            );
400
401 15
            $alias = $parent;
402
        }
403
404 15
        return $this->getAssociationPathValue($entity, $path);
405
    }
406
407
    /**
408
     * @param mixed $value
409
     * @param array $path
410
     *
411
     * @return array|object|null
412
     */
413 15
    private function getAssociationPathValue($value, array $path)
414
    {
415 15
        $mapping  = array_shift($path);
416 15
        $metadata = $this->em->getClassMetadata($mapping['class']);
417 15
        $assoc    = $metadata->associationMappings[$mapping['field']];
0 ignored issues
show
Bug introduced by
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

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
418 15
        $value    = $metadata->getFieldValue($value, $mapping['field']);
419
420 15
        if ($value === null) {
421 1
            return null;
422
        }
423
424 14
        if (empty($path)) {
425 14
            return $value;
426
        }
427
428
        // Handle *-to-one associations
429 3
        if ($assoc['type'] & ClassMetadata::TO_ONE) {
430 1
            return $this->getAssociationPathValue($value, $path);
431
        }
432
433 2
        $values = [];
434
435 2
        foreach ($value as $item) {
436 2
            $values[] = $this->getAssociationPathValue($item, $path);
437
        }
438
439 2
        return $values;
440
    }
441
442
    /**
443
     * {@inheritdoc}
444
     */
445 48
    public function clear()
446
    {
447 48
        return $this->region->evictAll();
448
    }
449
450
    /**
451
     * {@inheritdoc}
452
     */
453 28
    public function getRegion()
454
    {
455 28
        return $this->region;
456
    }
457
}
458