Completed
Pull Request — master (#213)
by
unknown
05:47
created

AnnotationReader::__construct()   B

Complexity

Conditions 9
Paths 3

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 9.2363

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 12
cts 14
cp 0.8571
rs 8.0555
c 0
b 0
f 0
cc 9
nc 3
nop 1
crap 9.2363
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)
0 ignored issues
show
Coding Style introduced by
As per PSR2, the static declaration should come after the visibility declaration.
Loading history...
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)
0 ignored issues
show
Coding Style introduced by
As per PSR2, the static declaration should come after the visibility declaration.
Loading history...
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+')	&&
166 84
			function_exists('ini_get') &&
167 84
			(ini_get('zend_optimizerplus.save_comments') === "0" || ini_get('opcache.save_comments') === "0")) {
168
            throw AnnotationException::optimizerPlusSaveComments();
169
        }
170
171 84
		if (extension_loaded('Zend OPcache') &&
172 84
			function_exists('ini_get') &&
173 84
			ini_get('opcache.save_comments') == 0) {
174
            throw AnnotationException::optimizerPlusSaveComments();
175
        }
176
177 84
        $this->parser = $parser ?: new DocParser();
178
179 84
        $this->preParser = new DocParser;
180
181 84
        $this->preParser->setImports(self::$globalImports);
182 84
        $this->preParser->setIgnoreNotImportedAnnotations(true);
183
184 84
        $this->phpParser = new PhpParser;
185 84
    }
186
187
    /**
188
     * {@inheritDoc}
189
     */
190 37
    public function getClassAnnotations(ReflectionClass $class)
191
    {
192 37
        $this->parser->setTarget(Target::TARGET_CLASS);
193 37
        $this->parser->setImports($this->getClassImports($class));
194 37
        $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
195 37
        $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
196
197 37
        return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName());
198
    }
199
200
    /**
201
     * {@inheritDoc}
202
     */
203 2
    public function getClassAnnotation(ReflectionClass $class, $annotationName)
204
    {
205 2
        $annotations = $this->getClassAnnotations($class);
206
207 2
        foreach ($annotations as $annotation) {
208 2
            if ($annotation instanceof $annotationName) {
209 2
                return $annotation;
210
            }
211
        }
212
213
        return null;
214
    }
215
216
    /**
217
     * {@inheritDoc}
218
     */
219 40
    public function getPropertyAnnotations(ReflectionProperty $property)
220
    {
221 40
        $class   = $property->getDeclaringClass();
222 40
        $context = 'property ' . $class->getName() . "::\$" . $property->getName();
223
224 40
        $this->parser->setTarget(Target::TARGET_PROPERTY);
225 40
        $this->parser->setImports($this->getPropertyImports($property));
226 40
        $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
227 40
        $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
228
229 40
        return $this->parser->parse($property->getDocComment(), $context);
230
    }
231
232
    /**
233
     * {@inheritDoc}
234
     */
235 3
    public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
236
    {
237 3
        $annotations = $this->getPropertyAnnotations($property);
238
239 3
        foreach ($annotations as $annotation) {
240 3
            if ($annotation instanceof $annotationName) {
241 3
                return $annotation;
242
            }
243
        }
244
245
        return null;
246
    }
247
248
    /**
249
     * {@inheritDoc}
250
     */
251 22
    public function getMethodAnnotations(ReflectionMethod $method)
252
    {
253 22
        $class   = $method->getDeclaringClass();
254 22
        $context = 'method ' . $class->getName() . '::' . $method->getName() . '()';
255
256 22
        $this->parser->setTarget(Target::TARGET_METHOD);
257 22
        $this->parser->setImports($this->getMethodImports($method));
258 22
        $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
259 22
        $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
260
261 22
        return $this->parser->parse($method->getDocComment(), $context);
262
    }
263
264
    /**
265
     * {@inheritDoc}
266
     */
267 1
    public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
268
    {
269 1
        $annotations = $this->getMethodAnnotations($method);
270
271 1
        foreach ($annotations as $annotation) {
272 1
            if ($annotation instanceof $annotationName) {
273 1
                return $annotation;
274
            }
275
        }
276
277
        return null;
278
    }
279
280
    /**
281
     * Returns the ignored annotations for the given class.
282
     *
283
     * @param \ReflectionClass $class
284
     *
285
     * @return array
286
     */
287 83
    private function getIgnoredAnnotationNames(ReflectionClass $class)
288
    {
289 83
        $name = $class->getName();
290 83
        if (isset($this->ignoredAnnotationNames[$name])) {
291 83
            return $this->ignoredAnnotationNames[$name];
292
        }
293
294
        $this->collectParsingMetadata($class);
295
296
        return $this->ignoredAnnotationNames[$name];
297
    }
298
299
    /**
300
     * Retrieves imports.
301
     *
302
     * @param \ReflectionClass $class
303
     *
304
     * @return array
305
     */
306 83
    private function getClassImports(ReflectionClass $class)
307
    {
308 83
        $name = $class->getName();
309 83
        if (isset($this->imports[$name])) {
310 15
            return $this->imports[$name];
311
        }
312
313 83
        $this->collectParsingMetadata($class);
314
315 83
        return $this->imports[$name];
316
    }
317
318
    /**
319
     * Retrieves imports for methods.
320
     *
321
     * @param \ReflectionMethod $method
322
     *
323
     * @return array
324
     */
325 22
    private function getMethodImports(ReflectionMethod $method)
326
    {
327 22
        $class = $method->getDeclaringClass();
328 22
        $classImports = $this->getClassImports($class);
329
330 22
        $traitImports = [];
331
332 22
        foreach ($class->getTraits() as $trait) {
333 2
            if ($trait->hasMethod($method->getName())
334 2
                && $trait->getFileName() === $method->getFileName()
335
            ) {
336 2
                $traitImports = array_merge($traitImports, $this->phpParser->parseClass($trait));
337
            }
338
        }
339
340 22
        return array_merge($classImports, $traitImports);
341
    }
342
343
    /**
344
     * Retrieves imports for properties.
345
     *
346
     * @param \ReflectionProperty $property
347
     *
348
     * @return array
349
     */
350 40
    private function getPropertyImports(ReflectionProperty $property)
351
    {
352 40
        $class = $property->getDeclaringClass();
353 40
        $classImports = $this->getClassImports($class);
354
355 40
        $traitImports = [];
356
357 40
        foreach ($class->getTraits() as $trait) {
358 1
            if ($trait->hasProperty($property->getName())) {
359 1
                $traitImports = array_merge($traitImports, $this->phpParser->parseClass($trait));
360
            }
361
        }
362
363 40
        return array_merge($classImports, $traitImports);
364
    }
365
366
    /**
367
     * Collects parsing metadata for a given class.
368
     *
369
     * @param \ReflectionClass $class
370
     */
371 83
    private function collectParsingMetadata(ReflectionClass $class)
372
    {
373 83
        $ignoredAnnotationNames = self::$globalIgnoredNames;
374 83
        $annotations            = $this->preParser->parse($class->getDocComment(), 'class ' . $class->name);
375
376 83
        foreach ($annotations as $annotation) {
377 9
            if ($annotation instanceof IgnoreAnnotation) {
378 8
                foreach ($annotation->names AS $annot) {
379 9
                    $ignoredAnnotationNames[$annot] = true;
380
                }
381
            }
382
        }
383
384 83
        $name = $class->getName();
385
386 83
        $this->imports[$name] = array_merge(
387 83
            self::$globalImports,
388 83
            $this->phpParser->parseClass($class),
389 83
            ['__NAMESPACE__' => $class->getNamespaceName()]
390
        );
391
392 83
        $this->ignoredAnnotationNames[$name] = $ignoredAnnotationNames;
393 83
    }
394
}
395