Completed
Push — master ( 8a6821...f9deab )
by Andreas
04:47
created

CachedReader   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 246
Duplicated Lines 0 %

Importance

Changes 8
Bugs 0 Features 0
Metric Value
eloc 76
dl 0
loc 246
rs 9.6
c 8
b 0
f 0
wmc 35

13 Methods

Rating   Name   Duplication   Size   Complexity  
A clearLoadedAnnotations() 0 4 1
A getMethodAnnotation() 0 9 3
A getPropertyAnnotations() 0 15 3
A isCacheFresh() 0 8 2
A getTraitLastModificationTime() 0 16 3
A getPropertyAnnotation() 0 9 3
A getLastModification() 0 20 4
A saveToCache() 0 5 2
A getMethodAnnotations() 0 15 3
A fetchFromCache() 0 9 4
A __construct() 0 5 1
A getClassAnnotations() 0 14 3
A getClassAnnotation() 0 9 3
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\Common\Annotations;
21
22
use Doctrine\Common\Cache\Cache;
23
use ReflectionClass;
24
25
/**
26
 * A cache aware annotation reader.
27
 *
28
 * @author Johannes M. Schmitt <[email protected]>
29
 * @author Benjamin Eberlei <[email protected]>
30
 */
31
final class CachedReader implements Reader
32
{
33
    /**
34
     * @var Reader
35
     */
36
    private $delegate;
37
38
    /**
39
     * @var Cache
40
     */
41
    private $cache;
42
43
    /**
44
     * @var boolean
45
     */
46
    private $debug;
47
48
    /**
49
     * @var array
50
     */
51
    private $loadedAnnotations = [];
52
53
    /**
54
     * @var int[]
55
     */
56
    private $loadedFilemtimes = [];
57
58
    /**
59
     * @param bool $debug
60
     */
61
    public function __construct(Reader $reader, Cache $cache, $debug = false)
62
    {
63
        $this->delegate = $reader;
64
        $this->cache = $cache;
65
        $this->debug = (boolean) $debug;
66
    }
67
68
    /**
69
     * {@inheritDoc}
70
     */
71
    public function getClassAnnotations(ReflectionClass $class)
72
    {
73
        $cacheKey = $class->getName();
74
75
        if (isset($this->loadedAnnotations[$cacheKey])) {
76
            return $this->loadedAnnotations[$cacheKey];
77
        }
78
79
        if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) {
80
            $annots = $this->delegate->getClassAnnotations($class);
81
            $this->saveToCache($cacheKey, $annots);
82
        }
83
84
        return $this->loadedAnnotations[$cacheKey] = $annots;
85
    }
86
87
    /**
88
     * {@inheritDoc}
89
     */
90
    public function getClassAnnotation(ReflectionClass $class, $annotationName)
91
    {
92
        foreach ($this->getClassAnnotations($class) as $annot) {
93
            if ($annot instanceof $annotationName) {
94
                return $annot;
95
            }
96
        }
97
98
        return null;
99
    }
100
101
    /**
102
     * {@inheritDoc}
103
     */
104
    public function getPropertyAnnotations(\ReflectionProperty $property)
105
    {
106
        $class = $property->getDeclaringClass();
107
        $cacheKey = $class->getName().'$'.$property->getName();
108
109
        if (isset($this->loadedAnnotations[$cacheKey])) {
110
            return $this->loadedAnnotations[$cacheKey];
111
        }
112
113
        if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) {
114
            $annots = $this->delegate->getPropertyAnnotations($property);
115
            $this->saveToCache($cacheKey, $annots);
116
        }
117
118
        return $this->loadedAnnotations[$cacheKey] = $annots;
119
    }
120
121
    /**
122
     * {@inheritDoc}
123
     */
124
    public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName)
125
    {
126
        foreach ($this->getPropertyAnnotations($property) as $annot) {
127
            if ($annot instanceof $annotationName) {
128
                return $annot;
129
            }
130
        }
131
132
        return null;
133
    }
134
135
    /**
136
     * {@inheritDoc}
137
     */
138
    public function getMethodAnnotations(\ReflectionMethod $method)
139
    {
140
        $class = $method->getDeclaringClass();
141
        $cacheKey = $class->getName().'#'.$method->getName();
142
143
        if (isset($this->loadedAnnotations[$cacheKey])) {
144
            return $this->loadedAnnotations[$cacheKey];
145
        }
146
147
        if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) {
148
            $annots = $this->delegate->getMethodAnnotations($method);
149
            $this->saveToCache($cacheKey, $annots);
150
        }
151
152
        return $this->loadedAnnotations[$cacheKey] = $annots;
153
    }
154
155
    /**
156
     * {@inheritDoc}
157
     */
158
    public function getMethodAnnotation(\ReflectionMethod $method, $annotationName)
159
    {
160
        foreach ($this->getMethodAnnotations($method) as $annot) {
161
            if ($annot instanceof $annotationName) {
162
                return $annot;
163
            }
164
        }
165
166
        return null;
167
    }
168
169
    /**
170
     * Clears loaded annotations.
171
     *
172
     * @return void
173
     */
174
    public function clearLoadedAnnotations()
175
    {
176
        $this->loadedAnnotations = [];
177
        $this->loadedFilemtimes = [];
178
    }
179
180
    /**
181
     * Fetches a value from the cache.
182
     *
183
     * @param string $cacheKey The cache key.
184
     *
185
     * @return mixed The cached value or false when the value is not in cache.
186
     */
187
    private function fetchFromCache($cacheKey, ReflectionClass $class)
188
    {
189
        if (($data = $this->cache->fetch($cacheKey)) !== false) {
190
            if (!$this->debug || $this->isCacheFresh($cacheKey, $class)) {
191
                return $data;
192
            }
193
        }
194
195
        return false;
196
    }
197
198
    /**
199
     * Saves a value to the cache.
200
     *
201
     * @param string $cacheKey The cache key.
202
     * @param mixed  $value    The value.
203
     *
204
     * @return void
205
     */
206
    private function saveToCache($cacheKey, $value)
207
    {
208
        $this->cache->save($cacheKey, $value);
209
        if ($this->debug) {
210
            $this->cache->save('[C]'.$cacheKey, time());
211
        }
212
    }
213
214
    /**
215
     * Checks if the cache is fresh.
216
     *
217
     * @param string $cacheKey
218
     *
219
     * @return boolean
220
     */
221
    private function isCacheFresh($cacheKey, ReflectionClass $class)
222
    {
223
        $lastModification = $this->getLastModification($class);
224
        if ($lastModification === 0) {
225
            return true;
226
        }
227
228
        return $this->cache->fetch('[C]'.$cacheKey) >= $lastModification;
229
    }
230
231
    /**
232
     * Returns the time the class was last modified, testing traits and parents
233
     *
234
     * @return int
235
     */
236
    private function getLastModification(ReflectionClass $class)
237
    {
238
        $filename = $class->getFileName();
239
240
        if (isset($this->loadedFilemtimes[$filename])) {
241
            return $this->loadedFilemtimes[$filename];
242
        }
243
244
        $parent   = $class->getParentClass();
245
246
        $lastModification =  max(array_merge(
247
            [$filename ? filemtime($filename) : 0],
248
            array_map([$this, 'getTraitLastModificationTime'], $class->getTraits()),
249
            array_map([$this, 'getLastModification'], $class->getInterfaces()),
250
            $parent ? [$this->getLastModification($parent)] : []
251
        ));
252
253
        assert($lastModification !== false);
254
255
        return $this->loadedFilemtimes[$filename] = $lastModification;
256
    }
257
258
    /**
259
     * @return int
260
     */
261
    private function getTraitLastModificationTime(ReflectionClass $reflectionTrait)
262
    {
263
        $fileName = $reflectionTrait->getFileName();
264
265
        if (isset($this->loadedFilemtimes[$fileName])) {
266
            return $this->loadedFilemtimes[$fileName];
267
        }
268
269
        $lastModificationTime = max(array_merge(
270
            [$fileName ? filemtime($fileName) : 0],
271
            array_map([$this, 'getTraitLastModificationTime'], $reflectionTrait->getTraits())
272
        ));
273
274
        assert($lastModificationTime !== false);
275
276
        return $this->loadedFilemtimes[$fileName] = $lastModificationTime;
277
    }
278
}
279