1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Utils;
9:
10: use Nette;
11: use Nette\MemberAccessException;
12:
13:
14: 15: 16: 17:
18: class ObjectMixin
19: {
20: use Nette\StaticClass;
21:
22:
23: private static $extMethods = [];
24:
25:
26:
27:
28:
29: 30: 31:
32: public static function strictGet($class, $name)
33: {
34: trigger_error('Class Nette\Utils\ObjectMixin is deprecated', E_USER_DEPRECATED);
35: ObjectHelpers::strictGet($class, $name);
36: }
37:
38:
39: 40: 41:
42: public static function strictSet($class, $name)
43: {
44: trigger_error('Class Nette\Utils\ObjectMixin is deprecated', E_USER_DEPRECATED);
45: ObjectHelpers::strictSet($class, $name);
46: }
47:
48:
49: 50: 51:
52: public static function strictCall($class, $method, $additionalMethods = [])
53: {
54: trigger_error('Class Nette\Utils\ObjectMixin is deprecated', E_USER_DEPRECATED);
55: ObjectHelpers::strictCall($class, $method, $additionalMethods);
56: }
57:
58:
59: 60: 61:
62: public static function strictStaticCall($class, $method)
63: {
64: trigger_error('Class Nette\Utils\ObjectMixin is deprecated', E_USER_DEPRECATED);
65: ObjectHelpers::strictStaticCall($class, $method);
66: }
67:
68:
69:
70:
71:
72: 73: 74: 75: 76: 77: 78: 79:
80: public static function call($_this, $name, $args)
81: {
82: trigger_error('Class Nette\Utils\ObjectMixin is deprecated', E_USER_DEPRECATED);
83: $class = get_class($_this);
84: $isProp = ObjectHelpers::hasProperty($class, $name);
85:
86: if ($name === '') {
87: throw new MemberAccessException("Call to class '$class' method without name.");
88:
89: } elseif ($isProp === 'event') {
90: if (is_array($_this->$name) || $_this->$name instanceof \Traversable) {
91: foreach ($_this->$name as $handler) {
92: Callback::invokeArgs($handler, $args);
93: }
94: } elseif ($_this->$name !== null) {
95: throw new Nette\UnexpectedValueException("Property $class::$$name must be array or null, " . gettype($_this->$name) . ' given.');
96: }
97:
98: } elseif ($isProp && $_this->$name instanceof \Closure) {
99: return call_user_func_array($_this->$name, $args);
100:
101: } elseif (($methods = &self::getMethods($class)) && isset($methods[$name]) && is_array($methods[$name])) {
102: list($op, $rp, $type) = $methods[$name];
103: if (count($args) !== ($op === 'get' ? 0 : 1)) {
104: throw new Nette\InvalidArgumentException("$class::$name() expects " . ($op === 'get' ? 'no' : '1') . ' argument, ' . count($args) . ' given.');
105:
106: } elseif ($type && $args && !self::checkType($args[0], $type)) {
107: throw new Nette\InvalidArgumentException("Argument passed to $class::$name() must be $type, " . gettype($args[0]) . ' given.');
108: }
109:
110: if ($op === 'get') {
111: return $rp->getValue($_this);
112: } elseif ($op === 'set') {
113: $rp->setValue($_this, $args[0]);
114: } elseif ($op === 'add') {
115: $val = $rp->getValue($_this);
116: $val[] = $args[0];
117: $rp->setValue($_this, $val);
118: }
119: return $_this;
120:
121: } elseif ($cb = self::getExtensionMethod($class, $name)) {
122: return Callback::invoke($cb, $_this, ...$args);
123:
124: } else {
125: ObjectHelpers::strictCall($class, $name, array_keys(self::getExtensionMethods($class)));
126: }
127: }
128:
129:
130: 131: 132: 133: 134: 135: 136: 137:
138: public static function callStatic($class, $method, $args)
139: {
140: trigger_error('Class Nette\Utils\ObjectMixin is deprecated', E_USER_DEPRECATED);
141: self::strictStaticCall($class, $method);
142: }
143:
144:
145: 146: 147: 148: 149: 150: 151:
152: public static function &get($_this, $name)
153: {
154: $class = get_class($_this);
155: $uname = ucfirst($name);
156: $methods = &self::getMethods($class);
157:
158: if ($name === '') {
159: throw new MemberAccessException("Cannot read a class '$class' property without name.");
160:
161: } elseif (isset($methods[$m = 'get' . $uname]) || isset($methods[$m = 'is' . $uname])) {
162: if ($methods[$m] === 0) {
163: $methods[$m] = (new \ReflectionMethod($class, $m))->returnsReference();
164: }
165: if ($methods[$m] === true) {
166: return $_this->$m();
167: } else {
168: $val = $_this->$m();
169: return $val;
170: }
171:
172: } elseif (isset($methods[$name])) {
173: if (preg_match('#^(is|get|has)([A-Z]|$)#', $name) && !(new \ReflectionMethod($class, $name))->getNumberOfRequiredParameters()) {
174: trigger_error("Did you forget parentheses after $name" . self::getSource() . '?', E_USER_WARNING);
175: }
176: $val = Callback::closure($_this, $name);
177: return $val;
178:
179: } elseif (isset($methods['set' . $uname])) {
180: throw new MemberAccessException("Cannot read a write-only property $class::\$$name.");
181:
182: } else {
183: ObjectHelpers::strictGet($class, $name);
184: }
185: }
186:
187:
188: 189: 190: 191: 192: 193: 194: 195:
196: public static function set($_this, $name, $value)
197: {
198: trigger_error('Class Nette\Utils\ObjectMixin is deprecated', E_USER_DEPRECATED);
199: $class = get_class($_this);
200: $uname = ucfirst($name);
201: $methods = &self::getMethods($class);
202:
203: if ($name === '') {
204: throw new MemberAccessException("Cannot write to a class '$class' property without name.");
205:
206: } elseif (ObjectHelpers::hasProperty($class, $name)) {
207: $_this->$name = $value;
208:
209: } elseif (isset($methods[$m = 'set' . $uname])) {
210: $_this->$m($value);
211:
212: } elseif (isset($methods['get' . $uname]) || isset($methods['is' . $uname])) {
213: throw new MemberAccessException("Cannot write to a read-only property $class::\$$name.");
214:
215: } else {
216: ObjectHelpers::strictSet($class, $name);
217: }
218: }
219:
220:
221: 222: 223: 224: 225: 226: 227:
228: public static function remove($_this, $name)
229: {
230: trigger_error('Class Nette\Utils\ObjectMixin is deprecated', E_USER_DEPRECATED);
231: $class = get_class($_this);
232: if (!ObjectHelpers::hasProperty($class, $name)) {
233: throw new MemberAccessException("Cannot unset the property $class::\$$name.");
234: }
235: }
236:
237:
238: 239: 240: 241: 242: 243:
244: public static function has($_this, $name)
245: {
246: trigger_error('Class Nette\Utils\ObjectMixin is deprecated', E_USER_DEPRECATED);
247: $name = ucfirst($name);
248: $methods = &self::getMethods(get_class($_this));
249: return $name !== '' && (isset($methods['get' . $name]) || isset($methods['is' . $name]));
250: }
251:
252:
253:
254:
255:
256: 257: 258:
259: public static function getMagicProperties($class)
260: {
261: trigger_error('Class Nette\Utils\ObjectMixin is deprecated', E_USER_DEPRECATED);
262: return ObjectHelpers::getMagicProperties($class);
263: }
264:
265:
266:
267: public static function getMagicProperty($class, $name)
268: {
269: $props = ObjectHelpers::getMagicProperties($class);
270: return isset($props[$name]) ? $props[$name] : null;
271: }
272:
273:
274:
275:
276:
277: 278: 279: 280:
281: public static function getMagicMethods($class)
282: {
283: trigger_error('Class Nette\Utils\ObjectMixin is deprecated', E_USER_DEPRECATED);
284: $rc = new \ReflectionClass($class);
285: preg_match_all('~^
286: [ \t*]* @method [ \t]+
287: (?: [^\s(]+ [ \t]+ )?
288: (set|get|is|add) ([A-Z]\w*)
289: (?: ([ \t]* \() [ \t]* ([^)$\s]*) )?
290: ()~mx', (string) $rc->getDocComment(), $matches, PREG_SET_ORDER);
291:
292: $methods = [];
293: foreach ($matches as list(, $op, $prop, $bracket, $type)) {
294: if ($bracket !== '(') {
295: trigger_error("Bracket must be immediately after @method $op$prop() in class $class.", E_USER_WARNING);
296: }
297: $name = $op . $prop;
298: $prop = strtolower($prop[0]) . substr($prop, 1) . ($op === 'add' ? 's' : '');
299: if ($rc->hasProperty($prop) && ($rp = $rc->getProperty($prop)) && !$rp->isStatic()) {
300: $rp->setAccessible(true);
301: if ($op === 'get' || $op === 'is') {
302: $type = null;
303: $op = 'get';
304: } elseif (!$type && preg_match('#@var[ \t]+(\S+)' . ($op === 'add' ? '\[\]#' : '#'), (string) $rp->getDocComment(), $m)) {
305: $type = $m[1];
306: }
307: if ($rc->inNamespace() && preg_match('#^[A-Z]\w+(\[|\||\z)#', (string) $type)) {
308: $type = $rc->getNamespaceName() . '\\' . $type;
309: }
310: $methods[$name] = [$op, $rp, $type];
311: }
312: }
313: return $methods;
314: }
315:
316:
317: 318: 319: 320: 321:
322: public static function checkType(&$val, $type)
323: {
324: trigger_error('Class Nette\Utils\ObjectMixin is deprecated', E_USER_DEPRECATED);
325: if (strpos($type, '|') !== false) {
326: $found = null;
327: foreach (explode('|', $type) as $type) {
328: $tmp = $val;
329: if (self::checkType($tmp, $type)) {
330: if ($val === $tmp) {
331: return true;
332: }
333: $found[] = $tmp;
334: }
335: }
336: if ($found) {
337: $val = $found[0];
338: return true;
339: }
340: return false;
341:
342: } elseif (substr($type, -2) === '[]') {
343: if (!is_array($val)) {
344: return false;
345: }
346: $type = substr($type, 0, -2);
347: $res = [];
348: foreach ($val as $k => $v) {
349: if (!self::checkType($v, $type)) {
350: return false;
351: }
352: $res[$k] = $v;
353: }
354: $val = $res;
355: return true;
356: }
357:
358: switch (strtolower($type)) {
359: case null:
360: case 'mixed':
361: return true;
362: case 'bool':
363: case 'boolean':
364: return ($val === null || is_scalar($val)) && settype($val, 'bool');
365: case 'string':
366: return ($val === null || is_scalar($val) || (is_object($val) && method_exists($val, '__toString'))) && settype($val, 'string');
367: case 'int':
368: case 'integer':
369: return ($val === null || is_bool($val) || is_numeric($val)) && ((float) (int) $val === (float) $val) && settype($val, 'int');
370: case 'float':
371: return ($val === null || is_bool($val) || is_numeric($val)) && settype($val, 'float');
372: case 'scalar':
373: case 'array':
374: case 'object':
375: case 'callable':
376: case 'resource':
377: case 'null':
378: return call_user_func("is_$type", $val);
379: default:
380: return $val instanceof $type;
381: }
382: }
383:
384:
385:
386:
387:
388: 389: 390: 391: 392: 393: 394:
395: public static function setExtensionMethod($class, $name, $callback)
396: {
397: $name = strtolower($name);
398: self::$extMethods[$name][$class] = Callback::check($callback);
399: self::$extMethods[$name][''] = null;
400: }
401:
402:
403: 404: 405: 406: 407: 408:
409: public static function getExtensionMethod($class, $name)
410: {
411: $list = &self::$extMethods[strtolower($name)];
412: $cache = &$list[''][$class];
413: if (isset($cache)) {
414: return $cache;
415: }
416:
417: foreach ([$class] + class_parents($class) + class_implements($class) as $cl) {
418: if (isset($list[$cl])) {
419: return $cache = $list[$cl];
420: }
421: }
422: return $cache = false;
423: }
424:
425:
426: 427: 428: 429: 430:
431: public static function getExtensionMethods($class)
432: {
433: trigger_error('Class Nette\Utils\ObjectMixin is deprecated', E_USER_DEPRECATED);
434: $res = [];
435: foreach (array_keys(self::$extMethods) as $name) {
436: if ($cb = self::getExtensionMethod($class, $name)) {
437: $res[$name] = $cb;
438: }
439: }
440: return $res;
441: }
442:
443:
444:
445:
446:
447: 448: 449:
450: public static function getSuggestion(array $possibilities, $value)
451: {
452: return ObjectHelpers::getSuggestion($possibilities, $value);
453: }
454:
455:
456: 457: 458:
459: public static function hasProperty($class, $name)
460: {
461: trigger_error('Class Nette\Utils\ObjectMixin is deprecated', E_USER_DEPRECATED);
462: return ObjectHelpers::hasProperty($class, $name);
463: }
464:
465:
466: 467: 468: 469: 470:
471: public static function &getMethods($class)
472: {
473: static $cache;
474: if (!isset($cache[$class])) {
475: $cache[$class] = array_fill_keys(get_class_methods($class), 0) + @self::getMagicMethods($class);
476: if ($parent = get_parent_class($class)) {
477: $cache[$class] += self::getMethods($parent);
478: }
479: }
480: return $cache[$class];
481: }
482:
483:
484:
485: public static function getSource()
486: {
487: foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $item) {
488: if (isset($item['file']) && dirname($item['file']) !== __DIR__) {
489: return " in $item[file]:$item[line]";
490: }
491: }
492: }
493: }
494: