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