StaticReflectionParser.php 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. <?php
  2. namespace Doctrine\Common\Reflection;
  3. use Doctrine\Common\Annotations\TokenParser;
  4. use ReflectionException;
  5. use const T_CLASS;
  6. use const T_DOC_COMMENT;
  7. use const T_EXTENDS;
  8. use const T_FUNCTION;
  9. use const T_PAAMAYIM_NEKUDOTAYIM;
  10. use const T_PRIVATE;
  11. use const T_PROTECTED;
  12. use const T_PUBLIC;
  13. use const T_STRING;
  14. use const T_USE;
  15. use const T_VAR;
  16. use const T_VARIABLE;
  17. use function array_merge;
  18. use function file_get_contents;
  19. use function ltrim;
  20. use function preg_match;
  21. use function sprintf;
  22. use function strpos;
  23. use function strrpos;
  24. use function strtolower;
  25. use function substr;
  26. /**
  27. * Parses a file for namespaces/use/class declarations.
  28. */
  29. class StaticReflectionParser implements ReflectionProviderInterface
  30. {
  31. /**
  32. * The fully qualified class name.
  33. *
  34. * @var string
  35. */
  36. protected $className;
  37. /**
  38. * The short class name.
  39. *
  40. * @var string
  41. */
  42. protected $shortClassName;
  43. /**
  44. * Whether the caller only wants class annotations.
  45. *
  46. * @var bool
  47. */
  48. protected $classAnnotationOptimize;
  49. /**
  50. * A ClassFinder object which finds the class.
  51. *
  52. * @var ClassFinderInterface
  53. */
  54. protected $finder;
  55. /**
  56. * Whether the parser has run.
  57. *
  58. * @var bool
  59. */
  60. protected $parsed = false;
  61. /**
  62. * The namespace of the class.
  63. *
  64. * @var string
  65. */
  66. protected $namespace = '';
  67. /**
  68. * The use statements of the class.
  69. *
  70. * @var string[]
  71. */
  72. protected $useStatements = [];
  73. /**
  74. * The docComment of the class.
  75. *
  76. * @var mixed[]
  77. */
  78. protected $docComment = [
  79. 'class' => '',
  80. 'property' => [],
  81. 'method' => [],
  82. ];
  83. /**
  84. * The name of the class this class extends, if any.
  85. *
  86. * @var string
  87. */
  88. protected $parentClassName = '';
  89. /**
  90. * The parent PSR-0 Parser.
  91. *
  92. * @var \Doctrine\Common\Reflection\StaticReflectionParser
  93. */
  94. protected $parentStaticReflectionParser;
  95. /**
  96. * Parses a class residing in a PSR-0 hierarchy.
  97. *
  98. * @param string $className The full, namespaced class name.
  99. * @param ClassFinderInterface $finder A ClassFinder object which finds the class.
  100. * @param bool $classAnnotationOptimize Only retrieve the class docComment.
  101. * Presumes there is only one statement per line.
  102. */
  103. public function __construct($className, $finder, $classAnnotationOptimize = false)
  104. {
  105. $this->className = ltrim($className, '\\');
  106. $lastNsPos = strrpos($this->className, '\\');
  107. if ($lastNsPos !== false) {
  108. $this->namespace = substr($this->className, 0, $lastNsPos);
  109. $this->shortClassName = substr($this->className, $lastNsPos + 1);
  110. } else {
  111. $this->shortClassName = $this->className;
  112. }
  113. $this->finder = $finder;
  114. $this->classAnnotationOptimize = $classAnnotationOptimize;
  115. }
  116. /**
  117. * @return void
  118. */
  119. protected function parse()
  120. {
  121. $fileName = $this->finder->findFile($this->className);
  122. if ($this->parsed || ! $fileName) {
  123. return;
  124. }
  125. $this->parsed = true;
  126. $contents = file_get_contents($fileName);
  127. if ($this->classAnnotationOptimize) {
  128. $regex = sprintf('/\A.*^\s*((abstract|final)\s+)?class\s+%s\s+/sm', $this->shortClassName);
  129. if (preg_match($regex, $contents, $matches)) {
  130. $contents = $matches[0];
  131. }
  132. }
  133. $tokenParser = new TokenParser($contents);
  134. $docComment = '';
  135. $last_token = false;
  136. while ($token = $tokenParser->next(false)) {
  137. switch ($token[0]) {
  138. case T_USE:
  139. $this->useStatements = array_merge($this->useStatements, $tokenParser->parseUseStatement());
  140. break;
  141. case T_DOC_COMMENT:
  142. $docComment = $token[1];
  143. break;
  144. case T_CLASS:
  145. if ($last_token !== T_PAAMAYIM_NEKUDOTAYIM) {
  146. $this->docComment['class'] = $docComment;
  147. $docComment = '';
  148. }
  149. break;
  150. case T_VAR:
  151. case T_PRIVATE:
  152. case T_PROTECTED:
  153. case T_PUBLIC:
  154. $token = $tokenParser->next();
  155. if ($token[0] === T_VARIABLE) {
  156. $propertyName = substr($token[1], 1);
  157. $this->docComment['property'][$propertyName] = $docComment;
  158. continue 2;
  159. }
  160. if ($token[0] !== T_FUNCTION) {
  161. // For example, it can be T_FINAL.
  162. continue 2;
  163. }
  164. // No break.
  165. case T_FUNCTION:
  166. // The next string after function is the name, but
  167. // there can be & before the function name so find the
  168. // string.
  169. while (($token = $tokenParser->next()) && $token[0] !== T_STRING) {
  170. continue;
  171. }
  172. $methodName = $token[1];
  173. $this->docComment['method'][$methodName] = $docComment;
  174. $docComment = '';
  175. break;
  176. case T_EXTENDS:
  177. $this->parentClassName = $tokenParser->parseClass();
  178. $nsPos = strpos($this->parentClassName, '\\');
  179. $fullySpecified = false;
  180. if ($nsPos === 0) {
  181. $fullySpecified = true;
  182. } else {
  183. if ($nsPos) {
  184. $prefix = strtolower(substr($this->parentClassName, 0, $nsPos));
  185. $postfix = substr($this->parentClassName, $nsPos);
  186. } else {
  187. $prefix = strtolower($this->parentClassName);
  188. $postfix = '';
  189. }
  190. foreach ($this->useStatements as $alias => $use) {
  191. if ($alias !== $prefix) {
  192. continue;
  193. }
  194. $this->parentClassName = '\\' . $use . $postfix;
  195. $fullySpecified = true;
  196. }
  197. }
  198. if (! $fullySpecified) {
  199. $this->parentClassName = '\\' . $this->namespace . '\\' . $this->parentClassName;
  200. }
  201. break;
  202. }
  203. $last_token = $token[0];
  204. }
  205. }
  206. /**
  207. * @return StaticReflectionParser
  208. */
  209. protected function getParentStaticReflectionParser()
  210. {
  211. if (empty($this->parentStaticReflectionParser)) {
  212. $this->parentStaticReflectionParser = new static($this->parentClassName, $this->finder);
  213. }
  214. return $this->parentStaticReflectionParser;
  215. }
  216. /**
  217. * @return string
  218. */
  219. public function getClassName()
  220. {
  221. return $this->className;
  222. }
  223. /**
  224. * @return string
  225. */
  226. public function getNamespaceName()
  227. {
  228. return $this->namespace;
  229. }
  230. /**
  231. * {@inheritDoc}
  232. */
  233. public function getReflectionClass()
  234. {
  235. return new StaticReflectionClass($this);
  236. }
  237. /**
  238. * {@inheritDoc}
  239. */
  240. public function getReflectionMethod($methodName)
  241. {
  242. return new StaticReflectionMethod($this, $methodName);
  243. }
  244. /**
  245. * {@inheritDoc}
  246. */
  247. public function getReflectionProperty($propertyName)
  248. {
  249. return new StaticReflectionProperty($this, $propertyName);
  250. }
  251. /**
  252. * Gets the use statements from this file.
  253. *
  254. * @return string[]
  255. */
  256. public function getUseStatements()
  257. {
  258. $this->parse();
  259. return $this->useStatements;
  260. }
  261. /**
  262. * Gets the doc comment.
  263. *
  264. * @param string $type The type: 'class', 'property' or 'method'.
  265. * @param string $name The name of the property or method, not needed for 'class'.
  266. *
  267. * @return string The doc comment, empty string if none.
  268. */
  269. public function getDocComment($type = 'class', $name = '')
  270. {
  271. $this->parse();
  272. return $name ? $this->docComment[$type][$name] : $this->docComment[$type];
  273. }
  274. /**
  275. * Gets the PSR-0 parser for the declaring class.
  276. *
  277. * @param string $type The type: 'property' or 'method'.
  278. * @param string $name The name of the property or method.
  279. *
  280. * @return StaticReflectionParser A static reflection parser for the declaring class.
  281. *
  282. * @throws ReflectionException
  283. */
  284. public function getStaticReflectionParserForDeclaringClass($type, $name)
  285. {
  286. $this->parse();
  287. if (isset($this->docComment[$type][$name])) {
  288. return $this;
  289. }
  290. if (! empty($this->parentClassName)) {
  291. return $this->getParentStaticReflectionParser()->getStaticReflectionParserForDeclaringClass($type, $name);
  292. }
  293. throw new ReflectionException('Invalid ' . $type . ' "' . $name . '"');
  294. }
  295. }