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 NAnnotationsParser
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: private static $cache;
35:
36:
37: private static $timestamps;
38:
39:
40:
41: 42: 43:
44: final public function __construct()
45: {
46: throw new LogicException("Cannot instantiate static class " . get_class($this));
47: }
48:
49:
50:
51: 52: 53: 54: 55:
56: public static function getAll(Reflector $r)
57: {
58: if ($r instanceof ReflectionClass) {
59: $type = $r->getName();
60: $member = '';
61:
62: } elseif ($r instanceof ReflectionMethod) {
63: $type = $r->getDeclaringClass()->getName();
64: $member = $r->getName();
65:
66: } else {
67: $type = $r->getDeclaringClass()->getName();
68: $member = '$' . $r->getName();
69: }
70:
71: if (!self::$useReflection) {
72: $file = $r instanceof ReflectionClass ? $r->getFileName() : $r->getDeclaringClass()->getFileName();
73: if ($file && isset(self::$timestamps[$file]) && self::$timestamps[$file] !== filemtime($file)) {
74: unset(self::$cache[$type]);
75: }
76: unset(self::$timestamps[$file]);
77: }
78:
79: if (isset(self::$cache[$type][$member])) {
80: return self::$cache[$type][$member];
81: }
82:
83: if (self::$useReflection === NULL) {
84: self::$useReflection = (bool) NClassReflection::from(__CLASS__)->getDocComment();
85: }
86:
87: if (self::$useReflection) {
88: return self::$cache[$type][$member] = self::parseComment($r->getDocComment());
89:
90: } else {
91: if (self::$cache === NULL) {
92: self::$cache = (array) self::getCache()->offsetGet('list');
93: self::$timestamps = isset(self::$cache['*']) ? self::$cache['*'] : array();
94: }
95:
96: if (!isset(self::$cache[$type]) && $file) {
97: self::$cache['*'][$file] = filemtime($file);
98: self::parseScript($file);
99: self::getCache()->save('list', self::$cache);
100: }
101:
102: if (isset(self::$cache[$type][$member])) {
103: return self::$cache[$type][$member];
104: } else {
105: return self::$cache[$type][$member] = array();
106: }
107: }
108: }
109:
110:
111:
112: 113: 114: 115: 116:
117: private static function parseComment($comment)
118: {
119: static $tokens = array('true' => TRUE, 'false' => FALSE, 'null' => NULL, '' => TRUE);
120:
121: preg_match_all('~
122: @('.self::RE_IDENTIFIER.')[ \t]* ## annotation
123: (
124: \((?>'.self::RE_STRING.'|[^\'")@]+)+\)| ## (value)
125: [^(@\r\n][^@\r\n]*|) ## value
126: ~xi', trim($comment, '/*'), $matches, PREG_SET_ORDER);
127:
128: $res = array();
129: foreach ($matches as $match)
130: {
131: list(, $name, $value) = $match;
132:
133: if (substr($value, 0, 1) === '(') {
134: $items = array();
135: $key = '';
136: $val = TRUE;
137: $value[0] = ',';
138: while (preg_match('#\s*,\s*(?>('.self::RE_IDENTIFIER.')\s*=\s*)?('.self::RE_STRING.'|[^\'"),\s][^\'"),]*)#A', $value, $m)) {
139: $value = substr($value, strlen($m[0]));
140: list(, $key, $val) = $m;
141: if ($val[0] === "'" || $val[0] === '"') {
142: $val = substr($val, 1, -1);
143:
144: } elseif (is_numeric($val)) {
145: $val = 1 * $val;
146:
147: } else {
148: $lval = strtolower($val);
149: $val = array_key_exists($lval, $tokens) ? $tokens[$lval] : $val;
150: }
151:
152: if ($key === '') {
153: $items[] = $val;
154:
155: } else {
156: $items[$key] = $val;
157: }
158: }
159:
160: $value = count($items) < 2 && $key === '' ? $val : $items;
161:
162: } else {
163: $value = trim($value);
164: if (is_numeric($value)) {
165: $value = 1 * $value;
166:
167: } else {
168: $lval = strtolower($value);
169: $value = array_key_exists($lval, $tokens) ? $tokens[$lval] : $value;
170: }
171: }
172:
173: $class = $name . 'Annotation';
174: if (class_exists($class)) {
175: $res[$name][] = new $class(is_array($value) ? $value : array('value' => $value));
176:
177: } else {
178: $res[$name][] = is_array($value) ? new ArrayObject($value, ArrayObject::ARRAY_AS_PROPS) : $value;
179: }
180: }
181:
182: return $res;
183: }
184:
185:
186:
187: 188: 189: 190: 191:
192: private static function parseScript($file)
193: {
194: $T_NAMESPACE = PHP_VERSION_ID < 50300 ? -1 : T_NAMESPACE;
195: $T_NS_SEPARATOR = PHP_VERSION_ID < 50300 ? -1 : T_NS_SEPARATOR;
196:
197: $s = file_get_contents($file);
198:
199: if (preg_match('#//nette'.'loader=(\S*)#', $s)) {
200: return;
201: }
202:
203: $expected = $namespace = $class = $docComment = NULL;
204: $level = $classLevel = 0;
205:
206: foreach (token_get_all($s) as $token)
207: {
208: if (is_array($token)) {
209: switch ($token[0]) {
210: case T_DOC_COMMENT:
211: $docComment = $token[1];
212: case T_WHITESPACE:
213: case T_COMMENT:
214: continue 2;
215:
216: case T_STRING:
217: case $T_NS_SEPARATOR:
218: case T_VARIABLE:
219: if ($expected) {
220: $name .= $token[1];
221: }
222: continue 2;
223:
224: case T_FUNCTION:
225: case T_VAR:
226: case T_PUBLIC:
227: case T_PROTECTED:
228: case $T_NAMESPACE:
229: case T_CLASS:
230: case T_INTERFACE:
231: $expected = $token[0];
232: $name = NULL;
233: continue 2;
234:
235: case T_STATIC:
236: case T_ABSTRACT:
237: case T_FINAL:
238: continue 2;
239:
240: case T_CURLY_OPEN:
241: case T_DOLLAR_OPEN_CURLY_BRACES:
242: $level++;
243: }
244: }
245:
246: if ($expected) {
247: switch ($expected) {
248: case T_CLASS:
249: case T_INTERFACE:
250: $class = $namespace . $name;
251: $classLevel = $level;
252: $name = '';
253:
254: case T_FUNCTION:
255: if ($token === '&') continue 2;
256: case T_VAR:
257: case T_PUBLIC:
258: case T_PROTECTED:
259: if ($class && $name !== NULL && $docComment) {
260: self::$cache[$class][$name] = self::parseComment($docComment);
261: }
262: break;
263:
264: case $T_NAMESPACE:
265: $namespace = $name . '\\';
266: }
267:
268: $expected = $docComment = NULL;
269: }
270:
271: if ($token === ';') {
272: $docComment = NULL;
273: } elseif ($token === '{') {
274: $docComment = NULL;
275: $level++;
276: } elseif ($token === '}') {
277: $level--;
278: if ($level === $classLevel) {
279: $class = NULL;
280: }
281: }
282: }
283: }
284:
285:
286:
287:
288:
289:
290:
291: 292: 293:
294: protected static function getCache()
295: {
296: return NEnvironment::getCache('Nette.Annotations');
297: }
298:
299: }
300: