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