vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationReader.php line 146

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