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: final class ObjectHelpers
18: {
19: use Nette\StaticClass;
20:
21: 22: 23:
24: public static function strictGet($class, $name)
25: {
26: $rc = new \ReflectionClass($class);
27: $hint = self::getSuggestion(array_merge(
28: array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }),
29: self::parseFullDoc($rc, '~^[ \t*]*@property(?:-read)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m')
30: ), $name);
31: throw new MemberAccessException("Cannot read an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.'));
32: }
33:
34:
35: 36: 37:
38: public static function strictSet($class, $name)
39: {
40: $rc = new \ReflectionClass($class);
41: $hint = self::getSuggestion(array_merge(
42: array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }),
43: self::parseFullDoc($rc, '~^[ \t*]*@property(?:-write)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m')
44: ), $name);
45: throw new MemberAccessException("Cannot write to an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.'));
46: }
47:
48:
49: 50: 51:
52: public static function strictCall($class, $method, $additionalMethods = [])
53: {
54: $hint = self::getSuggestion(array_merge(
55: get_class_methods($class),
56: self::parseFullDoc(new \ReflectionClass($class), '~^[ \t*]*@method[ \t]+(?:\S+[ \t]+)??(\w+)\(~m'),
57: $additionalMethods
58: ), $method);
59:
60: if (method_exists($class, $method)) {
61: $class = 'parent';
62: }
63: throw new MemberAccessException("Call to undefined method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
64: }
65:
66:
67: 68: 69:
70: public static function strictStaticCall($class, $method)
71: {
72: $hint = self::getSuggestion(
73: array_filter((new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC), function ($m) { return $m->isStatic(); }),
74: $method
75: );
76: throw new MemberAccessException("Call to undefined static method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.'));
77: }
78:
79:
80: 81: 82: 83: 84:
85: public static function getMagicProperties($class)
86: {
87: static $cache;
88: $props = &$cache[$class];
89: if ($props !== null) {
90: return $props;
91: }
92:
93: $rc = new \ReflectionClass($class);
94: preg_match_all(
95: '~^ [ \t*]* @property(|-read|-write) [ \t]+ [^\s$]+ [ \t]+ \$ (\w+) ()~mx',
96: (string) $rc->getDocComment(), $matches, PREG_SET_ORDER
97: );
98:
99: $props = [];
100: foreach ($matches as list(, $type, $name)) {
101: $uname = ucfirst($name);
102: $write = $type !== '-read'
103: && $rc->hasMethod($nm = 'set' . $uname)
104: && ($rm = $rc->getMethod($nm)) && $rm->getName() === $nm && !$rm->isPrivate() && !$rm->isStatic();
105: $read = $type !== '-write'
106: && ($rc->hasMethod($nm = 'get' . $uname) || $rc->hasMethod($nm = 'is' . $uname))
107: && ($rm = $rc->getMethod($nm)) && $rm->getName() === $nm && !$rm->isPrivate() && !$rm->isStatic();
108:
109: if ($read || $write) {
110: $props[$name] = $read << 0 | ($nm[0] === 'g') << 1 | $rm->returnsReference() << 2 | $write << 3;
111: }
112: }
113:
114: foreach ($rc->getTraits() as $trait) {
115: $props += self::getMagicProperties($trait->getName());
116: }
117:
118: if ($parent = get_parent_class($class)) {
119: $props += self::getMagicProperties($parent);
120: }
121: return $props;
122: }
123:
124:
125: 126: 127: 128: 129:
130: public static function getSuggestion(array $possibilities, $value)
131: {
132: $norm = preg_replace($re = '#^(get|set|has|is|add)(?=[A-Z])#', '', $value);
133: $best = null;
134: $min = (strlen($value) / 4 + 1) * 10 + .1;
135: foreach (array_unique($possibilities, SORT_REGULAR) as $item) {
136: $item = $item instanceof \Reflector ? $item->getName() : $item;
137: if ($item !== $value && (
138: ($len = levenshtein($item, $value, 10, 11, 10)) < $min
139: || ($len = levenshtein(preg_replace($re, '', $item), $norm, 10, 11, 10) + 20) < $min
140: )) {
141: $min = $len;
142: $best = $item;
143: }
144: }
145: return $best;
146: }
147:
148:
149: private static function parseFullDoc(\ReflectionClass $rc, $pattern)
150: {
151: do {
152: $doc[] = $rc->getDocComment();
153: $traits = $rc->getTraits();
154: while ($trait = array_pop($traits)) {
155: $doc[] = $trait->getDocComment();
156: $traits += $trait->getTraits();
157: }
158: } while ($rc = $rc->getParentClass());
159: return preg_match_all($pattern, implode($doc), $m) ? $m[1] : [];
160: }
161:
162:
163: 164: 165: 166: 167:
168: public static function hasProperty($class, $name)
169: {
170: static $cache;
171: $prop = &$cache[$class][$name];
172: if ($prop === null) {
173: $prop = false;
174: try {
175: $rp = new \ReflectionProperty($class, $name);
176: if ($rp->isPublic() && !$rp->isStatic()) {
177: $prop = $name >= 'onA' && $name < 'on_' ? 'event' : true;
178: }
179: } catch (\ReflectionException $e) {
180: }
181: }
182: return $prop;
183: }
184: }
185: