PluginProbe ʕ •ᴥ•ʔ
FAPI Member / trunk
FAPI Member vtrunk
2.2.33 2.2.32 trunk 1.9.47 2.1.18 2.2.24 2.2.25 2.2.26 2.2.28 2.2.29 2.2.30 2.2.31
fapi-member / libs / nette / utils / src / Utils / Reflection.php
fapi-member / libs / nette / utils / src / Utils Last commit date
ArrayHash.php 2 years ago ArrayList.php 2 years ago Arrays.php 2 years ago Callback.php 2 years ago DateTime.php 2 years ago FileSystem.php 2 years ago Floats.php 2 years ago Helpers.php 2 years ago Html.php 2 years ago Image.php 2 years ago Json.php 2 years ago ObjectHelpers.php 2 years ago ObjectMixin.php 2 years ago Paginator.php 2 years ago Random.php 2 years ago Reflection.php 2 years ago Strings.php 2 years ago Type.php 2 years ago Validators.php 2 years ago exceptions.php 2 years ago
Reflection.php
327 lines
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 declare (strict_types=1);
8 namespace FapiMember\Library\Nette\Utils;
9
10 use FapiMember\Library\Nette;
11 /**
12 * PHP reflection helpers.
13 */
14 final class Reflection
15 {
16 use Nette\StaticClass;
17 /**
18 * Determines if type is PHP built-in type. Otherwise, it is the class name.
19 */
20 public static function isBuiltinType(string $type): bool
21 {
22 return Validators::isBuiltinType($type);
23 }
24 /**
25 * Determines if type is special class name self/parent/static.
26 */
27 public static function isClassKeyword(string $name): bool
28 {
29 return Validators::isClassKeyword($name);
30 }
31 /**
32 * Returns the type of return value of given function or method and normalizes `self`, `static`, and `parent` to actual class names.
33 * If the function does not have a return type, it returns null.
34 * If the function has union or intersection type, it throws Nette\InvalidStateException.
35 * @deprecated use Nette\Utils\Type::fromReflection()
36 */
37 public static function getReturnType(\ReflectionFunctionAbstract $func): ?string
38 {
39 $type = $func->getReturnType() ?? ((\PHP_VERSION_ID >= 80100 && $func instanceof \ReflectionMethod) ? $func->getTentativeReturnType() : null);
40 return self::getType($func, $type);
41 }
42 /**
43 * @deprecated
44 */
45 public static function getReturnTypes(\ReflectionFunctionAbstract $func): array
46 {
47 $type = Type::fromReflection($func);
48 return $type ? $type->getNames() : [];
49 }
50 /**
51 * Returns the type of given parameter and normalizes `self` and `parent` to the actual class names.
52 * If the parameter does not have a type, it returns null.
53 * If the parameter has union or intersection type, it throws Nette\InvalidStateException.
54 * @deprecated use Nette\Utils\Type::fromReflection()
55 */
56 public static function getParameterType(\ReflectionParameter $param): ?string
57 {
58 return self::getType($param, $param->getType());
59 }
60 /**
61 * @deprecated
62 */
63 public static function getParameterTypes(\ReflectionParameter $param): array
64 {
65 $type = Type::fromReflection($param);
66 return $type ? $type->getNames() : [];
67 }
68 /**
69 * Returns the type of given property and normalizes `self` and `parent` to the actual class names.
70 * If the property does not have a type, it returns null.
71 * If the property has union or intersection type, it throws Nette\InvalidStateException.
72 * @deprecated use Nette\Utils\Type::fromReflection()
73 */
74 public static function getPropertyType(\ReflectionProperty $prop): ?string
75 {
76 return self::getType($prop, (\PHP_VERSION_ID >= 70400) ? $prop->getType() : null);
77 }
78 /**
79 * @deprecated
80 */
81 public static function getPropertyTypes(\ReflectionProperty $prop): array
82 {
83 $type = Type::fromReflection($prop);
84 return $type ? $type->getNames() : [];
85 }
86 /**
87 * @param \ReflectionFunction|\ReflectionMethod|\ReflectionParameter|\ReflectionProperty $reflection
88 */
89 private static function getType($reflection, ?\ReflectionType $type): ?string
90 {
91 if ($type === null) {
92 return null;
93 } elseif ($type instanceof \ReflectionNamedType) {
94 return Type::resolve($type->getName(), $reflection);
95 } elseif ($type instanceof \ReflectionUnionType || $type instanceof \ReflectionIntersectionType) {
96 throw new Nette\InvalidStateException('The ' . self::toString($reflection) . ' is not expected to have a union or intersection type.');
97 } else {
98 throw new Nette\InvalidStateException('Unexpected type of ' . self::toString($reflection));
99 }
100 }
101 /**
102 * Returns the default value of parameter. If it is a constant, it returns its value.
103 * @return mixed
104 * @throws \ReflectionException If the parameter does not have a default value or the constant cannot be resolved
105 */
106 public static function getParameterDefaultValue(\ReflectionParameter $param)
107 {
108 if ($param->isDefaultValueConstant()) {
109 $const = $orig = $param->getDefaultValueConstantName();
110 $pair = explode('::', $const);
111 if (isset($pair[1])) {
112 $pair[0] = Type::resolve($pair[0], $param);
113 try {
114 $rcc = new \ReflectionClassConstant($pair[0], $pair[1]);
115 } catch (\ReflectionException $e) {
116 $name = self::toString($param);
117 throw new \ReflectionException("Unable to resolve constant {$orig} used as default value of {$name}.", 0, $e);
118 }
119 return $rcc->getValue();
120 } elseif (!defined($const)) {
121 $const = substr((string) strrchr($const, '\\'), 1);
122 if (!defined($const)) {
123 $name = self::toString($param);
124 throw new \ReflectionException("Unable to resolve constant {$orig} used as default value of {$name}.");
125 }
126 }
127 return constant($const);
128 }
129 return $param->getDefaultValue();
130 }
131 /**
132 * Returns a reflection of a class or trait that contains a declaration of given property. Property can also be declared in the trait.
133 */
134 public static function getPropertyDeclaringClass(\ReflectionProperty $prop): \ReflectionClass
135 {
136 foreach ($prop->getDeclaringClass()->getTraits() as $trait) {
137 if ($trait->hasProperty($prop->name) && $trait->getProperty($prop->name)->getDocComment() === $prop->getDocComment()) {
138 return self::getPropertyDeclaringClass($trait->getProperty($prop->name));
139 }
140 }
141 return $prop->getDeclaringClass();
142 }
143 /**
144 * Returns a reflection of a method that contains a declaration of $method.
145 * Usually, each method is its own declaration, but the body of the method can also be in the trait and under a different name.
146 */
147 public static function getMethodDeclaringMethod(\ReflectionMethod $method): \ReflectionMethod
148 {
149 // file & line guessing as workaround for insufficient PHP reflection
150 $decl = $method->getDeclaringClass();
151 if ($decl->getFileName() === $method->getFileName() && $decl->getStartLine() <= $method->getStartLine() && $decl->getEndLine() >= $method->getEndLine()) {
152 return $method;
153 }
154 $hash = [$method->getFileName(), $method->getStartLine(), $method->getEndLine()];
155 if (($alias = $decl->getTraitAliases()[$method->name] ?? null) && ($m = new \ReflectionMethod($alias)) && $hash === [$m->getFileName(), $m->getStartLine(), $m->getEndLine()]) {
156 return self::getMethodDeclaringMethod($m);
157 }
158 foreach ($decl->getTraits() as $trait) {
159 if ($trait->hasMethod($method->name) && ($m = $trait->getMethod($method->name)) && $hash === [$m->getFileName(), $m->getStartLine(), $m->getEndLine()]) {
160 return self::getMethodDeclaringMethod($m);
161 }
162 }
163 return $method;
164 }
165 /**
166 * Finds out if reflection has access to PHPdoc comments. Comments may not be available due to the opcode cache.
167 */
168 public static function areCommentsAvailable(): bool
169 {
170 static $res;
171 return $res ?? $res = (bool) (new \ReflectionMethod(__METHOD__))->getDocComment();
172 }
173 public static function toString(\Reflector $ref): string
174 {
175 if ($ref instanceof \ReflectionClass) {
176 return $ref->name;
177 } elseif ($ref instanceof \ReflectionMethod) {
178 return $ref->getDeclaringClass()->name . '::' . $ref->name . '()';
179 } elseif ($ref instanceof \ReflectionFunction) {
180 return $ref->name . '()';
181 } elseif ($ref instanceof \ReflectionProperty) {
182 return self::getPropertyDeclaringClass($ref)->name . '::$' . $ref->name;
183 } elseif ($ref instanceof \ReflectionParameter) {
184 return '$' . $ref->name . ' in ' . self::toString($ref->getDeclaringFunction());
185 } else {
186 throw new Nette\InvalidArgumentException();
187 }
188 }
189 /**
190 * Expands the name of the class to full name in the given context of given class.
191 * Thus, it returns how the PHP parser would understand $name if it were written in the body of the class $context.
192 * @throws Nette\InvalidArgumentException
193 */
194 public static function expandClassName(string $name, \ReflectionClass $context): string
195 {
196 $lower = strtolower($name);
197 if (empty($name)) {
198 throw new Nette\InvalidArgumentException('Class name must not be empty.');
199 } elseif (Validators::isBuiltinType($lower)) {
200 return $lower;
201 } elseif ($lower === 'self' || $lower === 'static') {
202 return $context->name;
203 } elseif ($lower === 'parent') {
204 return $context->getParentClass() ? $context->getParentClass()->name : 'parent';
205 } elseif ($name[0] === '\\') {
206 // fully qualified name
207 return ltrim($name, '\\');
208 }
209 $uses = self::getUseStatements($context);
210 $parts = explode('\\', $name, 2);
211 if (isset($uses[$parts[0]])) {
212 $parts[0] = $uses[$parts[0]];
213 return implode('\\', $parts);
214 } elseif ($context->inNamespace()) {
215 return $context->getNamespaceName() . '\\' . $name;
216 } else {
217 return $name;
218 }
219 }
220 /** @return array<string, class-string> of [alias => class] */
221 public static function getUseStatements(\ReflectionClass $class): array
222 {
223 if ($class->isAnonymous()) {
224 throw new Nette\NotImplementedException('Anonymous classes are not supported.');
225 }
226 static $cache = [];
227 if (!isset($cache[$name = $class->name])) {
228 if ($class->isInternal()) {
229 $cache[$name] = [];
230 } else {
231 $code = file_get_contents($class->getFileName());
232 $cache = self::parseUseStatements($code, $name) + $cache;
233 }
234 }
235 return $cache[$name];
236 }
237 /**
238 * Parses PHP code to [class => [alias => class, ...]]
239 */
240 private static function parseUseStatements(string $code, ?string $forClass = null): array
241 {
242 try {
243 $tokens = token_get_all($code, \TOKEN_PARSE);
244 } catch (\ParseError $e) {
245 trigger_error($e->getMessage(), \E_USER_NOTICE);
246 $tokens = [];
247 }
248 $namespace = $class = null;
249 $classLevel = $level = 0;
250 $res = $uses = [];
251 $nameTokens = (\PHP_VERSION_ID < 80000) ? [\T_STRING, \T_NS_SEPARATOR] : [\T_STRING, \T_NS_SEPARATOR, \T_NAME_QUALIFIED, \T_NAME_FULLY_QUALIFIED];
252 while ($token = current($tokens)) {
253 next($tokens);
254 switch (is_array($token) ? $token[0] : $token) {
255 case \T_NAMESPACE:
256 $namespace = ltrim(self::fetch($tokens, $nameTokens) . '\\', '\\');
257 $uses = [];
258 break;
259 case \T_CLASS:
260 case \T_INTERFACE:
261 case \T_TRAIT:
262 case (\PHP_VERSION_ID < 80100) ? \T_CLASS : \T_ENUM:
263 if ($name = self::fetch($tokens, \T_STRING)) {
264 $class = $namespace . $name;
265 $classLevel = $level + 1;
266 $res[$class] = $uses;
267 if ($class === $forClass) {
268 return $res;
269 }
270 }
271 break;
272 case \T_USE:
273 while (!$class && $name = self::fetch($tokens, $nameTokens)) {
274 $name = ltrim($name, '\\');
275 if (self::fetch($tokens, '{')) {
276 while ($suffix = self::fetch($tokens, $nameTokens)) {
277 if (self::fetch($tokens, \T_AS)) {
278 $uses[self::fetch($tokens, \T_STRING)] = $name . $suffix;
279 } else {
280 $tmp = explode('\\', $suffix);
281 $uses[end($tmp)] = $name . $suffix;
282 }
283 if (!self::fetch($tokens, ',')) {
284 break;
285 }
286 }
287 } elseif (self::fetch($tokens, \T_AS)) {
288 $uses[self::fetch($tokens, \T_STRING)] = $name;
289 } else {
290 $tmp = explode('\\', $name);
291 $uses[end($tmp)] = $name;
292 }
293 if (!self::fetch($tokens, ',')) {
294 break;
295 }
296 }
297 break;
298 case \T_CURLY_OPEN:
299 case \T_DOLLAR_OPEN_CURLY_BRACES:
300 case '{':
301 $level++;
302 break;
303 case '}':
304 if ($level === $classLevel) {
305 $class = $classLevel = 0;
306 }
307 $level--;
308 }
309 }
310 return $res;
311 }
312 private static function fetch(array &$tokens, $take): ?string
313 {
314 $res = null;
315 while ($token = current($tokens)) {
316 [$token, $s] = is_array($token) ? $token : [$token, $token];
317 if (in_array($token, (array) $take, \true)) {
318 $res .= $s;
319 } elseif (!in_array($token, [\T_DOC_COMMENT, \T_WHITESPACE, \T_COMMENT], \true)) {
320 break;
321 }
322 next($tokens);
323 }
324 return $res;
325 }
326 }
327