DocParser.php 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142
  1. <?php
  2. // @codingStandardsIgnoreFile
  3. /**
  4. * @file
  5. *
  6. * This class is a near-copy of Doctrine\Common\Annotations\DocParser, which is
  7. * part of the Doctrine project: <http://www.doctrine-project.org>. It was
  8. * copied from version 1.2.7.
  9. *
  10. * Original copyright:
  11. *
  12. * Copyright (c) 2006-2013 Doctrine Project
  13. *
  14. * Permission is hereby granted, free of charge, to any person obtaining a copy of
  15. * this software and associated documentation files (the "Software"), to deal in
  16. * the Software without restriction, including without limitation the rights to
  17. * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
  18. * of the Software, and to permit persons to whom the Software is furnished to do
  19. * so, subject to the following conditions:
  20. *
  21. * The above copyright notice and this permission notice shall be included in all
  22. * copies or substantial portions of the Software.
  23. */
  24. namespace Drupal\Component\Annotation\Doctrine;
  25. use Doctrine\Common\Annotations\Annotation\Attribute;
  26. use Doctrine\Common\Annotations\Annotation\Enum;
  27. use Doctrine\Common\Annotations\Annotation\Target;
  28. use Doctrine\Common\Annotations\Annotation\Attributes;
  29. use Doctrine\Common\Annotations\AnnotationException;
  30. use Doctrine\Common\Annotations\AnnotationRegistry;
  31. use Doctrine\Common\Annotations\DocLexer;
  32. /**
  33. * A parser for docblock annotations.
  34. *
  35. * This Drupal version allows for ignoring annotations when namespaces are
  36. * present.
  37. *
  38. * @internal
  39. */
  40. final class DocParser
  41. {
  42. /**
  43. * An array of all valid tokens for a class name.
  44. *
  45. * @var array
  46. */
  47. private static $classIdentifiers = array(
  48. DocLexer::T_IDENTIFIER,
  49. DocLexer::T_TRUE,
  50. DocLexer::T_FALSE,
  51. DocLexer::T_NULL
  52. );
  53. /**
  54. * The lexer.
  55. *
  56. * @var \Doctrine\Common\Annotations\DocLexer
  57. */
  58. private $lexer;
  59. /**
  60. * Current target context.
  61. *
  62. * @var string
  63. */
  64. private $target;
  65. /**
  66. * Doc parser used to collect annotation target.
  67. *
  68. * @var \Doctrine\Common\Annotations\DocParser
  69. */
  70. private static $metadataParser;
  71. /**
  72. * Flag to control if the current annotation is nested or not.
  73. *
  74. * @var boolean
  75. */
  76. private $isNestedAnnotation = false;
  77. /**
  78. * Hashmap containing all use-statements that are to be used when parsing
  79. * the given doc block.
  80. *
  81. * @var array
  82. */
  83. private $imports = array();
  84. /**
  85. * This hashmap is used internally to cache results of class_exists()
  86. * look-ups.
  87. *
  88. * @var array
  89. */
  90. private $classExists = array();
  91. /**
  92. * Whether annotations that have not been imported should be ignored.
  93. *
  94. * @var boolean
  95. */
  96. private $ignoreNotImportedAnnotations = false;
  97. /**
  98. * An array of default namespaces if operating in simple mode.
  99. *
  100. * @var array
  101. */
  102. private $namespaces = array();
  103. /**
  104. * A list with annotations that are not causing exceptions when not resolved to an annotation class.
  105. *
  106. * The names must be the raw names as used in the class, not the fully qualified
  107. * class names.
  108. *
  109. * @var array
  110. */
  111. private $ignoredAnnotationNames = array();
  112. /**
  113. * @var string
  114. */
  115. private $context = '';
  116. /**
  117. * Hash-map for caching annotation metadata.
  118. *
  119. * @var array
  120. */
  121. private static $annotationMetadata = array(
  122. 'Doctrine\Common\Annotations\Annotation\Target' => array(
  123. 'is_annotation' => true,
  124. 'has_constructor' => true,
  125. 'properties' => array(),
  126. 'targets_literal' => 'ANNOTATION_CLASS',
  127. 'targets' => Target::TARGET_CLASS,
  128. 'default_property' => 'value',
  129. 'attribute_types' => array(
  130. 'value' => array(
  131. 'required' => false,
  132. 'type' =>'array',
  133. 'array_type'=>'string',
  134. 'value' =>'array<string>'
  135. )
  136. ),
  137. ),
  138. 'Doctrine\Common\Annotations\Annotation\Attribute' => array(
  139. 'is_annotation' => true,
  140. 'has_constructor' => false,
  141. 'targets_literal' => 'ANNOTATION_ANNOTATION',
  142. 'targets' => Target::TARGET_ANNOTATION,
  143. 'default_property' => 'name',
  144. 'properties' => array(
  145. 'name' => 'name',
  146. 'type' => 'type',
  147. 'required' => 'required'
  148. ),
  149. 'attribute_types' => array(
  150. 'value' => array(
  151. 'required' => true,
  152. 'type' =>'string',
  153. 'value' =>'string'
  154. ),
  155. 'type' => array(
  156. 'required' =>true,
  157. 'type' =>'string',
  158. 'value' =>'string'
  159. ),
  160. 'required' => array(
  161. 'required' =>false,
  162. 'type' =>'boolean',
  163. 'value' =>'boolean'
  164. )
  165. ),
  166. ),
  167. 'Doctrine\Common\Annotations\Annotation\Attributes' => array(
  168. 'is_annotation' => true,
  169. 'has_constructor' => false,
  170. 'targets_literal' => 'ANNOTATION_CLASS',
  171. 'targets' => Target::TARGET_CLASS,
  172. 'default_property' => 'value',
  173. 'properties' => array(
  174. 'value' => 'value'
  175. ),
  176. 'attribute_types' => array(
  177. 'value' => array(
  178. 'type' =>'array',
  179. 'required' =>true,
  180. 'array_type'=>'Doctrine\Common\Annotations\Annotation\Attribute',
  181. 'value' =>'array<Doctrine\Common\Annotations\Annotation\Attribute>'
  182. )
  183. ),
  184. ),
  185. 'Doctrine\Common\Annotations\Annotation\Enum' => array(
  186. 'is_annotation' => true,
  187. 'has_constructor' => true,
  188. 'targets_literal' => 'ANNOTATION_PROPERTY',
  189. 'targets' => Target::TARGET_PROPERTY,
  190. 'default_property' => 'value',
  191. 'properties' => array(
  192. 'value' => 'value'
  193. ),
  194. 'attribute_types' => array(
  195. 'value' => array(
  196. 'type' => 'array',
  197. 'required' => true,
  198. ),
  199. 'literal' => array(
  200. 'type' => 'array',
  201. 'required' => false,
  202. ),
  203. ),
  204. ),
  205. );
  206. /**
  207. * Hash-map for handle types declaration.
  208. *
  209. * @var array
  210. */
  211. private static $typeMap = array(
  212. 'float' => 'double',
  213. 'bool' => 'boolean',
  214. // allow uppercase Boolean in honor of George Boole
  215. 'Boolean' => 'boolean',
  216. 'int' => 'integer',
  217. );
  218. /**
  219. * Constructs a new DocParser.
  220. */
  221. public function __construct()
  222. {
  223. $this->lexer = new DocLexer;
  224. }
  225. /**
  226. * Sets the annotation names that are ignored during the parsing process.
  227. *
  228. * The names are supposed to be the raw names as used in the class, not the
  229. * fully qualified class names.
  230. *
  231. * @param array $names
  232. *
  233. * @return void
  234. */
  235. public function setIgnoredAnnotationNames(array $names)
  236. {
  237. $this->ignoredAnnotationNames = $names;
  238. }
  239. /**
  240. * Sets ignore on not-imported annotations.
  241. *
  242. * @param boolean $bool
  243. *
  244. * @return void
  245. */
  246. public function setIgnoreNotImportedAnnotations($bool)
  247. {
  248. $this->ignoreNotImportedAnnotations = (boolean) $bool;
  249. }
  250. /**
  251. * Sets the default namespaces.
  252. *
  253. * @param array $namespace
  254. *
  255. * @return void
  256. *
  257. * @throws \RuntimeException
  258. */
  259. public function addNamespace($namespace)
  260. {
  261. if ($this->imports) {
  262. throw new \RuntimeException('You must either use addNamespace(), or setImports(), but not both.');
  263. }
  264. $this->namespaces[] = $namespace;
  265. }
  266. /**
  267. * Sets the imports.
  268. *
  269. * @param array $imports
  270. *
  271. * @return void
  272. *
  273. * @throws \RuntimeException
  274. */
  275. public function setImports(array $imports)
  276. {
  277. if ($this->namespaces) {
  278. throw new \RuntimeException('You must either use addNamespace(), or setImports(), but not both.');
  279. }
  280. $this->imports = $imports;
  281. }
  282. /**
  283. * Sets current target context as bitmask.
  284. *
  285. * @param integer $target
  286. *
  287. * @return void
  288. */
  289. public function setTarget($target)
  290. {
  291. $this->target = $target;
  292. }
  293. /**
  294. * Parses the given docblock string for annotations.
  295. *
  296. * @param string $input The docblock string to parse.
  297. * @param string $context The parsing context.
  298. *
  299. * @return array Array of annotations. If no annotations are found, an empty array is returned.
  300. */
  301. public function parse($input, $context = '')
  302. {
  303. $pos = $this->findInitialTokenPosition($input);
  304. if ($pos === null) {
  305. return array();
  306. }
  307. $this->context = $context;
  308. $this->lexer->setInput(trim(substr($input, $pos), '* /'));
  309. $this->lexer->moveNext();
  310. return $this->Annotations();
  311. }
  312. /**
  313. * Finds the first valid annotation
  314. *
  315. * @param string $input The docblock string to parse
  316. *
  317. * @return int|null
  318. */
  319. private function findInitialTokenPosition($input)
  320. {
  321. $pos = 0;
  322. // search for first valid annotation
  323. while (($pos = strpos($input, '@', $pos)) !== false) {
  324. // if the @ is preceded by a space or * it is valid
  325. if ($pos === 0 || $input[$pos - 1] === ' ' || $input[$pos - 1] === '*') {
  326. return $pos;
  327. }
  328. $pos++;
  329. }
  330. return null;
  331. }
  332. /**
  333. * Attempts to match the given token with the current lookahead token.
  334. * If they match, updates the lookahead token; otherwise raises a syntax error.
  335. *
  336. * @param integer $token Type of token.
  337. *
  338. * @return boolean True if tokens match; false otherwise.
  339. */
  340. private function match($token)
  341. {
  342. if ( ! $this->lexer->isNextToken($token) ) {
  343. $this->syntaxError($this->lexer->getLiteral($token));
  344. }
  345. return $this->lexer->moveNext();
  346. }
  347. /**
  348. * Attempts to match the current lookahead token with any of the given tokens.
  349. *
  350. * If any of them matches, this method updates the lookahead token; otherwise
  351. * a syntax error is raised.
  352. *
  353. * @param array $tokens
  354. *
  355. * @return boolean
  356. */
  357. private function matchAny(array $tokens)
  358. {
  359. if ( ! $this->lexer->isNextTokenAny($tokens)) {
  360. $this->syntaxError(implode(' or ', array_map(array($this->lexer, 'getLiteral'), $tokens)));
  361. }
  362. return $this->lexer->moveNext();
  363. }
  364. /**
  365. * Generates a new syntax error.
  366. *
  367. * @param string $expected Expected string.
  368. * @param array|null $token Optional token.
  369. *
  370. * @return void
  371. *
  372. * @throws AnnotationException
  373. */
  374. private function syntaxError($expected, $token = null)
  375. {
  376. if ($token === null) {
  377. $token = $this->lexer->lookahead;
  378. }
  379. $message = sprintf('Expected %s, got ', $expected);
  380. $message .= ($this->lexer->lookahead === null)
  381. ? 'end of string'
  382. : sprintf("'%s' at position %s", $token['value'], $token['position']);
  383. if (strlen($this->context)) {
  384. $message .= ' in ' . $this->context;
  385. }
  386. $message .= '.';
  387. throw AnnotationException::syntaxError($message);
  388. }
  389. /**
  390. * Attempts to check if a class exists or not. This never goes through the PHP autoloading mechanism
  391. * but uses the {@link AnnotationRegistry} to load classes.
  392. *
  393. * @param string $fqcn
  394. *
  395. * @return boolean
  396. */
  397. private function classExists($fqcn)
  398. {
  399. if (isset($this->classExists[$fqcn])) {
  400. return $this->classExists[$fqcn];
  401. }
  402. // first check if the class already exists, maybe loaded through another AnnotationReader
  403. if (class_exists($fqcn, false)) {
  404. return $this->classExists[$fqcn] = true;
  405. }
  406. // final check, does this class exist?
  407. return $this->classExists[$fqcn] = AnnotationRegistry::loadAnnotationClass($fqcn);
  408. }
  409. /**
  410. * Collects parsing metadata for a given annotation class
  411. *
  412. * @param string $name The annotation name
  413. *
  414. * @return void
  415. */
  416. private function collectAnnotationMetadata($name)
  417. {
  418. if (self::$metadataParser === null) {
  419. self::$metadataParser = new self();
  420. self::$metadataParser->setIgnoreNotImportedAnnotations(true);
  421. self::$metadataParser->setIgnoredAnnotationNames($this->ignoredAnnotationNames);
  422. self::$metadataParser->setImports(array(
  423. 'enum' => 'Doctrine\Common\Annotations\Annotation\Enum',
  424. 'target' => 'Doctrine\Common\Annotations\Annotation\Target',
  425. 'attribute' => 'Doctrine\Common\Annotations\Annotation\Attribute',
  426. 'attributes' => 'Doctrine\Common\Annotations\Annotation\Attributes'
  427. ));
  428. }
  429. $class = new \ReflectionClass($name);
  430. $docComment = $class->getDocComment();
  431. // Sets default values for annotation metadata
  432. $metadata = array(
  433. 'default_property' => null,
  434. 'has_constructor' => (null !== $constructor = $class->getConstructor()) && $constructor->getNumberOfParameters() > 0,
  435. 'properties' => array(),
  436. 'property_types' => array(),
  437. 'attribute_types' => array(),
  438. 'targets_literal' => null,
  439. 'targets' => Target::TARGET_ALL,
  440. 'is_annotation' => false !== strpos($docComment, '@Annotation'),
  441. );
  442. // verify that the class is really meant to be an annotation
  443. if ($metadata['is_annotation']) {
  444. self::$metadataParser->setTarget(Target::TARGET_CLASS);
  445. foreach (self::$metadataParser->parse($docComment, 'class @' . $name) as $annotation) {
  446. if ($annotation instanceof Target) {
  447. $metadata['targets'] = $annotation->targets;
  448. $metadata['targets_literal'] = $annotation->literal;
  449. continue;
  450. }
  451. if ($annotation instanceof Attributes) {
  452. foreach ($annotation->value as $attribute) {
  453. $this->collectAttributeTypeMetadata($metadata, $attribute);
  454. }
  455. }
  456. }
  457. // if not has a constructor will inject values into public properties
  458. if (false === $metadata['has_constructor']) {
  459. // collect all public properties
  460. foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
  461. $metadata['properties'][$property->name] = $property->name;
  462. if (false === ($propertyComment = $property->getDocComment())) {
  463. continue;
  464. }
  465. $attribute = new Attribute();
  466. $attribute->required = (false !== strpos($propertyComment, '@Required'));
  467. $attribute->name = $property->name;
  468. $attribute->type = (false !== strpos($propertyComment, '@var') && preg_match('/@var\s+([^\s]+)/',$propertyComment, $matches))
  469. ? $matches[1]
  470. : 'mixed';
  471. $this->collectAttributeTypeMetadata($metadata, $attribute);
  472. // checks if the property has @Enum
  473. if (false !== strpos($propertyComment, '@Enum')) {
  474. $context = 'property ' . $class->name . "::\$" . $property->name;
  475. self::$metadataParser->setTarget(Target::TARGET_PROPERTY);
  476. foreach (self::$metadataParser->parse($propertyComment, $context) as $annotation) {
  477. if ( ! $annotation instanceof Enum) {
  478. continue;
  479. }
  480. $metadata['enum'][$property->name]['value'] = $annotation->value;
  481. $metadata['enum'][$property->name]['literal'] = ( ! empty($annotation->literal))
  482. ? $annotation->literal
  483. : $annotation->value;
  484. }
  485. }
  486. }
  487. // choose the first property as default property
  488. $metadata['default_property'] = reset($metadata['properties']);
  489. }
  490. }
  491. self::$annotationMetadata[$name] = $metadata;
  492. }
  493. /**
  494. * Collects parsing metadata for a given attribute.
  495. *
  496. * @param array $metadata
  497. * @param Attribute $attribute
  498. *
  499. * @return void
  500. */
  501. private function collectAttributeTypeMetadata(&$metadata, Attribute $attribute)
  502. {
  503. // handle internal type declaration
  504. $type = isset(self::$typeMap[$attribute->type])
  505. ? self::$typeMap[$attribute->type]
  506. : $attribute->type;
  507. // handle the case if the property type is mixed
  508. if ('mixed' === $type) {
  509. return;
  510. }
  511. // Evaluate type
  512. switch (true) {
  513. // Checks if the property has array<type>
  514. case (false !== $pos = strpos($type, '<')):
  515. $arrayType = substr($type, $pos + 1, -1);
  516. $type = 'array';
  517. if (isset(self::$typeMap[$arrayType])) {
  518. $arrayType = self::$typeMap[$arrayType];
  519. }
  520. $metadata['attribute_types'][$attribute->name]['array_type'] = $arrayType;
  521. break;
  522. // Checks if the property has type[]
  523. case (false !== $pos = strrpos($type, '[')):
  524. $arrayType = substr($type, 0, $pos);
  525. $type = 'array';
  526. if (isset(self::$typeMap[$arrayType])) {
  527. $arrayType = self::$typeMap[$arrayType];
  528. }
  529. $metadata['attribute_types'][$attribute->name]['array_type'] = $arrayType;
  530. break;
  531. }
  532. $metadata['attribute_types'][$attribute->name]['type'] = $type;
  533. $metadata['attribute_types'][$attribute->name]['value'] = $attribute->type;
  534. $metadata['attribute_types'][$attribute->name]['required'] = $attribute->required;
  535. }
  536. /**
  537. * Annotations ::= Annotation {[ "*" ]* [Annotation]}*
  538. *
  539. * @return array
  540. */
  541. private function Annotations()
  542. {
  543. $annotations = array();
  544. while (null !== $this->lexer->lookahead) {
  545. if (DocLexer::T_AT !== $this->lexer->lookahead['type']) {
  546. $this->lexer->moveNext();
  547. continue;
  548. }
  549. // make sure the @ is preceded by non-catchable pattern
  550. if (null !== $this->lexer->token && $this->lexer->lookahead['position'] === $this->lexer->token['position'] + strlen($this->lexer->token['value'])) {
  551. $this->lexer->moveNext();
  552. continue;
  553. }
  554. // make sure the @ is followed by either a namespace separator, or
  555. // an identifier token
  556. if ((null === $peek = $this->lexer->glimpse())
  557. || (DocLexer::T_NAMESPACE_SEPARATOR !== $peek['type'] && !in_array($peek['type'], self::$classIdentifiers, true))
  558. || $peek['position'] !== $this->lexer->lookahead['position'] + 1) {
  559. $this->lexer->moveNext();
  560. continue;
  561. }
  562. $this->isNestedAnnotation = false;
  563. if (false !== $annot = $this->Annotation()) {
  564. $annotations[] = $annot;
  565. }
  566. }
  567. return $annotations;
  568. }
  569. /**
  570. * Annotation ::= "@" AnnotationName MethodCall
  571. * AnnotationName ::= QualifiedName | SimpleName
  572. * QualifiedName ::= NameSpacePart "\" {NameSpacePart "\"}* SimpleName
  573. * NameSpacePart ::= identifier | null | false | true
  574. * SimpleName ::= identifier | null | false | true
  575. *
  576. * @return mixed False if it is not a valid annotation.
  577. *
  578. * @throws AnnotationException
  579. */
  580. private function Annotation()
  581. {
  582. $this->match(DocLexer::T_AT);
  583. // check if we have an annotation
  584. $name = $this->Identifier();
  585. // only process names which are not fully qualified, yet
  586. // fully qualified names must start with a \
  587. $originalName = $name;
  588. if ('\\' !== $name[0]) {
  589. $alias = (false === $pos = strpos($name, '\\'))? $name : substr($name, 0, $pos);
  590. $found = false;
  591. if ($this->namespaces) {
  592. if (isset($this->ignoredAnnotationNames[$name])) {
  593. return false;
  594. }
  595. foreach ($this->namespaces as $namespace) {
  596. if ($this->classExists($namespace.'\\'.$name)) {
  597. $name = $namespace.'\\'.$name;
  598. $found = true;
  599. break;
  600. }
  601. }
  602. } elseif (isset($this->imports[$loweredAlias = strtolower($alias)])) {
  603. $found = true;
  604. $name = (false !== $pos)
  605. ? $this->imports[$loweredAlias] . substr($name, $pos)
  606. : $this->imports[$loweredAlias];
  607. } elseif ( ! isset($this->ignoredAnnotationNames[$name])
  608. && isset($this->imports['__NAMESPACE__'])
  609. && $this->classExists($this->imports['__NAMESPACE__'] . '\\' . $name)
  610. ) {
  611. $name = $this->imports['__NAMESPACE__'].'\\'.$name;
  612. $found = true;
  613. } elseif (! isset($this->ignoredAnnotationNames[$name]) && $this->classExists($name)) {
  614. $found = true;
  615. }
  616. if ( ! $found) {
  617. if ($this->ignoreNotImportedAnnotations || isset($this->ignoredAnnotationNames[$name])) {
  618. return false;
  619. }
  620. throw AnnotationException::semanticalError(sprintf('The annotation "@%s" in %s was never imported. Did you maybe forget to add a "use" statement for this annotation?', $name, $this->context));
  621. }
  622. }
  623. if ( ! $this->classExists($name)) {
  624. throw AnnotationException::semanticalError(sprintf('The annotation "@%s" in %s does not exist, or could not be auto-loaded.', $name, $this->context));
  625. }
  626. // at this point, $name contains the fully qualified class name of the
  627. // annotation, and it is also guaranteed that this class exists, and
  628. // that it is loaded
  629. // collects the metadata annotation only if there is not yet
  630. if ( ! isset(self::$annotationMetadata[$name])) {
  631. $this->collectAnnotationMetadata($name);
  632. }
  633. // verify that the class is really meant to be an annotation and not just any ordinary class
  634. if (self::$annotationMetadata[$name]['is_annotation'] === false) {
  635. if (isset($this->ignoredAnnotationNames[$originalName])) {
  636. return false;
  637. }
  638. throw AnnotationException::semanticalError(sprintf('The class "%s" is not annotated with @Annotation. Are you sure this class can be used as annotation? If so, then you need to add @Annotation to the _class_ doc comment of "%s". If it is indeed no annotation, then you need to add @IgnoreAnnotation("%s") to the _class_ doc comment of %s.', $name, $name, $originalName, $this->context));
  639. }
  640. //if target is nested annotation
  641. $target = $this->isNestedAnnotation ? Target::TARGET_ANNOTATION : $this->target;
  642. // Next will be nested
  643. $this->isNestedAnnotation = true;
  644. //if annotation does not support current target
  645. if (0 === (self::$annotationMetadata[$name]['targets'] & $target) && $target) {
  646. throw AnnotationException::semanticalError(
  647. sprintf('Annotation @%s is not allowed to be declared on %s. You may only use this annotation on these code elements: %s.',
  648. $originalName, $this->context, self::$annotationMetadata[$name]['targets_literal'])
  649. );
  650. }
  651. $values = $this->MethodCall();
  652. if (isset(self::$annotationMetadata[$name]['enum'])) {
  653. // checks all declared attributes
  654. foreach (self::$annotationMetadata[$name]['enum'] as $property => $enum) {
  655. // checks if the attribute is a valid enumerator
  656. if (isset($values[$property]) && ! in_array($values[$property], $enum['value'])) {
  657. throw AnnotationException::enumeratorError($property, $name, $this->context, $enum['literal'], $values[$property]);
  658. }
  659. }
  660. }
  661. // checks all declared attributes
  662. foreach (self::$annotationMetadata[$name]['attribute_types'] as $property => $type) {
  663. if ($property === self::$annotationMetadata[$name]['default_property']
  664. && !isset($values[$property]) && isset($values['value'])) {
  665. $property = 'value';
  666. }
  667. // handle a not given attribute or null value
  668. if (!isset($values[$property])) {
  669. if ($type['required']) {
  670. throw AnnotationException::requiredError($property, $originalName, $this->context, 'a(n) '.$type['value']);
  671. }
  672. continue;
  673. }
  674. if ($type['type'] === 'array') {
  675. // handle the case of a single value
  676. if ( ! is_array($values[$property])) {
  677. $values[$property] = array($values[$property]);
  678. }
  679. // checks if the attribute has array type declaration, such as "array<string>"
  680. if (isset($type['array_type'])) {
  681. foreach ($values[$property] as $item) {
  682. if (gettype($item) !== $type['array_type'] && !$item instanceof $type['array_type']) {
  683. throw AnnotationException::attributeTypeError($property, $originalName, $this->context, 'either a(n) '.$type['array_type'].', or an array of '.$type['array_type'].'s', $item);
  684. }
  685. }
  686. }
  687. } elseif (gettype($values[$property]) !== $type['type'] && !$values[$property] instanceof $type['type']) {
  688. throw AnnotationException::attributeTypeError($property, $originalName, $this->context, 'a(n) '.$type['value'], $values[$property]);
  689. }
  690. }
  691. // check if the annotation expects values via the constructor,
  692. // or directly injected into public properties
  693. if (self::$annotationMetadata[$name]['has_constructor'] === true) {
  694. return new $name($values);
  695. }
  696. $instance = new $name();
  697. foreach ($values as $property => $value) {
  698. if (!isset(self::$annotationMetadata[$name]['properties'][$property])) {
  699. if ('value' !== $property) {
  700. throw AnnotationException::creationError(sprintf('The annotation @%s declared on %s does not have a property named "%s". Available properties: %s', $originalName, $this->context, $property, implode(', ', self::$annotationMetadata[$name]['properties'])));
  701. }
  702. // handle the case if the property has no annotations
  703. if ( ! $property = self::$annotationMetadata[$name]['default_property']) {
  704. throw AnnotationException::creationError(sprintf('The annotation @%s declared on %s does not accept any values, but got %s.', $originalName, $this->context, json_encode($values)));
  705. }
  706. }
  707. $instance->{$property} = $value;
  708. }
  709. return $instance;
  710. }
  711. /**
  712. * MethodCall ::= ["(" [Values] ")"]
  713. *
  714. * @return array
  715. */
  716. private function MethodCall()
  717. {
  718. $values = array();
  719. if ( ! $this->lexer->isNextToken(DocLexer::T_OPEN_PARENTHESIS)) {
  720. return $values;
  721. }
  722. $this->match(DocLexer::T_OPEN_PARENTHESIS);
  723. if ( ! $this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) {
  724. $values = $this->Values();
  725. }
  726. $this->match(DocLexer::T_CLOSE_PARENTHESIS);
  727. return $values;
  728. }
  729. /**
  730. * Values ::= Array | Value {"," Value}* [","]
  731. *
  732. * @return array
  733. */
  734. private function Values()
  735. {
  736. $values = array($this->Value());
  737. while ($this->lexer->isNextToken(DocLexer::T_COMMA)) {
  738. $this->match(DocLexer::T_COMMA);
  739. if ($this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) {
  740. break;
  741. }
  742. $token = $this->lexer->lookahead;
  743. $value = $this->Value();
  744. if ( ! is_object($value) && ! is_array($value)) {
  745. $this->syntaxError('Value', $token);
  746. }
  747. $values[] = $value;
  748. }
  749. foreach ($values as $k => $value) {
  750. if (is_object($value) && $value instanceof \stdClass) {
  751. $values[$value->name] = $value->value;
  752. } else if ( ! isset($values['value'])){
  753. $values['value'] = $value;
  754. } else {
  755. if ( ! is_array($values['value'])) {
  756. $values['value'] = array($values['value']);
  757. }
  758. $values['value'][] = $value;
  759. }
  760. unset($values[$k]);
  761. }
  762. return $values;
  763. }
  764. /**
  765. * Constant ::= integer | string | float | boolean
  766. *
  767. * @return mixed
  768. *
  769. * @throws AnnotationException
  770. */
  771. private function Constant()
  772. {
  773. $identifier = $this->Identifier();
  774. if ( ! defined($identifier) && false !== strpos($identifier, '::') && '\\' !== $identifier[0]) {
  775. list($className, $const) = explode('::', $identifier);
  776. $alias = (false === $pos = strpos($className, '\\')) ? $className : substr($className, 0, $pos);
  777. $found = false;
  778. switch (true) {
  779. case !empty ($this->namespaces):
  780. foreach ($this->namespaces as $ns) {
  781. if (class_exists($ns.'\\'.$className) || interface_exists($ns.'\\'.$className)) {
  782. $className = $ns.'\\'.$className;
  783. $found = true;
  784. break;
  785. }
  786. }
  787. break;
  788. case isset($this->imports[$loweredAlias = strtolower($alias)]):
  789. $found = true;
  790. $className = (false !== $pos)
  791. ? $this->imports[$loweredAlias] . substr($className, $pos)
  792. : $this->imports[$loweredAlias];
  793. break;
  794. default:
  795. if(isset($this->imports['__NAMESPACE__'])) {
  796. $ns = $this->imports['__NAMESPACE__'];
  797. if (class_exists($ns.'\\'.$className) || interface_exists($ns.'\\'.$className)) {
  798. $className = $ns.'\\'.$className;
  799. $found = true;
  800. }
  801. }
  802. break;
  803. }
  804. if ($found) {
  805. $identifier = $className . '::' . $const;
  806. }
  807. }
  808. // checks if identifier ends with ::class, \strlen('::class') === 7
  809. $classPos = stripos($identifier, '::class');
  810. if ($classPos === strlen($identifier) - 7) {
  811. return substr($identifier, 0, $classPos);
  812. }
  813. if (!defined($identifier)) {
  814. throw AnnotationException::semanticalErrorConstants($identifier, $this->context);
  815. }
  816. return constant($identifier);
  817. }
  818. /**
  819. * Identifier ::= string
  820. *
  821. * @return string
  822. */
  823. private function Identifier()
  824. {
  825. // check if we have an annotation
  826. if ( ! $this->lexer->isNextTokenAny(self::$classIdentifiers)) {
  827. $this->syntaxError('namespace separator or identifier');
  828. }
  829. $this->lexer->moveNext();
  830. $className = $this->lexer->token['value'];
  831. while (
  832. null !== $this->lexer->lookahead &&
  833. $this->lexer->lookahead['position'] === ($this->lexer->token['position'] + strlen($this->lexer->token['value'])) &&
  834. $this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR)
  835. ) {
  836. $this->match(DocLexer::T_NAMESPACE_SEPARATOR);
  837. $this->matchAny(self::$classIdentifiers);
  838. $className .= '\\' . $this->lexer->token['value'];
  839. }
  840. return $className;
  841. }
  842. /**
  843. * Value ::= PlainValue | FieldAssignment
  844. *
  845. * @return mixed
  846. */
  847. private function Value()
  848. {
  849. $peek = $this->lexer->glimpse();
  850. if (DocLexer::T_EQUALS === $peek['type']) {
  851. return $this->FieldAssignment();
  852. }
  853. return $this->PlainValue();
  854. }
  855. /**
  856. * PlainValue ::= integer | string | float | boolean | Array | Annotation
  857. *
  858. * @return mixed
  859. */
  860. private function PlainValue()
  861. {
  862. if ($this->lexer->isNextToken(DocLexer::T_OPEN_CURLY_BRACES)) {
  863. return $this->Arrayx();
  864. }
  865. if ($this->lexer->isNextToken(DocLexer::T_AT)) {
  866. return $this->Annotation();
  867. }
  868. if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) {
  869. return $this->Constant();
  870. }
  871. switch ($this->lexer->lookahead['type']) {
  872. case DocLexer::T_STRING:
  873. $this->match(DocLexer::T_STRING);
  874. return $this->lexer->token['value'];
  875. case DocLexer::T_INTEGER:
  876. $this->match(DocLexer::T_INTEGER);
  877. return (int)$this->lexer->token['value'];
  878. case DocLexer::T_FLOAT:
  879. $this->match(DocLexer::T_FLOAT);
  880. return (float)$this->lexer->token['value'];
  881. case DocLexer::T_TRUE:
  882. $this->match(DocLexer::T_TRUE);
  883. return true;
  884. case DocLexer::T_FALSE:
  885. $this->match(DocLexer::T_FALSE);
  886. return false;
  887. case DocLexer::T_NULL:
  888. $this->match(DocLexer::T_NULL);
  889. return null;
  890. default:
  891. $this->syntaxError('PlainValue');
  892. }
  893. }
  894. /**
  895. * FieldAssignment ::= FieldName "=" PlainValue
  896. * FieldName ::= identifier
  897. *
  898. * @return array
  899. */
  900. private function FieldAssignment()
  901. {
  902. $this->match(DocLexer::T_IDENTIFIER);
  903. $fieldName = $this->lexer->token['value'];
  904. $this->match(DocLexer::T_EQUALS);
  905. $item = new \stdClass();
  906. $item->name = $fieldName;
  907. $item->value = $this->PlainValue();
  908. return $item;
  909. }
  910. /**
  911. * Array ::= "{" ArrayEntry {"," ArrayEntry}* [","] "}"
  912. *
  913. * @return array
  914. */
  915. private function Arrayx()
  916. {
  917. $array = $values = array();
  918. $this->match(DocLexer::T_OPEN_CURLY_BRACES);
  919. // If the array is empty, stop parsing and return.
  920. if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) {
  921. $this->match(DocLexer::T_CLOSE_CURLY_BRACES);
  922. return $array;
  923. }
  924. $values[] = $this->ArrayEntry();
  925. while ($this->lexer->isNextToken(DocLexer::T_COMMA)) {
  926. $this->match(DocLexer::T_COMMA);
  927. // optional trailing comma
  928. if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) {
  929. break;
  930. }
  931. $values[] = $this->ArrayEntry();
  932. }
  933. $this->match(DocLexer::T_CLOSE_CURLY_BRACES);
  934. foreach ($values as $value) {
  935. list ($key, $val) = $value;
  936. if ($key !== null) {
  937. $array[$key] = $val;
  938. } else {
  939. $array[] = $val;
  940. }
  941. }
  942. return $array;
  943. }
  944. /**
  945. * ArrayEntry ::= Value | KeyValuePair
  946. * KeyValuePair ::= Key ("=" | ":") PlainValue | Constant
  947. * Key ::= string | integer | Constant
  948. *
  949. * @return array
  950. */
  951. private function ArrayEntry()
  952. {
  953. $peek = $this->lexer->glimpse();
  954. if (DocLexer::T_EQUALS === $peek['type']
  955. || DocLexer::T_COLON === $peek['type']) {
  956. if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) {
  957. $key = $this->Constant();
  958. } else {
  959. $this->matchAny(array(DocLexer::T_INTEGER, DocLexer::T_STRING));
  960. $key = $this->lexer->token['value'];
  961. }
  962. $this->matchAny(array(DocLexer::T_EQUALS, DocLexer::T_COLON));
  963. return array($key, $this->PlainValue());
  964. }
  965. return array(null, $this->Value());
  966. }
  967. }