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

AnnotationReader::getConstantImports()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 4
eloc 9
c 2
b 0
f 0
nc 4
nop 1
dl 0
loc 17
rs 9.9666
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, 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

37
class AnnotationReader 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...
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
     * @throws AnnotationException
184
     */
185
    public function __construct(DocParser $parser = null)
186
    {
187
        if (extension_loaded('Zend Optimizer+') && (ini_get('zend_optimizerplus.save_comments') === "0" || ini_get('opcache.save_comments') === "0")) {
188
            throw AnnotationException::optimizerPlusSaveComments();
189
        }
190
191
        if (extension_loaded('Zend OPcache') && ini_get('opcache.save_comments') == 0) {
192
            throw AnnotationException::optimizerPlusSaveComments();
193
        }
194
195
        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. Annotations will be autoloaded in 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

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