Passed
Pull Request — master (#289)
by
unknown
02:44
created

AnnotationReader   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 415
Duplicated Lines 0 %

Importance

Changes 14
Bugs 1 Features 1
Metric Value
eloc 150
c 14
b 1
f 1
dl 0
loc 415
rs 8.96
wmc 43

16 Methods

Rating   Name   Duplication   Size   Complexity  
A getIgnoredAnnotationNames() 0 12 3
A getMethodAnnotation() 0 11 3
A getClassAnnotations() 0 8 1
A getFunctionAnnotations() 0 10 1
A getFunctionAnnotation() 0 11 3
A getPropertyImports() 0 14 3
A getImports() 0 12 3
A getMethodAnnotations() 0 11 1
A collectParsingMetadata() 0 23 5
A getMethodImports() 0 16 4
A getPropertyAnnotation() 0 11 3
A addGlobalIgnoredNamespace() 0 3 1
B __construct() 0 21 7
A getPropertyAnnotations() 0 11 1
A addGlobalIgnoredName() 0 3 1
A getClassAnnotation() 0 11 3

How to fix   Complexity   

Complex Class

Complex classes like AnnotationReader often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AnnotationReader, and based on these observations, apply Extract Interface, too.

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

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