Subversion-Projekte lars-tiefland.laravel_shop

Revision

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