DocParser.php 36 KB

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