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