Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | 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
use Nette\MemberAccessException;
14
 
15
 
16
/**
17
 * Nette\SmartObject helpers.
399 lars 18
 * @internal
148 lars 19
 */
20
final class ObjectHelpers
21
{
22
	use Nette\StaticClass;
23
 
24
	/**
25
	 * @return never
26
	 * @throws MemberAccessException
27
	 */
28
	public static function strictGet(string $class, string $name): void
29
	{
30
		$rc = new \ReflectionClass($class);
31
		$hint = self::getSuggestion(array_merge(
399 lars 32
			array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), fn($p) => !$p->isStatic()),
33
			self::parseFullDoc($rc, '~^[ \t*]*@property(?:-read)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m'),
148 lars 34
		), $name);
35
		throw new MemberAccessException("Cannot read an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.'));
36
	}
37
 
38
 
39
	/**
40
	 * @return never
41
	 * @throws MemberAccessException
42
	 */
43
	public static function strictSet(string $class, string $name): void
44
	{
45
		$rc = new \ReflectionClass($class);
46
		$hint = self::getSuggestion(array_merge(
399 lars 47
			array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), fn($p) => !$p->isStatic()),
48
			self::parseFullDoc($rc, '~^[ \t*]*@property(?:-write)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m'),
148 lars 49
		), $name);
50
		throw new MemberAccessException("Cannot write to an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.'));
51
	}
52
 
53
 
54
	/**
55
	 * @return never
56
	 * @throws MemberAccessException
57
	 */
58
	public static function strictCall(string $class, string $method, array $additionalMethods = []): void
59
	{
60
		$trace = debug_backtrace(0, 3); // suppose this method is called from __call()
61
		$context = ($trace[1]['function'] ?? null) === '__call'
62
			? ($trace[2]['class'] ?? null)
63
			: null;
64
 
65
		if ($context && is_a($class, $context, true) && method_exists($context, $method)) { // called parent::$method()
66
			$class = get_parent_class($context);
67
		}
68
 
69
		if (method_exists($class, $method)) { // insufficient visibility
70
			$rm = new \ReflectionMethod($class, $method);
71
			$visibility = $rm->isPrivate()
72
				? 'private '
73
				: ($rm->isProtected() ? 'protected ' : '');
74
			throw new MemberAccessException("Call to {$visibility}method $class::$method() from " . ($context ? "scope $context." : 'global scope.'));
75
 
76
		} else {
77
			$hint = self::getSuggestion(array_merge(
78
				get_class_methods($class),
79
				self::parseFullDoc(new \ReflectionClass($class), '~^[ \t*]*@method[ \t]+(?:static[ \t]+)?(?:\S+[ \t]+)??(\w+)\(~m'),
399 lars 80
				$additionalMethods,
148 lars 81
			), $method);
82
			throw new MemberAccessException("Call to undefined method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
83
		}
84
	}
85
 
86
 
87
	/**
88
	 * @return never
89
	 * @throws MemberAccessException
90
	 */
91
	public static function strictStaticCall(string $class, string $method): void
92
	{
93
		$trace = debug_backtrace(0, 3); // suppose this method is called from __callStatic()
94
		$context = ($trace[1]['function'] ?? null) === '__callStatic'
95
			? ($trace[2]['class'] ?? null)
96
			: null;
97
 
98
		if ($context && is_a($class, $context, true) && method_exists($context, $method)) { // called parent::$method()
99
			$class = get_parent_class($context);
100
		}
101
 
102
		if (method_exists($class, $method)) { // insufficient visibility
103
			$rm = new \ReflectionMethod($class, $method);
104
			$visibility = $rm->isPrivate()
105
				? 'private '
106
				: ($rm->isProtected() ? 'protected ' : '');
107
			throw new MemberAccessException("Call to {$visibility}method $class::$method() from " . ($context ? "scope $context." : 'global scope.'));
108
 
109
		} else {
110
			$hint = self::getSuggestion(
399 lars 111
				array_filter((new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC), fn($m) => $m->isStatic()),
112
				$method,
148 lars 113
			);
114
			throw new MemberAccessException("Call to undefined static method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
115
		}
116
	}
117
 
118
 
119
	/**
120
	 * Returns array of magic properties defined by annotation @property.
121
	 * @return array of [name => bit mask]
122
	 * @internal
123
	 */
124
	public static function getMagicProperties(string $class): array
125
	{
126
		static $cache;
127
		$props = &$cache[$class];
128
		if ($props !== null) {
129
			return $props;
130
		}
131
 
132
		$rc = new \ReflectionClass($class);
133
		preg_match_all(
134
			'~^  [ \t*]*  @property(|-read|-write|-deprecated)  [ \t]+  [^\s$]+  [ \t]+  \$  (\w+)  ()~mx',
135
			(string) $rc->getDocComment(),
136
			$matches,
399 lars 137
			PREG_SET_ORDER,
148 lars 138
		);
139
 
140
		$props = [];
141
		foreach ($matches as [, $type, $name]) {
142
			$uname = ucfirst($name);
143
			$write = $type !== '-read'
144
				&& $rc->hasMethod($nm = 'set' . $uname)
145
				&& ($rm = $rc->getMethod($nm))->name === $nm && !$rm->isPrivate() && !$rm->isStatic();
146
			$read = $type !== '-write'
147
				&& ($rc->hasMethod($nm = 'get' . $uname) || $rc->hasMethod($nm = 'is' . $uname))
148
				&& ($rm = $rc->getMethod($nm))->name === $nm && !$rm->isPrivate() && !$rm->isStatic();
149
 
150
			if ($read || $write) {
151
				$props[$name] = $read << 0 | ($nm[0] === 'g') << 1 | $rm->returnsReference() << 2 | $write << 3 | ($type === '-deprecated') << 4;
152
			}
153
		}
154
 
155
		foreach ($rc->getTraits() as $trait) {
156
			$props += self::getMagicProperties($trait->name);
157
		}
158
 
159
		if ($parent = get_parent_class($class)) {
160
			$props += self::getMagicProperties($parent);
161
		}
162
 
163
		return $props;
164
	}
165
 
166
 
167
	/**
168
	 * Finds the best suggestion (for 8-bit encoding).
169
	 * @param  (\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionClass|\ReflectionProperty|string)[]  $possibilities
170
	 * @internal
171
	 */
172
	public static function getSuggestion(array $possibilities, string $value): ?string
173
	{
174
		$norm = preg_replace($re = '#^(get|set|has|is|add)(?=[A-Z])#', '+', $value);
175
		$best = null;
176
		$min = (strlen($value) / 4 + 1) * 10 + .1;
177
		foreach (array_unique($possibilities, SORT_REGULAR) as $item) {
178
			$item = $item instanceof \Reflector ? $item->name : $item;
179
			if ($item !== $value && (
180
				($len = levenshtein($item, $value, 10, 11, 10)) < $min
181
				|| ($len = levenshtein(preg_replace($re, '*', $item), $norm, 10, 11, 10)) < $min
182
			)) {
183
				$min = $len;
184
				$best = $item;
185
			}
186
		}
187
 
188
		return $best;
189
	}
190
 
191
 
192
	private static function parseFullDoc(\ReflectionClass $rc, string $pattern): array
193
	{
194
		do {
195
			$doc[] = $rc->getDocComment();
196
			$traits = $rc->getTraits();
197
			while ($trait = array_pop($traits)) {
198
				$doc[] = $trait->getDocComment();
199
				$traits += $trait->getTraits();
200
			}
201
		} while ($rc = $rc->getParentClass());
202
 
399 lars 203
		return preg_match_all($pattern, implode('', $doc), $m) ? $m[1] : [];
148 lars 204
	}
205
 
206
 
207
	/**
208
	 * Checks if the public non-static property exists.
399 lars 209
	 * Returns 'event' if the property exists and has event like name
148 lars 210
	 * @internal
211
	 */
399 lars 212
	public static function hasProperty(string $class, string $name): bool|string
148 lars 213
	{
214
		static $cache;
215
		$prop = &$cache[$class][$name];
216
		if ($prop === null) {
217
			$prop = false;
218
			try {
219
				$rp = new \ReflectionProperty($class, $name);
220
				if ($rp->isPublic() && !$rp->isStatic()) {
221
					$prop = $name >= 'onA' && $name < 'on_' ? 'event' : true;
222
				}
223
			} catch (\ReflectionException $e) {
224
			}
225
		}
226
 
227
		return $prop;
228
	}
229
}