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: self::$cacheStorage = new DevNullStorage;
93: }
94: $outerCache = new Cache(self::$cacheStorage, 'Nette.Reflection.Annotations');
95:
96: if (self::$cache === NULL) {
97: self::$cache = (array) $outerCache->offsetGet('list');
98: self::$timestamps = isset(self::$cache['*']) ? self::$cache['*'] : array();
99: }
100:
101: if (!isset(self::$cache[$type]) && $file) {
102: self::$cache['*'][$file] = filemtime($file);
103: self::parseScript($file);
104: $outerCache->save('list', self::$cache);
105: }
106:
107: if (isset(self::$cache[$type][$member])) {
108: $annotations = self::$cache[$type][$member];
109: } else {
110: $annotations = array();
111: }
112: }
113:
114: if ($r instanceof ReflectionMethod && !$r->isPrivate()
115: && (!$r->isConstructor() || !empty($annotations['inheritdoc'][0])))
116: {
117: try {
118: $inherited = self::getAll(new ReflectionMethod(get_parent_class($type), $member));
119: } catch (ReflectionException $e) {
120: try {
121: $inherited = self::getAll($r->getPrototype());
122: } catch (ReflectionException $e) {
123: $inherited = array();
124: }
125: }
126: $annotations += array_intersect_key($inherited, array_flip(self::$inherited));
127: }
128:
129: return self::$cache[$type][$member] = $annotations;
130: }
131:
132:
133: 134: 135: 136: 137:
138: private static function ($comment)
139: {
140: static $tokens = array('true' => TRUE, 'false' => FALSE, 'null' => NULL, '' => TRUE);
141:
142: $res = array();
143: $comment = preg_replace('#^\s*\*\s?#ms', '', trim($comment, '/*'));
144: $parts = preg_split('#^\s*(?=@'.self::RE_IDENTIFIER.')#m', $comment, 2);
145:
146: $description = trim($parts[0]);
147: if ($description !== '') {
148: $res['description'] = array($description);
149: }
150:
151: $matches = Strings::matchAll(
152: isset($parts[1]) ? $parts[1] : '',
153: '~
154: (?<=\s|^)@('.self::RE_IDENTIFIER.')[ \t]* ## annotation
155: (
156: \((?>'.self::RE_STRING.'|[^\'")@]+)+\)| ## (value)
157: [^(@\r\n][^@\r\n]*|) ## value
158: ~xi'
159: );
160:
161: foreach ($matches as $match) {
162: list(, $name, $value) = $match;
163:
164: if (substr($value, 0, 1) === '(') {
165: $items = array();
166: $key = '';
167: $val = TRUE;
168: $value[0] = ',';
169: while ($m = Strings::match(
170: $value,
171: '#\s*,\s*(?>(' . self::RE_IDENTIFIER . ')\s*=\s*)?(' . self::RE_STRING . '|[^\'"),\s][^\'"),]*)#A')
172: ) {
173: $value = substr($value, strlen($m[0]));
174: list(, $key, $val) = $m;
175: $val = rtrim($val);
176: if ($val[0] === "'" || $val[0] === '"') {
177: $val = substr($val, 1, -1);
178:
179: } elseif (is_numeric($val)) {
180: $val = 1 * $val;
181:
182: } else {
183: $lval = strtolower($val);
184: $val = array_key_exists($lval, $tokens) ? $tokens[$lval] : $val;
185: }
186:
187: if ($key === '') {
188: $items[] = $val;
189:
190: } else {
191: $items[$key] = $val;
192: }
193: }
194:
195: $value = count($items) < 2 && $key === '' ? $val : $items;
196:
197: } else {
198: $value = trim($value);
199: if (is_numeric($value)) {
200: $value = 1 * $value;
201:
202: } else {
203: $lval = strtolower($value);
204: $value = array_key_exists($lval, $tokens) ? $tokens[$lval] : $value;
205: }
206: }
207:
208: $class = $name . 'Annotation';
209: if (class_exists($class)) {
210: $res[$name][] = new $class(is_array($value) ? $value : array('value' => $value));
211:
212: } else {
213: $res[$name][] = is_array($value) ? new ArrayObject($value, ArrayObject::ARRAY_AS_PROPS) : $value;
214: }
215: }
216:
217: return $res;
218: }
219:
220:
221: 222: 223: 224: 225:
226: private static function parseScript($file)
227: {
228: $T_NAMESPACE = PHP_VERSION_ID < 50300 ? -1 : T_NAMESPACE;
229: $T_NS_SEPARATOR = PHP_VERSION_ID < 50300 ? -1 : T_NS_SEPARATOR;
230:
231: $s = file_get_contents($file);
232:
233: if (Strings::match($s, '#//nette'.'loader=(\S*)#')) {
234: return;
235: }
236:
237: $expected = $namespace = $class = $docComment = NULL;
238: $level = $classLevel = 0;
239:
240: foreach (token_get_all($s) as $token) {
241:
242: if (is_array($token)) {
243: switch ($token[0]) {
244: case T_DOC_COMMENT:
245: $docComment = $token[1];
246:
247: case T_WHITESPACE:
248: case T_COMMENT:
249: continue 2;
250:
251: case T_STRING:
252: case $T_NS_SEPARATOR:
253: case T_VARIABLE:
254: if ($expected) {
255: $name .= $token[1];
256: }
257: continue 2;
258:
259: case T_FUNCTION:
260: case T_VAR:
261: case T_PUBLIC:
262: case T_PROTECTED:
263: case $T_NAMESPACE:
264: case T_CLASS:
265: case T_INTERFACE:
266: $expected = $token[0];
267: $name = NULL;
268: continue 2;
269:
270: case T_STATIC:
271: case T_ABSTRACT:
272: case T_FINAL:
273: continue 2;
274:
275: case T_CURLY_OPEN:
276: case T_DOLLAR_OPEN_CURLY_BRACES:
277: $level++;
278: }
279: }
280:
281: if ($expected) {
282: switch ($expected) {
283: case T_CLASS:
284: case T_INTERFACE:
285: $class = $namespace . $name;
286: $classLevel = $level;
287: $name = '';
288:
289: case T_FUNCTION:
290: if ($token === '&') {
291: continue 2;
292: }
293: case T_VAR:
294: case T_PUBLIC:
295: case T_PROTECTED:
296: if ($class && $name !== NULL && $docComment) {
297: self::$cache[$class][$name] = self::parseComment($docComment);
298: }
299: break;
300:
301: case $T_NAMESPACE:
302: $namespace = $name . '\\';
303: }
304:
305: $expected = $docComment = NULL;
306: }
307:
308: if ($token === ';') {
309: $docComment = NULL;
310: } elseif ($token === '{') {
311: $docComment = NULL;
312: $level++;
313: } elseif ($token === '}') {
314: $level--;
315: if ($level === $classLevel) {
316: $class = NULL;
317: }
318: }
319: }
320: }
321:
322:
323:
324:
325:
326: 327: 328:
329: public static function setCacheStorage(ICacheStorage $storage)
330: {
331: self::$cacheStorage = $storage;
332: }
333:
334:
335: 336: 337:
338: public static function getCacheStorage()
339: {
340: return self::$cacheStorage;
341: }
342:
343: }
344: