1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Utils;
9:
10: use Nette;
11:
12:
13: 14: 15:
16: class Reflection
17: {
18: use Nette\StaticClass;
19:
20: private static $builtinTypes = [
21: 'string' => 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1,
22: 'callable' => 1, 'iterable' => 1, 'void' => 1,
23: ];
24:
25:
26: 27: 28: 29:
30: public static function isBuiltinType($type)
31: {
32: return isset(self::$builtinTypes[strtolower($type)]);
33: }
34:
35:
36: 37: 38:
39: public static function getReturnType(\ReflectionFunctionAbstract $func)
40: {
41: return PHP_VERSION_ID >= 70000 && $func->hasReturnType()
42: ? self::normalizeType($func->getReturnType(), $func)
43: : null;
44: }
45:
46:
47: 48: 49:
50: public static function getParameterType(\ReflectionParameter $param)
51: {
52: if (PHP_VERSION_ID >= 70000) {
53: return $param->hasType()
54: ? self::normalizeType($param->getType(), $param)
55: : null;
56: } elseif ($param->isArray() || $param->isCallable()) {
57: return $param->isArray() ? 'array' : 'callable';
58: } else {
59: try {
60: return ($ref = $param->getClass()) ? $ref->getName() : null;
61: } catch (\ReflectionException $e) {
62: if (preg_match('#Class (.+) does not exist#', $e->getMessage(), $m)) {
63: return $m[1];
64: }
65: throw $e;
66: }
67: }
68: }
69:
70:
71: private static function normalizeType($type, $reflection)
72: {
73: $type = PHP_VERSION_ID >= 70100 ? $type->getName() : (string) $type;
74: $lower = strtolower($type);
75: if ($lower === 'self') {
76: return $reflection->getDeclaringClass()->getName();
77: } elseif ($lower === 'parent' && $reflection->getDeclaringClass()->getParentClass()) {
78: return $reflection->getDeclaringClass()->getParentClass()->getName();
79: } else {
80: return $type;
81: }
82: }
83:
84:
85: 86: 87: 88:
89: public static function getParameterDefaultValue(\ReflectionParameter $param)
90: {
91: if ($param->isDefaultValueConstant()) {
92: $const = $orig = $param->getDefaultValueConstantName();
93: $pair = explode('::', $const);
94: if (isset($pair[1]) && strtolower($pair[0]) === 'self') {
95: $pair[0] = $param->getDeclaringClass()->getName();
96: }
97: if (isset($pair[1]) && PHP_VERSION_ID >= 70100) {
98: try {
99: $rcc = new \ReflectionClassConstant($pair[0], $pair[1]);
100: } catch (\ReflectionException $e) {
101: $name = self::toString($param);
102: throw new \ReflectionException("Unable to resolve constant $orig used as default value of $name.", 0, $e);
103: }
104: return $rcc->getValue();
105: }
106: $const = implode('::', $pair);
107: if (!defined($const)) {
108: $const = substr((string) strrchr($const, '\\'), 1);
109: if (isset($pair[1]) || !defined($const)) {
110: $name = self::toString($param);
111: throw new \ReflectionException("Unable to resolve constant $orig used as default value of $name.");
112: }
113: }
114: return constant($const);
115: }
116: return $param->getDefaultValue();
117: }
118:
119:
120: 121: 122: 123:
124: public static function getPropertyDeclaringClass(\ReflectionProperty $prop)
125: {
126: foreach ($prop->getDeclaringClass()->getTraits() as $trait) {
127: if ($trait->hasProperty($prop->getName())) {
128: return self::getPropertyDeclaringClass($trait->getProperty($prop->getName()));
129: }
130: }
131: return $prop->getDeclaringClass();
132: }
133:
134:
135: 136: 137: 138:
139: public static function ()
140: {
141: static $res;
142: return $res === null
143: ? $res = (bool) (new \ReflectionMethod(__METHOD__))->getDocComment()
144: : $res;
145: }
146:
147:
148: 149: 150:
151: public static function toString(\Reflector $ref)
152: {
153: if ($ref instanceof \ReflectionClass) {
154: return $ref->getName();
155: } elseif ($ref instanceof \ReflectionMethod) {
156: return $ref->getDeclaringClass()->getName() . '::' . $ref->getName();
157: } elseif ($ref instanceof \ReflectionFunction) {
158: return $ref->getName();
159: } elseif ($ref instanceof \ReflectionProperty) {
160: return self::getPropertyDeclaringClass($ref)->getName() . '::$' . $ref->getName();
161: } elseif ($ref instanceof \ReflectionParameter) {
162: return '$' . $ref->getName() . ' in ' . self::toString($ref->getDeclaringFunction()) . '()';
163: } else {
164: throw new Nette\InvalidArgumentException;
165: }
166: }
167:
168:
169: 170: 171: 172: 173: 174:
175: public static function expandClassName($name, \ReflectionClass $rc)
176: {
177: $lower = strtolower($name);
178: if (empty($name)) {
179: throw new Nette\InvalidArgumentException('Class name must not be empty.');
180:
181: } elseif (isset(self::$builtinTypes[$lower])) {
182: return $lower;
183:
184: } elseif ($lower === 'self') {
185: return $rc->getName();
186:
187: } elseif ($name[0] === '\\') {
188: return ltrim($name, '\\');
189: }
190:
191: $uses = self::getUseStatements($rc);
192: $parts = explode('\\', $name, 2);
193: if (isset($uses[$parts[0]])) {
194: $parts[0] = $uses[$parts[0]];
195: return implode('\\', $parts);
196:
197: } elseif ($rc->inNamespace()) {
198: return $rc->getNamespaceName() . '\\' . $name;
199:
200: } else {
201: return $name;
202: }
203: }
204:
205:
206: 207: 208:
209: public static function getUseStatements(\ReflectionClass $class)
210: {
211: static $cache = [];
212: if (!isset($cache[$name = $class->getName()])) {
213: if ($class->isInternal()) {
214: $cache[$name] = [];
215: } else {
216: $code = file_get_contents($class->getFileName());
217: $cache = self::parseUseStatements($code, $name) + $cache;
218: }
219: }
220: return $cache[$name];
221: }
222:
223:
224: 225: 226: 227: 228:
229: private static function parseUseStatements($code, $forClass = null)
230: {
231: $tokens = PHP_VERSION_ID >= 70000 ? token_get_all($code, TOKEN_PARSE) : token_get_all($code);
232: $namespace = $class = $classLevel = $level = null;
233: $res = $uses = [];
234:
235: while ($token = current($tokens)) {
236: next($tokens);
237: switch (is_array($token) ? $token[0] : $token) {
238: case T_NAMESPACE:
239: $namespace = ltrim(self::fetch($tokens, [T_STRING, T_NS_SEPARATOR]) . '\\', '\\');
240: $uses = [];
241: break;
242:
243: case T_CLASS:
244: case T_INTERFACE:
245: case T_TRAIT:
246: if ($name = self::fetch($tokens, T_STRING)) {
247: $class = $namespace . $name;
248: $classLevel = $level + 1;
249: $res[$class] = $uses;
250: if ($class === $forClass) {
251: return $res;
252: }
253: }
254: break;
255:
256: case T_USE:
257: while (!$class && ($name = self::fetch($tokens, [T_STRING, T_NS_SEPARATOR]))) {
258: $name = ltrim($name, '\\');
259: if (self::fetch($tokens, '{')) {
260: while ($suffix = self::fetch($tokens, [T_STRING, T_NS_SEPARATOR])) {
261: if (self::fetch($tokens, T_AS)) {
262: $uses[self::fetch($tokens, T_STRING)] = $name . $suffix;
263: } else {
264: $tmp = explode('\\', $suffix);
265: $uses[end($tmp)] = $name . $suffix;
266: }
267: if (!self::fetch($tokens, ',')) {
268: break;
269: }
270: }
271:
272: } elseif (self::fetch($tokens, T_AS)) {
273: $uses[self::fetch($tokens, T_STRING)] = $name;
274:
275: } else {
276: $tmp = explode('\\', $name);
277: $uses[end($tmp)] = $name;
278: }
279: if (!self::fetch($tokens, ',')) {
280: break;
281: }
282: }
283: break;
284:
285: case T_CURLY_OPEN:
286: case T_DOLLAR_OPEN_CURLY_BRACES:
287: case '{':
288: $level++;
289: break;
290:
291: case '}':
292: if ($level === $classLevel) {
293: $class = $classLevel = null;
294: }
295: $level--;
296: }
297: }
298:
299: return $res;
300: }
301:
302:
303: private static function fetch(&$tokens, $take)
304: {
305: $res = null;
306: while ($token = current($tokens)) {
307: list($token, $s) = is_array($token) ? $token : [$token, $token];
308: if (in_array($token, (array) $take, true)) {
309: $res .= $s;
310: } elseif (!in_array($token, [T_DOC_COMMENT, T_WHITESPACE, T_COMMENT], true)) {
311: break;
312: }
313: next($tokens);
314: }
315: return $res;
316: }
317: }
318: