1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace Nette\Reflection;
13:
14: use Nette;
15:
16:
17:
18: 19: 20: 21: 22: 23:
24: final class AnnotationsParser
25: {
26:
27: const RE_STRING = '\'(?:\\\\.|[^\'\\\\])*\'|"(?:\\\\.|[^"\\\\])*"';
28:
29:
30: const RE_IDENTIFIER = '[_a-zA-Z\x7F-\xFF][_a-zA-Z0-9\x7F-\xFF]*';
31:
32:
33: public static $useReflection;
34:
35:
36: private static $cache;
37:
38:
39: private static $timestamps;
40:
41:
42:
43: 44: 45:
46: final public function __construct()
47: {
48: throw new \LogicException("Cannot instantiate static class " . get_class($this));
49: }
50:
51:
52:
53: 54: 55: 56: 57:
58: public static function getAll(\Reflector $r)
59: {
60: if ($r instanceof \ReflectionClass) {
61: $type = $r->getName();
62: $member = '';
63:
64: } elseif ($r instanceof \ReflectionMethod) {
65: $type = $r->getDeclaringClass()->getName();
66: $member = $r->getName();
67:
68: } else {
69: $type = $r->getDeclaringClass()->getName();
70: $member = '$' . $r->getName();
71: }
72:
73: if (!self::$useReflection) {
74: $file = $r instanceof \ReflectionClass ? $r->getFileName() : $r->getDeclaringClass()->getFileName();
75: if ($file && isset(self::$timestamps[$file]) && self::$timestamps[$file] !== filemtime($file)) {
76: unset(self::$cache[$type]);
77: }
78: unset(self::$timestamps[$file]);
79: }
80:
81: if (isset(self::$cache[$type][$member])) {
82: return self::$cache[$type][$member];
83: }
84:
85: if (self::$useReflection === NULL) {
86: self::$useReflection = (bool) Nette\Reflection\ClassReflection::from(__CLASS__)->getDocComment();
87: }
88:
89: if (self::$useReflection) {
90: return self::$cache[$type][$member] = self::parseComment($r->getDocComment());
91:
92: } else {
93: if (self::$cache === NULL) {
94: self::$cache = (array) self::getCache()->offsetGet('list');
95: self::$timestamps = isset(self::$cache['*']) ? self::$cache['*'] : array();
96: }
97:
98: if (!isset(self::$cache[$type]) && $file) {
99: self::$cache['*'][$file] = filemtime($file);
100: self::parseScript($file);
101: self::getCache()->save('list', self::$cache);
102: }
103:
104: if (isset(self::$cache[$type][$member])) {
105: return self::$cache[$type][$member];
106: } else {
107: return self::$cache[$type][$member] = array();
108: }
109: }
110: }
111:
112:
113:
114: 115: 116: 117: 118:
119: private static function parseComment($comment)
120: {
121: static $tokens = array('true' => TRUE, 'false' => FALSE, 'null' => NULL, '' => TRUE);
122:
123: preg_match_all('~
124: @('.self::RE_IDENTIFIER.')[ \t]* ## annotation
125: (
126: \((?>'.self::RE_STRING.'|[^\'")@]+)+\)| ## (value)
127: [^(@\r\n][^@\r\n]*|) ## value
128: ~xi', trim($comment, '/*'), $matches, PREG_SET_ORDER);
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 (preg_match('#\s*,\s*(?>('.self::RE_IDENTIFIER.')\s*=\s*)?('.self::RE_STRING.'|[^\'"),\s][^\'"),]*)#A', $value, $m)) {
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 (preg_match('#//nette'.'loader=(\S*)#', $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 Nette\Environment::getCache('Nette.Annotations');
299: }
300:
301: }
302: