1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette;
9:
10: use Nette\Utils\Callback;
11: use Nette\Utils\ObjectHelpers;
12: use Nette\Utils\ObjectMixin;
13:
14:
15: 16: 17: 18: 19: 20: 21: 22:
23: trait SmartObject
24: {
25:
26: 27: 28: 29:
30: public function __call($name, $args)
31: {
32: $class = get_class($this);
33: $isProp = ObjectHelpers::hasProperty($class, $name);
34:
35: if ($name === '') {
36: throw new MemberAccessException("Call to class '$class' method without name.");
37:
38: } elseif ($isProp === 'event') {
39: if (is_array($this->$name) || $this->$name instanceof \Traversable) {
40: foreach ($this->$name as $handler) {
41: Callback::invokeArgs($handler, $args);
42: }
43: } elseif ($this->$name !== null) {
44: throw new UnexpectedValueException("Property $class::$$name must be array or null, " . gettype($this->$name) . ' given.');
45: }
46:
47: } elseif ($isProp && $this->$name instanceof \Closure) {
48: trigger_error("Invoking closure in property via \$obj->$name() is deprecated" . ObjectMixin::getSource(), E_USER_DEPRECATED);
49: return call_user_func_array($this->$name, $args);
50:
51: } elseif (($methods = &ObjectMixin::getMethods($class)) && isset($methods[$name]) && is_array($methods[$name])) {
52: trigger_error("Magic methods such as $class::$name() are deprecated" . ObjectMixin::getSource(), E_USER_DEPRECATED);
53: list($op, $rp, $type) = $methods[$name];
54: if (count($args) !== ($op === 'get' ? 0 : 1)) {
55: throw new InvalidArgumentException("$class::$name() expects " . ($op === 'get' ? 'no' : '1') . ' argument, ' . count($args) . ' given.');
56:
57: } elseif ($type && $args && !ObjectMixin::checkType($args[0], $type)) {
58: throw new InvalidArgumentException("Argument passed to $class::$name() must be $type, " . gettype($args[0]) . ' given.');
59: }
60:
61: if ($op === 'get') {
62: return $rp->getValue($this);
63: } elseif ($op === 'set') {
64: $rp->setValue($this, $args[0]);
65: } elseif ($op === 'add') {
66: $val = $rp->getValue($this);
67: $val[] = $args[0];
68: $rp->setValue($this, $val);
69: }
70: return $this;
71:
72: } elseif ($cb = ObjectMixin::getExtensionMethod($class, $name)) {
73: trigger_error("Extension methods such as $class::$name() are deprecated" . ObjectMixin::getSource(), E_USER_DEPRECATED);
74: return Callback::invoke($cb, $this, ...$args);
75:
76: } else {
77: ObjectHelpers::strictCall($class, $name);
78: }
79: }
80:
81:
82: 83: 84: 85:
86: public static function __callStatic($name, $args)
87: {
88: ObjectHelpers::strictStaticCall(get_called_class(), $name);
89: }
90:
91:
92: 93: 94: 95:
96: public function &__get($name)
97: {
98: $class = get_class($this);
99: $uname = ucfirst($name);
100:
101: if ($prop = ObjectMixin::getMagicProperty($class, $name)) {
102: if (!($prop & 0b0001)) {
103: throw new MemberAccessException("Cannot read a write-only property $class::\$$name.");
104: }
105: $m = ($prop & 0b0010 ? 'get' : 'is') . $uname;
106: if ($prop & 0b0100) {
107: return $this->$m();
108: } else {
109: $val = $this->$m();
110: return $val;
111: }
112:
113: } elseif ($name === '') {
114: throw new MemberAccessException("Cannot read a class '$class' property without name.");
115:
116: } elseif (($methods = &ObjectMixin::getMethods($class)) && isset($methods[$m = 'get' . $uname]) || isset($methods[$m = 'is' . $uname])) {
117: trigger_error("Use $m() or add annotation @property for $class::\$$name" . ObjectMixin::getSource(), E_USER_DEPRECATED);
118: if ($methods[$m] === 0) {
119: $methods[$m] = (new \ReflectionMethod($class, $m))->returnsReference();
120: }
121: if ($methods[$m] === true) {
122: return $this->$m();
123: } else {
124: $val = $this->$m();
125: return $val;
126: }
127:
128: } elseif (isset($methods[$name])) {
129: trigger_error("Accessing methods as properties via \$obj->$name is deprecated, use PHP callback [\$obj, '$name']" . ObjectMixin::getSource(), E_USER_DEPRECATED);
130: $val = Callback::closure($this, $name);
131: return $val;
132:
133: } elseif (isset($methods['set' . $uname])) {
134: throw new MemberAccessException("Cannot read a write-only property $class::\$$name.");
135:
136: } else {
137: ObjectHelpers::strictGet($class, $name);
138: }
139: }
140:
141:
142: 143: 144: 145:
146: public function __set($name, $value)
147: {
148: $class = get_class($this);
149: $uname = ucfirst($name);
150:
151: if (ObjectHelpers::hasProperty($class, $name)) {
152: $this->$name = $value;
153:
154: } elseif ($prop = ObjectMixin::getMagicProperty($class, $name)) {
155: if (!($prop & 0b1000)) {
156: throw new MemberAccessException("Cannot write to a read-only property $class::\$$name.");
157: }
158: $this->{'set' . $name}($value);
159:
160: } elseif ($name === '') {
161: throw new MemberAccessException("Cannot write to a class '$class' property without name.");
162:
163: } elseif (($methods = &ObjectMixin::getMethods($class)) && isset($methods[$m = 'set' . $uname])) {
164: trigger_error("Use $m() or add annotation @property for $class::\$$name" . ObjectMixin::getSource(), E_USER_DEPRECATED);
165: $this->$m($value);
166:
167: } elseif (isset($methods['get' . $uname]) || isset($methods['is' . $uname])) {
168: throw new MemberAccessException("Cannot write to a read-only property $class::\$$name.");
169:
170: } else {
171: ObjectHelpers::strictSet($class, $name);
172: }
173: }
174:
175:
176: 177: 178: 179:
180: public function __unset($name)
181: {
182: $class = get_class($this);
183: if (!ObjectHelpers::hasProperty($class, $name)) {
184: throw new MemberAccessException("Cannot unset the property $class::\$$name.");
185: }
186: }
187:
188:
189: 190: 191:
192: public function __isset($name)
193: {
194: $uname = ucfirst($name);
195: return ObjectMixin::getMagicProperty(get_class($this), $name)
196: || ($name !== '' && ($methods = ObjectMixin::getMethods(get_class($this))) && (isset($methods['get' . $uname]) || isset($methods['is' . $uname])));
197: }
198:
199:
200: 201: 202: 203:
204: public static function getReflection()
205: {
206: trigger_error(get_called_class() . '::getReflection() is deprecated' . ObjectMixin::getSource(), E_USER_DEPRECATED);
207: $class = class_exists(Reflection\ClassType::class) ? Reflection\ClassType::class : \ReflectionClass::class;
208: return new $class(get_called_class());
209: }
210:
211:
212: 213: 214: 215:
216: public static function extensionMethod($name, $callback = null)
217: {
218: if (strpos($name, '::') === false) {
219: $class = get_called_class();
220: } else {
221: list($class, $name) = explode('::', $name);
222: $class = (new \ReflectionClass($class))->getName();
223: }
224: trigger_error("Extension methods such as $class::$name() are deprecated" . ObjectMixin::getSource(), E_USER_DEPRECATED);
225: if ($callback === null) {
226: return ObjectMixin::getExtensionMethod($class, $name);
227: } else {
228: ObjectMixin::setExtensionMethod($class, $name, $callback);
229: }
230: }
231: }
232: