DocParser.php 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891
  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 LGPL. 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. public function setIgnoreNotImportedAnnotations($bool)
  209. {
  210. $this->ignoreNotImportedAnnotations = (Boolean) $bool;
  211. }
  212. /**
  213. * Sets the default namespaces.
  214. * @param array $namespaces
  215. */
  216. public function addNamespace($namespace)
  217. {
  218. if ($this->imports) {
  219. throw new \RuntimeException('You must either use addNamespace(), or setImports(), but not both.');
  220. }
  221. $this->namespaces[] = $namespace;
  222. }
  223. public function setImports(array $imports)
  224. {
  225. if ($this->namespaces) {
  226. throw new \RuntimeException('You must either use addNamespace(), or setImports(), but not both.');
  227. }
  228. $this->imports = $imports;
  229. }
  230. /**
  231. * Sets current target context as bitmask.
  232. *
  233. * @param integer $target
  234. */
  235. public function setTarget($target)
  236. {
  237. $this->target = $target;
  238. }
  239. /**
  240. * Parses the given docblock string for annotations.
  241. *
  242. * @param string $input The docblock string to parse.
  243. * @param string $context The parsing context.
  244. * @return array Array of annotations. If no annotations are found, an empty array is returned.
  245. */
  246. public function parse($input, $context = '')
  247. {
  248. if (false === $pos = strpos($input, '@')) {
  249. return array();
  250. }
  251. // also parse whatever character is before the @
  252. if ($pos > 0) {
  253. $pos -= 1;
  254. }
  255. $this->context = $context;
  256. $this->lexer->setInput(trim(substr($input, $pos), '* /'));
  257. $this->lexer->moveNext();
  258. return $this->Annotations();
  259. }
  260. /**
  261. * Attempts to match the given token with the current lookahead token.
  262. * If they match, updates the lookahead token; otherwise raises a syntax error.
  263. *
  264. * @param int Token type.
  265. * @return bool True if tokens match; false otherwise.
  266. */
  267. private function match($token)
  268. {
  269. if ( ! $this->lexer->isNextToken($token) ) {
  270. $this->syntaxError($this->lexer->getLiteral($token));
  271. }
  272. return $this->lexer->moveNext();
  273. }
  274. /**
  275. * Attempts to match the current lookahead token with any of the given tokens.
  276. *
  277. * If any of them matches, this method updates the lookahead token; otherwise
  278. * a syntax error is raised.
  279. *
  280. * @param array $tokens
  281. * @return bool
  282. */
  283. private function matchAny(array $tokens)
  284. {
  285. if ( ! $this->lexer->isNextTokenAny($tokens)) {
  286. $this->syntaxError(implode(' or ', array_map(array($this->lexer, 'getLiteral'), $tokens)));
  287. }
  288. return $this->lexer->moveNext();
  289. }
  290. /**
  291. * Generates a new syntax error.
  292. *
  293. * @param string $expected Expected string.
  294. * @param array $token Optional token.
  295. * @throws SyntaxException
  296. */
  297. private function syntaxError($expected, $token = null)
  298. {
  299. if ($token === null) {
  300. $token = $this->lexer->lookahead;
  301. }
  302. $message = "Expected {$expected}, got ";
  303. if ($this->lexer->lookahead === null) {
  304. $message .= 'end of string';
  305. } else {
  306. $message .= "'{$token['value']}' at position {$token['position']}";
  307. }
  308. if (strlen($this->context)) {
  309. $message .= ' in ' . $this->context;
  310. }
  311. $message .= '.';
  312. throw AnnotationException::syntaxError($message);
  313. }
  314. /**
  315. * Attempt to check if a class exists or not. This never goes through the PHP autoloading mechanism
  316. * but uses the {@link AnnotationRegistry} to load classes.
  317. *
  318. * @param string $fqcn
  319. * @return boolean
  320. */
  321. private function classExists($fqcn)
  322. {
  323. if (isset($this->classExists[$fqcn])) {
  324. return $this->classExists[$fqcn];
  325. }
  326. // first check if the class already exists, maybe loaded through another AnnotationReader
  327. if (class_exists($fqcn, false)) {
  328. return $this->classExists[$fqcn] = true;
  329. }
  330. // final check, does this class exist?
  331. return $this->classExists[$fqcn] = AnnotationRegistry::loadAnnotationClass($fqcn);
  332. }
  333. /**
  334. * Collects parsing metadata for a given annotation class
  335. *
  336. * @param string $name The annotation name
  337. */
  338. private function collectAnnotationMetadata($name)
  339. {
  340. if (self::$metadataParser == null){
  341. self::$metadataParser = new self();
  342. self::$metadataParser->setTarget(Target::TARGET_CLASS);
  343. self::$metadataParser->setIgnoreNotImportedAnnotations(true);
  344. self::$metadataParser->setImports(array(
  345. 'target' => 'Doctrine\Common\Annotations\Annotation\Target',
  346. 'attribute' => 'Doctrine\Common\Annotations\Annotation\Attribute',
  347. 'attributes' => 'Doctrine\Common\Annotations\Annotation\Attributes'
  348. ));
  349. AnnotationRegistry::registerFile(__DIR__ . '/Annotation/Target.php');
  350. AnnotationRegistry::registerFile(__DIR__ . '/Annotation/Attribute.php');
  351. AnnotationRegistry::registerFile(__DIR__ . '/Annotation/Attributes.php');
  352. }
  353. $class = new \ReflectionClass($name);
  354. $docComment = $class->getDocComment();
  355. // Sets default values for annotation metadata
  356. $metadata = array(
  357. 'default_property' => null,
  358. 'has_constructor' => (null !== $constructor = $class->getConstructor()) && $constructor->getNumberOfParameters() > 0,
  359. 'properties' => array(),
  360. 'property_types' => array(),
  361. 'attribute_types' => array(),
  362. 'targets_literal' => null,
  363. 'targets' => Target::TARGET_ALL,
  364. 'is_annotation' => false !== strpos($docComment, '@Annotation'),
  365. );
  366. // verify that the class is really meant to be an annotation
  367. if ($metadata['is_annotation']) {
  368. foreach (self::$metadataParser->parse($docComment, 'class @' . $name) as $annotation) {
  369. if ($annotation instanceof Target) {
  370. $metadata['targets'] = $annotation->targets;
  371. $metadata['targets_literal'] = $annotation->literal;
  372. } elseif ($annotation instanceof Attributes) {
  373. foreach ($annotation->value as $attrib) {
  374. // handle internal type declaration
  375. $type = isset(self::$typeMap[$attrib->type]) ? self::$typeMap[$attrib->type] : $attrib->type;
  376. // handle the case if the property type is mixed
  377. if ('mixed' !== $type) {
  378. // Checks if the property has array<type>
  379. if (false !== $pos = strpos($type, '<')) {
  380. $arrayType = substr($type, $pos+1, -1);
  381. $type = 'array';
  382. if (isset(self::$typeMap[$arrayType])) {
  383. $arrayType = self::$typeMap[$arrayType];
  384. }
  385. $metadata['attribute_types'][$attrib->name]['array_type'] = $arrayType;
  386. }
  387. $metadata['attribute_types'][$attrib->name]['type'] = $type;
  388. $metadata['attribute_types'][$attrib->name]['value'] = $attrib->type;
  389. $metadata['attribute_types'][$attrib->name]['required'] = $attrib->required;
  390. }
  391. }
  392. }
  393. }
  394. // if not has a constructor will inject values into public properties
  395. if (false === $metadata['has_constructor']) {
  396. // collect all public properties
  397. foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
  398. $metadata['properties'][$property->name] = $property->name;
  399. // checks if the property has @var annotation
  400. if ((false !== $propertyComment = $property->getDocComment())
  401. && false !== strpos($propertyComment, '@var')
  402. && preg_match('/@var\s+([^\s]+)/',$propertyComment, $matches)) {
  403. // literal type declaration
  404. $value = $matches[1];
  405. // handle internal type declaration
  406. $type = isset(self::$typeMap[$value]) ? self::$typeMap[$value] : $value;
  407. // handle the case if the property type is mixed
  408. if ('mixed' !== $type) {
  409. // Checks if the property has @var array<type> annotation
  410. if (false !== $pos = strpos($type, '<')) {
  411. $arrayType = substr($type, $pos+1, -1);
  412. $type = 'array';
  413. if (isset(self::$typeMap[$arrayType])) {
  414. $arrayType = self::$typeMap[$arrayType];
  415. }
  416. $metadata['attribute_types'][$property->name]['array_type'] = $arrayType;
  417. }
  418. $metadata['attribute_types'][$property->name]['type'] = $type;
  419. $metadata['attribute_types'][$property->name]['value'] = $value;
  420. $metadata['attribute_types'][$property->name]['required'] = false !== strpos($propertyComment, '@Required');
  421. }
  422. }
  423. }
  424. // choose the first property as default property
  425. $metadata['default_property'] = reset($metadata['properties']);
  426. }
  427. }
  428. self::$annotationMetadata[$name] = $metadata;
  429. }
  430. /**
  431. * Annotations ::= Annotation {[ "*" ]* [Annotation]}*
  432. *
  433. * @return array
  434. */
  435. private function Annotations()
  436. {
  437. $annotations = array();
  438. while (null !== $this->lexer->lookahead) {
  439. if (DocLexer::T_AT !== $this->lexer->lookahead['type']) {
  440. $this->lexer->moveNext();
  441. continue;
  442. }
  443. // make sure the @ is preceded by non-catchable pattern
  444. if (null !== $this->lexer->token && $this->lexer->lookahead['position'] === $this->lexer->token['position'] + strlen($this->lexer->token['value'])) {
  445. $this->lexer->moveNext();
  446. continue;
  447. }
  448. // make sure the @ is followed by either a namespace separator, or
  449. // an identifier token
  450. if ((null === $peek = $this->lexer->glimpse())
  451. || (DocLexer::T_NAMESPACE_SEPARATOR !== $peek['type'] && !in_array($peek['type'], self::$classIdentifiers, true))
  452. || $peek['position'] !== $this->lexer->lookahead['position'] + 1) {
  453. $this->lexer->moveNext();
  454. continue;
  455. }
  456. $this->isNestedAnnotation = false;
  457. if (false !== $annot = $this->Annotation()) {
  458. $annotations[] = $annot;
  459. }
  460. }
  461. return $annotations;
  462. }
  463. /**
  464. * Annotation ::= "@" AnnotationName ["(" [Values] ")"]
  465. * AnnotationName ::= QualifiedName | SimpleName
  466. * QualifiedName ::= NameSpacePart "\" {NameSpacePart "\"}* SimpleName
  467. * NameSpacePart ::= identifier | null | false | true
  468. * SimpleName ::= identifier | null | false | true
  469. *
  470. * @return mixed False if it is not a valid annotation.
  471. */
  472. private function Annotation()
  473. {
  474. $this->match(DocLexer::T_AT);
  475. // check if we have an annotation
  476. if ($this->lexer->isNextTokenAny(self::$classIdentifiers)) {
  477. $this->lexer->moveNext();
  478. $name = $this->lexer->token['value'];
  479. } else if ($this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR)) {
  480. $name = '';
  481. } else {
  482. $this->syntaxError('namespace separator or identifier');
  483. }
  484. while ($this->lexer->lookahead['position'] === $this->lexer->token['position'] + strlen($this->lexer->token['value']) && $this->lexer->isNextToken(DocLexer::T_NAMESPACE_SEPARATOR)) {
  485. $this->match(DocLexer::T_NAMESPACE_SEPARATOR);
  486. $this->matchAny(self::$classIdentifiers);
  487. $name .= '\\'.$this->lexer->token['value'];
  488. }
  489. // only process names which are not fully qualified, yet
  490. // fully qualified names must start with a \
  491. $originalName = $name;
  492. if ('\\' !== $name[0]) {
  493. $alias = (false === $pos = strpos($name, '\\'))? $name : substr($name, 0, $pos);
  494. $found = false;
  495. if ($this->namespaces) {
  496. foreach ($this->namespaces as $namespace) {
  497. if ($this->classExists($namespace.'\\'.$name)) {
  498. $name = $namespace.'\\'.$name;
  499. $found = true;
  500. break;
  501. }
  502. }
  503. } elseif (isset($this->imports[$loweredAlias = strtolower($alias)])) {
  504. if (false !== $pos) {
  505. $name = $this->imports[$loweredAlias].substr($name, $pos);
  506. } else {
  507. $name = $this->imports[$loweredAlias];
  508. }
  509. $found = true;
  510. } elseif (isset($this->imports['__NAMESPACE__']) && $this->classExists($this->imports['__NAMESPACE__'].'\\'.$name)) {
  511. $name = $this->imports['__NAMESPACE__'].'\\'.$name;
  512. $found = true;
  513. } elseif ($this->classExists($name)) {
  514. $found = true;
  515. }
  516. if (!$found) {
  517. if ($this->ignoreNotImportedAnnotations || isset($this->ignoredAnnotationNames[$name])) {
  518. return false;
  519. }
  520. 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));
  521. }
  522. }
  523. if (!$this->classExists($name)) {
  524. throw AnnotationException::semanticalError(sprintf('The annotation "@%s" in %s does not exist, or could not be auto-loaded.', $name, $this->context));
  525. }
  526. // at this point, $name contains the fully qualified class name of the
  527. // annotation, and it is also guaranteed that this class exists, and
  528. // that it is loaded
  529. // collects the metadata annotation only if there is not yet
  530. if (!isset(self::$annotationMetadata[$name])) {
  531. $this->collectAnnotationMetadata($name);
  532. }
  533. // verify that the class is really meant to be an annotation and not just any ordinary class
  534. if (self::$annotationMetadata[$name]['is_annotation'] === false) {
  535. if (isset($this->ignoredAnnotationNames[$originalName])) {
  536. return false;
  537. }
  538. 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));
  539. }
  540. //if target is nested annotation
  541. $target = $this->isNestedAnnotation ? Target::TARGET_ANNOTATION : $this->target;
  542. // Next will be nested
  543. $this->isNestedAnnotation = true;
  544. //if annotation does not support current target
  545. if (0 === (self::$annotationMetadata[$name]['targets'] & $target) && $target) {
  546. throw AnnotationException::semanticalError(
  547. sprintf('Annotation @%s is not allowed to be declared on %s. You may only use this annotation on these code elements: %s.',
  548. $originalName, $this->context, self::$annotationMetadata[$name]['targets_literal'])
  549. );
  550. }
  551. $values = array();
  552. if ($this->lexer->isNextToken(DocLexer::T_OPEN_PARENTHESIS)) {
  553. $this->match(DocLexer::T_OPEN_PARENTHESIS);
  554. if ( ! $this->lexer->isNextToken(DocLexer::T_CLOSE_PARENTHESIS)) {
  555. $values = $this->Values();
  556. }
  557. $this->match(DocLexer::T_CLOSE_PARENTHESIS);
  558. }
  559. // checks all declared attributes
  560. foreach (self::$annotationMetadata[$name]['attribute_types'] as $property => $type) {
  561. if ($property === self::$annotationMetadata[$name]['default_property']
  562. && !isset($values[$property]) && isset($values['value'])) {
  563. $property = 'value';
  564. }
  565. // handle a not given attribute or null value
  566. if (!isset($values[$property])) {
  567. if ($type['required']) {
  568. throw AnnotationException::requiredError($property, $originalName, $this->context, 'a(n) '.$type['value']);
  569. }
  570. continue;
  571. }
  572. if ($type['type'] === 'array') {
  573. // handle the case of a single value
  574. if (!is_array($values[$property])) {
  575. $values[$property] = array($values[$property]);
  576. }
  577. // checks if the attribute has array type declaration, such as "array<string>"
  578. if (isset($type['array_type'])) {
  579. foreach ($values[$property] as $item) {
  580. if (gettype($item) !== $type['array_type'] && !$item instanceof $type['array_type']) {
  581. throw AnnotationException::typeError($property, $originalName, $this->context, 'either a(n) '.$type['array_type'].', or an array of '.$type['array_type'].'s', $item);
  582. }
  583. }
  584. }
  585. } elseif (gettype($values[$property]) !== $type['type'] && !$values[$property] instanceof $type['type']) {
  586. throw AnnotationException::typeError($property, $originalName, $this->context, 'a(n) '.$type['value'], $values[$property]);
  587. }
  588. }
  589. // check if the annotation expects values via the constructor,
  590. // or directly injected into public properties
  591. if (self::$annotationMetadata[$name]['has_constructor'] === true) {
  592. return new $name($values);
  593. }
  594. $instance = new $name();
  595. foreach ($values as $property => $value) {
  596. if (!isset(self::$annotationMetadata[$name]['properties'][$property])) {
  597. if ('value' !== $property) {
  598. 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'])));
  599. }
  600. // handle the case if the property has no annotations
  601. if (!$property = self::$annotationMetadata[$name]['default_property']) {
  602. throw AnnotationException::creationError(sprintf('The annotation @%s declared on %s does not accept any values, but got %s.', $originalName, $this->context, json_encode($values)));
  603. }
  604. }
  605. $instance->{$property} = $value;
  606. }
  607. return $instance;
  608. }
  609. /**
  610. * Values ::= Array | Value {"," Value}*
  611. *
  612. * @return array
  613. */
  614. private function Values()
  615. {
  616. $values = array();
  617. // Handle the case of a single array as value, i.e. @Foo({....})
  618. if ($this->lexer->isNextToken(DocLexer::T_OPEN_CURLY_BRACES)) {
  619. $values['value'] = $this->Value();
  620. return $values;
  621. }
  622. $values[] = $this->Value();
  623. while ($this->lexer->isNextToken(DocLexer::T_COMMA)) {
  624. $this->match(DocLexer::T_COMMA);
  625. $token = $this->lexer->lookahead;
  626. $value = $this->Value();
  627. if ( ! is_object($value) && ! is_array($value)) {
  628. $this->syntaxError('Value', $token);
  629. }
  630. $values[] = $value;
  631. }
  632. foreach ($values as $k => $value) {
  633. if (is_object($value) && $value instanceof \stdClass) {
  634. $values[$value->name] = $value->value;
  635. } else if ( ! isset($values['value'])){
  636. $values['value'] = $value;
  637. } else {
  638. if ( ! is_array($values['value'])) {
  639. $values['value'] = array($values['value']);
  640. }
  641. $values['value'][] = $value;
  642. }
  643. unset($values[$k]);
  644. }
  645. return $values;
  646. }
  647. /**
  648. * Value ::= PlainValue | FieldAssignment
  649. *
  650. * @return mixed
  651. */
  652. private function Value()
  653. {
  654. $peek = $this->lexer->glimpse();
  655. if (DocLexer::T_EQUALS === $peek['type']) {
  656. return $this->FieldAssignment();
  657. }
  658. return $this->PlainValue();
  659. }
  660. /**
  661. * PlainValue ::= integer | string | float | boolean | Array | Annotation
  662. *
  663. * @return mixed
  664. */
  665. private function PlainValue()
  666. {
  667. if ($this->lexer->isNextToken(DocLexer::T_OPEN_CURLY_BRACES)) {
  668. return $this->Arrayx();
  669. }
  670. if ($this->lexer->isNextToken(DocLexer::T_AT)) {
  671. return $this->Annotation();
  672. }
  673. switch ($this->lexer->lookahead['type']) {
  674. case DocLexer::T_STRING:
  675. $this->match(DocLexer::T_STRING);
  676. return $this->lexer->token['value'];
  677. case DocLexer::T_INTEGER:
  678. $this->match(DocLexer::T_INTEGER);
  679. return (int)$this->lexer->token['value'];
  680. case DocLexer::T_FLOAT:
  681. $this->match(DocLexer::T_FLOAT);
  682. return (float)$this->lexer->token['value'];
  683. case DocLexer::T_TRUE:
  684. $this->match(DocLexer::T_TRUE);
  685. return true;
  686. case DocLexer::T_FALSE:
  687. $this->match(DocLexer::T_FALSE);
  688. return false;
  689. case DocLexer::T_NULL:
  690. $this->match(DocLexer::T_NULL);
  691. return null;
  692. default:
  693. $this->syntaxError('PlainValue');
  694. }
  695. }
  696. /**
  697. * FieldAssignment ::= FieldName "=" PlainValue
  698. * FieldName ::= identifier
  699. *
  700. * @return array
  701. */
  702. private function FieldAssignment()
  703. {
  704. $this->match(DocLexer::T_IDENTIFIER);
  705. $fieldName = $this->lexer->token['value'];
  706. $this->match(DocLexer::T_EQUALS);
  707. $item = new \stdClass();
  708. $item->name = $fieldName;
  709. $item->value = $this->PlainValue();
  710. return $item;
  711. }
  712. /**
  713. * Array ::= "{" ArrayEntry {"," ArrayEntry}* [","] "}"
  714. *
  715. * @return array
  716. */
  717. private function Arrayx()
  718. {
  719. $array = $values = array();
  720. $this->match(DocLexer::T_OPEN_CURLY_BRACES);
  721. $values[] = $this->ArrayEntry();
  722. while ($this->lexer->isNextToken(DocLexer::T_COMMA)) {
  723. $this->match(DocLexer::T_COMMA);
  724. // optional trailing comma
  725. if ($this->lexer->isNextToken(DocLexer::T_CLOSE_CURLY_BRACES)) {
  726. break;
  727. }
  728. $values[] = $this->ArrayEntry();
  729. }
  730. $this->match(DocLexer::T_CLOSE_CURLY_BRACES);
  731. foreach ($values as $value) {
  732. list ($key, $val) = $value;
  733. if ($key !== null) {
  734. $array[$key] = $val;
  735. } else {
  736. $array[] = $val;
  737. }
  738. }
  739. return $array;
  740. }
  741. /**
  742. * ArrayEntry ::= Value | KeyValuePair
  743. * KeyValuePair ::= Key ("=" | ":") PlainValue
  744. * Key ::= string | integer
  745. *
  746. * @return array
  747. */
  748. private function ArrayEntry()
  749. {
  750. $peek = $this->lexer->glimpse();
  751. if (DocLexer::T_EQUALS === $peek['type']
  752. || DocLexer::T_COLON === $peek['type']) {
  753. $this->matchAny(array(DocLexer::T_INTEGER, DocLexer::T_STRING));
  754. $key = $this->lexer->token['value'];
  755. $this->matchAny(array(DocLexer::T_EQUALS, DocLexer::T_COLON));
  756. return array($key, $this->PlainValue());
  757. }
  758. return array(null, $this->Value());
  759. }
  760. }