1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\DI;
9:
10: use Nette;
11:
12:
13: 14: 15: 16: 17:
18: class PhpReflection
19: {
20: use Nette\StaticClass;
21:
22: 23: 24: 25:
26: public static function parseAnnotation(\Reflector $ref, $name)
27: {
28: static $ok;
29: if (!$ok) {
30: if (!(new \ReflectionMethod(__METHOD__))->getDocComment()) {
31: throw new Nette\InvalidStateException('You have to enable phpDoc comments in opcode cache.');
32: }
33: $ok = true;
34: }
35: $name = preg_quote($name, '#');
36: if ($ref->getDocComment() && preg_match("#[\\s*]@$name(?:\\s++([^@]\\S*)?|$)#", trim($ref->getDocComment(), '/*'), $m)) {
37: return isset($m[1]) ? $m[1] : '';
38: }
39: }
40:
41:
42: 43: 44: 45:
46: public static function getDeclaringClass(\ReflectionProperty $prop)
47: {
48: foreach ($prop->getDeclaringClass()->getTraits() as $trait) {
49: if ($trait->hasProperty($prop->getName())) {
50: return self::getDeclaringClass($trait->getProperty($prop->getName()));
51: }
52: }
53: return $prop->getDeclaringClass();
54: }
55:
56:
57: 58: 59:
60: public static function getParameterType(\ReflectionParameter $param)
61: {
62: if (PHP_VERSION_ID >= 70000) {
63: $type = $param->hasType() ? (string) $param->getType() : null;
64: return strtolower($type) === 'self' ? $param->getDeclaringClass()->getName() : $type;
65: } elseif ($param->isArray() || $param->isCallable()) {
66: return $param->isArray() ? 'array' : 'callable';
67: } else {
68: try {
69: return ($ref = $param->getClass()) ? $ref->getName() : null;
70: } catch (\ReflectionException $e) {
71: if (preg_match('#Class (.+) does not exist#', $e->getMessage(), $m)) {
72: return $m[1];
73: }
74: throw $e;
75: }
76: }
77: }
78:
79:
80: 81: 82:
83: public static function getReturnType(\ReflectionFunctionAbstract $func)
84: {
85: if (PHP_VERSION_ID >= 70000 && $func->hasReturnType()) {
86: $type = (string) $func->getReturnType();
87: return strtolower($type) === 'self' ? $func->getDeclaringClass()->getName() : $type;
88: }
89: $type = preg_replace('#[|\s].*#', '', (string) self::parseAnnotation($func, 'return'));
90: if ($type) {
91: return $func instanceof \ReflectionMethod
92: ? self::expandClassName($type, $func->getDeclaringClass())
93: : ltrim($type, '\\');
94: }
95: }
96:
97:
98: 99: 100: 101:
102: public static function isBuiltinType($type)
103: {
104: return in_array(strtolower($type), ['string', 'int', 'float', 'bool', 'array', 'callable'], true);
105: }
106:
107:
108: 109: 110: 111:
112: public static function getClassTree(\ReflectionClass $class)
113: {
114: $addTraits = function ($types) use (&$addTraits) {
115: if ($traits = array_merge(...array_map('class_uses', array_values($types)))) {
116: $types += $traits + $addTraits($traits);
117: }
118: return $types;
119: };
120: $class = $class->getName();
121: return array_values($addTraits([$class] + class_parents($class) + class_implements($class)));
122: }
123:
124:
125: 126: 127: 128: 129: 130:
131: public static function expandClassName($name, \ReflectionClass $rc)
132: {
133: $lower = strtolower($name);
134: if (empty($name)) {
135: throw new Nette\InvalidArgumentException('Class name must not be empty.');
136:
137: } elseif (self::isBuiltinType($lower)) {
138: return $lower;
139:
140: } elseif ($lower === 'self' || $lower === 'static' || $lower === '$this') {
141: return $rc->getName();
142:
143: } elseif ($name[0] === '\\') {
144: return ltrim($name, '\\');
145: }
146:
147: $uses = self::getUseStatements($rc);
148: $parts = explode('\\', $name, 2);
149: if (isset($uses[$parts[0]])) {
150: $parts[0] = $uses[$parts[0]];
151: return implode('\\', $parts);
152:
153: } elseif ($rc->inNamespace()) {
154: return $rc->getNamespaceName() . '\\' . $name;
155:
156: } else {
157: return $name;
158: }
159: }
160:
161:
162: 163: 164:
165: public static function getUseStatements(\ReflectionClass $class)
166: {
167: static $cache = [];
168: if (!isset($cache[$name = $class->getName()])) {
169: if ($class->isInternal()) {
170: $cache[$name] = [];
171: } else {
172: $code = file_get_contents($class->getFileName());
173: $cache = self::parseUseStatements($code, $name) + $cache;
174: }
175: }
176: return $cache[$name];
177: }
178:
179:
180: 181: 182: 183: 184:
185: public static function parseUseStatements($code, $forClass = null)
186: {
187: $tokens = token_get_all($code);
188: $namespace = $class = $classLevel = $level = null;
189: $res = $uses = [];
190:
191: while ($token = current($tokens)) {
192: next($tokens);
193: switch (is_array($token) ? $token[0] : $token) {
194: case T_NAMESPACE:
195: $namespace = ltrim(self::fetch($tokens, [T_STRING, T_NS_SEPARATOR]) . '\\', '\\');
196: $uses = [];
197: break;
198:
199: case T_CLASS:
200: case T_INTERFACE:
201: case T_TRAIT:
202: if ($name = self::fetch($tokens, T_STRING)) {
203: $class = $namespace . $name;
204: $classLevel = $level + 1;
205: $res[$class] = $uses;
206: if ($class === $forClass) {
207: return $res;
208: }
209: }
210: break;
211:
212: case T_USE:
213: while (!$class && ($name = self::fetch($tokens, [T_STRING, T_NS_SEPARATOR]))) {
214: $name = ltrim($name, '\\');
215: if (self::fetch($tokens, '{')) {
216: while ($suffix = self::fetch($tokens, [T_STRING, T_NS_SEPARATOR])) {
217: if (self::fetch($tokens, T_AS)) {
218: $uses[self::fetch($tokens, T_STRING)] = $name . $suffix;
219: } else {
220: $tmp = explode('\\', $suffix);
221: $uses[end($tmp)] = $name . $suffix;
222: }
223: if (!self::fetch($tokens, ',')) {
224: break;
225: }
226: }
227:
228: } elseif (self::fetch($tokens, T_AS)) {
229: $uses[self::fetch($tokens, T_STRING)] = $name;
230:
231: } else {
232: $tmp = explode('\\', $name);
233: $uses[end($tmp)] = $name;
234: }
235: if (!self::fetch($tokens, ',')) {
236: break;
237: }
238: }
239: break;
240:
241: case T_CURLY_OPEN:
242: case T_DOLLAR_OPEN_CURLY_BRACES:
243: case '{':
244: $level++;
245: break;
246:
247: case '}':
248: if ($level === $classLevel) {
249: $class = $classLevel = null;
250: }
251: $level--;
252: }
253: }
254:
255: return $res;
256: }
257:
258:
259: private static function fetch(&$tokens, $take)
260: {
261: $res = null;
262: while ($token = current($tokens)) {
263: list($token, $s) = is_array($token) ? $token : [$token, $token];
264: if (in_array($token, (array) $take, true)) {
265: $res .= $s;
266: } elseif (!in_array($token, [T_DOC_COMMENT, T_WHITESPACE, T_COMMENT], true)) {
267: break;
268: }
269: next($tokens);
270: }
271: return $res;
272: }
273: }
274: