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