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