PluginProbe
Age Gate / 3.7.3
Age Gate v3.7.3
3.7.3 trunk 1.0.0 1.0.1 1.1.0 1.2.0 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.4.0 1.4.1 1.4.10 1.4.11 1.4.12 1.4.13 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 All 113 releases
age-gate / vendor / nette / utils / src / Utils / Reflection.php

Reflection.php in Age Gate 3.7.3, at vendor/nette/utils/src/Utils/Reflection.php

419 lines 11.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 declare(strict_types=1);
9
10 namespace Nette\Utils;
11
12 use Nette;
13
14
15 /**
16 * PHP reflection helpers.
17 */
18 final class Reflection
19 {
20 use Nette\StaticClass;
21
22 /**
23 * Determines if type is PHP built-in type. Otherwise, it is the class name.
24 */
25 public static function isBuiltinType(string $type): bool
26 {
27 return Validators::isBuiltinType($type);
28 }
29
30
31 /**
32 * Determines if type is special class name self/parent/static.
33 */
34 public static function isClassKeyword(string $name): bool
35 {
36 return Validators::isClassKeyword($name);
37 }
38
39
40 /**
41 * Returns the type of return value of given function or method and normalizes `self`, `static`, and `parent` to actual class names.
42 * If the function does not have a return type, it returns null.
43 * If the function has union or intersection type, it throws Nette\InvalidStateException.
44 * @deprecated use Nette\Utils\Type::fromReflection()
45 */
46 public static function getReturnType(\ReflectionFunctionAbstract $func): ?string
47 {
48 $type = $func->getReturnType() ?? (PHP_VERSION_ID >= 80100 && $func instanceof \ReflectionMethod ? $func->getTentativeReturnType() : null);
49 return self::getType($func, $type);
50 }
51
52
53 /**
54 * @deprecated
55 */
56 public static function getReturnTypes(\ReflectionFunctionAbstract $func): array
57 {
58 $type = Type::fromReflection($func);
59 return $type ? $type->getNames() : [];
60 }
61
62
63 /**
64 * Returns the type of given parameter and normalizes `self` and `parent` to the actual class names.
65 * If the parameter does not have a type, it returns null.
66 * If the parameter has union or intersection type, it throws Nette\InvalidStateException.
67 * @deprecated use Nette\Utils\Type::fromReflection()
68 */
69 public static function getParameterType(\ReflectionParameter $param): ?string
70 {
71 return self::getType($param, $param->getType());
72 }
73
74
75 /**
76 * @deprecated
77 */
78 public static function getParameterTypes(\ReflectionParameter $param): array
79 {
80 $type = Type::fromReflection($param);
81 return $type ? $type->getNames() : [];
82 }
83
84
85 /**
86 * Returns the type of given property and normalizes `self` and `parent` to the actual class names.
87 * If the property does not have a type, it returns null.
88 * If the property has union or intersection type, it throws Nette\InvalidStateException.
89 * @deprecated use Nette\Utils\Type::fromReflection()
90 */
91 public static function getPropertyType(\ReflectionProperty $prop): ?string
92 {
93 return self::getType($prop, PHP_VERSION_ID >= 70400 ? $prop->getType() : null);
94 }
95
96
97 /**
98 * @deprecated
99 */
100 public static function getPropertyTypes(\ReflectionProperty $prop): array
101 {
102 $type = Type::fromReflection($prop);
103 return $type ? $type->getNames() : [];
104 }
105
106
107 /**
108 * @param \ReflectionFunction|\ReflectionMethod|\ReflectionParameter|\ReflectionProperty $reflection
109 */
110 private static function getType($reflection, ?\ReflectionType $type): ?string
111 {
112 if ($type === null) {
113 return null;
114
115 } elseif ($type instanceof \ReflectionNamedType) {
116 return Type::resolve($type->getName(), $reflection);
117
118 } elseif ($type instanceof \ReflectionUnionType || $type instanceof \ReflectionIntersectionType) {
119 throw new Nette\InvalidStateException('The ' . self::toString($reflection) . ' is not expected to have a union or intersection type.');
120
121 } else {
122 throw new Nette\InvalidStateException('Unexpected type of ' . self::toString($reflection));
123 }
124 }
125
126
127 /**
128 * Returns the default value of parameter. If it is a constant, it returns its value.
129 * @return mixed
130 * @throws \ReflectionException If the parameter does not have a default value or the constant cannot be resolved
131 */
132 public static function getParameterDefaultValue(\ReflectionParameter $param)
133 {
134 if ($param->isDefaultValueConstant()) {
135 $const = $orig = $param->getDefaultValueConstantName();
136 $pair = explode('::', $const);
137 if (isset($pair[1])) {
138 $pair[0] = Type::resolve($pair[0], $param);
139 try {
140 $rcc = new \ReflectionClassConstant($pair[0], $pair[1]);
141 } catch (\ReflectionException $e) {
142 $name = self::toString($param);
143 throw new \ReflectionException("Unable to resolve constant $orig used as default value of $name.", 0, $e);
144 }
145
146 return $rcc->getValue();
147
148 } elseif (!defined($const)) {
149 $const = substr((string) strrchr($const, '\\'), 1);
150 if (!defined($const)) {
151 $name = self::toString($param);
152 throw new \ReflectionException("Unable to resolve constant $orig used as default value of $name.");
153 }
154 }
155
156 return constant($const);
157 }
158
159 return $param->getDefaultValue();
160 }
161
162
163 /**
164 * Returns a reflection of a class or trait that contains a declaration of given property. Property can also be declared in the trait.
165 */
166 public static function getPropertyDeclaringClass(\ReflectionProperty $prop): \ReflectionClass
167 {
168 foreach ($prop->getDeclaringClass()->getTraits() as $trait) {
169 if ($trait->hasProperty($prop->name)
170 // doc-comment guessing as workaround for insufficient PHP reflection
171 && $trait->getProperty($prop->name)->getDocComment() === $prop->getDocComment()
172 ) {
173 return self::getPropertyDeclaringClass($trait->getProperty($prop->name));
174 }
175 }
176
177 return $prop->getDeclaringClass();
178 }
179
180
181 /**
182 * Returns a reflection of a method that contains a declaration of $method.
183 * Usually, each method is its own declaration, but the body of the method can also be in the trait and under a different name.
184 */
185 public static function getMethodDeclaringMethod(\ReflectionMethod $method): \ReflectionMethod
186 {
187 // file & line guessing as workaround for insufficient PHP reflection
188 $decl = $method->getDeclaringClass();
189 if ($decl->getFileName() === $method->getFileName()
190 && $decl->getStartLine() <= $method->getStartLine()
191 && $decl->getEndLine() >= $method->getEndLine()
192 ) {
193 return $method;
194 }
195
196 $hash = [$method->getFileName(), $method->getStartLine(), $method->getEndLine()];
197 if (($alias = $decl->getTraitAliases()[$method->name] ?? null)
198 && ($m = new \ReflectionMethod($alias))
199 && $hash === [$m->getFileName(), $m->getStartLine(), $m->getEndLine()]
200 ) {
201 return self::getMethodDeclaringMethod($m);
202 }
203
204 foreach ($decl->getTraits() as $trait) {
205 if ($trait->hasMethod($method->name)
206 && ($m = $trait->getMethod($method->name))
207 && $hash === [$m->getFileName(), $m->getStartLine(), $m->getEndLine()]
208 ) {
209 return self::getMethodDeclaringMethod($m);
210 }
211 }
212
213 return $method;
214 }
215
216
217 /**
218 * Finds out if reflection has access to PHPdoc comments. Comments may not be available due to the opcode cache.
219 */
220 public static function areCommentsAvailable(): bool
221 {
222 static $res;
223 return $res ?? $res = (bool) (new \ReflectionMethod(__METHOD__))->getDocComment();
224 }
225
226
227 public static function toString(\Reflector $ref): string
228 {
229 if ($ref instanceof \ReflectionClass) {
230 return $ref->name;
231 } elseif ($ref instanceof \ReflectionMethod) {
232 return $ref->getDeclaringClass()->name . '::' . $ref->name . '()';
233 } elseif ($ref instanceof \ReflectionFunction) {
234 return $ref->name . '()';
235 } elseif ($ref instanceof \ReflectionProperty) {
236 return self::getPropertyDeclaringClass($ref)->name . '::$' . $ref->name;
237 } elseif ($ref instanceof \ReflectionParameter) {
238 return '$' . $ref->name . ' in ' . self::toString($ref->getDeclaringFunction());
239 } else {
240 throw new Nette\InvalidArgumentException;
241 }
242 }
243
244
245 /**
246 * Expands the name of the class to full name in the given context of given class.
247 * Thus, it returns how the PHP parser would understand $name if it were written in the body of the class $context.
248 * @throws Nette\InvalidArgumentException
249 */
250 public static function expandClassName(string $name, \ReflectionClass $context): string
251 {
252 $lower = strtolower($name);
253 if (empty($name)) {
254 throw new Nette\InvalidArgumentException('Class name must not be empty.');
255
256 } elseif (Validators::isBuiltinType($lower)) {
257 return $lower;
258
259 } elseif ($lower === 'self' || $lower === 'static') {
260 return $context->name;
261
262 } elseif ($lower === 'parent') {
263 return $context->getParentClass()
264 ? $context->getParentClass()->name
265 : 'parent';
266
267 } elseif ($name[0] === '\\') { // fully qualified name
268 return ltrim($name, '\\');
269 }
270
271 $uses = self::getUseStatements($context);
272 $parts = explode('\\', $name, 2);
273 if (isset($uses[$parts[0]])) {
274 $parts[0] = $uses[$parts[0]];
275 return implode('\\', $parts);
276
277 } elseif ($context->inNamespace()) {
278 return $context->getNamespaceName() . '\\' . $name;
279
280 } else {
281 return $name;
282 }
283 }
284
285
286 /** @return array<string, class-string> of [alias => class] */
287 public static function getUseStatements(\ReflectionClass $class): array
288 {
289 if ($class->isAnonymous()) {
290 throw new Nette\NotImplementedException('Anonymous classes are not supported.');
291 }
292
293 static $cache = [];
294 if (!isset($cache[$name = $class->name])) {
295 if ($class->isInternal()) {
296 $cache[$name] = [];
297 } else {
298 $code = file_get_contents($class->getFileName());
299 $cache = self::parseUseStatements($code, $name) + $cache;
300 }
301 }
302
303 return $cache[$name];
304 }
305
306
307 /**
308 * Parses PHP code to [class => [alias => class, ...]]
309 */
310 private static function parseUseStatements(string $code, ?string $forClass = null): array
311 {
312 try {
313 $tokens = token_get_all($code, TOKEN_PARSE);
314 } catch (\ParseError $e) {
315 trigger_error($e->getMessage(), E_USER_NOTICE);
316 $tokens = [];
317 }
318
319 $namespace = $class = null;
320 $classLevel = $level = 0;
321 $res = $uses = [];
322
323 $nameTokens = PHP_VERSION_ID < 80000
324 ? [T_STRING, T_NS_SEPARATOR]
325 : [T_STRING, T_NS_SEPARATOR, T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED];
326
327 while ($token = current($tokens)) {
328 next($tokens);
329 switch (is_array($token) ? $token[0] : $token) {
330 case T_NAMESPACE:
331 $namespace = ltrim(self::fetch($tokens, $nameTokens) . '\\', '\\');
332 $uses = [];
333 break;
334
335 case T_CLASS:
336 case T_INTERFACE:
337 case T_TRAIT:
338 case PHP_VERSION_ID < 80100
339 ? T_CLASS
340 : T_ENUM:
341 if ($name = self::fetch($tokens, T_STRING)) {
342 $class = $namespace . $name;
343 $classLevel = $level + 1;
344 $res[$class] = $uses;
345 if ($class === $forClass) {
346 return $res;
347 }
348 }
349
350 break;
351
352 case T_USE:
353 while (!$class && ($name = self::fetch($tokens, $nameTokens))) {
354 $name = ltrim($name, '\\');
355 if (self::fetch($tokens, '{')) {
356 while ($suffix = self::fetch($tokens, $nameTokens)) {
357 if (self::fetch($tokens, T_AS)) {
358 $uses[self::fetch($tokens, T_STRING)] = $name . $suffix;
359 } else {
360 $tmp = explode('\\', $suffix);
361 $uses[end($tmp)] = $name . $suffix;
362 }
363
364 if (!self::fetch($tokens, ',')) {
365 break;
366 }
367 }
368 } elseif (self::fetch($tokens, T_AS)) {
369 $uses[self::fetch($tokens, T_STRING)] = $name;
370
371 } else {
372 $tmp = explode('\\', $name);
373 $uses[end($tmp)] = $name;
374 }
375
376 if (!self::fetch($tokens, ',')) {
377 break;
378 }
379 }
380
381 break;
382
383 case T_CURLY_OPEN:
384 case T_DOLLAR_OPEN_CURLY_BRACES:
385 case '{':
386 $level++;
387 break;
388
389 case '}':
390 if ($level === $classLevel) {
391 $class = $classLevel = 0;
392 }
393
394 $level--;
395 }
396 }
397
398 return $res;
399 }
400
401
402 private static function fetch(array &$tokens, $take): ?string
403 {
404 $res = null;
405 while ($token = current($tokens)) {
406 [$token, $s] = is_array($token) ? $token : [$token, $token];
407 if (in_array($token, (array) $take, true)) {
408 $res .= $s;
409 } elseif (!in_array($token, [T_DOC_COMMENT, T_WHITESPACE, T_COMMENT], true)) {
410 break;
411 }
412
413 next($tokens);
414 }
415
416 return $res;
417 }
418 }
419