Skip to content

Commit d8eadeb

Browse files
committed
added Reflection
1 parent 153ad25 commit d8eadeb

16 files changed

+815
-0
lines changed

src/Utils/Reflection.php

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the Nette Framework (https://nette.org)
5+
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6+
*/
7+
8+
namespace Nette\Utils;
9+
10+
use Nette;
11+
12+
13+
/**
14+
* PHP reflection helpers.
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,
22+
'callable' => 1, 'iterable' => 1, 'void' => 1
23+
];
24+
25+
26+
/**
27+
* @param string
28+
* @return bool
29+
*/
30+
public static function isBuiltinType($type)
31+
{
32+
return isset(self::$builtinTypes[strtolower($type)]);
33+
}
34+
35+
36+
/**
37+
* @return string|NULL
38+
*/
39+
public static function getReturnType(\ReflectionFunctionAbstract $func)
40+
{
41+
if (PHP_VERSION_ID >= 70000 && $func->hasReturnType()) {
42+
$type = (string) $func->getReturnType();
43+
return strtolower($type) === 'self' ? $func->getDeclaringClass()->getName() : $type;
44+
}
45+
}
46+
47+
48+
/**
49+
* @return string|NULL
50+
*/
51+
public static function getParameterType(\ReflectionParameter $param)
52+
{
53+
if (PHP_VERSION_ID >= 70000) {
54+
$type = $param->hasType() ? (string) $param->getType() : NULL;
55+
return strtolower($type) === 'self' ? $param->getDeclaringClass()->getName() : $type;
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+
/**
72+
* @return mixed
73+
* @throws \ReflectionException when default value is not available or resolvable
74+
*/
75+
public static function getParameterDefaultValue(\ReflectionParameter $param)
76+
{
77+
if ($param->isDefaultValueConstant()) {
78+
$const = $param->getDefaultValueConstantName();
79+
$pair = explode('::', $const);
80+
if (isset($pair[1]) && strtolower($pair[0]) === 'self') {
81+
$const = $param->getDeclaringClass()->getName() . '::' . $pair[1];
82+
}
83+
if (!defined($const)) {
84+
$name = self::toString($param);
85+
throw new \ReflectionException("Unable to resolve constant $const used as default value of $name.");
86+
}
87+
return constant($const);
88+
}
89+
return $param->getDefaultValue();
90+
}
91+
92+
93+
/**
94+
* Returns declaring class or trait.
95+
* @return \ReflectionClass
96+
*/
97+
public static function getPropertyDeclaringClass(\ReflectionProperty $prop)
98+
{
99+
foreach ($prop->getDeclaringClass()->getTraits() as $trait) {
100+
if ($trait->hasProperty($prop->getName())) {
101+
return self::getPropertyDeclaringClass($trait->getProperty($prop->getName()));
102+
}
103+
}
104+
return $prop->getDeclaringClass();
105+
}
106+
107+
108+
/**
109+
* Are documentation comments available?
110+
* @return bool
111+
*/
112+
public static function areCommentsAvailable()
113+
{
114+
static $res;
115+
return $res === NULL
116+
? $res = (bool) (new \ReflectionMethod(__METHOD__))->getDocComment()
117+
: $res;
118+
}
119+
120+
121+
/**
122+
* @return string
123+
*/
124+
public static function toString(\Reflector $ref)
125+
{
126+
if ($ref instanceof \ReflectionClass) {
127+
return $ref->getName();
128+
} elseif ($ref instanceof \ReflectionMethod) {
129+
return $ref->getDeclaringClass()->getName() . '::' . $ref->getName();
130+
} elseif ($ref instanceof \ReflectionFunction) {
131+
return $ref->getName();
132+
} elseif ($ref instanceof \ReflectionProperty) {
133+
return self::getPropertyDeclaringClass($ref)->getName() . '::$' . $ref->getName();
134+
} elseif ($ref instanceof \ReflectionParameter) {
135+
return '$' . $ref->getName() . ' in ' . self::toString($ref->getDeclaringFunction()) . '()';
136+
} else {
137+
throw new Nette\InvalidArgumentException;
138+
}
139+
}
140+
141+
142+
/**
143+
* Expands class name into full name.
144+
* @param string
145+
* @return string full name
146+
* @throws Nette\InvalidArgumentException
147+
*/
148+
public static function expandClassName($name, \ReflectionClass $rc)
149+
{
150+
$lower = strtolower($name);
151+
if (empty($name)) {
152+
throw new Nette\InvalidArgumentException('Class name must not be empty.');
153+
154+
} elseif (isset(self::$builtinTypes[$lower])) {
155+
return $lower;
156+
157+
} elseif ($lower === 'self') {
158+
return $rc->getName();
159+
160+
} elseif ($name[0] === '\\') { // fully qualified name
161+
return ltrim($name, '\\');
162+
}
163+
164+
$uses = self::getUseStatements($rc);
165+
$parts = explode('\\', $name, 2);
166+
if (isset($uses[$parts[0]])) {
167+
$parts[0] = $uses[$parts[0]];
168+
return implode('\\', $parts);
169+
170+
} elseif ($rc->inNamespace()) {
171+
return $rc->getNamespaceName() . '\\' . $name;
172+
173+
} else {
174+
return $name;
175+
}
176+
}
177+
178+
179+
/**
180+
* @return array of [alias => class]
181+
*/
182+
public static function getUseStatements(\ReflectionClass $class)
183+
{
184+
static $cache = [];
185+
if (!isset($cache[$name = $class->getName()])) {
186+
if ($class->isInternal()) {
187+
$cache[$name] = [];
188+
} else {
189+
$code = file_get_contents($class->getFileName());
190+
$cache = self::parseUseStatements($code, $name) + $cache;
191+
}
192+
}
193+
return $cache[$name];
194+
}
195+
196+
197+
/**
198+
* Parses PHP code.
199+
* @param string
200+
* @return array of [class => [alias => class, ...]]
201+
*/
202+
private static function parseUseStatements($code, $forClass = NULL)
203+
{
204+
$tokens = token_get_all($code);
205+
$namespace = $class = $classLevel = $level = NULL;
206+
$res = $uses = [];
207+
208+
while (list(, $token) = each($tokens)) {
209+
switch (is_array($token) ? $token[0] : $token) {
210+
case T_NAMESPACE:
211+
$namespace = ltrim(self::fetch($tokens, [T_STRING, T_NS_SEPARATOR]) . '\\', '\\');
212+
$uses = [];
213+
break;
214+
215+
case T_CLASS:
216+
case T_INTERFACE:
217+
case T_TRAIT:
218+
if ($name = self::fetch($tokens, T_STRING)) {
219+
$class = $namespace . $name;
220+
$classLevel = $level + 1;
221+
$res[$class] = $uses;
222+
if ($class === $forClass) {
223+
return $res;
224+
}
225+
}
226+
break;
227+
228+
case T_USE:
229+
while (!$class && ($name = self::fetch($tokens, [T_STRING, T_NS_SEPARATOR]))) {
230+
$name = ltrim($name, '\\');
231+
if (self::fetch($tokens, '{')) {
232+
while ($suffix = self::fetch($tokens, [T_STRING, T_NS_SEPARATOR])) {
233+
if (self::fetch($tokens, T_AS)) {
234+
$uses[self::fetch($tokens, T_STRING)] = $name . $suffix;
235+
} else {
236+
$tmp = explode('\\', $suffix);
237+
$uses[end($tmp)] = $name . $suffix;
238+
}
239+
if (!self::fetch($tokens, ',')) {
240+
break;
241+
}
242+
}
243+
244+
} elseif (self::fetch($tokens, T_AS)) {
245+
$uses[self::fetch($tokens, T_STRING)] = $name;
246+
247+
} else {
248+
$tmp = explode('\\', $name);
249+
$uses[end($tmp)] = $name;
250+
}
251+
if (!self::fetch($tokens, ',')) {
252+
break;
253+
}
254+
}
255+
break;
256+
257+
case T_CURLY_OPEN:
258+
case T_DOLLAR_OPEN_CURLY_BRACES:
259+
case '{':
260+
$level++;
261+
break;
262+
263+
case '}':
264+
if ($level === $classLevel) {
265+
$class = $classLevel = NULL;
266+
}
267+
$level--;
268+
}
269+
}
270+
271+
return $res;
272+
}
273+
274+
275+
private static function fetch(& $tokens, $take)
276+
{
277+
$res = NULL;
278+
while ($token = current($tokens)) {
279+
list($token, $s) = is_array($token) ? $token : [$token, $token];
280+
if (in_array($token, (array) $take, TRUE)) {
281+
$res .= $s;
282+
} elseif (!in_array($token, [T_DOC_COMMENT, T_WHITESPACE, T_COMMENT], TRUE)) {
283+
break;
284+
}
285+
next($tokens);
286+
}
287+
return $res;
288+
}
289+
290+
}

0 commit comments

Comments
 (0)