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