PageRenderTime 60ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 1ms

/Nette/Reflection/AnnotationsParser.php

http://github.com/nette/nette
PHP | 357 lines | 274 code | 47 blank | 36 comment | 33 complexity | 390e8615105100b1c2fe9c7cdee74daa MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * This file is part of the Nette Framework (http://nette.org)
  4. *
  5. * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
  6. *
  7. * For the full copyright and license information, please view
  8. * the file license.txt that was distributed with this source code.
  9. */
  10. namespace Nette\Reflection;
  11. use Nette,
  12. Nette\Utils\Strings;
  13. /**
  14. * Annotations support for PHP.
  15. *
  16. * @author David Grudl
  17. * @Annotation
  18. */
  19. final class AnnotationsParser
  20. {
  21. /** @internal single & double quoted PHP string */
  22. const RE_STRING = '\'(?:\\\\.|[^\'\\\\])*\'|"(?:\\\\.|[^"\\\\])*"';
  23. /** @internal identifier */
  24. const RE_IDENTIFIER = '[_a-zA-Z\x7F-\xFF][_a-zA-Z0-9\x7F-\xFF-]*';
  25. /** @var bool */
  26. public static $useReflection;
  27. /** @var array */
  28. public static $inherited = array('description', 'param', 'return');
  29. /** @var array */
  30. private static $cache;
  31. /** @var array */
  32. private static $timestamps;
  33. /** @var Nette\Caching\IStorage */
  34. private static $cacheStorage;
  35. /**
  36. * Static class - cannot be instantiated.
  37. */
  38. final public function __construct()
  39. {
  40. throw new Nette\StaticClassException;
  41. }
  42. /**
  43. * Returns annotations.
  44. * @param \ReflectionClass|\ReflectionMethod|\ReflectionProperty
  45. * @return array
  46. */
  47. public static function getAll(\Reflector $r)
  48. {
  49. if ($r instanceof \ReflectionClass) {
  50. $type = $r->getName();
  51. $member = '';
  52. } elseif ($r instanceof \ReflectionMethod) {
  53. $type = $r->getDeclaringClass()->getName();
  54. $member = $r->getName();
  55. } else {
  56. $type = $r->getDeclaringClass()->getName();
  57. $member = '$' . $r->getName();
  58. }
  59. if (!self::$useReflection) { // auto-expire cache
  60. $file = $r instanceof \ReflectionClass ? $r->getFileName() : $r->getDeclaringClass()->getFileName(); // will be used later
  61. if ($file && isset(self::$timestamps[$file]) && self::$timestamps[$file] !== filemtime($file)) {
  62. unset(self::$cache[$type]);
  63. }
  64. unset(self::$timestamps[$file]);
  65. }
  66. if (isset(self::$cache[$type][$member])) { // is value cached?
  67. return self::$cache[$type][$member];
  68. }
  69. if (self::$useReflection === NULL) { // detects whether is reflection available
  70. self::$useReflection = (bool) ClassType::from(__CLASS__)->getDocComment();
  71. }
  72. if (self::$useReflection) {
  73. $annotations = self::parseComment($r->getDocComment());
  74. } else {
  75. if (!self::$cacheStorage) {
  76. // trigger_error('Set a cache storage for annotations parser via Nette\Reflection\AnnotationParser::setCacheStorage().', E_USER_WARNING);
  77. self::$cacheStorage = new Nette\Caching\Storages\DevNullStorage;
  78. }
  79. $outerCache = new Nette\Caching\Cache(self::$cacheStorage, 'Nette.Reflection.Annotations');
  80. if (self::$cache === NULL) {
  81. self::$cache = (array) $outerCache->offsetGet('list');
  82. self::$timestamps = isset(self::$cache['*']) ? self::$cache['*'] : array();
  83. }
  84. if (!isset(self::$cache[$type]) && $file) {
  85. self::$cache['*'][$file] = filemtime($file);
  86. self::parseScript($file);
  87. $outerCache->save('list', self::$cache);
  88. }
  89. if (isset(self::$cache[$type][$member])) {
  90. $annotations = self::$cache[$type][$member];
  91. } else {
  92. $annotations = array();
  93. }
  94. }
  95. if ($r instanceof \ReflectionMethod && !$r->isPrivate()
  96. && (!$r->isConstructor() || !empty($annotations['inheritdoc'][0])))
  97. {
  98. try {
  99. $inherited = self::getAll(new \ReflectionMethod(get_parent_class($type), $member));
  100. } catch (\ReflectionException $e) {
  101. try {
  102. $inherited = self::getAll($r->getPrototype());
  103. } catch (\ReflectionException $e) {
  104. $inherited = array();
  105. }
  106. }
  107. $annotations += array_intersect_key($inherited, array_flip(self::$inherited));
  108. }
  109. return self::$cache[$type][$member] = $annotations;
  110. }
  111. /**
  112. * Parses phpDoc comment.
  113. * @param string
  114. * @return array
  115. */
  116. private static function parseComment($comment)
  117. {
  118. static $tokens = array('true' => TRUE, 'false' => FALSE, 'null' => NULL, '' => TRUE);
  119. $res = array();
  120. $comment = preg_replace('#^\s*\*\s?#ms', '', trim($comment, '/*'));
  121. $parts = preg_split('#^\s*(?=@'.self::RE_IDENTIFIER.')#m', $comment, 2);
  122. $description = trim($parts[0]);
  123. if ($description !== '') {
  124. $res['description'] = array($description);
  125. }
  126. $matches = Strings::matchAll(
  127. isset($parts[1]) ? $parts[1] : '',
  128. '~
  129. (?<=\s|^)@('.self::RE_IDENTIFIER.')[ \t]* ## annotation
  130. (
  131. \((?>'.self::RE_STRING.'|[^\'")@]+)+\)| ## (value)
  132. [^(@\r\n][^@\r\n]*|) ## value
  133. ~xi'
  134. );
  135. foreach ($matches as $match) {
  136. list(, $name, $value) = $match;
  137. if (substr($value, 0, 1) === '(') {
  138. $items = array();
  139. $key = '';
  140. $val = TRUE;
  141. $value[0] = ',';
  142. while ($m = Strings::match(
  143. $value,
  144. '#\s*,\s*(?>(' . self::RE_IDENTIFIER . ')\s*=\s*)?(' . self::RE_STRING . '|[^\'"),\s][^\'"),]*)#A')
  145. ) {
  146. $value = substr($value, strlen($m[0]));
  147. list(, $key, $val) = $m;
  148. if ($val[0] === "'" || $val[0] === '"') {
  149. $val = substr($val, 1, -1);
  150. } elseif (is_numeric($val)) {
  151. $val = 1 * $val;
  152. } else {
  153. $lval = strtolower($val);
  154. $val = array_key_exists($lval, $tokens) ? $tokens[$lval] : $val;
  155. }
  156. if ($key === '') {
  157. $items[] = $val;
  158. } else {
  159. $items[$key] = $val;
  160. }
  161. }
  162. $value = count($items) < 2 && $key === '' ? $val : $items;
  163. } else {
  164. $value = trim($value);
  165. if (is_numeric($value)) {
  166. $value = 1 * $value;
  167. } else {
  168. $lval = strtolower($value);
  169. $value = array_key_exists($lval, $tokens) ? $tokens[$lval] : $value;
  170. }
  171. }
  172. $class = $name . 'Annotation';
  173. if (class_exists($class)) {
  174. $res[$name][] = new $class(is_array($value) ? $value : array('value' => $value));
  175. } else {
  176. $res[$name][] = is_array($value) ? new \ArrayObject($value, \ArrayObject::ARRAY_AS_PROPS) : $value;
  177. }
  178. }
  179. return $res;
  180. }
  181. /**
  182. * Parses PHP file.
  183. * @param string
  184. * @return void
  185. */
  186. private static function parseScript($file)
  187. {
  188. $T_NAMESPACE = PHP_VERSION_ID < 50300 ? -1 : T_NAMESPACE;
  189. $T_NS_SEPARATOR = PHP_VERSION_ID < 50300 ? -1 : T_NS_SEPARATOR;
  190. $s = file_get_contents($file);
  191. if (Strings::match($s, '#//nette'.'loader=(\S*)#')) {
  192. return; // TODO: allways ignore?
  193. }
  194. $expected = $namespace = $class = $docComment = NULL;
  195. $level = $classLevel = 0;
  196. foreach (token_get_all($s) as $token) {
  197. if (is_array($token)) {
  198. switch ($token[0]) {
  199. case T_DOC_COMMENT:
  200. $docComment = $token[1];
  201. case T_WHITESPACE:
  202. case T_COMMENT:
  203. continue 2;
  204. case T_STRING:
  205. case $T_NS_SEPARATOR:
  206. case T_VARIABLE:
  207. if ($expected) {
  208. $name .= $token[1];
  209. }
  210. continue 2;
  211. case T_FUNCTION:
  212. case T_VAR:
  213. case T_PUBLIC:
  214. case T_PROTECTED:
  215. case $T_NAMESPACE:
  216. case T_CLASS:
  217. case T_INTERFACE:
  218. $expected = $token[0];
  219. $name = NULL;
  220. continue 2;
  221. case T_STATIC:
  222. case T_ABSTRACT:
  223. case T_FINAL:
  224. continue 2; // ignore in expectation
  225. case T_CURLY_OPEN:
  226. case T_DOLLAR_OPEN_CURLY_BRACES:
  227. $level++;
  228. }
  229. }
  230. if ($expected) {
  231. switch ($expected) {
  232. case T_CLASS:
  233. case T_INTERFACE:
  234. $class = $namespace . $name;
  235. $classLevel = $level;
  236. $name = '';
  237. // break intentionally omitted
  238. case T_FUNCTION:
  239. if ($token === '&') {
  240. continue 2; // ignore
  241. }
  242. case T_VAR:
  243. case T_PUBLIC:
  244. case T_PROTECTED:
  245. if ($class && $name !== NULL && $docComment) {
  246. self::$cache[$class][$name] = self::parseComment($docComment);
  247. }
  248. break;
  249. case $T_NAMESPACE:
  250. $namespace = $name . '\\';
  251. }
  252. $expected = $docComment = NULL;
  253. }
  254. if ($token === ';') {
  255. $docComment = NULL;
  256. } elseif ($token === '{') {
  257. $docComment = NULL;
  258. $level++;
  259. } elseif ($token === '}') {
  260. $level--;
  261. if ($level === $classLevel) {
  262. $class = NULL;
  263. }
  264. }
  265. }
  266. }
  267. /********************* backend ****************d*g**/
  268. /**
  269. * @param Nette\Caching\IStorage
  270. * @return void
  271. */
  272. public static function setCacheStorage(Nette\Caching\IStorage $storage)
  273. {
  274. self::$cacheStorage = $storage;
  275. }
  276. /**
  277. * @return Nette\Caching\IStorage
  278. */
  279. public static function getCacheStorage()
  280. {
  281. return self::$cacheStorage;
  282. }
  283. }