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 ($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: $val = rtrim($val);
184: if ($val[0] === "'" || $val[0] === '"') {
185: $val = substr($val, 1, -1);
186:
187: } elseif (is_numeric($val)) {
188: $val = 1 * $val;
189:
190: } else {
191: $lval = strtolower($val);
192: $val = array_key_exists($lval, $tokens) ? $tokens[$lval] : $val;
193: }
194:
195: if ($key === '') {
196: $items[] = $val;
197:
198: } else {
199: $items[$key] = $val;
200: }
201: }
202:
203: $value = count($items) < 2 && $key === '' ? $val : $items;
204:
205: } else {
206: $value = trim($value);
207: if (is_numeric($value)) {
208: $value = 1 * $value;
209:
210: } else {
211: $lval = strtolower($value);
212: $value = array_key_exists($lval, $tokens) ? $tokens[$lval] : $value;
213: }
214: }
215:
216: $class = $name . 'Annotation';
217: if (class_exists($class)) {
218: $res[$name][] = new $class(is_array($value) ? $value : array('value' => $value));
219:
220: } else {
221: $res[$name][] = is_array($value) ? new ArrayObject($value, ArrayObject::ARRAY_AS_PROPS) : $value;
222: }
223: }
224:
225: return $res;
226: }
227:
228:
229:
230: 231: 232: 233: 234:
235: private static function parseScript($file)
236: {
237: $T_NAMESPACE = PHP_VERSION_ID < 50300 ? -1 : T_NAMESPACE;
238: $T_NS_SEPARATOR = PHP_VERSION_ID < 50300 ? -1 : T_NS_SEPARATOR;
239:
240: $s = file_get_contents($file);
241:
242: if (Strings::match($s, '#//nette'.'loader=(\S*)#')) {
243: return;
244: }
245:
246: $expected = $namespace = $class = $docComment = NULL;
247: $level = $classLevel = 0;
248:
249: foreach (token_get_all($s) as $token) {
250:
251: if (is_array($token)) {
252: switch ($token[0]) {
253: case T_DOC_COMMENT:
254: $docComment = $token[1];
255: case T_WHITESPACE:
256: case T_COMMENT:
257: continue 2;
258:
259: case T_STRING:
260: case $T_NS_SEPARATOR:
261: case T_VARIABLE:
262: if ($expected) {
263: $name .= $token[1];
264: }
265: continue 2;
266:
267: case T_FUNCTION:
268: case T_VAR:
269: case T_PUBLIC:
270: case T_PROTECTED:
271: case $T_NAMESPACE:
272: case T_CLASS:
273: case T_INTERFACE:
274: $expected = $token[0];
275: $name = NULL;
276: continue 2;
277:
278: case T_STATIC:
279: case T_ABSTRACT:
280: case T_FINAL:
281: continue 2;
282:
283: case T_CURLY_OPEN:
284: case T_DOLLAR_OPEN_CURLY_BRACES:
285: $level++;
286: }
287: }
288:
289: if ($expected) {
290: switch ($expected) {
291: case T_CLASS:
292: case T_INTERFACE:
293: $class = $namespace . $name;
294: $classLevel = $level;
295: $name = '';
296:
297: case T_FUNCTION:
298: if ($token === '&') {
299: continue 2;
300: }
301: case T_VAR:
302: case T_PUBLIC:
303: case T_PROTECTED:
304: if ($class && $name !== NULL && $docComment) {
305: self::$cache[$class][$name] = self::parseComment($docComment);
306: }
307: break;
308:
309: case $T_NAMESPACE:
310: $namespace = $name . '\\';
311: }
312:
313: $expected = $docComment = NULL;
314: }
315:
316: if ($token === ';') {
317: $docComment = NULL;
318: } elseif ($token === '{') {
319: $docComment = NULL;
320: $level++;
321: } elseif ($token === '}') {
322: $level--;
323: if ($level === $classLevel) {
324: $class = NULL;
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: