Completed
Pull Request — master (#226)
by
unknown
04:00
created

AnnotationReader::getMethodImports()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 16
ccs 5
cts 5
cp 1
rs 10
c 0
b 0
f 0
cc 4
nc 3
nop 1
crap 4
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 = [
39
        // Annotation tags
40
        'Annotation' => true, 'Attribute' => true, 'Attributes' => true,
41
        /* Can we enable this? 'Enum' => true, */
42
        'Required' => true,
43
        'Target' => true,
44
        // Widely used tags (but not existent in phpdoc)
45
        'fix' => true , 'fixme' => true,
46
        'override' => true,
47
        // PHPDocumentor 1 tags
48
        'abstract'=> true, 'access'=> true,
49
        'code' => true,
50
        'deprec'=> true,
51
        'endcode' => true, 'exception'=> true,
52
        'final'=> true,
53
        'ingroup' => true, 'inheritdoc'=> true, 'inheritDoc'=> true,
54
        'magic' => true,
55
        'name'=> true,
56
        'toc' => true, 'tutorial'=> true,
57
        'private' => true,
58
        'static'=> true, 'staticvar'=> true, 'staticVar'=> true,
59
        'throw' => true,
60
        // PHPDocumentor 2 tags.
61
        'api' => true, 'author'=> true,
62
        'category'=> true, 'copyright'=> true,
63
        'deprecated'=> true,
64
        'example'=> true,
65
        'filesource'=> true,
66
        'global'=> true,
67
        'ignore'=> true, /* Can we enable this? 'index' => true, */ 'internal'=> true,
68
        'license'=> true, 'link'=> true,
69
        'method' => true,
70
        'package'=> true, 'param'=> true, 'property' => true, 'property-read' => true, 'property-write' => true,
71
        'return'=> true,
72
        'see'=> true, 'since'=> true, 'source' => true, 'subpackage'=> true,
73
        'throws'=> true, 'todo'=> true, 'TODO'=> true,
74
        'usedby'=> true, 'uses' => true,
75
        'var'=> true, 'version'=> true,
76
        // PHPUnit tags
77
        'codeCoverageIgnore' => true, 'codeCoverageIgnoreStart' => true, 'codeCoverageIgnoreEnd' => true,
78
        // PHPCheckStyle
79
        'SuppressWarnings' => true,
80
        // PHPStorm
81
        'noinspection' => true,
82
        // PEAR
83
        'package_version' => true,
84
        // PlantUML
85
        'startuml' => true, 'enduml' => true,
86
        // Symfony 3.3 Cache Adapter
87
        'experimental' => true
88
    ];
89
90
    /**
91
     * A list with annotations that are not causing exceptions when not resolved to an annotation class.
92
     *
93
     * The names are case sensitive.
94
     *
95
     * @var array
96
     */
97
    private static $globalIgnoredNamespaces = [];
98
99
    /**
100
     * Add a new annotation to the globally ignored annotation names with regard to exception handling.
101
     *
102
     * @param string $name
103
     */
104
    static public function addGlobalIgnoredName($name)
105
    {
106
        self::$globalIgnoredNames[$name] = true;
107
    }
108
109
    /**
110
     * Add a new annotation to the globally ignored annotation namespaces with regard to exception handling.
111
     *
112
     * @param string $namespace
113
     */
114 3
    static public function addGlobalIgnoredNamespace($namespace)
115
    {
116 3
        self::$globalIgnoredNamespaces[$namespace] = true;
117 3
    }
118
119
    /**
120
     * Annotations parser.
121
     *
122
     * @var \Doctrine\Annotations\DocParser
123
     */
124
    private $parser;
125
126
    /**
127
     * Annotations parser used to collect parsing metadata.
128
     *
129
     * @var \Doctrine\Annotations\DocParser
130
     */
131
    private $preParser;
132
133
    /**
134
     * PHP parser used to collect imports.
135
     *
136
     * @var \Doctrine\Annotations\PhpParser
137
     */
138
    private $phpParser;
139
140
    /**
141
     * In-memory cache mechanism to store imported annotations per class.
142
     *
143
     * @var array
144
     */
145
    private $imports = [];
146
147
    /**
148
     * In-memory cache mechanism to store ignored annotations per class.
149
     *
150
     * @var array
151
     */
152
    private $ignoredAnnotationNames = [];
153
154
    /**
155
     * Constructor.
156
     *
157
     * Initializes a new AnnotationReader.
158
     *
159
     * @param DocParser $parser
160
     *
161
     * @throws AnnotationException
162
     */
163 84
    public function __construct(DocParser $parser = null)
164
    {
165 84
        if (extension_loaded('Zend Optimizer+') && (ini_get('zend_optimizerplus.save_comments') === "0" || ini_get('opcache.save_comments') === "0")) {
166
            throw AnnotationException::optimizerPlusSaveComments();
167
        }
168
169 84
        if (extension_loaded('Zend OPcache') && ini_get('opcache.save_comments') == 0) {
170
            throw AnnotationException::optimizerPlusSaveComments();
171
        }
172
173 84
        $this->parser = $parser ?: new DocParser();
174
175 84
        $this->preParser = new DocParser;
176
177 84
        $this->preParser->setImports(self::$globalImports);
178 84
        $this->preParser->setIgnoreNotImportedAnnotations(true);
179
180 84
        $this->phpParser = new PhpParser;
181 84
    }
182
183
    /**
184
     * {@inheritDoc}
185
     */
186 37
    public function getClassAnnotations(ReflectionClass $class)
187
    {
188 37
        $this->parser->setTarget(Target::TARGET_CLASS);
189 37
        $this->parser->setImports($this->getClassImports($class));
190 37
        $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
191 37
        $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
192
193 37
        return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName());
194
    }
195
196
    /**
197
     * {@inheritDoc}
198
     */
199 2
    public function getClassAnnotation(ReflectionClass $class, $annotationName)
200
    {
201 2
        $annotations = $this->getClassAnnotations($class);
202
203 2
        foreach ($annotations as $annotation) {
204 2
            if ($annotation instanceof $annotationName) {
205 2
                return $annotation;
206
            }
207
        }
208
209
        return null;
210
    }
211
212
    /**
213
     * {@inheritDoc}
214
     */
215 40
    public function getPropertyAnnotations(ReflectionProperty $property)
216
    {
217 40
        $class   = $property->getDeclaringClass();
218 40
        $context = 'property ' . $class->getName() . "::\$" . $property->getName();
219
220 40
        $this->parser->setTarget(Target::TARGET_PROPERTY);
221 40
        $this->parser->setImports($this->getPropertyImports($property));
222 40
        $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
223 40
        $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
224
225 40
        return $this->parser->parse($property->getDocComment(), $context);
226
    }
227
228
    /**
229
     * {@inheritDoc}
230
     */
231 3
    public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
232
    {
233 3
        $annotations = $this->getPropertyAnnotations($property);
234
235 3
        foreach ($annotations as $annotation) {
236 3
            if ($annotation instanceof $annotationName) {
237 3
                return $annotation;
238
            }
239
        }
240
241
        return null;
242
    }
243
244
    /**
245
     * {@inheritDoc}
246
     */
247 22
    public function getMethodAnnotations(ReflectionMethod $method)
248
    {
249 22
        $class   = $method->getDeclaringClass();
250 22
        $context = 'method ' . $class->getName() . '::' . $method->getName() . '()';
251
252 22
        $this->parser->setTarget(Target::TARGET_METHOD);
253 22
        $this->parser->setImports($this->getMethodImports($method));
254 22
        $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
255 22
        $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
256
257 22
        return $this->parser->parse($method->getDocComment(), $context);
258
    }
259
260
    /**
261
     * {@inheritDoc}
262
     */
263 1
    public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
264
    {
265 1
        $annotations = $this->getMethodAnnotations($method);
266
267 1
        foreach ($annotations as $annotation) {
268 1
            if ($annotation instanceof $annotationName) {
269 1
                return $annotation;
270
            }
271
        }
272
273
        return null;
274
    }
275
276
    /**
277
     * {@inheritDoc}
278
     */
279
    public function getConstantAnnotations(\ReflectionClassConstant $constant)
280
    {
281
        $class   = $constant->getDeclaringClass();
282
        $context = 'constant ' . $class->getName() . "::\$" . $constant->getName();
283 83
284
        $this->parser->setTarget(Target::TARGET_CONSTANT);
285 83
        $this->parser->setImports($this->getConstantImports($constant));
286 83
        $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
287 83
        $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
288
289
        return $this->parser->parse($constant->getDocComment(), $context);
290
    }
291
292
    /**
293
     * {@inheritDoc}
294
     */
295
    public function getConstantAnnotation(\ReflectionClassConstant $constant, $annotationName)
296
    {
297
        $annotations = $this->getConstantAnnotations($constant);
298
299
        foreach ($annotations as $annotation) {
300
            if ($annotation instanceof $annotationName) {
301
                return $annotation;
302 83
            }
303
        }
304 83
305 83
        return null;
306 15
    }
307
308
    /**
309 83
     * Returns the ignored annotations for the given class.
310
     *
311 83
     * @param \ReflectionClass $class
312
     *
313
     * @return array
314
     */
315
    private function getIgnoredAnnotationNames(ReflectionClass $class)
316
    {
317
        $name = $class->getName();
318
        if (isset($this->ignoredAnnotationNames[$name])) {
319
            return $this->ignoredAnnotationNames[$name];
320
        }
321 22
322
        $this->collectParsingMetadata($class);
323 22
324 22
        return $this->ignoredAnnotationNames[$name];
325
    }
326 22
327
    /**
328 22
     * Retrieves imports.
329 2
     *
330 2
     * @param \ReflectionClass $class
331
     *
332 2
     * @return array
333
     */
334
    private function getClassImports(ReflectionClass $class)
335
    {
336 22
        $name = $class->getName();
337
        if (isset($this->imports[$name])) {
338
            return $this->imports[$name];
339
        }
340
341
        $this->collectParsingMetadata($class);
342
343
        return $this->imports[$name];
344
    }
345
346 40
    /**
347
     * Retrieves imports for methods.
348 40
     *
349 40
     * @param \ReflectionMethod $method
350
     *
351 40
     * @return array
352
     */
353 40
    private function getMethodImports(ReflectionMethod $method)
354 1
    {
355 1
        $class = $method->getDeclaringClass();
356
        $classImports = $this->getClassImports($class);
357
358
        $traitImports = [];
359 40
360
        foreach ($class->getTraits() as $trait) {
361
            if ($trait->hasMethod($method->getName())
362
                && $trait->getFileName() === $method->getFileName()
363
            ) {
364
                $traitImports = array_merge($traitImports, $this->phpParser->parseClass($trait));
365
            }
366
        }
367 83
368
        return array_merge($classImports, $traitImports);
369 83
    }
370 83
371
    /**
372 83
     * Retrieves imports for properties.
373 9
     *
374 8
     * @param \ReflectionProperty $property
375 9
     *
376
     * @return array
377
     */
378
    private function getPropertyImports(ReflectionProperty $property)
379
    {
380 83
        $class = $property->getDeclaringClass();
381
        $classImports = $this->getClassImports($class);
382 83
383 83
        $traitImports = [];
384 83
385 83
        foreach ($class->getTraits() as $trait) {
386
            if ($trait->hasProperty($property->getName())) {
387
                $traitImports = array_merge($traitImports, $this->phpParser->parseClass($trait));
388 83
            }
389 83
        }
390
391
        return array_merge($classImports, $traitImports);
392
    }
393
394
    /**
395
     * Retrieves imports for constants.
396
     *
397
     * @param \ReflectionClassConstant $constant
398
     *
399
     * @return array
400
     */
401
    private function getConstantImports(\ReflectionClassConstant $constant)
402
    {
403
        $class = $constant->getDeclaringClass();
404
        $classImports = $this->getClassImports($class);
405
        if (!method_exists($class, 'getTraits')) {
406
            return $classImports;
407
        }
408
409
        $traitImports = array();
410
411
        foreach ($class->getTraits() as $trait) {
412
            if ($trait->hasProperty($constant->getName())) {
413
                $traitImports = array_merge($traitImports, $this->phpParser->parseClass($trait));
414
            }
415
        }
416
417
        return array_merge($classImports, $traitImports);
418
    }
419
    
420
    /**
421
     * Collects parsing metadata for a given class.
422
     *
423
     * @param \ReflectionClass $class
424
     */
425
    private function collectParsingMetadata(ReflectionClass $class)
426
    {
427
        $ignoredAnnotationNames = self::$globalIgnoredNames;
428
        $annotations            = $this->preParser->parse($class->getDocComment(), 'class ' . $class->name);
429
430
        foreach ($annotations as $annotation) {
431
            if ($annotation instanceof IgnoreAnnotation) {
432
                foreach ($annotation->names AS $annot) {
433
                    $ignoredAnnotationNames[$annot] = true;
434
                }
435
            }
436
        }
437
438
        $name = $class->getName();
439
440
        $this->imports[$name] = array_merge(
441
            self::$globalImports,
442
            $this->phpParser->parseClass($class),
443
            ['__NAMESPACE__' => $class->getNamespaceName()]
444
        );
445
446
        $this->ignoredAnnotationNames[$name] = $ignoredAnnotationNames;
447
    }
448
}
449