CachedReader::getPropertyAnnotations()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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