Failed Conditions
Pull Request — master (#264)
by
unknown
11:23
created

AnnotationReader::getConstantAnnotation()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3.0416

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 11
ccs 5
cts 6
cp 0.8333
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 2
crap 3.0416
1
<?php
2
3
namespace Doctrine\Annotations;
4
5
use Doctrine\Annotations\Annotation\IgnoreAnnotation;
6
use Doctrine\Annotations\Annotation\Target;
7
use ReflectionClass;
8
use ReflectionMethod;
9
use ReflectionProperty;
10
11
/**
12
 * A reader for docblock annotations.
13
 *
14
 * @author  Benjamin Eberlei <[email protected]>
15
 * @author  Guilherme Blanco <[email protected]>
16
 * @author  Jonathan Wage <[email protected]>
17
 * @author  Roman Borschel <[email protected]>
18
 * @author  Johannes M. Schmitt <[email protected]>
19
 */
20
class AnnotationReader implements Reader
21
{
22
    /**
23
     * Global map for imports.
24
     *
25
     * @var array
26
     */
27
    private static $globalImports = [
28
        'ignoreannotation' => 'Doctrine\Annotations\Annotation\IgnoreAnnotation',
29
    ];
30
31
    /**
32
     * A list with annotations that are not causing exceptions when not resolved to an annotation class.
33
     *
34
     * The names are case sensitive.
35
     *
36
     * @var array
37
     */
38
    private static $globalIgnoredNames = ImplicitlyIgnoredAnnotationNames::LIST;
39
40
    /**
41
     * A list with annotations that are not causing exceptions when not resolved to an annotation class.
42
     *
43
     * The names are case sensitive.
44
     *
45
     * @var array
46
     */
47
    private static $globalIgnoredNamespaces = [];
48
49
    /**
50
     * Add a new annotation to the globally ignored annotation names with regard to exception handling.
51
     *
52
     * @param string $name
53
     */
54
    static public function addGlobalIgnoredName($name)
55
    {
56
        self::$globalIgnoredNames[$name] = true;
57
    }
58
59
    /**
60
     * Add a new annotation to the globally ignored annotation namespaces with regard to exception handling.
61
     *
62
     * @param string $namespace
63
     */
64 4
    static public function addGlobalIgnoredNamespace($namespace)
65
    {
66 4
        self::$globalIgnoredNamespaces[$namespace] = true;
67 4
    }
68
69
    /**
70
     * Annotations parser.
71
     *
72
     * @var \Doctrine\Annotations\DocParser
73
     */
74
    private $parser;
75
76
    /**
77
     * Annotations parser used to collect parsing metadata.
78
     *
79
     * @var \Doctrine\Annotations\DocParser
80
     */
81
    private $preParser;
82
83
    /**
84
     * PHP parser used to collect imports.
85
     *
86
     * @var \Doctrine\Annotations\PhpParser
87
     */
88
    private $phpParser;
89
90
    /**
91
     * In-memory cache mechanism to store imported annotations per class.
92
     *
93
     * @var array
94
     */
95
    private $imports = [];
96
97
    /**
98
     * In-memory cache mechanism to store ignored annotations per class.
99
     *
100
     * @var array
101
     */
102
    private $ignoredAnnotationNames = [];
103
104
    /**
105
     * Constructor.
106
     *
107
     * Initializes a new AnnotationReader.
108
     *
109
     * @param DocParser $parser
110
     *
111
     * @throws AnnotationException
112
     */
113 50
    public function __construct(DocParser $parser = null)
114
    {
115 50
        if (extension_loaded('Zend Optimizer+') && (ini_get('zend_optimizerplus.save_comments') === "0" || ini_get('opcache.save_comments') === "0")) {
116
            throw AnnotationException::optimizerPlusSaveComments();
117
        }
118
119 50
        if (extension_loaded('Zend OPcache') && ini_get('opcache.save_comments') == 0) {
120
            throw AnnotationException::optimizerPlusSaveComments();
121
        }
122
123 50
        $this->parser = $parser ?: new DocParser();
124
125 50
        $this->preParser = new DocParser;
126
127 50
        $this->preParser->setImports(self::$globalImports);
128 50
        $this->preParser->setIgnoreNotImportedAnnotations(true);
129 50
        $this->preParser->setIgnoredAnnotationNames(self::$globalIgnoredNames);
130
131 50
        $this->phpParser = new PhpParser;
132 50
    }
133
134
    /**
135
     * {@inheritDoc}
136
     */
137 22
    public function getClassAnnotations(ReflectionClass $class)
138
    {
139 22
        $this->parser->setTarget(Target::TARGET_CLASS);
140 22
        $this->parser->setImports($this->getClassImports($class));
141 22
        $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
142 22
        $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
143
144 22
        return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName());
145
    }
146
147
    /**
148
     * {@inheritDoc}
149
     */
150 2
    public function getClassAnnotation(ReflectionClass $class, $annotationName)
151
    {
152 2
        $annotations = $this->getClassAnnotations($class);
153
154 2
        foreach ($annotations as $annotation) {
155 2
            if ($annotation instanceof $annotationName) {
156 2
                return $annotation;
157
            }
158
        }
159
160
        return null;
161
    }
162
163
    /**
164
     * {@inheritDoc}
165
     */
166 21
    public function getPropertyAnnotations(ReflectionProperty $property)
167
    {
168 21
        $class   = $property->getDeclaringClass();
169 21
        $context = 'property ' . $class->getName() . "::\$" . $property->getName();
170
171 21
        $this->parser->setTarget(Target::TARGET_PROPERTY);
172 21
        $this->parser->setImports($this->getPropertyImports($property));
173 21
        $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
174 21
        $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
175
176 21
        return $this->parser->parse($property->getDocComment(), $context);
177
    }
178
179
    /**
180
     * {@inheritDoc}
181
     */
182 3
    public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
183
    {
184 3
        $annotations = $this->getPropertyAnnotations($property);
185
186 3
        foreach ($annotations as $annotation) {
187 3
            if ($annotation instanceof $annotationName) {
188 3
                return $annotation;
189
            }
190
        }
191
192
        return null;
193
    }
194
195
    /**
196
     * {@inheritDoc}
197
     */
198 13
    public function getMethodAnnotations(ReflectionMethod $method)
199
    {
200 13
        $class   = $method->getDeclaringClass();
201 13
        $context = 'method ' . $class->getName() . '::' . $method->getName() . '()';
202
203 13
        $this->parser->setTarget(Target::TARGET_METHOD);
204 13
        $this->parser->setImports($this->getMethodImports($method));
205 13
        $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
206 13
        $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
207
208 13
        return $this->parser->parse($method->getDocComment(), $context);
209
    }
210
211
    /**
212
     * {@inheritDoc}
213
     */
214 1
    public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
215
    {
216 1
        $annotations = $this->getMethodAnnotations($method);
217
218 1
        foreach ($annotations as $annotation) {
219 1
            if ($annotation instanceof $annotationName) {
220 1
                return $annotation;
221
            }
222
        }
223
224
        return null;
225
    }
226
227
    /**
228
     * {@inheritDoc}
229
     */
230 3
    public function getConstantAnnotations(\ReflectionClassConstant $constant)
231
    {
232 3
        $class   = $constant->getDeclaringClass();
233 3
        $context = 'constant ' . $class->getName() . "::" . $constant->getName();
234
235 3
        $this->parser->setTarget(Target::TARGET_CONSTANT);
236 3
        $this->parser->setImports($this->getConstantImports($constant));
237 3
        $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
238 3
        $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
239
240 3
        return $this->parser->parse($constant->getDocComment(), $context);
241
    }
242
243
    /**
244
     * {@inheritDoc}
245
     */
246 1
    public function getConstantAnnotation(\ReflectionClassConstant $constant, $annotationName)
247
    {
248 1
        $annotations = $this->getConstantAnnotations($constant);
249
250 1
        foreach ($annotations as $annotation) {
251 1
            if ($annotation instanceof $annotationName) {
252 1
                return $annotation;
253
            }
254
        }
255
256
        return null;
257
    }
258
259
    /**
260
     * Returns the ignored annotations for the given class.
261
     *
262
     * @param \ReflectionClass $class
263
     *
264
     * @return array
265
     */
266 50
    private function getIgnoredAnnotationNames(ReflectionClass $class)
267
    {
268 50
        $name = $class->getName();
269 50
        if (isset($this->ignoredAnnotationNames[$name])) {
270 50
            return $this->ignoredAnnotationNames[$name];
271
        }
272
273
        $this->collectParsingMetadata($class);
274
275
        return $this->ignoredAnnotationNames[$name];
276
    }
277
278
    /**
279
     * Retrieves imports.
280
     *
281
     * @param \ReflectionClass $class
282
     *
283
     * @return array
284
     */
285 50
    private function getClassImports(ReflectionClass $class)
286
    {
287 50
        $name = $class->getName();
288 50
        if (isset($this->imports[$name])) {
289 9
            return $this->imports[$name];
290
        }
291
292 50
        $this->collectParsingMetadata($class);
293
294 50
        return $this->imports[$name];
295
    }
296
297
    /**
298
     * Retrieves imports for methods.
299
     *
300
     * @param \ReflectionMethod $method
301
     *
302
     * @return array
303
     */
304 13
    private function getMethodImports(ReflectionMethod $method)
305
    {
306 13
        $class = $method->getDeclaringClass();
307 13
        $classImports = $this->getClassImports($class);
308
309 13
        $traitImports = [];
310
311 13
        foreach ($class->getTraits() as $trait) {
312 2
            if ($trait->hasMethod($method->getName())
313 2
                && $trait->getFileName() === $method->getFileName()
314
            ) {
315 1
                $traitImports = array_merge($traitImports, $this->phpParser->parseClass($trait));
316
            }
317
        }
318
319 13
        return array_merge($classImports, $traitImports);
320
    }
321
322
    /**
323
     * Retrieves imports for properties.
324
     *
325
     * @param \ReflectionProperty $property
326
     *
327
     * @return array
328
     */
329 21
    private function getPropertyImports(ReflectionProperty $property)
330
    {
331 21
        $class = $property->getDeclaringClass();
332 21
        $classImports = $this->getClassImports($class);
333
334 21
        $traitImports = [];
335
336 21
        foreach ($class->getTraits() as $trait) {
337 1
            if ($trait->hasProperty($property->getName())) {
338 1
                $traitImports = array_merge($traitImports, $this->phpParser->parseClass($trait));
339
            }
340
        }
341
342 21
        return array_merge($classImports, $traitImports);
343
    }
344
345
    /**
346
     * Retrieves imports for constants.
347
     *
348
     * @param \ReflectionClassConstant $constant
349
     *
350
     * @return array
351
     */
352 3
    private function getConstantImports(\ReflectionClassConstant $constant)
353
    {
354 3
        $class = $constant->getDeclaringClass();
355 3
        $classImports = $this->getClassImports($class);
356 3
        if (!method_exists($class, 'getTraits')) {
357
            return $classImports;
358
        }
359
360 3
        $traitImports = array();
361
362 3
        foreach ($class->getTraits() as $trait) {
363 1
            if ($trait->hasConstant($constant->getName())) {
364
                $traitImports = array_merge($traitImports, $this->phpParser->parseClass($trait));
365
            }
366
        }
367
368 3
        return array_merge($classImports, $traitImports);
369
    }
370
    
371
    /**
372
     * Collects parsing metadata for a given class.
373
     *
374
     * @param \ReflectionClass $class
375
     */
376 50
    private function collectParsingMetadata(ReflectionClass $class)
377
    {
378 50
        $ignoredAnnotationNames = self::$globalIgnoredNames;
379 50
        $annotations            = $this->preParser->parse($class->getDocComment(), 'class ' . $class->name);
380
381 50
        foreach ($annotations as $annotation) {
382 5
            if ($annotation instanceof IgnoreAnnotation) {
383 4
                foreach ($annotation->names AS $annot) {
384 4
                    $ignoredAnnotationNames[$annot] = true;
385
                }
386
            }
387
        }
388
389 50
        $name = $class->getName();
390
391 50
        $this->imports[$name] = array_merge(
392 50
            self::$globalImports,
393 50
            $this->phpParser->parseClass($class),
394 50
            ['__NAMESPACE__' => $class->getNamespaceName()]
395
        );
396
397 50
        $this->ignoredAnnotationNames[$name] = $ignoredAnnotationNames;
398 50
    }
399
}
400