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