Subversion-Projekte lars-tiefland.laravel_shop

Revision

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