1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Reflection;
9:
10: use Nette,
11: Nette\Utils\Strings;
12:
13:
14: 15: 16: 17: 18: 19:
20: class AnnotationsParser
21: {
22:
23: const RE_STRING = '\'(?:\\\\.|[^\'\\\\])*\'|"(?:\\\\.|[^"\\\\])*"';
24:
25:
26: const RE_IDENTIFIER = '[_a-zA-Z\x7F-\xFF][_a-zA-Z0-9\x7F-\xFF-\\\]*';
27:
28:
29: public static $useReflection;
30:
31:
32: public static $inherited = array('description', 'param', 'return');
33:
34:
35: private static $cache;
36:
37:
38: private static $timestamps;
39:
40:
41: private static $cacheStorage;
42:
43:
44: 45: 46:
47: final public function __construct()
48: {
49: throw new Nette\StaticClassException;
50: }
51:
52:
53: 54: 55: 56: 57:
58: public static function getAll(\Reflector $r)
59: {
60: if ($r instanceof \ReflectionClass) {
61: $type = $r->getName();
62: $member = 'class';
63:
64: } elseif ($r instanceof \ReflectionMethod) {
65: $type = $r->getDeclaringClass()->getName();
66: $member = $r->getName();
67:
68: } else {
69: $type = $r->getDeclaringClass()->getName();
70: $member = '$' . $r->getName();
71: }
72:
73: if (!self::$useReflection) {
74: $file = $r instanceof \ReflectionClass ? $r->getFileName() : $r->getDeclaringClass()->getFileName();
75: if ($file && isset(self::$timestamps[$file]) && self::$timestamps[$file] !== filemtime($file)) {
76: unset(self::$cache[$type]);
77: }
78: unset(self::$timestamps[$file]);
79: }
80:
81: if (isset(self::$cache[$type][$member])) {
82: return self::$cache[$type][$member];
83: }
84:
85: if (self::$useReflection === NULL) {
86: self::$useReflection = (bool) ClassType::from(__CLASS__)->getDocComment();
87: }
88:
89: if (self::$useReflection) {
90: $annotations = self::parseComment($r->getDocComment());
91:
92: } else {
93: if (!self::$cacheStorage) {
94:
95: self::$cacheStorage = new Nette\Caching\Storages\DevNullStorage;
96: }
97: $outerCache = new Nette\Caching\Cache(self::$cacheStorage, 'Nette.Reflection.Annotations');
98:
99: if (self::$cache === NULL) {
100: self::$cache = (array) $outerCache->load('list');
101: self::$timestamps = isset(self::$cache['*']) ? self::$cache['*'] : array();
102: }
103:
104: if (!isset(self::$cache[$type]) && $file) {
105: self::$cache['*'][$file] = filemtime($file);
106: foreach (self::parsePhp(file_get_contents($file)) as $class => $info) {
107: foreach ($info as $prop => $comment) {
108: if ($prop !== 'use') {
109: self::$cache[$class][$prop] = self::parseComment($comment);
110: }
111: }
112: }
113: $outerCache->save('list', self::$cache);
114: }
115:
116: if (isset(self::$cache[$type][$member])) {
117: $annotations = self::$cache[$type][$member];
118: } else {
119: $annotations = array();
120: }
121: }
122:
123: if ($r instanceof \ReflectionMethod && !$r->isPrivate()
124: && (!$r->isConstructor() || !empty($annotations['inheritdoc'][0]))
125: ) {
126: try {
127: $inherited = self::getAll(new \ReflectionMethod(get_parent_class($type), $member));
128: } catch (\ReflectionException $e) {
129: try {
130: $inherited = self::getAll($r->getPrototype());
131: } catch (\ReflectionException $e) {
132: $inherited = array();
133: }
134: }
135: $annotations += array_intersect_key($inherited, array_flip(self::$inherited));
136: }
137:
138: return self::$cache[$type][$member] = $annotations;
139: }
140:
141:
142: 143: 144: 145: 146: 147:
148: public static function expandClassName($name, \ReflectionClass $reflector)
149: {
150: if (empty($name)) {
151: throw new Nette\InvalidArgumentException('Class name must not be empty.');
152: }
153:
154: if ($name[0] === '\\') {
155: return ltrim($name, '\\');
156: }
157:
158: $parsed = static::parsePhp(file_get_contents($reflector->getFileName()));
159: $uses = array_change_key_case((array) $tmp = & $parsed[$reflector->getName()]['use']);
160: $parts = explode('\\', $name, 2);
161: $parts[0] = strtolower($parts[0]);
162: if (isset($uses[$parts[0]])) {
163: $parts[0] = $uses[$parts[0]];
164: return implode('\\', $parts);
165:
166: } elseif ($reflector->inNamespace()) {
167: return $reflector->getNamespaceName() . '\\' . $name;
168:
169: } else {
170: return $name;
171: }
172: }
173:
174:
175: 176: 177: 178: 179:
180: private static function ($comment)
181: {
182: static $tokens = array('true' => TRUE, 'false' => FALSE, 'null' => NULL, '' => TRUE);
183:
184: $res = array();
185: $comment = preg_replace('#^\s*\*\s?#ms', '', trim($comment, '/*'));
186: $parts = preg_split('#^\s*(?=@'.self::RE_IDENTIFIER.')#m', $comment, 2);
187:
188: $description = trim($parts[0]);
189: if ($description !== '') {
190: $res['description'] = array($description);
191: }
192:
193: $matches = Strings::matchAll(
194: isset($parts[1]) ? $parts[1] : '',
195: '~
196: (?<=\s|^)@('.self::RE_IDENTIFIER.')[ \t]* ## annotation
197: (
198: \((?>'.self::RE_STRING.'|[^\'")@]+)+\)| ## (value)
199: [^(@\r\n][^@\r\n]*|) ## value
200: ~xi'
201: );
202:
203: foreach ($matches as $match) {
204: list(, $name, $value) = $match;
205:
206: if (substr($value, 0, 1) === '(') {
207: $items = array();
208: $key = '';
209: $val = TRUE;
210: $value[0] = ',';
211: while ($m = Strings::match(
212: $value,
213: '#\s*,\s*(?>(' . self::RE_IDENTIFIER . ')\s*=\s*)?(' . self::RE_STRING . '|[^\'"),\s][^\'"),]*)#A')
214: ) {
215: $value = substr($value, strlen($m[0]));
216: list(, $key, $val) = $m;
217: $val = rtrim($val);
218: if ($val[0] === "'" || $val[0] === '"') {
219: $val = substr($val, 1, -1);
220:
221: } elseif (is_numeric($val)) {
222: $val = 1 * $val;
223:
224: } else {
225: $lval = strtolower($val);
226: $val = array_key_exists($lval, $tokens) ? $tokens[$lval] : $val;
227: }
228:
229: if ($key === '') {
230: $items[] = $val;
231:
232: } else {
233: $items[$key] = $val;
234: }
235: }
236:
237: $value = count($items) < 2 && $key === '' ? $val : $items;
238:
239: } else {
240: $value = trim($value);
241: if (is_numeric($value)) {
242: $value = 1 * $value;
243:
244: } else {
245: $lval = strtolower($value);
246: $value = array_key_exists($lval, $tokens) ? $tokens[$lval] : $value;
247: }
248: }
249:
250: $class = $name . 'Annotation';
251: if (class_exists($class)) {
252: $res[$name][] = new $class(is_array($value) ? $value : array('value' => $value));
253:
254: } else {
255: $res[$name][] = is_array($value) ? Nette\ArrayHash::from($value) : $value;
256: }
257: }
258:
259: return $res;
260: }
261:
262:
263: 264: 265: 266: 267:
268: public static function parsePhp($code)
269: {
270: if (Strings::match($code, '#//nette'.'loader=(\S*)#')) {
271: return;
272: }
273:
274: $tokens = @token_get_all($code);
275: $namespace = $class = $classLevel = $level = $docComment = NULL;
276: $res = $uses = array();
277:
278: while (list($key, $token) = each($tokens)) {
279: switch (is_array($token) ? $token[0] : $token) {
280: case T_DOC_COMMENT:
281: $docComment = $token[1];
282: break;
283:
284: case T_NAMESPACE:
285: $namespace = self::fetch($tokens, array(T_STRING, T_NS_SEPARATOR)) . '\\';
286: $uses = array();
287: break;
288:
289: case T_CLASS:
290: case T_INTERFACE:
291: case PHP_VERSION_ID < 50400 ? -1 : T_TRAIT:
292: if ($name = self::fetch($tokens, T_STRING)) {
293: $class = $namespace . $name;
294: $classLevel = $level + 1;
295: if ($docComment) {
296: $res[$class]['class'] = $docComment;
297: }
298: if ($uses) {
299: $res[$class]['use'] = $uses;
300: }
301: }
302: break;
303:
304: case T_FUNCTION:
305: self::fetch($tokens, '&');
306: if ($level === $classLevel && $docComment && ($name = self::fetch($tokens, T_STRING))) {
307: $res[$class][$name] = $docComment;
308: }
309: break;
310:
311: case T_VAR:
312: case T_PUBLIC:
313: case T_PROTECTED:
314: self::fetch($tokens, T_STATIC);
315: if ($level === $classLevel && $docComment && ($name = self::fetch($tokens, T_VARIABLE))) {
316: $res[$class][$name] = $docComment;
317: }
318: break;
319:
320: case T_USE:
321: while (!$class && ($name = self::fetch($tokens, array(T_STRING, T_NS_SEPARATOR)))) {
322: if (self::fetch($tokens, T_AS)) {
323: $uses[self::fetch($tokens, T_STRING)] = ltrim($name, '\\');
324: } else {
325: $tmp = explode('\\', $name);
326: $uses[end($tmp)] = $name;
327: }
328: if (!self::fetch($tokens, ',')) {
329: break;
330: }
331: }
332: break;
333:
334: case T_CURLY_OPEN:
335: case T_DOLLAR_OPEN_CURLY_BRACES:
336: case '{':
337: $level++;
338: break;
339:
340: case '}':
341: if ($level === $classLevel) {
342: $class = $classLevel = NULL;
343: }
344: $level--;
345:
346: case ';':
347: $docComment = NULL;
348: }
349: }
350:
351: return $res;
352: }
353:
354:
355: private static function fetch(& $tokens, $take)
356: {
357: $res = NULL;
358: while ($token = current($tokens)) {
359: list($token, $s) = is_array($token) ? $token : array($token, $token);
360: if (in_array($token, (array) $take, TRUE)) {
361: $res .= $s;
362: } elseif (!in_array($token, array(T_DOC_COMMENT, T_WHITESPACE, T_COMMENT), TRUE)) {
363: break;
364: }
365: next($tokens);
366: }
367: return $res;
368: }
369:
370:
371:
372:
373:
374: 375: 376:
377: public static function setCacheStorage(Nette\Caching\IStorage $storage)
378: {
379: self::$cacheStorage = $storage;
380: }
381:
382:
383: 384: 385:
386: public static function getCacheStorage()
387: {
388: return self::$cacheStorage;
389: }
390:
391: }
392: