AnnotationReader::addGlobalIgnoredNamespace()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
namespace Doctrine\Common\Annotations;
4
5
use Doctrine\Common\Annotations\Annotation\IgnoreAnnotation;
6
use Doctrine\Common\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' => Annotation\IgnoreAnnotation::class,
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 = ImplicitlyIgnoredAnnotationNames::LIST;
39
40
    /**
41
     * A list with annotations that are not causing exceptions when not resolved to an annotation class.
42
     *
43
     * The names are case sensitive.
44
     *
45
     * @var array
46
     */
47
    private static $globalIgnoredNamespaces = [];
48
49
    /**
50
     * Add a new annotation to the globally ignored annotation names with regard to exception handling.
51
     *
52
     * @param string $name
53
     */
54
    static public function addGlobalIgnoredName($name)
55
    {
56
        self::$globalIgnoredNames[$name] = true;
57
    }
58
59
    /**
60
     * Add a new annotation to the globally ignored annotation namespaces with regard to exception handling.
61
     *
62
     * @param string $namespace
63
     */
64
    static public function addGlobalIgnoredNamespace($namespace)
65
    {
66
        self::$globalIgnoredNamespaces[$namespace] = true;
67
    }
68
69
    /**
70
     * Annotations parser.
71
     *
72
     * @var \Doctrine\Common\Annotations\DocParser
73
     */
74
    private $parser;
75
76
    /**
77
     * Annotations parser used to collect parsing metadata.
78
     *
79
     * @var \Doctrine\Common\Annotations\DocParser
80
     */
81
    private $preParser;
82
83
    /**
84
     * PHP parser used to collect imports.
85
     *
86
     * @var \Doctrine\Common\Annotations\PhpParser
87
     */
88
    private $phpParser;
89
90
    /**
91
     * In-memory cache mechanism to store imported annotations per class.
92
     *
93
     * @var array
94
     */
95
    private $imports = [];
96
97
    /**
98
     * In-memory cache mechanism to store ignored annotations per class.
99
     *
100
     * @var array
101
     */
102
    private $ignoredAnnotationNames = [];
103
104
    /**
105
     * Constructor.
106
     *
107
     * Initializes a new AnnotationReader.
108
     *
109
     * @param DocParser $parser
110
     *
111
     * @throws AnnotationException
112
     */
113
    public function __construct(DocParser $parser = null)
114
    {
115
        if (extension_loaded('Zend Optimizer+') && (ini_get('zend_optimizerplus.save_comments') === "0" || ini_get('opcache.save_comments') === "0")) {
116
            throw AnnotationException::optimizerPlusSaveComments();
117
        }
118
119
        if (extension_loaded('Zend OPcache') && ini_get('opcache.save_comments') == 0) {
120
            throw AnnotationException::optimizerPlusSaveComments();
121
        }
122
123
        // Make sure that the IgnoreAnnotation annotation is loaded
124
        class_exists(IgnoreAnnotation::class);
125
126
        $this->parser = $parser ?: new DocParser();
127
128
        $this->preParser = new DocParser;
129
130
        $this->preParser->setImports(self::$globalImports);
131
        $this->preParser->setIgnoreNotImportedAnnotations(true);
132
        $this->preParser->setIgnoredAnnotationNames(self::$globalIgnoredNames);
133
134
        $this->phpParser = new PhpParser;
135
    }
136
137
    /**
138
     * {@inheritDoc}
139
     */
140
    public function getClassAnnotations(ReflectionClass $class)
141
    {
142
        $this->parser->setTarget(Target::TARGET_CLASS);
143
        $this->parser->setImports($this->getClassImports($class));
144
        $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
145
        $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
146
147
        return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName());
148
    }
149
150
    /**
151
     * {@inheritDoc}
152
     */
153
    public function getClassAnnotation(ReflectionClass $class, $annotationName)
154
    {
155
        $annotations = $this->getClassAnnotations($class);
156
157
        foreach ($annotations as $annotation) {
158
            if ($annotation instanceof $annotationName) {
159
                return $annotation;
160
            }
161
        }
162
163
        return null;
164
    }
165
166
    /**
167
     * {@inheritDoc}
168
     */
169
    public function getPropertyAnnotations(ReflectionProperty $property)
170
    {
171
        $class   = $property->getDeclaringClass();
172
        $context = 'property ' . $class->getName() . "::\$" . $property->getName();
173
174
        $this->parser->setTarget(Target::TARGET_PROPERTY);
175
        $this->parser->setImports($this->getPropertyImports($property));
176
        $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
177
        $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
178
179
        return $this->parser->parse($property->getDocComment(), $context);
180
    }
181
182
    /**
183
     * {@inheritDoc}
184
     */
185
    public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
186
    {
187
        $annotations = $this->getPropertyAnnotations($property);
188
189
        foreach ($annotations as $annotation) {
190
            if ($annotation instanceof $annotationName) {
191
                return $annotation;
192
            }
193
        }
194
195
        return null;
196
    }
197
198
    /**
199
     * {@inheritDoc}
200
     */
201
    public function getMethodAnnotations(ReflectionMethod $method)
202
    {
203
        $class   = $method->getDeclaringClass();
204
        $context = 'method ' . $class->getName() . '::' . $method->getName() . '()';
205
206
        $this->parser->setTarget(Target::TARGET_METHOD);
207
        $this->parser->setImports($this->getMethodImports($method));
208
        $this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
209
        $this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
210
211
        return $this->parser->parse($method->getDocComment(), $context);
212
    }
213
214
    /**
215
     * {@inheritDoc}
216
     */
217
    public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
218
    {
219
        $annotations = $this->getMethodAnnotations($method);
220
221
        foreach ($annotations as $annotation) {
222
            if ($annotation instanceof $annotationName) {
223
                return $annotation;
224
            }
225
        }
226
227
        return null;
228
    }
229
230
    /**
231
     * Returns the ignored annotations for the given class.
232
     *
233
     * @param \ReflectionClass $class
234
     *
235
     * @return array
236
     */
237
    private function getIgnoredAnnotationNames(ReflectionClass $class)
238
    {
239
        $name = $class->getName();
240
        if (isset($this->ignoredAnnotationNames[$name])) {
241
            return $this->ignoredAnnotationNames[$name];
242
        }
243
244
        $this->collectParsingMetadata($class);
245
246
        return $this->ignoredAnnotationNames[$name];
247
    }
248
249
    /**
250
     * Retrieves imports.
251
     *
252
     * @param \ReflectionClass $class
253
     *
254
     * @return array
255
     */
256
    private function getClassImports(ReflectionClass $class)
257
    {
258
        $name = $class->getName();
259
        if (isset($this->imports[$name])) {
260
            return $this->imports[$name];
261
        }
262
263
        $this->collectParsingMetadata($class);
264
265
        return $this->imports[$name];
266
    }
267
268
    /**
269
     * Retrieves imports for methods.
270
     *
271
     * @param \ReflectionMethod $method
272
     *
273
     * @return array
274
     */
275
    private function getMethodImports(ReflectionMethod $method)
276
    {
277
        $class = $method->getDeclaringClass();
278
        $classImports = $this->getClassImports($class);
279
280
        $traitImports = [];
281
282
        foreach ($class->getTraits() as $trait) {
283
            if ($trait->hasMethod($method->getName())
284
                && $trait->getFileName() === $method->getFileName()
285
            ) {
286
                $traitImports = array_merge($traitImports, $this->phpParser->parseClass($trait));
287
            }
288
        }
289
290
        return array_merge($classImports, $traitImports);
291
    }
292
293
    /**
294
     * Retrieves imports for properties.
295
     *
296
     * @param \ReflectionProperty $property
297
     *
298
     * @return array
299
     */
300
    private function getPropertyImports(ReflectionProperty $property)
301
    {
302
        $class = $property->getDeclaringClass();
303
        $classImports = $this->getClassImports($class);
304
305
        $traitImports = [];
306
307
        foreach ($class->getTraits() as $trait) {
308
            if ($trait->hasProperty($property->getName())) {
309
                $traitImports = array_merge($traitImports, $this->phpParser->parseClass($trait));
310
            }
311
        }
312
313
        return array_merge($classImports, $traitImports);
314
    }
315
316
    /**
317
     * Collects parsing metadata for a given class.
318
     *
319
     * @param \ReflectionClass $class
320
     */
321
    private function collectParsingMetadata(ReflectionClass $class)
322
    {
323
        $ignoredAnnotationNames = self::$globalIgnoredNames;
324
        $annotations            = $this->preParser->parse($class->getDocComment(), 'class ' . $class->name);
325
326
        foreach ($annotations as $annotation) {
327
            if ($annotation instanceof IgnoreAnnotation) {
328
                foreach ($annotation->names AS $annot) {
329
                    $ignoredAnnotationNames[$annot] = true;
330
                }
331
            }
332
        }
333
334
        $name = $class->getName();
335
336
        $this->imports[$name] = array_merge(
337
            self::$globalImports,
338
            $this->phpParser->parseClass($class),
339
            ['__NAMESPACE__' => $class->getNamespaceName()]
340
        );
341
342
        $this->ignoredAnnotationNames[$name] = $ignoredAnnotationNames;
343
    }
344
}
345