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

AnnotationReader::getIgnoredAnnotationNames()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 5
nc 2
nop 1
dl 0
loc 10
rs 10
c 1
b 0
f 0
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\Annotations\Annotation\IgnoreAnnotation;
23
use Doctrine\Common\Annotations\Annotation\Target;
24
use ReflectionClass;
25
use ReflectionMethod;
26
use ReflectionProperty;
27
28
/**
29
 * A reader for docblock annotations.
30
 *
31
 * @author  Benjamin Eberlei <[email protected]>
32
 * @author  Guilherme Blanco <[email protected]>
33
 * @author  Jonathan Wage <[email protected]>
34
 * @author  Roman Borschel <[email protected]>
35
 * @author  Johannes M. Schmitt <[email protected]>
36
 */
37
class AnnotationReader implements Reader
38
{
39
    /**
40
     * Global map for imports.
41
     *
42
     * @var array
43
     */
44
    private static $globalImports = [
45
        'ignoreannotation' => 'Doctrine\Common\Annotations\Annotation\IgnoreAnnotation',
46
    ];
47
48
    /**
49
     * A list with annotations that are not causing exceptions when not resolved to an annotation class.
50
     *
51
     * The names are case sensitive.
52
     *
53
     * @var array
54
     */
55
    private static $globalIgnoredNames = [
56
        // Annotation tags
57
        'Annotation' => true, 'Attribute' => true, 'Attributes' => true,
58
        /* Can we enable this? 'Enum' => true, */
59
        'Required' => true,
60
        'Target' => true,
61
        // Widely used tags (but not existent in phpdoc)
62
        'fix' => true , 'fixme' => true,
63
        'override' => true,
64
        // PHPDocumentor 1 tags
65
        'abstract'=> true, 'access'=> true,
66
        'code' => true,
67
        'deprec'=> true,
68
        'endcode' => true, 'exception'=> true,
69
        'final'=> true,
70
        'ingroup' => true, 'inheritdoc'=> true, 'inheritDoc'=> true,
71
        'magic' => true,
72
        'name'=> true,
73
        'toc' => true, 'tutorial'=> true,
74
        'private' => true,
75
        'static'=> true, 'staticvar'=> true, 'staticVar'=> true,
76
        'throw' => true,
77
        // PHPDocumentor 2 tags.
78
        'api' => true, 'author'=> true,
79
        'category'=> true, 'copyright'=> true,
80
        'deprecated'=> true,
81
        'example'=> true,
82
        'filesource'=> true,
83
        'global'=> true,
84
        'ignore'=> true, /* Can we enable this? 'index' => true, */ 'internal'=> true,
85
        'license'=> true, 'link'=> true,
86
        'method' => true,
87
        'package'=> true, 'param'=> true, 'property' => true, 'property-read' => true, 'property-write' => true,
88
        'return'=> true,
89
        'see'=> true, 'since'=> true, 'source' => true, 'subpackage'=> true,
90
        'throws'=> true, 'todo'=> true, 'TODO'=> true,
91
        'usedby'=> true, 'uses' => true,
92
        'var'=> true, 'version'=> true,
93
        // PHPUnit tags
94
        'codeCoverageIgnore' => true, 'codeCoverageIgnoreStart' => true, 'codeCoverageIgnoreEnd' => true,
95
        // PHPCheckStyle
96
        'SuppressWarnings' => true,
97
        // PHPStorm
98
        'noinspection' => true,
99
        // PEAR
100
        'package_version' => true,
101
        // PlantUML
102
        'startuml' => true, 'enduml' => true,
103
        // Symfony 3.3 Cache Adapter
104
        'experimental' => true,
105
        // Slevomat Coding Standard
106
        'phpcsSuppress' => true,
107
        // PHP CodeSniffer
108
        'codingStandardsIgnoreStart' => true,
109
        'codingStandardsIgnoreEnd' => true,
110
        // PHPStan
111
        'template' => true, 'implements' => true, 'extends' => true, 'use' => true,
112
    ];
113
114
    /**
115
     * A list with annotations that are not causing exceptions when not resolved to an annotation class.
116
     *
117
     * The names are case sensitive.
118
     *
119
     * @var array
120
     */
121
    private static $globalIgnoredNamespaces = [];
122
123
    /**
124
     * Add a new annotation to the globally ignored annotation names with regard to exception handling.
125
     *
126
     * @param string $name
127
     */
128
    static public function addGlobalIgnoredName($name)
129
    {
130
        self::$globalIgnoredNames[$name] = true;
131
    }
132
133
    /**
134
     * Add a new annotation to the globally ignored annotation namespaces with regard to exception handling.
135
     *
136
     * @param string $namespace
137
     */
138
    static public function addGlobalIgnoredNamespace($namespace)
139
    {
140
        self::$globalIgnoredNamespaces[$namespace] = true;
141
    }
142
143
    /**
144
     * Annotations parser.
145
     *
146
     * @var \Doctrine\Common\Annotations\DocParser
147
     */
148
    private $parser;
149
150
    /**
151
     * Annotations parser used to collect parsing metadata.
152
     *
153
     * @var \Doctrine\Common\Annotations\DocParser
154
     */
155
    private $preParser;
156
157
    /**
158
     * PHP parser used to collect imports.
159
     *
160
     * @var \Doctrine\Common\Annotations\PhpParser
161
     */
162
    private $phpParser;
163
164
    /**
165
     * In-memory cache mechanism to store imported annotations per class.
166
     *
167
     * @var array
168
     */
169
    private $imports = [];
170
171
    /**
172
     * In-memory cache mechanism to store ignored annotations per class.
173
     *
174
     * @var array
175
     */
176
    private $ignoredAnnotationNames = [];
177
178
    /**
179
     * Constructor.
180
     *
181
     * Initializes a new AnnotationReader.
182
     *
183
     * @param DocParser $parser
184
     *
185
     * @throws AnnotationException
186
     */
187
    public function __construct(DocParser $parser = null)
188
    {
189
        if (extension_loaded('Zend Optimizer+') && (ini_get('zend_optimizerplus.save_comments') === "0" || ini_get('opcache.save_comments') === "0")) {
190
            throw AnnotationException::optimizerPlusSaveComments();
191
        }
192
193
        if (extension_loaded('Zend OPcache') && ini_get('opcache.save_comments') == 0) {
194
            throw AnnotationException::optimizerPlusSaveComments();
195
        }
196
197
        AnnotationRegistry::registerFile(__DIR__ . '/Annotation/IgnoreAnnotation.php');
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\Common\Annotati...egistry::registerFile() has been deprecated: this method is deprecated and will be removed in doctrine/annotations 2.0 autoloading should be deferred to the globally registered autoloader by then. For now, use @example AnnotationRegistry::registerLoader('class_exists') ( Ignorable by Annotation )

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

197
        /** @scrutinizer ignore-deprecated */ AnnotationRegistry::registerFile(__DIR__ . '/Annotation/IgnoreAnnotation.php');

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

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

Loading history...
198
199
        $this->parser = $parser ?: new DocParser();
200
201
        $this->preParser = new DocParser;
202
203
        $this->preParser->setImports(self::$globalImports);
204
        $this->preParser->setIgnoreNotImportedAnnotations(true);
205
        $this->preParser->setIgnoredAnnotationNames(self::$globalIgnoredNames);
206
207
        $this->phpParser = new PhpParser;
208
    }
209
210
    /**
211
     * {@inheritDoc}
212
     */
213
    public function getClassAnnotations(ReflectionClass $class)
214
    {
215
        $this->parser->setTarget(Target::TARGET_CLASS);
216
        $this->parser->setImports($this->getClassImports($class));
217
        $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
218
        $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
219
220
        return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName());
221
    }
222
223
    /**
224
     * {@inheritDoc}
225
     */
226
    public function getClassAnnotation(ReflectionClass $class, $annotationName)
227
    {
228
        $annotations = $this->getClassAnnotations($class);
229
230
        foreach ($annotations as $annotation) {
231
            if ($annotation instanceof $annotationName) {
232
                return $annotation;
233
            }
234
        }
235
236
        return null;
237
    }
238
239
    /**
240
     * {@inheritDoc}
241
     */
242
    public function getPropertyAnnotations(ReflectionProperty $property)
243
    {
244
        $class   = $property->getDeclaringClass();
245
        $context = 'property ' . $class->getName() . "::\$" . $property->getName();
246
247
        $this->parser->setTarget(Target::TARGET_PROPERTY);
248
        $this->parser->setImports($this->getPropertyImports($property));
249
        $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
250
        $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
251
252
        return $this->parser->parse($property->getDocComment(), $context);
253
    }
254
255
    /**
256
     * {@inheritDoc}
257
     */
258
    public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
259
    {
260
        $annotations = $this->getPropertyAnnotations($property);
261
262
        foreach ($annotations as $annotation) {
263
            if ($annotation instanceof $annotationName) {
264
                return $annotation;
265
            }
266
        }
267
268
        return null;
269
    }
270
271
    /**
272
     * {@inheritDoc}
273
     */
274
    public function getMethodAnnotations(ReflectionMethod $method)
275
    {
276
        $class   = $method->getDeclaringClass();
277
        $context = 'method ' . $class->getName() . '::' . $method->getName() . '()';
278
279
        $this->parser->setTarget(Target::TARGET_METHOD);
280
        $this->parser->setImports($this->getMethodImports($method));
281
        $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
282
        $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
283
284
        return $this->parser->parse($method->getDocComment(), $context);
285
    }
286
287
    /**
288
     * {@inheritDoc}
289
     */
290
    public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
291
    {
292
        $annotations = $this->getMethodAnnotations($method);
293
294
        foreach ($annotations as $annotation) {
295
            if ($annotation instanceof $annotationName) {
296
                return $annotation;
297
            }
298
        }
299
300
        return null;
301
    }
302
303
    /**
304
     * Returns the ignored annotations for the given class.
305
     *
306
     * @param \ReflectionClass $class
307
     *
308
     * @return array
309
     */
310
    private function getIgnoredAnnotationNames(ReflectionClass $class)
311
    {
312
        $name = $class->getName();
313
        if (isset($this->ignoredAnnotationNames[$name])) {
314
            return $this->ignoredAnnotationNames[$name];
315
        }
316
317
        $this->collectParsingMetadata($class);
318
319
        return $this->ignoredAnnotationNames[$name];
320
    }
321
322
    /**
323
     * Retrieves imports.
324
     *
325
     * @param \ReflectionClass $class
326
     *
327
     * @return array
328
     */
329
    private function getClassImports(ReflectionClass $class)
330
    {
331
        $name = $class->getName();
332
        if (isset($this->imports[$name])) {
333
            return $this->imports[$name];
334
        }
335
336
        $this->collectParsingMetadata($class);
337
338
        return $this->imports[$name];
339
    }
340
341
    /**
342
     * Retrieves imports for methods.
343
     *
344
     * @param \ReflectionMethod $method
345
     *
346
     * @return array
347
     */
348
    private function getMethodImports(ReflectionMethod $method)
349
    {
350
        $class = $method->getDeclaringClass();
351
        $classImports = $this->getClassImports($class);
352
353
        $traitImports = [];
354
355
        foreach ($class->getTraits() as $trait) {
356
            if ($trait->hasMethod($method->getName())
357
                && $trait->getFileName() === $method->getFileName()
358
            ) {
359
                $traitImports = array_merge($traitImports, $this->phpParser->parseClass($trait));
360
            }
361
        }
362
363
        return array_merge($classImports, $traitImports);
364
    }
365
366
    /**
367
     * Retrieves imports for properties.
368
     *
369
     * @param \ReflectionProperty $property
370
     *
371
     * @return array
372
     */
373
    private function getPropertyImports(ReflectionProperty $property)
374
    {
375
        $class = $property->getDeclaringClass();
376
        $classImports = $this->getClassImports($class);
377
378
        $traitImports = [];
379
380
        foreach ($class->getTraits() as $trait) {
381
            if ($trait->hasProperty($property->getName())) {
382
                $traitImports = array_merge($traitImports, $this->phpParser->parseClass($trait));
383
            }
384
        }
385
386
        return array_merge($classImports, $traitImports);
387
    }
388
389
    /**
390
     * Collects parsing metadata for a given class.
391
     *
392
     * @param \ReflectionClass $class
393
     */
394
    private function collectParsingMetadata(ReflectionClass $class)
395
    {
396
        $ignoredAnnotationNames = self::$globalIgnoredNames;
397
        $annotations            = $this->preParser->parse($class->getDocComment(), 'class ' . $class->name);
398
399
        foreach ($annotations as $annotation) {
400
            if ($annotation instanceof IgnoreAnnotation) {
401
                foreach ($annotation->names AS $annot) {
402
                    $ignoredAnnotationNames[$annot] = true;
403
                }
404
            }
405
        }
406
407
        $name = $class->getName();
408
409
        $this->imports[$name] = array_merge(
410
            self::$globalImports,
411
            $this->phpParser->parseClass($class),
412
            ['__NAMESPACE__' => $class->getNamespaceName()]
413
        );
414
415
        $this->ignoredAnnotationNames[$name] = $ignoredAnnotationNames;
416
    }
417
}
418