DocParser.php 34KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989
  1. <?php
  2. /*
  3. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  4. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  5. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  6. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  7. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  8. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  9. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  10. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  11. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  12. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  13. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  14. *
  15. * This software consists of voluntary contributions made by many individuals
  16. * and is licensed under the MIT license. For more information, see
  17. * <http://www.doctrine-project.org>.
  18. */
  19. namespace Doctrine\Common\Annotations;
  20. use Closure;
  21. use ReflectionClass;
  22. use Doctrine\Common\Annotations\Annotation\Target;
  23. use Doctrine\Common\Annotations\Annotation\Attribute;
  24. use Doctrine\Common\Annotations\Annotation\Attributes;
  25. /**
  26. * A parser for docblock annotations.
  27. *
  28. * It is strongly discouraged to change the default annotation parsing process.
  29. *
  30. * @author Benjamin Eberlei <kontakt@beberlei.de>
  31. * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
  32. * @author Jonathan Wage <jonwage@gmail.com>
  33. * @author Roman Borschel <roman@code-factory.org>
  34. * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  35. * @author Fabio B. Silva <fabio.bat.silva@gmail.com>
  36. */
  37. final class DocParser
  38. {
  39. /**
  40. * An array of all valid tokens for a class name.
  41. *
  42. * @var array
  43. */
  44. private static $classIdentifiers = array(DocLexer::T_IDENTIFIER, DocLexer::T_TRUE, DocLexer::T_FALSE, DocLexer::T_NULL);
  45. /**
  46. * The lexer.
  47. *
  48. * @var \Doctrine\Common\Annotations\DocLexer
  49. */
  50. private $lexer;
  51. /**
  52. * Current target context
  53. *
  54. * @var string
  55. */
  56. private $target;
  57. /**
  58. * Doc Parser used to collect annotation target
  59. *
  60. * @var \Doctrine\Common\Annotations\DocParser
  61. */
  62. private static $metadataParser;
  63. /**
  64. * Flag to control if the current annotation is nested or not.
  65. *
  66. * @var boolean
  67. */
  68. private $isNestedAnnotation = false;
  69. /**
  70. * Hashmap containing all use-statements that are to be used when parsing
  71. * the given doc block.
  72. *
  73. * @var array
  74. */
  75. private $imports = array();
  76. /**
  77. * This hashmap is used internally to cache results of class_exists()
  78. * look-ups.
  79. *
  80. * @var array
  81. */
  82. private $classExists = array();
  83. /**
  84. * Whether annotations that have not been imported should be ignored.
  85. *
  86. * @var boolean
  87. */
  88. private $ignoreNotImportedAnnotations = false;
  89. /**
  90. * An array of default namespaces if operating in simple mode.
  91. *
  92. * @var array
  93. */
  94. private $namespaces = array();
  95. /**
  96. * A list with annotations that are not causing exceptions when not resolved to an annotation class.
  97. *
  98. * The names must be the raw names as used in the class, not the fully qualified
  99. * class names.
  100. *
  101. * @var array
  102. */
  103. private $ignoredAnnotationNames = array();
  104. /**
  105. * @var string
  106. */
  107. private $context = '';
  108. /**
  109. * Hash-map for caching annotation metadata
  110. * @var array
  111. */
  112. private static $annotationMetadata = array(
  113. 'Doctrine\Common\Annotations\Annotation\Target' => array(
  114. 'is_annotation' => true,
  115. 'has_constructor' => true,
  116. 'properties' => array(),
  117. 'targets_literal' => 'ANNOTATION_CLASS',
  118. 'targets' => Target::TARGET_CLASS,
  119. 'default_property' => 'value',
  120. 'attribute_types' => array(
  121. 'value' => array(
  122. 'required' => false,
  123. 'type' =>'array',
  124. 'array_type'=>'string',
  125. 'value' =>'array<string>'
  126. )
  127. ),
  128. ),
  129. 'Doctrine\Common\Annotations\Annotation\Attribute' => array(
  130. 'is_annotation' => true,
  131. 'has_constructor' => false,
  132. 'targets_literal' => 'ANNOTATION_ANNOTATION',
  133. 'targets' => Target::TARGET_ANNOTATION,
  134. 'default_property' => 'name',
  135. 'properties' => array(
  136. 'name' => 'name',
  137. 'type' => 'type',
  138. 'required' => 'required'
  139. ),
  140. 'attribute_types' => array(
  141. 'value' => array(
  142. 'required' => true,
  143. 'type' =>'string',
  144. 'value' =>'string'
  145. ),
  146. 'type' => array(
  147. 'required' =>true,
  148. 'type' =>'string',
  149. 'value' =>'string'
  150. ),
  151. 'required' => array(
  152. 'required' =>false,
  153. 'type' =>'boolean',
  154. 'value' =>'boolean'
  155. )
  156. ),
  157. ),
  158. 'Doctrine\Common\Annotations\Annotation\Attributes' => array(
  159. 'is_annotation' => true,
  160. 'has_constructor' => false,
  161. 'targets_literal' => 'ANNOTATION_CLASS',
  162. 'targets' => Target::TARGET_CLASS,
  163. 'default_property' => 'value',
  164. 'properties' => array(
  165. 'value' => 'value'
  166. ),
  167. 'attribute_types' => array(
  168. 'value' => array(
  169. 'type' =>'array',
  170. 'required' =>true,
  171. 'array_type'=>'Doctrine\Common\Annotations\Annotation\Attribute',
  172. 'value' =>'array<Doctrine\Common\Annotations\Annotation\Attribute>'
  173. )
  174. ),
  175. ),
  176. );
  177. /**
  178. * Hash-map for handle types declaration
  179. *
  180. * @var array
  181. */
  182. private static $typeMap = array(
  183. 'float' => 'double',
  184. 'bool' => 'boolean',
  185. // allow uppercase Boolean in honor of George Boole
  186. 'Boolean' => 'boolean',
  187. 'int' => 'integer',
  188. );
  189. /**
  190. * Constructs a new DocParser.
  191. */
  192. public function __construct()
  193. {
  194. $this->lexer = new DocLexer;
  195. }
  196. /**
  197. * Sets the annotation names that are ignored during the parsing process.
  198. *
  199. * The names are supposed to be the raw names as used in the class, not the
  200. * fully qualified class names.
  201. *
  202. * @param array $names
  203. */
  204. public function setIgnoredAnnotationNames(array $names)
  205. {
  206. $this->ignoredAnnotationNames = $names;
  207. }
  208. /**
  209. * Sets ignore on not-imported annotations
  210. *
  211. * @param $bool
  212. */
  213. public function setIgnoreNotImportedAnnotations($bool)
  214. {
  215. $this->ignoreNotImportedAnnotations = (Boolean) $bool;
  216. }
  217. /**
  218. * Sets the default namespaces.
  219. *
  220. * @param array $namespace
  221. *
  222. * @throws \RuntimeException
  223. */
  224. public function addNamespace($namespace)
  225. {
  226. if ($this->imports) {
  227. throw new \RuntimeException('You must either use addNamespace(), or setImports(), but not both.');
  228. }
  229. $this->namespaces[] = $namespace;
  230. }
  231. /**
  232. * Sets the imports
  233. *
  234. * @param array $imports
  235. * @throws \RuntimeException
  236. */
  237. public function setImports(array $imports)
  238. {
  239. if ($this->namespaces) {
  240. throw new \RuntimeException('You must either use addNamespace(), or setImports(), but not both.');
  241. }
  242. $this->imports = $imports;
  243. }
  244. /**
  245. * Sets current target context as bitmask.
  246. *
  247. * @param integer $target
  248. */
  249. public function setTarget($target)
  250. {
  251. $this->target = $target;
  252. }
  253. /**
  254. * Parses the given docblock string for annotations.
  255. *
  256. * @param string $input The docblock string to parse.
  257. * @param string $context The parsing context.
  258. * @return array Array of annotations. If no annotations are found, an empty array is returned.
  259. */
  260. public function parse($input, $context = '')
  261. {
  262. if (false === $pos = strpos($input, '@')) {
  263. return array();
  264. }
  265. // also parse whatever character is before the @
  266. if ($pos > 0) {
  267. $pos -= 1;
  268. }
  269. $this->context = $context;
  270. $this->lexer->setInput(trim(substr($input, $pos), '* /'));
  271. $this->lexer->moveNext();
  272. return $this->Annotations();
  273. }
  274. /**
  275. * Attempts to match the given token with the current lookahead token.
  276. * If they match, updates the lookahead token; otherwise raises a syntax error.
  277. *
  278. * @param int $token type of Token.
  279. * @return bool True if tokens match; false otherwise.
  280. */
  281. private function match($token)
  282. {
  283. if ( ! $this->lexer->isNextToken($token) ) {
  284. $this->syntaxError($this->lexer->getLiteral($token));
  285. }
  286. return $this->lexer->moveNext();
  287. }
  288. /**
  289. * Attempts to match the current lookahead token with any of the given tokens.
  290. *
  291. * If any of them matches, this method updates the lookahead token; otherwise
  292. * a syntax error is raised.
  293. *
  294. * @param array $tokens
  295. * @return bool
  296. */
  297. private function matchAny(array $tokens)
  298. {
  299. if ( ! $this->lexer->isNextTokenAny($tokens)) {
  300. $this->syntaxError(implode(' or ', array_map(array($this->lexer, 'getLiteral'), $tokens)));
  301. }
  302. return $this->lexer->moveNext();
  303. }
  304. /**
  305. * Generates a new syntax error.
  306. *
  307. * @param string $expected Expected string.
  308. * @param array $token Optional token.
  309. *
  310. * @throws AnnotationException
  311. */
  312. private function syntaxError($expected, $token = null)
  313. {
  314. if ($token === null) {
  315. $token = $this->lexer->lookahead;
  316. }
  317. $message = "Expected {$expected}, got ";
  318. if ($this->lexer->lookahead === null) {
  319. $message .= 'end of string';
  320. } else {
  321. $message .= "'{$token['value']}' at position {$token['position']}";
  322. }
  323. if (strlen($this->context)) {
  324. $message .= ' in ' . $this->context;
  325. }
  326. $message .= '.';
  327. throw AnnotationException::syntaxError($message);
  328. }
  329. /**
  330. * Attempt to check if a class exists or not. This never goes through the PHP autoloading mechanism
  331. * but uses the {@link AnnotationRegistry} to load classes.
  332. *
  333. * @param string $fqcn
  334. * @return boolean
  335. */
  336. private function classExists($fqcn)
  337. {
  338. if (isset($this->classExists[$fqcn])) {
  339. return $this->classExists[$fqcn];
  340. }
  341. // first check if the class already exists, maybe loaded through another AnnotationReader
  342. if (class_exists($fqcn, false)) {
  343. return $this->classExists[$fqcn] = true;
  344. }
  345. // final check, does this class exist?
  346. return $this->classExists[$fqcn] = AnnotationRegistry::loadAnnotationClass($fqcn);
  347. }
  348. /**
  349. * Collects parsing metadata for a given annotation class
  350. *
  351. * @param string $name The annotation name
  352. */
  353. private function collectAnnotationMetadata($name)
  354. {
  355. if (self::$metadataParser == null){
  356. self::$metadataParser = new self();
  357. self::$metadataParser->setTarget(Target::TARGET_CLASS);
  358. self::$metadataParser->setIgnoreNotImportedAnnotations(true);
  359. self::$metadataParser->setImports(array(
  360. 'target' => 'Doctrine\Common\Annotations\Annotation\Target',
  361. 'attribute' => 'Doctrine\Common\Annotations\Annotation\Attribute',
  362. 'attributes' => 'Doctrine\Common\Annotations\Annotation\Attributes'
  363. ));
  364. AnnotationRegistry::registerFile(__DIR__ . '/Annotation/Target.php');
  365. AnnotationRegistry::registerFile(__DIR__ . '/Annotation/Attribute.php');
  366. AnnotationRegistry::registerFile(__DIR__ . '/Annotation/Attributes.php');
  367. }
  368. $class = new \ReflectionClass($name);
  369. $docComment = $class->getDocComment();
  370. // Sets default values for annotation metadata
  371. $metadata = array(
  372. 'default_property' => null,
  373. 'has_constructor' => (null !== $constructor = $class->getConstructor()) && $constructor->getNumberOfParameters() > 0,
  374. 'properties' => array(),
  375. 'property_types' => array(),
  376. 'attribute_types' => array(),
  377. 'targets_literal' => null,
  378. 'targets' => Target::TARGET_ALL,
  379. 'is_annotation' => false !== strpos($docComment, '@Annotation'),
  380. );
  381. // verify that the class is really meant to be an annotation
  382. if ($metadata['is_annotation']) {
  383. foreach (self::$metadataParser->parse($docComment, 'class @' . $name) as $annotation) {
  384. if ($annotation instanceof Target) {
  385. $metadata['targets'] = $annotation->targets;
  386. $metadata['targets_literal'] = $annotation->literal;
  387. } elseif ($annotation instanceof Attributes) {
  388. foreach ($annotation->value as $attrib) {
  389. // handle internal type declaration
  390. $type = isset(self::$typeMap[$attrib->type]) ? self::$typeMap[$attrib->type] : $attrib->type;
  391. // handle the case if the property type is mixed
  392. if ('mixed' !== $type) {
  393. // Checks if the property has array<type>
  394. if (false !== $pos = strpos($type, '<')) {
  395. $arrayType = substr($type, $pos+1, -1);
  396. $type = 'array';
  397. if (isset(self::$typeMap[$arrayType])) {
  398. $arrayType = self::$typeMap[$arrayType];
  399. }
  400. $metadata['attribute_types'][$attrib->name]['array_type'] = $arrayType;
  401. }
  402. $metadata['attribute_types'][$attrib->name]['type'] = $type;
  403. $metadata['attribute_types'][$attrib->name]['value'] = $attrib->type;
  404. $metadata['attribute_types'][$attrib->name]['required'] = $attrib->required;
  405. }
  406. }
  407. }
  408. }
  409. // if not has a constructor will inject values into public properties
  410. if (false === $metadata['has_constructor']) {
  411. // collect all public properties
  412. foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
  413. $metadata['properties'][$property->name] = $property->name;
  414. // checks if the property has @var annotation
  415. if ((false !== $propertyComment = $property->getDocComment())
  416. && false !== strpos($propertyComment, '@var')
  417. && preg_match('/@var\s+([^\s]+)/',$propertyComment, $matches)) {
  418. // literal type declaration
  419. $value = $matches[1];
  420. // handle internal type declaration
  421. $type = isset(self::$typeMap[$value]) ? self::$typeMap[$value] : $value;
  422. // handle the case if the property type is mixed
  423. if ('mixed' !== $type) {
  424. // Checks if the property has @var array<type> annotation
  425. if (false !== $pos = strpos($type, '<')) {
  426. $arrayType = substr($type, $pos+1, -1);
  427. $type = 'array';
  428. if (isset(self::$typeMap[$arrayType])) {
  429. $arrayType = self::$typeMap[$arrayType];
  430. }
  431. $metadata['attribute_types'][$property->name]['array_type'] = $arrayType;
  432. }
  433. $metadata['attribute_types'][$property->name]['type'] = $type;
  434. $metadata['attribute_types'][$property->name]['value'] = $value;
  435. $metadata['attribute_types'][$property->name]['required'] = false !== strpos($propertyComment, '@Required');
  436. }
  437. }
  438. }
  439. // choose the first property as default property
  440. $metadata['default_property'] = reset($metadata['properties']);
  441. }
  442. }
  443. self::$annotationMetadata[$name] = $metadata;
  444. }
  445. /**
  446. * Annotations ::= Annotation {[ "*" ]* [Annotation]}*
  447. *
  448. * @return array
  449. */
  450. private function Annotations()
  451. {
  452. $annotations = array();
  453. while (null !== $this->lexer->lookahead) {
  454. if (DocLexer::T_AT !== $this->lexer->lookahead['type']) {
  455. $this->lexer->moveNext();
  456. continue;
  457. }
  458. // make sure the @ is preceded by non-catchable pattern
  459. if (null !== $this->lexer->token && $this->lexer->lookahead['position'] === $this->lexer->token['position'] + strlen($this->lexer->token['value'])) {
  460. $this->lexer->moveNext();
  461. continue;
  462. }
  463. // make sure the @ is followed by either a namespace separator, or
  464. // an identifier token
  465. if ((null === $peek = $this->lexer->glimpse())
  466. || (DocLexer::T_NAMESPACE_SEPARATOR !== $peek['type'] && !in_array($peek['type'], self::$classIdentifiers, true))
  467. || $peek['position'] !== $this->lexer->lookahead['position'] + 1) {
  468. $this->lexer->moveNext();
  469. continue;
  470. }
  471. $this->isNestedAnnotation = false;
  472. if (false !== $annot = $this->Annotation()) {
  473. $annotations[] = $annot;
  474. }
  475. }
  476. return $annotations;
  477. }
  478. /**
  479. * Annotation ::= "@" AnnotationName ["(" [Values] ")"]
  480. * AnnotationName ::= QualifiedName | SimpleName
  481. * QualifiedName ::= NameSpacePart "\" {NameSpacePart "\"}* SimpleName
  482. * NameSpacePart ::= identifier | null | false | true
  483. * SimpleName ::= identifier | null | false | true
  484. *
  485. * @throws AnnotationException
  486. * @return mixed False if it is not a valid annotation.
  487. */
  488. private function Annotation()
  489. {
  490. $this->match(DocLexer::T_AT);
  491. // check if we have an annotation
  492. $name = $this->Identifier();
  493. // only process names which are not fully qualified, yet
  494. // fully qualified names must start with a \
  495. $originalName = $name;
  496. if ('\\' !== $name[0]) {
  497. $alias = (false === $pos = strpos($name, '\\'))? $name : substr($name, 0, $pos);
  498. $found = false;
  499. if ($this->namespaces) {
  500. foreach ($this->namespaces as $namespace) {
  501. if ($this->classExists($namespace.'\\'.$name)) {
  502. $name = $namespace.'\\'.$name;
  503. $found = true;
  504. break;
  505. }
  506. }
  507. } elseif (isset($this->imports[$loweredAlias = strtolower($alias)])) {
  508. if (false !== $pos) {
  509. $name = $this->imports[$loweredAlias].substr($name, $pos);
  510. } else {
  511. $name = $this->imports[$loweredAlias];
  512. }
  513. $found = true;
  514. } elseif (isset($this->imports['__NAMESPACE__']) && $this->classExists($this->imports['__NAMESPACE__'].'\\'.$name)) {
  515. $name = $this->imports['__NAMESPACE__'].'\\'.$name;
  516. $found = true;
  517. } elseif ($this->classExists($name)) {
  518. $found = true;
  519. }
  520. if (!$found) {
  521. if ($this->ignoreNotImportedAnnotations || isset($this->ignoredAnnotationNames[$name])) {
  522. return false;
  523. }
  524. 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));
  525. }
  526. }
  527. if (!$this->classExists($name)) {
  528. throw AnnotationException::semanticalError(sprintf('The annotation "@%s" in %s does not exist, or could not be auto-loaded.', $name, $this->context));
  529. }
  530. // at this point, $name contains the fully qualified class name of the
  531. // annotation, and it is also guaranteed that this class exists, and
  532. // that it is loaded
  533. // collects the metadata annotation only if there is not yet
  534. if (!isset(self::$annotationMetadata[$name])) {
  535. $this->collectAnnotationMetadata($name);
  536. }
  537. // verify that the class is really meant to be an annotation and not just any ordinary class
  538. if (self::$annotationMetadata[$name]['is_annotation'] === false) {
  539. if (isset($this->ignoredAnnotationNames[$originalName])) {
  540. return false;
  541. }
  542. 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));
  543. }
  544. //if target is nested annotation
  545. $target = $this->isNestedAnnotation ? Target::TARGET_ANNOTATION : $this->target;
  546. // Next will be nested
  547. $this->isNestedAnnotation = true;
  548. //if annotation does not support current target
  549. if (0 === (self::$annotationMetadata[$name]['targets'] & $target) && $target) {
  550. throw AnnotationException::semanticalError(
  551. sprintf('Annotation @%s is not allowed to be declared on %s. You may only use this annotation on these code elements: %s.',
  552. $originalName, $this->context, self::$annotationMetadata[$name]['targets_literal'])
  553. );
  554. }
  555. $values = array();
  556. if ($this->lexer->isNextToken(DocLexer::T_OPEN_PARENTHESIS)) {
  557. $this->match(DocLexer::T_OPEN_PARENTHESIS);
  558. if ( ! $this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) {
  559. $values = $this->Values();
  560. }
  561. $this->match(DocLexer::T_CLOSE_PARENTHESIS);
  562. }
  563. // checks all declared attributes
  564. foreach (self::$annotationMetadata[$name]['attribute_types'] as $property => $type) {
  565. if ($property === self::$annotationMetadata[$name]['default_property']
  566. && !isset($values[$property]) && isset($values['value'])) {
  567. $property = 'value';
  568. }
  569. // handle a not given attribute or null value
  570. if (!isset($values[$property])) {
  571. if ($type['required']) {
  572. throw AnnotationException::requiredError($property, $originalName, $this->context, 'a(n) '.$type['value']);
  573. }
  574. continue;
  575. }
  576. if ($type['type'] === 'array') {
  577. // handle the case of a single value
  578. if (!is_array($values[$property])) {
  579. $values[$property] = array($values[$property]);
  580. }
  581. // checks if the attribute has array type declaration, such as "array<string>"
  582. if (isset($type['array_type'])) {
  583. foreach ($values[$property] as $item) {
  584. if (gettype($item) !== $type['array_type'] && !$item instanceof $type['array_type']) {
  585. throw AnnotationException::typeError($property, $originalName, $this->context, 'either a(n) '.$type['array_type'].', or an array of '.$type['array_type'].'s', $item);
  586. }
  587. }
  588. }
  589. } elseif (gettype($values[$property]) !== $type['type'] && !$values[$property] instanceof $type['type']) {
  590. throw AnnotationException::typeError($property, $originalName, $this->context, 'a(n) '.$type['value'], $values[$property]);
  591. }
  592. }
  593. // check if the annotation expects values via the constructor,
  594. // or directly injected into public properties
  595. if (self::$annotationMetadata[$name]['has_constructor'] === true) {
  596. return new $name($values);
  597. }
  598. $instance = new $name();
  599. foreach ($values as $property => $value) {
  600. if (!isset(self::$annotationMetadata[$name]['properties'][$property])) {
  601. if ('value' !== $property) {
  602. 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'])));
  603. }
  604. // handle the case if the property has no annotations
  605. if (!$property = self::$annotationMetadata[$name]['default_property']) {
  606. throw AnnotationException::creationError(sprintf('The annotation @%s declared on %s does not accept any values, but got %s.', $originalName, $this->context, json_encode($values)));
  607. }
  608. }
  609. $instance->{$property} = $value;
  610. }
  611. return $instance;
  612. }
  613. /**
  614. * Values ::= Array | Value {"," Value}*
  615. *
  616. * @return array
  617. */
  618. private function Values()
  619. {
  620. $values = array();
  621. // Handle the case of a single array as value, i.e. @Foo({....})
  622. if ($this->lexer->isNextToken(DocLexer::T_OPEN_CURLY_BRACES)) {
  623. $values['value'] = $this->Value();
  624. return $values;
  625. }
  626. $values[] = $this->Value();
  627. while ($this->lexer->isNextToken(DocLexer::T_COMMA)) {
  628. $this->match(DocLexer::T_COMMA);
  629. $token = $this->lexer->lookahead;
  630. $value = $this->Value();
  631. if ( ! is_object($value) && ! is_array($value)) {
  632. $this->syntaxError('Value', $token);
  633. }
  634. $values[] = $value;
  635. }
  636. foreach ($values as $k => $value) {
  637. if (is_object($value) && $value instanceof \stdClass) {
  638. $values[$value->name] = $value->value;
  639. } else if ( ! isset($values['value'])){
  640. $values['value'] = $value;
  641. } else {
  642. if ( ! is_array($values['value'])) {
  643. $values['value'] = array($values['value']);
  644. }
  645. $values['value'][] = $value;
  646. }
  647. unset($values[$k]);
  648. }
  649. return $values;
  650. }
  651. /**
  652. * Constant ::= integer | string | float | boolean
  653. *
  654. * @throws AnnotationException
  655. * @return mixed
  656. */
  657. private function Constant()
  658. {
  659. $identifier = $this->Identifier();
  660. if (!defined($identifier) && false !== strpos($identifier, '::') && '\\' !== $identifier[0]) {
  661. list($className, $const) = explode('::', $identifier);
  662. $alias = (false === $pos = strpos($className, '\\'))? $className : substr($className, 0, $pos);
  663. $found = false;
  664. switch (true) {
  665. case !empty ($this->namespaces):
  666. foreach ($this->namespaces as $ns) {
  667. if (class_exists($ns.'\\'.$className) || interface_exists($ns.'\\'.$className)) {
  668. $className = $ns.'\\'.$className;
  669. $found = true;
  670. break;
  671. }
  672. }
  673. break;
  674. case isset($this->imports[$loweredAlias = strtolower($alias)]):
  675. $found = true;
  676. if (false !== $pos) {
  677. $className = $this->imports[$loweredAlias].substr($className, $pos);
  678. } else {
  679. $className = $this->imports[$loweredAlias];
  680. }
  681. break;
  682. default:
  683. if(isset($this->imports['__NAMESPACE__'])) {
  684. $ns = $this->imports['__NAMESPACE__'];
  685. if (class_exists($ns.'\\'.$className) || interface_exists($ns.'\\'.$className)) {
  686. $className = $ns.'\\'.$className;
  687. $found = true;
  688. }
  689. }
  690. break;
  691. }
  692. if ($found) {
  693. $identifier = $className . '::' . $const;
  694. }
  695. }
  696. if (!defined($identifier)) {
  697. throw AnnotationException::semanticalErrorConstants($identifier, $this->context);
  698. }
  699. return constant($identifier);
  700. }
  701. /**
  702. * Identifier ::= string
  703. *
  704. * @return string
  705. */
  706. private function Identifier()
  707. {
  708. // check if we have an annotation
  709. if ($this->lexer->isNextTokenAny(self::$classIdentifiers)) {
  710. $this->lexer->moveNext();
  711. $className = $this->lexer->token['value'];
  712. } else {
  713. $this->syntaxError('namespace separator or identifier');
  714. }
  715. while ($this->lexer->lookahead['position'] === ($this->lexer->token['position'] + strlen($this->lexer->token['value']))
  716. && $this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR)) {
  717. $this->match(DocLexer::T_NAMESPACE_SEPARATOR);
  718. $this->matchAny(self::$classIdentifiers);
  719. $className .= '\\' . $this->lexer->token['value'];
  720. }
  721. return $className;
  722. }
  723. /**
  724. * Value ::= PlainValue | FieldAssignment
  725. *
  726. * @return mixed
  727. */
  728. private function Value()
  729. {
  730. $peek = $this->lexer->glimpse();
  731. if (DocLexer::T_EQUALS === $peek['type']) {
  732. return $this->FieldAssignment();
  733. }
  734. return $this->PlainValue();
  735. }
  736. /**
  737. * PlainValue ::= integer | string | float | boolean | Array | Annotation
  738. *
  739. * @return mixed
  740. */
  741. private function PlainValue()
  742. {
  743. if ($this->lexer->isNextToken(DocLexer::T_OPEN_CURLY_BRACES)) {
  744. return $this->Arrayx();
  745. }
  746. if ($this->lexer->isNextToken(DocLexer::T_AT)) {
  747. return $this->Annotation();
  748. }
  749. if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) {
  750. return $this->Constant();
  751. }
  752. switch ($this->lexer->lookahead['type']) {
  753. case DocLexer::T_STRING:
  754. $this->match(DocLexer::T_STRING);
  755. return $this->lexer->token['value'];
  756. case DocLexer::T_INTEGER:
  757. $this->match(DocLexer::T_INTEGER);
  758. return (int)$this->lexer->token['value'];
  759. case DocLexer::T_FLOAT:
  760. $this->match(DocLexer::T_FLOAT);
  761. return (float)$this->lexer->token['value'];
  762. case DocLexer::T_TRUE:
  763. $this->match(DocLexer::T_TRUE);
  764. return true;
  765. case DocLexer::T_FALSE:
  766. $this->match(DocLexer::T_FALSE);
  767. return false;
  768. case DocLexer::T_NULL:
  769. $this->match(DocLexer::T_NULL);
  770. return null;
  771. default:
  772. $this->syntaxError('PlainValue');
  773. }
  774. }
  775. /**
  776. * FieldAssignment ::= FieldName "=" PlainValue
  777. * FieldName ::= identifier
  778. *
  779. * @return array
  780. */
  781. private function FieldAssignment()
  782. {
  783. $this->match(DocLexer::T_IDENTIFIER);
  784. $fieldName = $this->lexer->token['value'];
  785. $this->match(DocLexer::T_EQUALS);
  786. $item = new \stdClass();
  787. $item->name = $fieldName;
  788. $item->value = $this->PlainValue();
  789. return $item;
  790. }
  791. /**
  792. * Array ::= "{" ArrayEntry {"," ArrayEntry}* [","] "}"
  793. *
  794. * @return array
  795. */
  796. private function Arrayx()
  797. {
  798. $array = $values = array();
  799. $this->match(DocLexer::T_OPEN_CURLY_BRACES);
  800. $values[] = $this->ArrayEntry();
  801. while ($this->lexer->isNextToken(DocLexer::T_COMMA)) {
  802. $this->match(DocLexer::T_COMMA);
  803. // optional trailing comma
  804. if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) {
  805. break;
  806. }
  807. $values[] = $this->ArrayEntry();
  808. }
  809. $this->match(DocLexer::T_CLOSE_CURLY_BRACES);
  810. foreach ($values as $value) {
  811. list ($key, $val) = $value;
  812. if ($key !== null) {
  813. $array[$key] = $val;
  814. } else {
  815. $array[] = $val;
  816. }
  817. }
  818. return $array;
  819. }
  820. /**
  821. * ArrayEntry ::= Value | KeyValuePair
  822. * KeyValuePair ::= Key ("=" | ":") PlainValue | Constant
  823. * Key ::= string | integer | Constant
  824. *
  825. * @return array
  826. */
  827. private function ArrayEntry()
  828. {
  829. $peek = $this->lexer->glimpse();
  830. if (DocLexer::T_EQUALS === $peek['type']
  831. || DocLexer::T_COLON === $peek['type']) {
  832. if ($this->lexer->isNextToken(DocLexer::T_IDENTIFIER)) {
  833. $key = $this->Constant();
  834. } else {
  835. $this->matchAny(array(DocLexer::T_INTEGER, DocLexer::T_STRING));
  836. $key = $this->lexer->token['value'];
  837. }
  838. $this->matchAny(array(DocLexer::T_EQUALS, DocLexer::T_COLON));
  839. return array($key, $this->PlainValue());
  840. }
  841. return array(null, $this->Value());
  842. }
  843. }