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