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') {
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') {
214: return is_array($val);
215:
216: } elseif (!is_scalar($val)) {
217: return false;
218:
219: } else {
220: $old = $tmp = ($val === false ? '0' : (string) $val);
221: settype($tmp, $type);
222: if ($old !== ($tmp === false ? '0' : (string) $tmp)) {
223: return false;
224: }
225: $val = $tmp;
226: }
227: return true;
228: }
229:
230:
231: 232: 233: 234:
235: public static function parseAnnotation(\Reflector $ref, $name)
236: {
237: if (!preg_match_all('#[\\s*]@' . preg_quote($name, '#') . '(?:\(\\s*([^)]*)\\s*\)|\\s|$)#', $ref->getDocComment(), $m)) {
238: return false;
239: }
240: static $tokens = ['true' => true, 'false' => false, 'null' => null];
241: $res = [];
242: foreach ($m[1] as $s) {
243: foreach (preg_split('#\s*,\s*#', $s, -1, PREG_SPLIT_NO_EMPTY) ?: ['true'] as $item) {
244: $res[] = array_key_exists($tmp = strtolower($item), $tokens) ? $tokens[$tmp] : $item;
245: }
246: }
247: return $res;
248: }
249:
250:
251: 252: 253:
254: public static function getParameterType(\ReflectionParameter $param)
255: {
256: $def = gettype($param->isDefaultValueAvailable() ? $param->getDefaultValue() : null);
257: if (PHP_VERSION_ID >= 70000) {
258: return $param->hasType()
259: ? [PHP_VERSION_ID >= 70100 ? $param->getType()->getName() : (string) $param->getType(), !$param->getType()->isBuiltin()]
260: : [$def, false];
261: } elseif ($param->isArray() || $param->isCallable()) {
262: return [$param->isArray() ? 'array' : 'callable', false];
263: } else {
264: try {
265: return ($ref = $param->getClass()) ? [$ref->getName(), true] : [$def, false];
266: } catch (\ReflectionException $e) {
267: if (preg_match('#Class (.+) does not exist#', $e->getMessage(), $m)) {
268: throw new \LogicException(sprintf(
269: "Class %s not found. Check type hint of parameter $%s in %s() or 'use' statements.",
270: $m[1],
271: $param->getName(),
272: $param->getDeclaringFunction()->getDeclaringClass()->getName() . '::' . $param->getDeclaringFunction()->getName()
273: ));
274: }
275: throw $e;
276: }
277: }
278: }
279:
280:
281:
282:
283:
284: 285: 286: 287: 288:
289: public function hasAnnotation($name)
290: {
291: return (bool) self::parseAnnotation($this, $name);
292: }
293:
294:
295: 296: 297: 298: 299:
300: public function getAnnotation($name)
301: {
302: $res = self::parseAnnotation($this, $name);
303: return $res ? end($res) : null;
304: }
305:
306:
307: 308: 309:
310: public function getMethod($name)
311: {
312: return new MethodReflection($this->getName(), $name);
313: }
314:
315:
316: 317: 318:
319: public function getMethods($filter = -1)
320: {
321: foreach ($res = parent::getMethods($filter) as $key => $val) {
322: $res[$key] = new MethodReflection($this->getName(), $val->getName());
323: }
324: return $res;
325: }
326:
327:
328: public function __toString()
329: {
330: trigger_error(__METHOD__ . ' is deprecated.', E_USER_DEPRECATED);
331: return $this->getName();
332: }
333:
334:
335: public function __get($name)
336: {
337: trigger_error("getReflection()->$name is deprecated.", E_USER_DEPRECATED);
338: return (new ClassType($this->getName()))->$name;
339: }
340:
341:
342: public function __call($name, $args)
343: {
344: if (method_exists(ClassType::class, $name)) {
345: trigger_error("getReflection()->$name() is deprecated, use Nette\\Reflection\\ClassType::from(\$presenter)->$name()", E_USER_DEPRECATED);
346: return call_user_func_array([new ClassType($this->getName()), $name], $args);
347: }
348: Nette\Utils\ObjectMixin::strictCall(get_class($this), $name);
349: }
350: }
351: