1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Application\UI;
9:
10: use Nette;
11: use Nette\Application\BadRequestException;
12: use Nette\Reflection\ClassType;
13:
14:
15: 16: 17: 18: 19: 20:
21: class ComponentReflection extends \ReflectionClass
22: {
23: use Nette\SmartObject;
24:
25:
26: private static $ppCache = [];
27:
28:
29: private static $pcCache = [];
30:
31:
32: private static $mcCache = [];
33:
34:
35: 36: 37: 38:
39: public function getPersistentParams($class = null)
40: {
41: $class = $class === null ? $this->getName() : $class;
42: $params = &self::$ppCache[$class];
43: if ($params !== null) {
44: return $params;
45: }
46: $params = [];
47: if (is_subclass_of($class, Component::class)) {
48: $isPresenter = is_subclass_of($class, Presenter::class);
49: $defaults = get_class_vars($class);
50: foreach ($class::getPersistentParams() as $name => $default) {
51: if (is_int($name)) {
52: $name = $default;
53: $default = $defaults[$name];
54: }
55: $params[$name] = [
56: 'def' => $default,
57: 'since' => $isPresenter ? $class : null,
58: ];
59: }
60: foreach ($this->getPersistentParams(get_parent_class($class)) as $name => $param) {
61: if (isset($params[$name])) {
62: $params[$name]['since'] = $param['since'];
63: continue;
64: }
65:
66: $params[$name] = $param;
67: }
68: }
69: return $params;
70: }
71:
72:
73: 74: 75: 76:
77: public function getPersistentComponents($class = null)
78: {
79: $class = $class === null ? $this->getName() : $class;
80: $components = &self::$pcCache[$class];
81: if ($components !== null) {
82: return $components;
83: }
84: $components = [];
85: if (is_subclass_of($class, Presenter::class)) {
86: foreach ($class::getPersistentComponents() as $name => $meta) {
87: if (is_string($meta)) {
88: $name = $meta;
89: }
90: $components[$name] = ['since' => $class];
91: }
92: $components = $this->getPersistentComponents(get_parent_class($class)) + $components;
93: }
94: return $components;
95: }
96:
97:
98: 99: 100:
101: public function saveState(Component $component, array &$params)
102: {
103: foreach ($this->getPersistentParams() as $name => $meta) {
104: if (isset($params[$name])) {
105:
106:
107: } elseif (
108: array_key_exists($name, $params)
109: || (isset($meta['since']) && !$component instanceof $meta['since'])
110: || !isset($component->$name)
111: ) {
112: continue;
113:
114: } else {
115: $params[$name] = $component->$name;
116: }
117:
118: $type = gettype($meta['def']);
119: if (!self::convertType($params[$name], $type)) {
120: throw new InvalidLinkException(sprintf(
121: "Value passed to persistent parameter '%s' in %s must be %s, %s given.",
122: $name,
123: $component instanceof Presenter ? 'presenter ' . $component->getName() : "component '{$component->getUniqueId()}'",
124: $type === 'NULL' ? 'scalar' : $type,
125: is_object($params[$name]) ? get_class($params[$name]) : gettype($params[$name])
126: ));
127: }
128:
129: if ($params[$name] === $meta['def'] || ($meta['def'] === null && $params[$name] === '')) {
130: $params[$name] = null;
131: }
132: }
133: }
134:
135:
136: 137: 138: 139: 140: 141:
142: public function hasCallableMethod($method)
143: {
144: $class = $this->getName();
145: $cache = &self::$mcCache[strtolower($class . ':' . $method)];
146: if ($cache === null) {
147: try {
148: $cache = false;
149: $rm = new \ReflectionMethod($class, $method);
150: $cache = $this->isInstantiable() && $rm->isPublic() && !$rm->isAbstract() && !$rm->isStatic();
151: } catch (\ReflectionException $e) {
152: }
153: }
154: return $cache;
155: }
156:
157:
158: 159: 160:
161: public static function combineArgs(\ReflectionFunctionAbstract $method, $args)
162: {
163: $res = [];
164: foreach ($method->getParameters() as $i => $param) {
165: $name = $param->getName();
166: list($type, $isClass) = self::getParameterType($param);
167: if (isset($args[$name])) {
168: $res[$i] = $args[$name];
169: if (!self::convertType($res[$i], $type, $isClass)) {
170: throw new BadRequestException(sprintf(
171: 'Argument $%s passed to %s() must be %s, %s given.',
172: $name,
173: ($method instanceof \ReflectionMethod ? $method->getDeclaringClass()->getName() . '::' : '') . $method->getName(),
174: $type === 'NULL' ? 'scalar' : $type,
175: is_object($args[$name]) ? get_class($args[$name]) : gettype($args[$name])
176: ));
177: }
178: } elseif ($param->isDefaultValueAvailable()) {
179: $res[$i] = $param->getDefaultValue();
180: } elseif ($type === 'NULL' || $param->allowsNull()) {
181: $res[$i] = null;
182: } elseif ($type === 'array' || $type === 'iterable') {
183: $res[$i] = [];
184: } else {
185: throw new BadRequestException(sprintf(
186: 'Missing parameter $%s required by %s()',
187: $name,
188: ($method instanceof \ReflectionMethod ? $method->getDeclaringClass()->getName() . '::' : '') . $method->getName()
189: ));
190: }
191: }
192: return $res;
193: }
194:
195:
196: 197: 198: 199: 200: 201:
202: public static function convertType(&$val, $type, $isClass = false)
203: {
204: if ($isClass) {
205: return $val instanceof $type;
206:
207: } elseif ($type === 'callable') {
208: return false;
209:
210: } elseif ($type === 'NULL') {
211: return !is_array($val);
212:
213: } elseif ($type === 'array' || $type === 'iterable') {
214: return is_array($val);
215:
216: } elseif (!is_scalar($val)) {
217: return false;
218:
219: } else {
220: $tmp = ($val === false ? '0' : (string) $val);
221: if ($type === 'double' || $type === 'float') {
222: $tmp = preg_replace('#\.0*\z#', '', $tmp);
223: }
224: $orig = $tmp;
225: settype($tmp, $type);
226: if ($orig !== ($tmp === false ? '0' : (string) $tmp)) {
227: return false;
228: }
229: $val = $tmp;
230: }
231: return true;
232: }
233:
234:
235: 236: 237: 238:
239: public static function parseAnnotation(\Reflector $ref, $name)
240: {
241: if (!preg_match_all('#[\\s*]@' . preg_quote($name, '#') . '(?:\(\\s*([^)]*)\\s*\)|\\s|$)#', $ref->getDocComment(), $m)) {
242: return false;
243: }
244: static $tokens = ['true' => true, 'false' => false, 'null' => null];
245: $res = [];
246: foreach ($m[1] as $s) {
247: foreach (preg_split('#\s*,\s*#', $s, -1, PREG_SPLIT_NO_EMPTY) ?: ['true'] as $item) {
248: $res[] = array_key_exists($tmp = strtolower($item), $tokens) ? $tokens[$tmp] : $item;
249: }
250: }
251: return $res;
252: }
253:
254:
255: 256: 257:
258: public static function getParameterType(\ReflectionParameter $param)
259: {
260: $def = gettype($param->isDefaultValueAvailable() ? $param->getDefaultValue() : null);
261: if (PHP_VERSION_ID >= 70000) {
262: return $param->hasType()
263: ? [PHP_VERSION_ID >= 70100 ? $param->getType()->getName() : (string) $param->getType(), !$param->getType()->isBuiltin()]
264: : [$def, false];
265: } elseif ($param->isArray() || $param->isCallable()) {
266: return [$param->isArray() ? 'array' : 'callable', false];
267: } else {
268: try {
269: return ($ref = $param->getClass()) ? [$ref->getName(), true] : [$def, false];
270: } catch (\ReflectionException $e) {
271: if (preg_match('#Class (.+) does not exist#', $e->getMessage(), $m)) {
272: throw new \LogicException(sprintf(
273: "Class %s not found. Check type hint of parameter $%s in %s() or 'use' statements.",
274: $m[1],
275: $param->getName(),
276: $param->getDeclaringFunction()->getDeclaringClass()->getName() . '::' . $param->getDeclaringFunction()->getName()
277: ));
278: }
279: throw $e;
280: }
281: }
282: }
283:
284:
285:
286:
287:
288: 289: 290: 291: 292:
293: public function hasAnnotation($name)
294: {
295: return (bool) self::parseAnnotation($this, $name);
296: }
297:
298:
299: 300: 301: 302: 303:
304: public function getAnnotation($name)
305: {
306: $res = self::parseAnnotation($this, $name);
307: return $res ? end($res) : null;
308: }
309:
310:
311: 312: 313:
314: public function getMethod($name)
315: {
316: return new MethodReflection($this->getName(), $name);
317: }
318:
319:
320: 321: 322:
323: public function getMethods($filter = -1)
324: {
325: foreach ($res = parent::getMethods($filter) as $key => $val) {
326: $res[$key] = new MethodReflection($this->getName(), $val->getName());
327: }
328: return $res;
329: }
330:
331:
332: public function __toString()
333: {
334: trigger_error(__METHOD__ . ' is deprecated.', E_USER_DEPRECATED);
335: return $this->getName();
336: }
337:
338:
339: public function __get($name)
340: {
341: trigger_error("getReflection()->$name is deprecated.", E_USER_DEPRECATED);
342: return (new ClassType($this->getName()))->$name;
343: }
344:
345:
346: public function __call($name, $args)
347: {
348: if (method_exists(ClassType::class, $name)) {
349: trigger_error("getReflection()->$name() is deprecated, use Nette\\Reflection\\ClassType::from(\$presenter)->$name()", E_USER_DEPRECATED);
350: return call_user_func_array([new ClassType($this->getName()), $name], $args);
351: }
352: Nette\Utils\ObjectMixin::strictCall(get_class($this), $name);
353: }
354: }
355: