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((string) $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((string) $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: $lower = strtolower($type);
74: if ($lower === 'self') {
75: return $reflection->getDeclaringClass()->getName();
76: } elseif ($lower === 'parent' && $reflection->getDeclaringClass()->getParentClass()) {
77: return $reflection->getDeclaringClass()->getParentClass()->getName();
78: } else {
79: return $type;
80: }
81: }
82:
83:
84: 85: 86: 87:
88: public static function getParameterDefaultValue(\ReflectionParameter $param)
89: {
90: if ($param->isDefaultValueConstant()) {
91: $const = $orig = $param->getDefaultValueConstantName();
92: $pair = explode('::', $const);
93: if (isset($pair[1]) && strtolower($pair[0]) === 'self') {
94: $pair[0] = $param->getDeclaringClass()->getName();
95: }
96: if (isset($pair[1]) && PHP_VERSION_ID >= 70100) {
97: try {
98: $rcc = new \ReflectionClassConstant($pair[0], $pair[1]);
99: } catch (\ReflectionException $e) {
100: $name = self::toString($param);
101: throw new \ReflectionException("Unable to resolve constant $orig used as default value of $name.", 0, $e);
102: }
103: return $rcc->getValue();
104: }
105: $const = implode('::', $pair);
106: if (!defined($const)) {
107: $const = substr((string) strrchr($const, '\\'), 1);
108: if (isset($pair[1]) || !defined($const)) {
109: $name = self::toString($param);
110: throw new \ReflectionException("Unable to resolve constant $orig used as default value of $name.");
111: }
112: }
113: return constant($const);
114: }
115: return $param->getDefaultValue();
116: }
117:
118:
119: 120: 121: 122:
123: public static function getPropertyDeclaringClass(\ReflectionProperty $prop)
124: {
125: foreach ($prop->getDeclaringClass()->getTraits() as $trait) {
126: if ($trait->hasProperty($prop->getName())) {
127: return self::getPropertyDeclaringClass($trait->getProperty($prop->getName()));
128: }
129: }
130: return $prop->getDeclaringClass();
131: }
132:
133:
134: 135: 136: 137:
138: public static function ()
139: {
140: static $res;
141: return $res === null
142: ? $res = (bool) (new \ReflectionMethod(__METHOD__))->getDocComment()
143: : $res;
144: }
145:
146:
147: 148: 149:
150: public static function toString(\Reflector $ref)
151: {
152: if ($ref instanceof \ReflectionClass) {
153: return $ref->getName();
154: } elseif ($ref instanceof \ReflectionMethod) {
155: return $ref->getDeclaringClass()->getName() . '::' . $ref->getName();
156: } elseif ($ref instanceof \ReflectionFunction) {
157: return $ref->getName();
158: } elseif ($ref instanceof \ReflectionProperty) {
159: return self::getPropertyDeclaringClass($ref)->getName() . '::$' . $ref->getName();
160: } elseif ($ref instanceof \ReflectionParameter) {
161: return '$' . $ref->getName() . ' in ' . self::toString($ref->getDeclaringFunction()) . '()';
162: } else {
163: throw new Nette\InvalidArgumentException;
164: }
165: }
166:
167:
168: 169: 170: 171: 172: 173:
174: public static function expandClassName($name, \ReflectionClass $rc)
175: {
176: $lower = strtolower($name);
177: if (empty($name)) {
178: throw new Nette\InvalidArgumentException('Class name must not be empty.');
179:
180: } elseif (isset(self::$builtinTypes[$lower])) {
181: return $lower;
182:
183: } elseif ($lower === 'self') {
184: return $rc->getName();
185:
186: } elseif ($name[0] === '\\') {
187: return ltrim($name, '\\');
188: }
189:
190: $uses = self::getUseStatements($rc);
191: $parts = explode('\\', $name, 2);
192: if (isset($uses[$parts[0]])) {
193: $parts[0] = $uses[$parts[0]];
194: return implode('\\', $parts);
195:
196: } elseif ($rc->inNamespace()) {
197: return $rc->getNamespaceName() . '\\' . $name;
198:
199: } else {
200: return $name;
201: }
202: }
203:
204:
205: 206: 207:
208: public static function getUseStatements(\ReflectionClass $class)
209: {
210: static $cache = [];
211: if (!isset($cache[$name = $class->getName()])) {
212: if ($class->isInternal()) {
213: $cache[$name] = [];
214: } else {
215: $code = file_get_contents($class->getFileName());
216: $cache = self::parseUseStatements($code, $name) + $cache;
217: }
218: }
219: return $cache[$name];
220: }
221:
222:
223: 224: 225: 226: 227:
228: private static function parseUseStatements($code, $forClass = null)
229: {
230: $tokens = PHP_VERSION_ID >= 70000 ? token_get_all($code, TOKEN_PARSE) : token_get_all($code);
231: $namespace = $class = $classLevel = $level = null;
232: $res = $uses = [];
233:
234: while ($token = current($tokens)) {
235: next($tokens);
236: switch (is_array($token) ? $token[0] : $token) {
237: case T_NAMESPACE:
238: $namespace = ltrim(self::fetch($tokens, [T_STRING, T_NS_SEPARATOR]) . '\\', '\\');
239: $uses = [];
240: break;
241:
242: case T_CLASS:
243: case T_INTERFACE:
244: case T_TRAIT:
245: if ($name = self::fetch($tokens, T_STRING)) {
246: $class = $namespace . $name;
247: $classLevel = $level + 1;
248: $res[$class] = $uses;
249: if ($class === $forClass) {
250: return $res;
251: }
252: }
253: break;
254:
255: case T_USE:
256: while (!$class && ($name = self::fetch($tokens, [T_STRING, T_NS_SEPARATOR]))) {
257: $name = ltrim($name, '\\');
258: if (self::fetch($tokens, '{')) {
259: while ($suffix = self::fetch($tokens, [T_STRING, T_NS_SEPARATOR])) {
260: if (self::fetch($tokens, T_AS)) {
261: $uses[self::fetch($tokens, T_STRING)] = $name . $suffix;
262: } else {
263: $tmp = explode('\\', $suffix);
264: $uses[end($tmp)] = $name . $suffix;
265: }
266: if (!self::fetch($tokens, ',')) {
267: break;
268: }
269: }
270:
271: } elseif (self::fetch($tokens, T_AS)) {
272: $uses[self::fetch($tokens, T_STRING)] = $name;
273:
274: } else {
275: $tmp = explode('\\', $name);
276: $uses[end($tmp)] = $name;
277: }
278: if (!self::fetch($tokens, ',')) {
279: break;
280: }
281: }
282: break;
283:
284: case T_CURLY_OPEN:
285: case T_DOLLAR_OPEN_CURLY_BRACES:
286: case '{':
287: $level++;
288: break;
289:
290: case '}':
291: if ($level === $classLevel) {
292: $class = $classLevel = null;
293: }
294: $level--;
295: }
296: }
297:
298: return $res;
299: }
300:
301:
302: private static function fetch(&$tokens, $take)
303: {
304: $res = null;
305: while ($token = current($tokens)) {
306: list($token, $s) = is_array($token) ? $token : [$token, $token];
307: if (in_array($token, (array) $take, true)) {
308: $res .= $s;
309: } elseif (!in_array($token, [T_DOC_COMMENT, T_WHITESPACE, T_COMMENT], true)) {
310: break;
311: }
312: next($tokens);
313: }
314: return $res;
315: }
316: }
317: