Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | Zur aktuellen Revision | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

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