Completed
Pull Request — master (#263)
by
unknown
03:26
created

CachedReader::getConstantAnnotations()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
c 0
b 0
f 0
nc 3
nop 1
dl 0
loc 15
rs 10
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, ReaderWithConstantsAnnotations
0 ignored issues
show
Deprecated Code introduced by
The interface Doctrine\Common\Annotati...ithConstantsAnnotations has been deprecated: This interface will be merged into Reader interface and removed in version 2.0. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

31
final class CachedReader implements Reader, /** @scrutinizer ignore-deprecated */ ReaderWithConstantsAnnotations

This interface has been deprecated. The supplier of the interface has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the interface will be removed and what other interface to use instead.

Loading history...
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 getConstantAnnotations(\ReflectionClassConstant $constant): array
139
    {
140
        $class = $constant->getDeclaringClass();
141
        $cacheKey = $class->getName().'::'.$constant->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->getConstantAnnotations($constant);
0 ignored issues
show
Bug introduced by
The method getConstantAnnotations() does not exist on Doctrine\Common\Annotations\Reader. Since it exists in all sub-types, consider adding an abstract or default implementation to Doctrine\Common\Annotations\Reader. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

148
            /** @scrutinizer ignore-call */ 
149
            $annots = $this->delegate->getConstantAnnotations($constant);
Loading history...
149
            $this->saveToCache($cacheKey, $annots);
150
        }
151
152
        return $this->loadedAnnotations[$cacheKey] = $annots;
153
    }
154
155
    /**
156
     * {@inheritDoc}
157
     */
158
    public function getConstantAnnotation(\ReflectionClassConstant $constant, $annotationName)
159
    {
160
        foreach ($this->getConstantAnnotations($constant) as $annot) {
161
            if ($annot instanceof $annotationName) {
162
                return $annot;
163
            }
164
        }
165
166
        return null;
167
    }
168
169
    /**
170
     * {@inheritDoc}
171
     */
172
    public function getMethodAnnotations(\ReflectionMethod $method)
173
    {
174
        $class = $method->getDeclaringClass();
175
        $cacheKey = $class->getName().'#'.$method->getName();
176
177
        if (isset($this->loadedAnnotations[$cacheKey])) {
178
            return $this->loadedAnnotations[$cacheKey];
179
        }
180
181
        if (false === ($annots = $this->fetchFromCache($cacheKey, $class))) {
182
            $annots = $this->delegate->getMethodAnnotations($method);
183
            $this->saveToCache($cacheKey, $annots);
184
        }
185
186
        return $this->loadedAnnotations[$cacheKey] = $annots;
187
    }
188
189
    /**
190
     * {@inheritDoc}
191
     */
192
    public function getMethodAnnotation(\ReflectionMethod $method, $annotationName)
193
    {
194
        foreach ($this->getMethodAnnotations($method) as $annot) {
195
            if ($annot instanceof $annotationName) {
196
                return $annot;
197
            }
198
        }
199
200
        return null;
201
    }
202
203
    /**
204
     * Clears loaded annotations.
205
     *
206
     * @return void
207
     */
208
    public function clearLoadedAnnotations()
209
    {
210
        $this->loadedAnnotations = [];
211
        $this->loadedFilemtimes = [];
212
    }
213
214
    /**
215
     * Fetches a value from the cache.
216
     *
217
     * @param string $cacheKey The cache key.
218
     *
219
     * @return mixed The cached value or false when the value is not in cache.
220
     */
221
    private function fetchFromCache($cacheKey, ReflectionClass $class)
222
    {
223
        if (($data = $this->cache->fetch($cacheKey)) !== false) {
224
            if (!$this->debug || $this->isCacheFresh($cacheKey, $class)) {
225
                return $data;
226
            }
227
        }
228
229
        return false;
230
    }
231
232
    /**
233
     * Saves a value to the cache.
234
     *
235
     * @param string $cacheKey The cache key.
236
     * @param mixed  $value    The value.
237
     *
238
     * @return void
239
     */
240
    private function saveToCache($cacheKey, $value)
241
    {
242
        $this->cache->save($cacheKey, $value);
243
        if ($this->debug) {
244
            $this->cache->save('[C]'.$cacheKey, time());
245
        }
246
    }
247
248
    /**
249
     * Checks if the cache is fresh.
250
     *
251
     * @param string $cacheKey
252
     *
253
     * @return boolean
254
     */
255
    private function isCacheFresh($cacheKey, ReflectionClass $class)
256
    {
257
        $lastModification = $this->getLastModification($class);
258
        if ($lastModification === 0) {
259
            return true;
260
        }
261
262
        return $this->cache->fetch('[C]'.$cacheKey) >= $lastModification;
263
    }
264
265
    /**
266
     * Returns the time the class was last modified, testing traits and parents
267
     *
268
     * @return int
269
     */
270
    private function getLastModification(ReflectionClass $class)
271
    {
272
        $filename = $class->getFileName();
273
274
        if (isset($this->loadedFilemtimes[$filename])) {
275
            return $this->loadedFilemtimes[$filename];
276
        }
277
278
        $parent   = $class->getParentClass();
279
280
        $lastModification =  max(array_merge(
281
            [$filename ? filemtime($filename) : 0],
282
            array_map([$this, 'getTraitLastModificationTime'], $class->getTraits()),
283
            array_map([$this, 'getLastModification'], $class->getInterfaces()),
284
            $parent ? [$this->getLastModification($parent)] : []
285
        ));
286
287
        assert($lastModification !== false);
288
289
        return $this->loadedFilemtimes[$filename] = $lastModification;
290
    }
291
292
    /**
293
     * @return int
294
     */
295
    private function getTraitLastModificationTime(ReflectionClass $reflectionTrait)
296
    {
297
        $fileName = $reflectionTrait->getFileName();
298
299
        if (isset($this->loadedFilemtimes[$fileName])) {
300
            return $this->loadedFilemtimes[$fileName];
301
        }
302
303
        $lastModificationTime = max(array_merge(
304
            [$fileName ? filemtime($fileName) : 0],
305
            array_map([$this, 'getTraitLastModificationTime'], $reflectionTrait->getTraits())
306
        ));
307
308
        assert($lastModificationTime !== false);
309
310
        return $this->loadedFilemtimes[$fileName] = $lastModificationTime;
311
    }
312
}
313