1: <?php
2:
3: /**
4: * This file is part of the "dibi" - smart database abstraction layer.
5: *
6: * Copyright (c) 2005, 2010 David Grudl (http://davidgrudl.com)
7: *
8: * This source file is subject to the "dibi license", and/or
9: * GPL license. For more information please see http://dibiphp.com
10: * @package dibi
11: */
12:
13:
14:
15: /**
16: * DibiObject is the ultimate ancestor of all instantiable classes.
17: *
18: * DibiObject is copy of Nette\Object from Nette Framework (http://nette.org).
19: *
20: * It defines some handful methods and enhances object core of PHP:
21: * - access to undeclared members throws exceptions
22: * - support for conventional properties with getters and setters
23: * - support for event raising functionality
24: * - ability to add new methods to class (extension methods)
25: *
26: * Properties is a syntactic sugar which allows access public getter and setter
27: * methods as normal object variables. A property is defined by a getter method
28: * and optional setter method (no setter method means read-only property).
29: * <code>
30: * $val = $obj->label; // equivalent to $val = $obj->getLabel();
31: * $obj->label = 'Nette'; // equivalent to $obj->setLabel('Nette');
32: * </code>
33: * Property names are case-sensitive, and they are written in the camelCaps
34: * or PascalCaps.
35: *
36: * Event functionality is provided by declaration of property named 'on{Something}'
37: * Multiple handlers are allowed.
38: * <code>
39: * public $onClick; // declaration in class
40: * $this->onClick[] = 'callback'; // attaching event handler
41: * if (!empty($this->onClick)) ... // are there any handlers?
42: * $this->onClick($sender, $arg); // raises the event with arguments
43: * </code>
44: *
45: * Adding method to class (i.e. to all instances) works similar to JavaScript
46: * prototype property. The syntax for adding a new method is:
47: * <code>
48: * MyClass::extensionMethod('newMethod', function(MyClass $obj, $arg, ...) { ... });
49: * $obj = new MyClass;
50: * $obj->newMethod($x);
51: * </code>
52: *
53: * @author David Grudl
54: */
55: abstract class DibiObject
56: {
57: /** @var array (method => array(type => callback)) */
58: private static $extMethods;
59:
60:
61:
62: /**
63: * Returns the name of the class of this object.
64: * @return string
65: */
66: final public /*static*/ function getClass()
67: {
68: return /*get_called_class()*/ /**/get_class($this)/**/;
69: }
70:
71:
72:
73: /**
74: * Access to reflection.
75: * @return \ReflectionObject
76: */
77: final public function getReflection()
78: {
79: return new ReflectionObject($this);
80: }
81:
82:
83:
84: /**
85: * Call to undefined method.
86: * @param string method name
87: * @param array arguments
88: * @return mixed
89: * @throws \MemberAccessException
90: */
91: public function __call($name, $args)
92: {
93: $class = get_class($this);
94:
95: if ($name === '') {
96: throw new MemberAccessException("Call to class '$class' method without name.");
97: }
98:
99: // event functionality
100: if (preg_match('#^on[A-Z]#', $name)) {
101: $rp = new ReflectionProperty($class, $name);
102: if ($rp->isPublic() && !$rp->isStatic()) {
103: $list = $this->$name;
104: if (is_array($list) || $list instanceof Traversable) {
105: foreach ($list as $handler) {
106: /**/if (is_object($handler)) {
107: call_user_func_array(array($handler, '__invoke'), $args);
108:
109: } else /**/{
110: call_user_func_array($handler, $args);
111: }
112: }
113: }
114: return NULL;
115: }
116: }
117:
118: // extension methods
119: if ($cb = self::extensionMethod("$class::$name")) {
120: array_unshift($args, $this);
121: return call_user_func_array($cb, $args);
122: }
123:
124: throw new MemberAccessException("Call to undefined method $class::$name().");
125: }
126:
127:
128:
129: /**
130: * Call to undefined static method.
131: * @param string method name (in lower case!)
132: * @param array arguments
133: * @return mixed
134: * @throws \MemberAccessException
135: */
136: public static function __callStatic($name, $args)
137: {
138: $class = get_called_class();
139: throw new MemberAccessException("Call to undefined static method $class::$name().");
140: }
141:
142:
143:
144: /**
145: * Adding method to class.
146: * @param string method name
147: * @param mixed callback or closure
148: * @return mixed
149: */
150: public static function extensionMethod($name, $callback = NULL)
151: {
152: if (self::$extMethods === NULL || $name === NULL) { // for backwards compatibility
153: $list = get_defined_functions();
154: foreach ($list['user'] as $fce) {
155: $pair = explode('_prototype_', $fce);
156: if (count($pair) === 2) {
157: self::$extMethods[$pair[1]][$pair[0]] = $fce;
158: self::$extMethods[$pair[1]][''] = NULL;
159: }
160: }
161: if ($name === NULL) return NULL;
162: }
163:
164: $name = strtolower($name);
165: $a = strrpos($name, ':'); // search ::
166: if ($a === FALSE) {
167: $class = strtolower(get_called_class());
168: $l = & self::$extMethods[$name];
169: } else {
170: $class = substr($name, 0, $a - 1);
171: $l = & self::$extMethods[substr($name, $a + 1)];
172: }
173:
174: if ($callback !== NULL) { // works as setter
175: $l[$class] = $callback;
176: $l[''] = NULL;
177: return NULL;
178: }
179:
180: // works as getter
181: if (empty($l)) {
182: return FALSE;
183:
184: } elseif (isset($l[''][$class])) { // cached value
185: return $l[''][$class];
186: }
187: $cl = $class;
188: do {
189: $cl = strtolower($cl);
190: if (isset($l[$cl])) {
191: return $l[''][$class] = $l[$cl];
192: }
193: } while (($cl = get_parent_class($cl)) !== FALSE);
194:
195: foreach (class_implements($class) as $cl) {
196: $cl = strtolower($cl);
197: if (isset($l[$cl])) {
198: return $l[''][$class] = $l[$cl];
199: }
200: }
201: return $l[''][$class] = FALSE;
202: }
203:
204:
205:
206: /**
207: * Returns property value. Do not call directly.
208: * @param string property name
209: * @return mixed property value
210: * @throws \MemberAccessException if the property is not defined.
211: */
212: public function &__get($name)
213: {
214: $class = get_class($this);
215:
216: if ($name === '') {
217: throw new MemberAccessException("Cannot read a class '$class' property without name.");
218: }
219:
220: // property getter support
221: $name[0] = $name[0] & "\xDF"; // case-sensitive checking, capitalize first character
222: $m = 'get' . $name;
223: if (self::hasAccessor($class, $m)) {
224: // ampersands:
225: // - uses &__get() because declaration should be forward compatible (e.g. with Nette\Web\Html)
226: // - doesn't call &$this->$m because user could bypass property setter by: $x = & $obj->property; $x = 'new value';
227: $val = $this->$m();
228: return $val;
229: }
230:
231: $m = 'is' . $name;
232: if (self::hasAccessor($class, $m)) {
233: $val = $this->$m();
234: return $val;
235: }
236:
237: $name = func_get_arg(0);
238: throw new MemberAccessException("Cannot read an undeclared property $class::\$$name.");
239: }
240:
241:
242:
243: /**
244: * Sets value of a property. Do not call directly.
245: * @param string property name
246: * @param mixed property value
247: * @return void
248: * @throws \MemberAccessException if the property is not defined or is read-only
249: */
250: public function __set($name, $value)
251: {
252: $class = get_class($this);
253:
254: if ($name === '') {
255: throw new MemberAccessException("Cannot assign to a class '$class' property without name.");
256: }
257:
258: // property setter support
259: $name[0] = $name[0] & "\xDF"; // case-sensitive checking, capitalize first character
260: if (self::hasAccessor($class, 'get' . $name) || self::hasAccessor($class, 'is' . $name)) {
261: $m = 'set' . $name;
262: if (self::hasAccessor($class, $m)) {
263: $this->$m($value);
264: return;
265:
266: } else {
267: $name = func_get_arg(0);
268: throw new MemberAccessException("Cannot assign to a read-only property $class::\$$name.");
269: }
270: }
271:
272: $name = func_get_arg(0);
273: throw new MemberAccessException("Cannot assign to an undeclared property $class::\$$name.");
274: }
275:
276:
277:
278: /**
279: * Is property defined?
280: * @param string property name
281: * @return bool
282: */
283: public function __isset($name)
284: {
285: $name[0] = $name[0] & "\xDF";
286: return $name !== '' && self::hasAccessor(get_class($this), 'get' . $name);
287: }
288:
289:
290:
291: /**
292: * Access to undeclared property.
293: * @param string property name
294: * @return void
295: * @throws \MemberAccessException
296: */
297: public function __unset($name)
298: {
299: $class = get_class($this);
300: throw new MemberAccessException("Cannot unset the property $class::\$$name.");
301: }
302:
303:
304:
305: /**
306: * Has property an accessor?
307: * @param string class name
308: * @param string method name
309: * @return bool
310: */
311: private static function hasAccessor($c, $m)
312: {
313: static $cache;
314: if (!isset($cache[$c])) {
315: // get_class_methods returns private, protected and public methods of Object (doesn't matter)
316: // and ONLY PUBLIC methods of descendants (perfect!)
317: // but returns static methods too (nothing doing...)
318: // and is much faster than reflection
319: // (works good since 5.0.4)
320: $cache[$c] = array_flip(get_class_methods($c));
321: }
322: return isset($cache[$c][$m]);
323: }
324:
325: }
326: