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 |
|
|
|
|
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'); |
|
|
|
|
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
|
|
|
|
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.