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