DocParser.php 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487
  1. <?php
  2. namespace Doctrine\Common\Annotations;
  3. use Doctrine\Common\Annotations\Annotation\Attribute;
  4. use Doctrine\Common\Annotations\Annotation\Attributes;
  5. use Doctrine\Common\Annotations\Annotation\Enum;
  6. use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
  7. use Doctrine\Common\Annotations\Annotation\Target;
  8. use ReflectionClass;
  9. use ReflectionException;
  10. use ReflectionProperty;
  11. use RuntimeException;
  12. use stdClass;
  13. use Throwable;
  14. use function array_keys;
  15. use function array_map;
  16. use function array_pop;
  17. use function array_values;
  18. use function class_exists;
  19. use function constant;
  20. use function count;
  21. use function defined;
  22. use function explode;
  23. use function gettype;
  24. use function implode;
  25. use function in_array;
  26. use function interface_exists;
  27. use function is_array;
  28. use function is_object;
  29. use function json_encode;
  30. use function ltrim;
  31. use function preg_match;
  32. use function reset;
  33. use function rtrim;
  34. use function sprintf;
  35. use function stripos;
  36. use function strlen;
  37. use function strpos;
  38. use function strrpos;
  39. use function strtolower;
  40. use function substr;
  41. use function trim;
  42. use const PHP_VERSION_ID;
  43. /**
  44. * A parser for docblock annotations.
  45. *
  46. * It is strongly discouraged to change the default annotation parsing process.
  47. */
  48. final class DocParser
  49. {
  50. /**
  51. * An array of all valid tokens for a class name.
  52. *
  53. * @phpstan-var list<int>
  54. */
  55. private static $classIdentifiers = [
  56. DocLexer::T_IDENTIFIER,
  57. DocLexer::T_TRUE,
  58. DocLexer::T_FALSE,
  59. DocLexer::T_NULL,
  60. ];
  61. /**
  62. * The lexer.
  63. *
  64. * @var DocLexer
  65. */
  66. private $lexer;
  67. /**
  68. * Current target context.
  69. *
  70. * @var int
  71. */
  72. private $target;
  73. /**
  74. * Doc parser used to collect annotation target.
  75. *
  76. * @var DocParser
  77. */
  78. private static $metadataParser;
  79. /**
  80. * Flag to control if the current annotation is nested or not.
  81. *
  82. * @var bool
  83. */
  84. private $isNestedAnnotation = false;
  85. /**
  86. * Hashmap containing all use-statements that are to be used when parsing
  87. * the given doc block.
  88. *
  89. * @var array<string, class-string>
  90. */
  91. private $imports = [];
  92. /**
  93. * This hashmap is used internally to cache results of class_exists()
  94. * look-ups.
  95. *
  96. * @var array<class-string, bool>
  97. */
  98. private $classExists = [];
  99. /**
  100. * Whether annotations that have not been imported should be ignored.
  101. *
  102. * @var bool
  103. */
  104. private $ignoreNotImportedAnnotations = false;
  105. /**
  106. * An array of default namespaces if operating in simple mode.
  107. *
  108. * @var string[]
  109. */
  110. private $namespaces = [];
  111. /**
  112. * A list with annotations that are not causing exceptions when not resolved to an annotation class.
  113. *
  114. * The names must be the raw names as used in the class, not the fully qualified
  115. *
  116. * @var bool[] indexed by annotation name
  117. */
  118. private $ignoredAnnotationNames = [];
  119. /**
  120. * A list with annotations in namespaced format
  121. * that are not causing exceptions when not resolved to an annotation class.
  122. *
  123. * @var bool[] indexed by namespace name
  124. */
  125. private $ignoredAnnotationNamespaces = [];
  126. /** @var string */
  127. private $context = '';
  128. /**
  129. * Hash-map for caching annotation metadata.
  130. *
  131. * @var array<class-string, mixed[]>
  132. */
  133. private static $annotationMetadata = [
  134. Annotation\Target::class => [
  135. 'is_annotation' => true,
  136. 'has_constructor' => true,
  137. 'has_named_argument_constructor' => false,
  138. 'properties' => [],
  139. 'targets_literal' => 'ANNOTATION_CLASS',
  140. 'targets' => Target::TARGET_CLASS,
  141. 'default_property' => 'value',
  142. 'attribute_types' => [
  143. 'value' => [
  144. 'required' => false,
  145. 'type' => 'array',
  146. 'array_type' => 'string',
  147. 'value' => 'array<string>',
  148. ],
  149. ],
  150. ],
  151. Annotation\Attribute::class => [
  152. 'is_annotation' => true,
  153. 'has_constructor' => false,
  154. 'has_named_argument_constructor' => false,
  155. 'targets_literal' => 'ANNOTATION_ANNOTATION',
  156. 'targets' => Target::TARGET_ANNOTATION,
  157. 'default_property' => 'name',
  158. 'properties' => [
  159. 'name' => 'name',
  160. 'type' => 'type',
  161. 'required' => 'required',
  162. ],
  163. 'attribute_types' => [
  164. 'value' => [
  165. 'required' => true,
  166. 'type' => 'string',
  167. 'value' => 'string',
  168. ],
  169. 'type' => [
  170. 'required' => true,
  171. 'type' => 'string',
  172. 'value' => 'string',
  173. ],
  174. 'required' => [
  175. 'required' => false,
  176. 'type' => 'boolean',
  177. 'value' => 'boolean',
  178. ],
  179. ],
  180. ],
  181. Annotation\Attributes::class => [
  182. 'is_annotation' => true,
  183. 'has_constructor' => false,
  184. 'has_named_argument_constructor' => false,
  185. 'targets_literal' => 'ANNOTATION_CLASS',
  186. 'targets' => Target::TARGET_CLASS,
  187. 'default_property' => 'value',
  188. 'properties' => ['value' => 'value'],
  189. 'attribute_types' => [
  190. 'value' => [
  191. 'type' => 'array',
  192. 'required' => true,
  193. 'array_type' => Annotation\Attribute::class,
  194. 'value' => 'array<' . Annotation\Attribute::class . '>',
  195. ],
  196. ],
  197. ],
  198. Annotation\Enum::class => [
  199. 'is_annotation' => true,
  200. 'has_constructor' => true,
  201. 'has_named_argument_constructor' => false,
  202. 'targets_literal' => 'ANNOTATION_PROPERTY',
  203. 'targets' => Target::TARGET_PROPERTY,
  204. 'default_property' => 'value',
  205. 'properties' => ['value' => 'value'],
  206. 'attribute_types' => [
  207. 'value' => [
  208. 'type' => 'array',
  209. 'required' => true,
  210. ],
  211. 'literal' => [
  212. 'type' => 'array',
  213. 'required' => false,
  214. ],
  215. ],
  216. ],
  217. Annotation\NamedArgumentConstructor::class => [
  218. 'is_annotation' => true,
  219. 'has_constructor' => false,
  220. 'has_named_argument_constructor' => false,
  221. 'targets_literal' => 'ANNOTATION_CLASS',
  222. 'targets' => Target::TARGET_CLASS,
  223. 'default_property' => null,
  224. 'properties' => [],
  225. 'attribute_types' => [],
  226. ],
  227. ];
  228. /**
  229. * Hash-map for handle types declaration.
  230. *
  231. * @var array<string, string>
  232. */
  233. private static $typeMap = [
  234. 'float' => 'double',
  235. 'bool' => 'boolean',
  236. // allow uppercase Boolean in honor of George Boole
  237. 'Boolean' => 'boolean',
  238. 'int' => 'integer',
  239. ];
  240. /**
  241. * Constructs a new DocParser.
  242. */
  243. public function __construct()
  244. {
  245. $this->lexer = new DocLexer();
  246. }
  247. /**
  248. * Sets the annotation names that are ignored during the parsing process.
  249. *
  250. * The names are supposed to be the raw names as used in the class, not the
  251. * fully qualified class names.
  252. *
  253. * @param bool[] $names indexed by annotation name
  254. *
  255. * @return void
  256. */
  257. public function setIgnoredAnnotationNames(array $names)
  258. {
  259. $this->ignoredAnnotationNames = $names;
  260. }
  261. /**
  262. * Sets the annotation namespaces that are ignored during the parsing process.
  263. *
  264. * @param bool[] $ignoredAnnotationNamespaces indexed by annotation namespace name
  265. *
  266. * @return void
  267. */
  268. public function setIgnoredAnnotationNamespaces($ignoredAnnotationNamespaces)
  269. {
  270. $this->ignoredAnnotationNamespaces = $ignoredAnnotationNamespaces;
  271. }
  272. /**
  273. * Sets ignore on not-imported annotations.
  274. *
  275. * @param bool $bool
  276. *
  277. * @return void
  278. */
  279. public function setIgnoreNotImportedAnnotations($bool)
  280. {
  281. $this->ignoreNotImportedAnnotations = (bool) $bool;
  282. }
  283. /**
  284. * Sets the default namespaces.
  285. *
  286. * @param string $namespace
  287. *
  288. * @return void
  289. *
  290. * @throws RuntimeException
  291. */
  292. public function addNamespace($namespace)
  293. {
  294. if ($this->imports) {
  295. throw new RuntimeException('You must either use addNamespace(), or setImports(), but not both.');
  296. }
  297. $this->namespaces[] = $namespace;
  298. }
  299. /**
  300. * Sets the imports.
  301. *
  302. * @param array<string, class-string> $imports
  303. *
  304. * @return void
  305. *
  306. * @throws RuntimeException
  307. */
  308. public function setImports(array $imports)
  309. {
  310. if ($this->namespaces) {
  311. throw new RuntimeException('You must either use addNamespace(), or setImports(), but not both.');
  312. }
  313. $this->imports = $imports;
  314. }
  315. /**
  316. * Sets current target context as bitmask.
  317. *
  318. * @param int $target
  319. *
  320. * @return void
  321. */
  322. public function setTarget($target)
  323. {
  324. $this->target = $target;
  325. }
  326. /**
  327. * Parses the given docblock string for annotations.
  328. *
  329. * @param string $input The docblock string to parse.
  330. * @param string $context The parsing context.
  331. *
  332. * @throws AnnotationException
  333. * @throws ReflectionException
  334. *
  335. * @phpstan-return list<object> Array of annotations. If no annotations are found, an empty array is returned.
  336. */
  337. public function parse($input, $context = '')
  338. {
  339. $pos = $this->findInitialTokenPosition($input);
  340. if ($pos === null) {
  341. return [];
  342. }
  343. $this->context = $context;
  344. $this->lexer->setInput(trim(substr($input, $pos), '* /'));
  345. $this->lexer->moveNext();
  346. return $this->Annotations();
  347. }
  348. /**
  349. * Finds the first valid annotation
  350. *
  351. * @param string $input The docblock string to parse
  352. */
  353. private function findInitialTokenPosition($input): ?int
  354. {
  355. $pos = 0;
  356. // search for first valid annotation
  357. while (($pos = strpos($input, '@', $pos)) !== false) {
  358. $preceding = substr($input, $pos - 1, 1);
  359. // if the @ is preceded by a space, a tab or * it is valid
  360. if ($pos === 0 || $preceding === ' ' || $preceding === '*' || $preceding === "\t") {
  361. return $pos;
  362. }
  363. $pos++;
  364. }
  365. return null;
  366. }
  367. /**
  368. * Attempts to match the given token with the current lookahead token.
  369. * If they match, updates the lookahead token; otherwise raises a syntax error.
  370. *
  371. * @param int $token Type of token.
  372. *
  373. * @return bool True if tokens match; false otherwise.
  374. *
  375. * @throws AnnotationException
  376. */
  377. private function match(int $token): bool
  378. {
  379. if (! $this->lexer->isNextToken($token)) {
  380. throw $this->syntaxError($this->lexer->getLiteral($token));
  381. }
  382. return $this->lexer->moveNext();
  383. }
  384. /**
  385. * Attempts to match the current lookahead token with any of the given tokens.
  386. *
  387. * If any of them matches, this method updates the lookahead token; otherwise
  388. * a syntax error is raised.
  389. *
  390. * @throws AnnotationException
  391. *
  392. * @phpstan-param list<mixed[]> $tokens
  393. */
  394. private function matchAny(array $tokens): bool
  395. {
  396. if (! $this->lexer->isNextTokenAny($tokens)) {
  397. throw $this->syntaxError(implode(' or ', array_map([$this->lexer, 'getLiteral'], $tokens)));
  398. }
  399. return $this->lexer->moveNext();
  400. }
  401. /**
  402. * Generates a new syntax error.
  403. *
  404. * @param string $expected Expected string.
  405. * @param mixed[]|null $token Optional token.
  406. */
  407. private function syntaxError(string $expected, ?array $token = null): AnnotationException
  408. {
  409. if ($token === null) {
  410. $token = $this->lexer->lookahead;
  411. }
  412. $message = sprintf('Expected %s, got ', $expected);
  413. $message .= $this->lexer->lookahead === null
  414. ? 'end of string'
  415. : sprintf("'%s' at position %s", $token['value'], $token['position']);
  416. if (strlen($this->context)) {
  417. $message .= ' in ' . $this->context;
  418. }
  419. $message .= '.';
  420. return AnnotationException::syntaxError($message);
  421. }
  422. /**
  423. * Attempts to check if a class exists or not. This never goes through the PHP autoloading mechanism
  424. * but uses the {@link AnnotationRegistry} to load classes.
  425. *
  426. * @param class-string $fqcn
  427. */
  428. private function classExists(string $fqcn): bool
  429. {
  430. if (isset($this->classExists[$fqcn])) {
  431. return $this->classExists[$fqcn];
  432. }
  433. // first check if the class already exists, maybe loaded through another AnnotationReader
  434. if (class_exists($fqcn, false)) {
  435. return $this->classExists[$fqcn] = true;
  436. }
  437. // final check, does this class exist?
  438. return $this->classExists[$fqcn] = AnnotationRegistry::loadAnnotationClass($fqcn);
  439. }
  440. /**
  441. * Collects parsing metadata for a given annotation class
  442. *
  443. * @param class-string $name The annotation name
  444. *
  445. * @throws AnnotationException
  446. * @throws ReflectionException
  447. */
  448. private function collectAnnotationMetadata(string $name): void
  449. {
  450. if (self::$metadataParser === null) {
  451. self::$metadataParser = new self();
  452. self::$metadataParser->setIgnoreNotImportedAnnotations(true);
  453. self::$metadataParser->setIgnoredAnnotationNames($this->ignoredAnnotationNames);
  454. self::$metadataParser->setImports([
  455. 'enum' => Enum::class,
  456. 'target' => Target::class,
  457. 'attribute' => Attribute::class,
  458. 'attributes' => Attributes::class,
  459. 'namedargumentconstructor' => NamedArgumentConstructor::class,
  460. ]);
  461. // Make sure that annotations from metadata are loaded
  462. class_exists(Enum::class);
  463. class_exists(Target::class);
  464. class_exists(Attribute::class);
  465. class_exists(Attributes::class);
  466. class_exists(NamedArgumentConstructor::class);
  467. }
  468. $class = new ReflectionClass($name);
  469. $docComment = $class->getDocComment();
  470. // Sets default values for annotation metadata
  471. $constructor = $class->getConstructor();
  472. $metadata = [
  473. 'default_property' => null,
  474. 'has_constructor' => $constructor !== null && $constructor->getNumberOfParameters() > 0,
  475. 'constructor_args' => [],
  476. 'properties' => [],
  477. 'property_types' => [],
  478. 'attribute_types' => [],
  479. 'targets_literal' => null,
  480. 'targets' => Target::TARGET_ALL,
  481. 'is_annotation' => strpos($docComment, '@Annotation') !== false,
  482. ];
  483. $metadata['has_named_argument_constructor'] = $metadata['has_constructor']
  484. && $class->implementsInterface(NamedArgumentConstructorAnnotation::class);
  485. // verify that the class is really meant to be an annotation
  486. if ($metadata['is_annotation']) {
  487. self::$metadataParser->setTarget(Target::TARGET_CLASS);
  488. foreach (self::$metadataParser->parse($docComment, 'class @' . $name) as $annotation) {
  489. if ($annotation instanceof Target) {
  490. $metadata['targets'] = $annotation->targets;
  491. $metadata['targets_literal'] = $annotation->literal;
  492. continue;
  493. }
  494. if ($annotation instanceof NamedArgumentConstructor) {
  495. $metadata['has_named_argument_constructor'] = $metadata['has_constructor'];
  496. if ($metadata['has_named_argument_constructor']) {
  497. // choose the first argument as the default property
  498. $metadata['default_property'] = $constructor->getParameters()[0]->getName();
  499. }
  500. }
  501. if (! ($annotation instanceof Attributes)) {
  502. continue;
  503. }
  504. foreach ($annotation->value as $attribute) {
  505. $this->collectAttributeTypeMetadata($metadata, $attribute);
  506. }
  507. }
  508. // if not has a constructor will inject values into public properties
  509. if ($metadata['has_constructor'] === false) {
  510. // collect all public properties
  511. foreach ($class->getProperties(ReflectionProperty::IS_PUBLIC) as $property) {
  512. $metadata['properties'][$property->name] = $property->name;
  513. $propertyComment = $property->getDocComment();
  514. if ($propertyComment === false) {
  515. continue;
  516. }
  517. $attribute = new Attribute();
  518. $attribute->required = (strpos($propertyComment, '@Required') !== false);
  519. $attribute->name = $property->name;
  520. $attribute->type = (strpos($propertyComment, '@var') !== false &&
  521. preg_match('/@var\s+([^\s]+)/', $propertyComment, $matches))
  522. ? $matches[1]
  523. : 'mixed';
  524. $this->collectAttributeTypeMetadata($metadata, $attribute);
  525. // checks if the property has @Enum
  526. if (strpos($propertyComment, '@Enum') === false) {
  527. continue;
  528. }
  529. $context = 'property ' . $class->name . '::$' . $property->name;
  530. self::$metadataParser->setTarget(Target::TARGET_PROPERTY);
  531. foreach (self::$metadataParser->parse($propertyComment, $context) as $annotation) {
  532. if (! $annotation instanceof Enum) {
  533. continue;
  534. }
  535. $metadata['enum'][$property->name]['value'] = $annotation->value;
  536. $metadata['enum'][$property->name]['literal'] = (! empty($annotation->literal))
  537. ? $annotation->literal
  538. : $annotation->value;
  539. }
  540. }
  541. // choose the first property as default property
  542. $metadata['default_property'] = reset($metadata['properties']);
  543. } elseif ($metadata['has_named_argument_constructor']) {
  544. foreach ($constructor->getParameters() as $parameter) {
  545. $metadata['constructor_args'][$parameter->getName()] = [
  546. 'position' => $parameter->getPosition(),
  547. 'default' => $parameter->isOptional() ? $parameter->getDefaultValue() : null,
  548. ];
  549. }
  550. }
  551. }
  552. self::$annotationMetadata[$name] = $metadata;
  553. }
  554. /**
  555. * Collects parsing metadata for a given attribute.
  556. *
  557. * @param mixed[] $metadata
  558. */
  559. private function collectAttributeTypeMetadata(array &$metadata, Attribute $attribute): void
  560. {
  561. // handle internal type declaration
  562. $type = self::$typeMap[$attribute->type] ?? $attribute->type;
  563. // handle the case if the property type is mixed
  564. if ($type === 'mixed') {
  565. return;
  566. }
  567. // Evaluate type
  568. $pos = strpos($type, '<');
  569. if ($pos !== false) {
  570. // Checks if the property has array<type>
  571. $arrayType = substr($type, $pos + 1, -1);
  572. $type = 'array';
  573. if (isset(self::$typeMap[$arrayType])) {
  574. $arrayType = self::$typeMap[$arrayType];
  575. }
  576. $metadata['attribute_types'][$attribute->name]['array_type'] = $arrayType;
  577. } else {
  578. // Checks if the property has type[]
  579. $pos = strrpos($type, '[');
  580. if ($pos !== false) {
  581. $arrayType = substr($type, 0, $pos);
  582. $type = 'array';
  583. if (isset(self::$typeMap[$arrayType])) {
  584. $arrayType = self::$typeMap[$arrayType];
  585. }
  586. $metadata['attribute_types'][$attribute->name]['array_type'] = $arrayType;
  587. }
  588. }
  589. $metadata['attribute_types'][$attribute->name]['type'] = $type;
  590. $metadata['attribute_types'][$attribute->name]['value'] = $attribute->type;
  591. $metadata['attribute_types'][$attribute->name]['required'] = $attribute->required;
  592. }
  593. /**
  594. * Annotations ::= Annotation {[ "*" ]* [Annotation]}*
  595. *
  596. * @throws AnnotationException
  597. * @throws ReflectionException
  598. *
  599. * @phpstan-return list<object>
  600. */
  601. private function Annotations(): array
  602. {
  603. $annotations = [];
  604. while ($this->lexer->lookahead !== null) {
  605. if ($this->lexer->lookahead['type'] !== DocLexer::T_AT) {
  606. $this->lexer->moveNext();
  607. continue;
  608. }
  609. // make sure the @ is preceded by non-catchable pattern
  610. if (
  611. $this->lexer->token !== null &&
  612. $this->lexer->lookahead['position'] === $this->lexer->token['position'] + strlen(
  613. $this->lexer->token['value']
  614. )
  615. ) {
  616. $this->lexer->moveNext();
  617. continue;
  618. }
  619. // make sure the @ is followed by either a namespace separator, or
  620. // an identifier token
  621. $peek = $this->lexer->glimpse();
  622. if (
  623. ($peek === null)
  624. || ($peek['type'] !== DocLexer::T_NAMESPACE_SEPARATOR && ! in_array(
  625. $peek['type'],
  626. self::$classIdentifiers,
  627. true
  628. ))
  629. || $peek['position'] !== $this->lexer->lookahead['position'] + 1
  630. ) {
  631. $this->lexer->moveNext();
  632. continue;
  633. }
  634. $this->isNestedAnnotation = false;
  635. $annot = $this->Annotation();
  636. if ($annot === false) {
  637. continue;
  638. }
  639. $annotations[] = $annot;
  640. }
  641. return $annotations;
  642. }
  643. /**
  644. * Annotation ::= "@" AnnotationName MethodCall
  645. * AnnotationName ::= QualifiedName | SimpleName
  646. * QualifiedName ::= NameSpacePart "\" {NameSpacePart "\"}* SimpleName
  647. * NameSpacePart ::= identifier | null | false | true
  648. * SimpleName ::= identifier | null | false | true
  649. *
  650. * @return object|false False if it is not a valid annotation.
  651. *
  652. * @throws AnnotationException
  653. * @throws ReflectionException
  654. */
  655. private function Annotation()
  656. {
  657. $this->match(DocLexer::T_AT);
  658. // check if we have an annotation
  659. $name = $this->Identifier();
  660. if (
  661. $this->lexer->isNextToken(DocLexer::T_MINUS)
  662. && $this->lexer->nextTokenIsAdjacent()
  663. ) {
  664. // Annotations with dashes, such as "@foo-" or "@foo-bar", are to be discarded
  665. return false;
  666. }
  667. // only process names which are not fully qualified, yet
  668. // fully qualified names must start with a \
  669. $originalName = $name;
  670. if ($name[0] !== '\\') {
  671. $pos = strpos($name, '\\');
  672. $alias = ($pos === false) ? $name : substr($name, 0, $pos);
  673. $found = false;
  674. $loweredAlias = strtolower($alias);
  675. if ($this->namespaces) {
  676. foreach ($this->namespaces as $namespace) {
  677. if ($this->classExists($namespace . '\\' . $name)) {
  678. $name = $namespace . '\\' . $name;
  679. $found = true;
  680. break;
  681. }
  682. }
  683. } elseif (isset($this->imports[$loweredAlias])) {
  684. $namespace = ltrim($this->imports[$loweredAlias], '\\');
  685. $name = ($pos !== false)
  686. ? $namespace . substr($name, $pos)
  687. : $namespace;
  688. $found = $this->classExists($name);
  689. } elseif (
  690. ! isset($this->ignoredAnnotationNames[$name])
  691. && isset($this->imports['__NAMESPACE__'])
  692. && $this->classExists($this->imports['__NAMESPACE__'] . '\\' . $name)
  693. ) {
  694. $name = $this->imports['__NAMESPACE__'] . '\\' . $name;
  695. $found = true;
  696. } elseif (! isset($this->ignoredAnnotationNames[$name]) && $this->classExists($name)) {
  697. $found = true;
  698. }
  699. if (! $found) {
  700. if ($this->isIgnoredAnnotation($name)) {
  701. return false;
  702. }
  703. throw AnnotationException::semanticalError(sprintf(
  704. <<<'EXCEPTION'
  705. The annotation "@%s" in %s was never imported. Did you maybe forget to add a "use" statement for this annotation?
  706. EXCEPTION
  707. ,
  708. $name,
  709. $this->context
  710. ));
  711. }
  712. }
  713. $name = ltrim($name, '\\');
  714. if (! $this->classExists($name)) {
  715. throw AnnotationException::semanticalError(sprintf(
  716. 'The annotation "@%s" in %s does not exist, or could not be auto-loaded.',
  717. $name,
  718. $this->context
  719. ));
  720. }
  721. // at this point, $name contains the fully qualified class name of the
  722. // annotation, and it is also guaranteed that this class exists, and
  723. // that it is loaded
  724. // collects the metadata annotation only if there is not yet
  725. if (! isset(self::$annotationMetadata[$name])) {
  726. $this->collectAnnotationMetadata($name);
  727. }
  728. // verify that the class is really meant to be an annotation and not just any ordinary class
  729. if (self::$annotationMetadata[$name]['is_annotation'] === false) {
  730. if ($this->isIgnoredAnnotation($originalName) || $this->isIgnoredAnnotation($name)) {
  731. return false;
  732. }
  733. throw AnnotationException::semanticalError(sprintf(
  734. <<<'EXCEPTION'
  735. The class "%s" is not annotated with @Annotation.
  736. Are you sure this class can be used as annotation?
  737. If so, then you need to add @Annotation to the _class_ doc comment of "%s".
  738. If it is indeed no annotation, then you need to add @IgnoreAnnotation("%s") to the _class_ doc comment of %s.
  739. EXCEPTION
  740. ,
  741. $name,
  742. $name,
  743. $originalName,
  744. $this->context
  745. ));
  746. }
  747. //if target is nested annotation
  748. $target = $this->isNestedAnnotation ? Target::TARGET_ANNOTATION : $this->target;
  749. // Next will be nested
  750. $this->isNestedAnnotation = true;
  751. //if annotation does not support current target
  752. if ((self::$annotationMetadata[$name]['targets'] & $target) === 0 && $target) {
  753. throw AnnotationException::semanticalError(
  754. sprintf(
  755. <<<'EXCEPTION'
  756. Annotation @%s is not allowed to be declared on %s. You may only use this annotation on these code elements: %s.
  757. EXCEPTION
  758. ,
  759. $originalName,
  760. $this->context,
  761. self::$annotationMetadata[$name]['targets_literal']
  762. )
  763. );
  764. }
  765. $arguments = $this->MethodCall();
  766. $values = $this->resolvePositionalValues($arguments, $name);
  767. if (isset(self::$annotationMetadata[$name]['enum'])) {
  768. // checks all declared attributes
  769. foreach (self::$annotationMetadata[$name]['enum'] as $property => $enum) {
  770. // checks if the attribute is a valid enumerator
  771. if (isset($values[$property]) && ! in_array($values[$property], $enum['value'])) {
  772. throw AnnotationException::enumeratorError(
  773. $property,
  774. $name,
  775. $this->context,
  776. $enum['literal'],
  777. $values[$property]
  778. );
  779. }
  780. }
  781. }
  782. // checks all declared attributes
  783. foreach (self::$annotationMetadata[$name]['attribute_types'] as $property => $type) {
  784. if (
  785. $property === self::$annotationMetadata[$name]['default_property']
  786. && ! isset($values[$property]) && isset($values['value'])
  787. ) {
  788. $property = 'value';
  789. }
  790. // handle a not given attribute or null value
  791. if (! isset($values[$property])) {
  792. if ($type['required']) {
  793. throw AnnotationException::requiredError(
  794. $property,
  795. $originalName,
  796. $this->context,
  797. 'a(n) ' . $type['value']
  798. );
  799. }
  800. continue;
  801. }
  802. if ($type['type'] === 'array') {
  803. // handle the case of a single value
  804. if (! is_array($values[$property])) {
  805. $values[$property] = [$values[$property]];
  806. }
  807. // checks if the attribute has array type declaration, such as "array<string>"
  808. if (isset($type['array_type'])) {
  809. foreach ($values[$property] as $item) {
  810. if (gettype($item) !== $type['array_type'] && ! $item instanceof $type['array_type']) {
  811. throw AnnotationException::attributeTypeError(
  812. $property,
  813. $originalName,
  814. $this->context,
  815. 'either a(n) ' . $type['array_type'] . ', or an array of ' . $type['array_type'] . 's',
  816. $item
  817. );
  818. }
  819. }
  820. }
  821. } elseif (gettype($values[$property]) !== $type['type'] && ! $values[$property] instanceof $type['type']) {
  822. throw AnnotationException::attributeTypeError(
  823. $property,
  824. $originalName,
  825. $this->context,
  826. 'a(n) ' . $type['value'],
  827. $values[$property]
  828. );
  829. }
  830. }
  831. if (self::$annotationMetadata[$name]['has_named_argument_constructor']) {
  832. if (PHP_VERSION_ID >= 80000) {
  833. return $this->instantiateAnnotiation($originalName, $this->context, $name, $values);
  834. }
  835. $positionalValues = [];
  836. foreach (self::$annotationMetadata[$name]['constructor_args'] as $property => $parameter) {
  837. $positionalValues[$parameter['position']] = $parameter['default'];
  838. }
  839. foreach ($values as $property => $value) {
  840. if (! isset(self::$annotationMetadata[$name]['constructor_args'][$property])) {
  841. throw AnnotationException::creationError(sprintf(
  842. <<<'EXCEPTION'
  843. The annotation @%s declared on %s does not have a property named "%s"
  844. that can be set through its named arguments constructor.
  845. Available named arguments: %s
  846. EXCEPTION
  847. ,
  848. $originalName,
  849. $this->context,
  850. $property,
  851. implode(', ', array_keys(self::$annotationMetadata[$name]['constructor_args']))
  852. ));
  853. }
  854. $positionalValues[self::$annotationMetadata[$name]['constructor_args'][$property]['position']] = $value;
  855. }
  856. return $this->instantiateAnnotiation($originalName, $this->context, $name, $positionalValues);
  857. }
  858. // check if the annotation expects values via the constructor,
  859. // or directly injected into public properties
  860. if (self::$annotationMetadata[$name]['has_constructor'] === true) {
  861. return $this->instantiateAnnotiation($originalName, $this->context, $name, [$values]);
  862. }
  863. $instance = $this->instantiateAnnotiation($originalName, $this->context, $name, []);
  864. foreach ($values as $property => $value) {
  865. if (! isset(self::$annotationMetadata[$name]['properties'][$property])) {
  866. if ($property !== 'value') {
  867. throw AnnotationException::creationError(sprintf(
  868. <<<'EXCEPTION'
  869. The annotation @%s declared on %s does not have a property named "%s".
  870. Available properties: %s
  871. EXCEPTION
  872. ,
  873. $originalName,
  874. $this->context,
  875. $property,
  876. implode(', ', self::$annotationMetadata[$name]['properties'])
  877. ));
  878. }
  879. // handle the case if the property has no annotations
  880. $property = self::$annotationMetadata[$name]['default_property'];
  881. if (! $property) {
  882. throw AnnotationException::creationError(sprintf(
  883. 'The annotation @%s declared on %s does not accept any values, but got %s.',
  884. $originalName,
  885. $this->context,
  886. json_encode($values)
  887. ));
  888. }
  889. }
  890. $instance->{$property} = $value;
  891. }
  892. return $instance;
  893. }
  894. /**
  895. * MethodCall ::= ["(" [Values] ")"]
  896. *
  897. * @return mixed[]
  898. *
  899. * @throws AnnotationException
  900. * @throws ReflectionException
  901. */
  902. private function MethodCall(): array
  903. {
  904. $values = [];
  905. if (! $this->lexer->isNextToken(DocLexer::T_OPEN_PARENTHESIS)) {
  906. return $values;
  907. }
  908. $this->match(DocLexer::T_OPEN_PARENTHESIS);
  909. if (! $this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) {
  910. $values = $this->Values();
  911. }
  912. $this->match(DocLexer::T_CLOSE_PARENTHESIS);
  913. return $values;
  914. }
  915. /**
  916. * Values ::= Array | Value {"," Value}* [","]
  917. *
  918. * @return mixed[]
  919. *
  920. * @throws AnnotationException
  921. * @throws ReflectionException
  922. */
  923. private function Values(): array
  924. {
  925. $values = [$this->Value()];
  926. while ($this->lexer->isNextToken(DocLexer::T_COMMA)) {
  927. $this->match(DocLexer::T_COMMA);
  928. if ($this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) {
  929. break;
  930. }
  931. $token = $this->lexer->lookahead;
  932. $value = $this->Value();
  933. $values[] = $value;
  934. }
  935. $namedArguments = [];
  936. $positionalArguments = [];
  937. foreach ($values as $k => $value) {
  938. if (is_object($value) && $value instanceof stdClass) {
  939. $namedArguments[$value->name] = $value->value;
  940. } else {
  941. $positionalArguments[$k] = $value;
  942. }
  943. }
  944. return ['named_arguments' => $namedArguments, 'positional_arguments' => $positionalArguments];
  945. }
  946. /**
  947. * Constant ::= integer | string | float | boolean
  948. *
  949. * @return mixed
  950. *
  951. * @throws AnnotationException
  952. */
  953. private function Constant()
  954. {
  955. $identifier = $this->Identifier();
  956. if (! defined($identifier) && strpos($identifier, '::') !== false && $identifier[0] !== '\\') {
  957. [$className, $const] = explode('::', $identifier);
  958. $pos = strpos($className, '\\');
  959. $alias = ($pos === false) ? $className : substr($className, 0, $pos);
  960. $found = false;
  961. $loweredAlias = strtolower($alias);
  962. switch (true) {
  963. case ! empty($this->namespaces):
  964. foreach ($this->namespaces as $ns) {
  965. if (class_exists($ns . '\\' . $className) || interface_exists($ns . '\\' . $className)) {
  966. $className = $ns . '\\' . $className;
  967. $found = true;
  968. break;
  969. }
  970. }
  971. break;
  972. case isset($this->imports[$loweredAlias]):
  973. $found = true;
  974. $className = ($pos !== false)
  975. ? $this->imports[$loweredAlias] . substr($className, $pos)
  976. : $this->imports[$loweredAlias];
  977. break;
  978. default:
  979. if (isset($this->imports['__NAMESPACE__'])) {
  980. $ns = $this->imports['__NAMESPACE__'];
  981. if (class_exists($ns . '\\' . $className) || interface_exists($ns . '\\' . $className)) {
  982. $className = $ns . '\\' . $className;
  983. $found = true;
  984. }
  985. }
  986. break;
  987. }
  988. if ($found) {
  989. $identifier = $className . '::' . $const;
  990. }
  991. }
  992. /**
  993. * Checks if identifier ends with ::class and remove the leading backslash if it exists.
  994. */
  995. if (
  996. $this->identifierEndsWithClassConstant($identifier) &&
  997. ! $this->identifierStartsWithBackslash($identifier)
  998. ) {
  999. return substr($identifier, 0, $this->getClassConstantPositionInIdentifier($identifier));
  1000. }
  1001. if ($this->identifierEndsWithClassConstant($identifier) && $this->identifierStartsWithBackslash($identifier)) {
  1002. return substr($identifier, 1, $this->getClassConstantPositionInIdentifier($identifier) - 1);
  1003. }
  1004. if (! defined($identifier)) {
  1005. throw AnnotationException::semanticalErrorConstants($identifier, $this->context);
  1006. }
  1007. return constant($identifier);
  1008. }
  1009. private function identifierStartsWithBackslash(string $identifier): bool
  1010. {
  1011. return $identifier[0] === '\\';
  1012. }
  1013. private function identifierEndsWithClassConstant(string $identifier): bool
  1014. {
  1015. return $this->getClassConstantPositionInIdentifier($identifier) === strlen($identifier) - strlen('::class');
  1016. }
  1017. /**
  1018. * @return int|false
  1019. */
  1020. private function getClassConstantPositionInIdentifier(string $identifier)
  1021. {
  1022. return stripos($identifier, '::class');
  1023. }
  1024. /**
  1025. * Identifier ::= string
  1026. *
  1027. * @throws AnnotationException
  1028. */
  1029. private function Identifier(): string
  1030. {
  1031. // check if we have an annotation
  1032. if (! $this->lexer->isNextTokenAny(self::$classIdentifiers)) {
  1033. throw $this->syntaxError('namespace separator or identifier');
  1034. }
  1035. $this->lexer->moveNext();
  1036. $className = $this->lexer->token['value'];
  1037. while (
  1038. $this->lexer->lookahead !== null &&
  1039. $this->lexer->lookahead['position'] === ($this->lexer->token['position'] +
  1040. strlen($this->lexer->token['value'])) &&
  1041. $this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR)
  1042. ) {
  1043. $this->match(DocLexer::T_NAMESPACE_SEPARATOR);
  1044. $this->matchAny(self::$classIdentifiers);
  1045. $className .= '\\' . $this->lexer->token['value'];
  1046. }
  1047. return $className;
  1048. }
  1049. /**
  1050. * Value ::= PlainValue | FieldAssignment
  1051. *
  1052. * @return mixed
  1053. *
  1054. * @throws AnnotationException
  1055. * @throws ReflectionException
  1056. */
  1057. private function Value()
  1058. {
  1059. $peek = $this->lexer->glimpse();
  1060. if ($peek['type'] === DocLexer::T_EQUALS) {
  1061. return $this->FieldAssignment();
  1062. }
  1063. return $this->PlainValue();
  1064. }
  1065. /**
  1066. * PlainValue ::= integer | string | float | boolean | Array | Annotation
  1067. *
  1068. * @return mixed
  1069. *
  1070. * @throws AnnotationException
  1071. * @throws ReflectionException
  1072. */
  1073. private function PlainValue()
  1074. {
  1075. if ($this->lexer->isNextToken(DocLexer::T_OPEN_CURLY_BRACES)) {
  1076. return $this->Arrayx();
  1077. }
  1078. if ($this->lexer->isNextToken(DocLexer::T_AT)) {
  1079. return $this->Annotation();
  1080. }
  1081. if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) {
  1082. return $this->Constant();
  1083. }
  1084. switch ($this->lexer->lookahead['type']) {
  1085. case DocLexer::T_STRING:
  1086. $this->match(DocLexer::T_STRING);
  1087. return $this->lexer->token['value'];
  1088. case DocLexer::T_INTEGER:
  1089. $this->match(DocLexer::T_INTEGER);
  1090. return (int) $this->lexer->token['value'];
  1091. case DocLexer::T_FLOAT:
  1092. $this->match(DocLexer::T_FLOAT);
  1093. return (float) $this->lexer->token['value'];
  1094. case DocLexer::T_TRUE:
  1095. $this->match(DocLexer::T_TRUE);
  1096. return true;
  1097. case DocLexer::T_FALSE:
  1098. $this->match(DocLexer::T_FALSE);
  1099. return false;
  1100. case DocLexer::T_NULL:
  1101. $this->match(DocLexer::T_NULL);
  1102. return null;
  1103. default:
  1104. throw $this->syntaxError('PlainValue');
  1105. }
  1106. }
  1107. /**
  1108. * FieldAssignment ::= FieldName "=" PlainValue
  1109. * FieldName ::= identifier
  1110. *
  1111. * @throws AnnotationException
  1112. * @throws ReflectionException
  1113. */
  1114. private function FieldAssignment(): stdClass
  1115. {
  1116. $this->match(DocLexer::T_IDENTIFIER);
  1117. $fieldName = $this->lexer->token['value'];
  1118. $this->match(DocLexer::T_EQUALS);
  1119. $item = new stdClass();
  1120. $item->name = $fieldName;
  1121. $item->value = $this->PlainValue();
  1122. return $item;
  1123. }
  1124. /**
  1125. * Array ::= "{" ArrayEntry {"," ArrayEntry}* [","] "}"
  1126. *
  1127. * @return mixed[]
  1128. *
  1129. * @throws AnnotationException
  1130. * @throws ReflectionException
  1131. */
  1132. private function Arrayx(): array
  1133. {
  1134. $array = $values = [];
  1135. $this->match(DocLexer::T_OPEN_CURLY_BRACES);
  1136. // If the array is empty, stop parsing and return.
  1137. if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) {
  1138. $this->match(DocLexer::T_CLOSE_CURLY_BRACES);
  1139. return $array;
  1140. }
  1141. $values[] = $this->ArrayEntry();
  1142. while ($this->lexer->isNextToken(DocLexer::T_COMMA)) {
  1143. $this->match(DocLexer::T_COMMA);
  1144. // optional trailing comma
  1145. if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) {
  1146. break;
  1147. }
  1148. $values[] = $this->ArrayEntry();
  1149. }
  1150. $this->match(DocLexer::T_CLOSE_CURLY_BRACES);
  1151. foreach ($values as $value) {
  1152. [$key, $val] = $value;
  1153. if ($key !== null) {
  1154. $array[$key] = $val;
  1155. } else {
  1156. $array[] = $val;
  1157. }
  1158. }
  1159. return $array;
  1160. }
  1161. /**
  1162. * ArrayEntry ::= Value | KeyValuePair
  1163. * KeyValuePair ::= Key ("=" | ":") PlainValue | Constant
  1164. * Key ::= string | integer | Constant
  1165. *
  1166. * @throws AnnotationException
  1167. * @throws ReflectionException
  1168. *
  1169. * @phpstan-return array{mixed, mixed}
  1170. */
  1171. private function ArrayEntry(): array
  1172. {
  1173. $peek = $this->lexer->glimpse();
  1174. if (
  1175. $peek['type'] === DocLexer::T_EQUALS
  1176. || $peek['type'] === DocLexer::T_COLON
  1177. ) {
  1178. if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) {
  1179. $key = $this->Constant();
  1180. } else {
  1181. $this->matchAny([DocLexer::T_INTEGER, DocLexer::T_STRING]);
  1182. $key = $this->lexer->token['value'];
  1183. }
  1184. $this->matchAny([DocLexer::T_EQUALS, DocLexer::T_COLON]);
  1185. return [$key, $this->PlainValue()];
  1186. }
  1187. return [null, $this->Value()];
  1188. }
  1189. /**
  1190. * Checks whether the given $name matches any ignored annotation name or namespace
  1191. */
  1192. private function isIgnoredAnnotation(string $name): bool
  1193. {
  1194. if ($this->ignoreNotImportedAnnotations || isset($this->ignoredAnnotationNames[$name])) {
  1195. return true;
  1196. }
  1197. foreach (array_keys($this->ignoredAnnotationNamespaces) as $ignoredAnnotationNamespace) {
  1198. $ignoredAnnotationNamespace = rtrim($ignoredAnnotationNamespace, '\\') . '\\';
  1199. if (stripos(rtrim($name, '\\') . '\\', $ignoredAnnotationNamespace) === 0) {
  1200. return true;
  1201. }
  1202. }
  1203. return false;
  1204. }
  1205. /**
  1206. * Resolve positional arguments (without name) to named ones
  1207. *
  1208. * @param array<string,mixed> $arguments
  1209. *
  1210. * @return array<string,mixed>
  1211. */
  1212. private function resolvePositionalValues(array $arguments, string $name): array
  1213. {
  1214. $positionalArguments = $arguments['positional_arguments'] ?? [];
  1215. $values = $arguments['named_arguments'] ?? [];
  1216. if (
  1217. self::$annotationMetadata[$name]['has_named_argument_constructor']
  1218. && self::$annotationMetadata[$name]['default_property'] !== null
  1219. ) {
  1220. // We must ensure that we don't have positional arguments after named ones
  1221. $positions = array_keys($positionalArguments);
  1222. $lastPosition = null;
  1223. foreach ($positions as $position) {
  1224. if (
  1225. ($lastPosition === null && $position !== 0) ||
  1226. ($lastPosition !== null && $position !== $lastPosition + 1)
  1227. ) {
  1228. throw $this->syntaxError('Positional arguments after named arguments is not allowed');
  1229. }
  1230. $lastPosition = $position;
  1231. }
  1232. foreach (self::$annotationMetadata[$name]['constructor_args'] as $property => $parameter) {
  1233. $position = $parameter['position'];
  1234. if (isset($values[$property]) || ! isset($positionalArguments[$position])) {
  1235. continue;
  1236. }
  1237. $values[$property] = $positionalArguments[$position];
  1238. }
  1239. } else {
  1240. if (count($positionalArguments) > 0 && ! isset($values['value'])) {
  1241. if (count($positionalArguments) === 1) {
  1242. $value = array_pop($positionalArguments);
  1243. } else {
  1244. $value = array_values($positionalArguments);
  1245. }
  1246. $values['value'] = $value;
  1247. }
  1248. }
  1249. return $values;
  1250. }
  1251. /**
  1252. * Try to instantiate the annotation and catch and process any exceptions related to failure
  1253. *
  1254. * @param class-string $name
  1255. * @param array<string,mixed> $arguments
  1256. *
  1257. * @return object
  1258. *
  1259. * @throws AnnotationException
  1260. */
  1261. private function instantiateAnnotiation(string $originalName, string $context, string $name, array $arguments)
  1262. {
  1263. try {
  1264. return new $name(...$arguments);
  1265. } catch (Throwable $exception) {
  1266. throw AnnotationException::creationError(
  1267. sprintf(
  1268. 'An error occurred while instantiating the annotation @%s declared on %s: "%s".',
  1269. $originalName,
  1270. $context,
  1271. $exception->getMessage()
  1272. ),
  1273. $exception
  1274. );
  1275. }
  1276. }
  1277. }