DocParser.php 37 KB

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