Completed
Pull Request — develop (#24)
by Дмитрий
03:29
created

AnnotationCacheReader::getMethodAnnotations()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3.0123

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 3
eloc 8
c 2
b 0
f 0
nc 3
nop 1
dl 0
loc 15
ccs 8
cts 9
cp 0.8889
crap 3.0123
rs 10
1
<?php
2
3
namespace yii\annotations;
4
5
use Doctrine\Common\Annotations\Reader;
6
use ReflectionClass;
7
use ReflectionMethod;
8
use ReflectionProperty;
9
10
/**
11
 * Class AnnotationCacheReader.
12
 */
13
final class AnnotationCacheReader implements Reader
14
{
15
    /**
16
     * @var Reader
17
     */
18
    private $delegate;
19
20
    /**
21
     * @var AnnotationCacheInterface
22
     */
23
    private $cache;
24
25
    /**
26
     * @var bool
27
     */
28
    private $debug;
29
30
    /**
31
     * @var array
32
     */
33
    private $loadedAnnotations = [];
34
35
    /**
36
     * Constructor.
37
     *
38
     * @param Reader                   $reader
39
     * @param AnnotationCacheInterface $cache
40
     * @param bool                     $debug
41
     */
42 11
    public function __construct(Reader $reader, AnnotationCacheInterface $cache, $debug = false)
43
    {
44 11
        $this->delegate = $reader;
45 11
        $this->cache = $cache;
46 11
        $this->debug = (bool) $debug;
47 11
    }
48
49
    /**
50
     * {@inheritdoc}
51
     */
52 4
    public function getClassAnnotations(ReflectionClass $class)
53
    {
54 4
        $cacheKey = $class->getName();
55
56 4
        if (isset($this->loadedAnnotations[$cacheKey])) {
57
            return $this->loadedAnnotations[$cacheKey];
58
        }
59
60 4
        if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) {
61 3
            $annots = $this->delegate->getClassAnnotations($class);
62 3
            $this->saveToCache($cacheKey, $annots);
63
        }
64
65 4
        return $this->loadedAnnotations[$cacheKey] = $annots;
66
    }
67
68
    /**
69
     * {@inheritdoc}
70
     */
71 1
    public function getClassAnnotation(ReflectionClass $class, $annotationName)
72
    {
73 1
        foreach ($this->getClassAnnotations($class) as $annot) {
74 1
            if ($annot instanceof $annotationName) {
75 1
                return $annot;
76
            }
77
        }
78
    }
79
80
    /**
81
     * {@inheritdoc}
82
     */
83 4
    public function getPropertyAnnotations(ReflectionProperty $property)
84
    {
85 4
        $class = $property->getDeclaringClass();
86 4
        $cacheKey = $class->getName().'$'.$property->getName();
87
88 4
        if (isset($this->loadedAnnotations[$cacheKey])) {
89
            return $this->loadedAnnotations[$cacheKey];
90
        }
91
92 4
        if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) {
93 3
            $annots = $this->delegate->getPropertyAnnotations($property);
94 3
            $this->saveToCache($cacheKey, $annots);
95
        }
96
97 4
        return $this->loadedAnnotations[$cacheKey] = $annots;
98
    }
99
100
    /**
101
     * {@inheritdoc}
102
     */
103 1
    public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
104
    {
105 1
        foreach ($this->getPropertyAnnotations($property) as $annot) {
106 1
            if ($annot instanceof $annotationName) {
107 1
                return $annot;
108
            }
109
        }
110
    }
111
112
    /**
113
     * {@inheritdoc}
114
     */
115 5
    public function getMethodAnnotations(ReflectionMethod $method)
116
    {
117 5
        $class = $method->getDeclaringClass();
118 5
        $cacheKey = $class->getName().'#'.$method->getName();
119
120 5
        if (isset($this->loadedAnnotations[$cacheKey])) {
121
            return $this->loadedAnnotations[$cacheKey];
122
        }
123
124 5
        if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) {
125 4
            $annots = $this->delegate->getMethodAnnotations($method);
126 4
            $this->saveToCache($cacheKey, $annots);
127
        }
128
129 5
        return $this->loadedAnnotations[$cacheKey] = $annots;
130
    }
131
132
    /**
133
     * {@inheritdoc}
134
     */
135 1
    public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
136
    {
137 1
        foreach ($this->getMethodAnnotations($method) as $annot) {
138 1
            if ($annot instanceof $annotationName) {
139 1
                return $annot;
140
            }
141
        }
142
    }
143
144
    /**
145
     * Clears loaded annotations.
146
     *
147
     * @return void
148
     */
149 1
    public function clearLoadedAnnotations(): void
150
    {
151 1
        $this->loadedAnnotations = [];
152 1
    }
153
154
    /**
155
     * Fetches a value from the cache.
156
     *
157
     * @param string          $cacheKey The cache key.
158
     * @param ReflectionClass $class    The related class.
159
     *
160
     * @return mixed The cached value or false when the value is not in cache.
161
     */
162 9
    private function fetchFromCache($cacheKey, ReflectionClass $class)
163
    {
164 9
        if ((($data = $this->cache->fetch($cacheKey)) !== false) && $this->isCanReturnCacheData($cacheKey, $class)) {
165 1
            return $data;
166
        }
167
168 8
        return false;
169
    }
170
171
    /**
172
     * @param $cacheKey
173
     * @param ReflectionClass $class
174
     *
175
     * @return bool
176
     */
177 1
    private function isCanReturnCacheData($cacheKey, ReflectionClass $class): bool
178
    {
179 1
        return !$this->debug || $this->isCacheFresh($cacheKey, $class);
180
    }
181
182
    /**
183
     * Saves a value to the cache.
184
     *
185
     * @param string $cacheKey The cache key.
186
     * @param mixed  $value    The value.
187
     *
188
     * @return void
189
     */
190 8
    private function saveToCache($cacheKey, $value): void
191
    {
192 8
        $this->cache->save($cacheKey, $value);
193 8
        if ($this->debug) {
194
            $this->cache->save('[C]'.$cacheKey, time());
195
        }
196 8
    }
197
198
    /**
199
     * Checks if the cache is fresh.
200
     *
201
     * @param string          $cacheKey
202
     * @param ReflectionClass $class
203
     *
204
     * @return bool
205
     */
206
    private function isCacheFresh($cacheKey, ReflectionClass $class): bool
207
    {
208
        $lastModification = $this->getLastModification($class);
209
        if (null === $lastModification) {
210
            return true;
211
        }
212
213
        return $this->cache->fetch('[C]'.$cacheKey) >= $lastModification;
214
    }
215
216
    /**
217
     * Returns the time the class was last modified, testing traits and parents.
218
     *
219
     * @param ReflectionClass $class
220
     *
221
     * @return mixed
222
     */
223
    private function getLastModification(ReflectionClass $class)
224
    {
225
        $filename = $class->getFileName();
226
        $parent = $class->getParentClass();
227
228
        return max(array_merge(
229
            [$filename ? filemtime($filename) : 0],
230
            array_map([$this, 'getTraitLastModificationTime'], $class->getTraits()),
231
            array_map([$this, 'getLastModification'], $class->getInterfaces()),
232
            $parent ? [$this->getLastModification($parent)] : []
233
        ));
234
    }
235
236
    /**
237
     * @param ReflectionClass $reflectionTrait
238
     *
239
     * @return mixed
240
     */
241
    private function getTraitLastModificationTime(ReflectionClass $reflectionTrait)
242
    {
243
        $fileName = $reflectionTrait->getFileName();
244
245
        return max(array_merge(
246
            [$fileName ? filemtime($fileName) : 0],
247
            array_map([$this, 'getTraitLastModificationTime'], $reflectionTrait->getTraits())
248
        ));
249
    }
250
}
251