Source for file DibiObject.php

Documentation is available at DibiObject.php

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