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) Nette\Reflection\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: {
137: list(, $name, $value) = $match;
138:
139: if (substr($value, 0, 1) === '(') {
140: $items = array();
141: $key = '';
142: $val = TRUE;
143: $value[0] = ',';
144: while ($m = String::match($value, '#\s*,\s*(?>('.self::RE_IDENTIFIER.')\s*=\s*)?('.self::RE_STRING.'|[^\'"),\s][^\'"),]*)#A')) {
145: $value = substr($value, strlen($m[0]));
146: list(, $key, $val) = $m;
147: if ($val[0] === "'" || $val[0] === '"') {
148: $val = substr($val, 1, -1);
149:
150: } elseif (is_numeric($val)) {
151: $val = 1 * $val;
152:
153: } else {
154: $lval = strtolower($val);
155: $val = array_key_exists($lval, $tokens) ? $tokens[$lval] : $val;
156: }
157:
158: if ($key === '') {
159: $items[] = $val;
160:
161: } else {
162: $items[$key] = $val;
163: }
164: }
165:
166: $value = count($items) < 2 && $key === '' ? $val : $items;
167:
168: } else {
169: $value = trim($value);
170: if (is_numeric($value)) {
171: $value = 1 * $value;
172:
173: } else {
174: $lval = strtolower($value);
175: $value = array_key_exists($lval, $tokens) ? $tokens[$lval] : $value;
176: }
177: }
178:
179: $class = $name . 'Annotation';
180: if (class_exists($class)) {
181: $res[$name][] = new $class(is_array($value) ? $value : array('value' => $value));
182:
183: } else {
184: $res[$name][] = is_array($value) ? new \ArrayObject($value, \ArrayObject::ARRAY_AS_PROPS) : $value;
185: }
186: }
187:
188: return $res;
189: }
190:
191:
192:
193: 194: 195: 196: 197:
198: private static function parseScript($file)
199: {
200: $T_NAMESPACE = PHP_VERSION_ID < 50300 ? -1 : T_NAMESPACE;
201: $T_NS_SEPARATOR = PHP_VERSION_ID < 50300 ? -1 : T_NS_SEPARATOR;
202:
203: $s = file_get_contents($file);
204:
205: if (String::match($s, '#//nette'.'loader=(\S*)#')) {
206: return; 207: }
208:
209: $expected = $namespace = $class = $docComment = NULL;
210: $level = $classLevel = 0;
211:
212: foreach (token_get_all($s) as $token)
213: {
214: if (is_array($token)) {
215: switch ($token[0]) {
216: case T_DOC_COMMENT:
217: $docComment = $token[1];
218: case T_WHITESPACE:
219: case T_COMMENT:
220: continue 2;
221:
222: case T_STRING:
223: case $T_NS_SEPARATOR:
224: case T_VARIABLE:
225: if ($expected) {
226: $name .= $token[1];
227: }
228: continue 2;
229:
230: case T_FUNCTION:
231: case T_VAR:
232: case T_PUBLIC:
233: case T_PROTECTED:
234: case $T_NAMESPACE:
235: case T_CLASS:
236: case T_INTERFACE:
237: $expected = $token[0];
238: $name = NULL;
239: continue 2;
240:
241: case T_STATIC:
242: case T_ABSTRACT:
243: case T_FINAL:
244: continue 2; 245:
246: case T_CURLY_OPEN:
247: case T_DOLLAR_OPEN_CURLY_BRACES:
248: $level++;
249: }
250: }
251:
252: if ($expected) {
253: switch ($expected) {
254: case T_CLASS:
255: case T_INTERFACE:
256: $class = $namespace . $name;
257: $classLevel = $level;
258: $name = '';
259: 260: case T_FUNCTION:
261: if ($token === '&') continue 2; 262: case T_VAR:
263: case T_PUBLIC:
264: case T_PROTECTED:
265: if ($class && $name !== NULL && $docComment) {
266: self::$cache[$class][$name] = self::parseComment($docComment);
267: }
268: break;
269:
270: case $T_NAMESPACE:
271: $namespace = $name . '\\';
272: }
273:
274: $expected = $docComment = NULL;
275: }
276:
277: if ($token === ';') {
278: $docComment = NULL;
279: } elseif ($token === '{') {
280: $docComment = NULL;
281: $level++;
282: } elseif ($token === '}') {
283: $level--;
284: if ($level === $classLevel) {
285: $class = NULL;
286: }
287: }
288: }
289: }
290:
291:
292:
293:
294:
295:
296:
297: 298: 299:
300: protected static function getCache()
301: {
302: return Nette\Environment::getCache('Nette.Annotations');
303: }
304:
305: }
306: