diff --git a/src/Dibi/Connection.php b/src/Dibi/Connection.php index ba445d22..2bf9b903 100644 --- a/src/Dibi/Connection.php +++ b/src/Dibi/Connection.php @@ -664,7 +664,7 @@ class DibiConnection extends DibiObject */ public function __wakeup() { - throw new DibiNotSupportedException('You cannot serialize or unserialize ' . $this->getClass() . ' instances.'); + throw new DibiNotSupportedException('You cannot serialize or unserialize ' . get_class($this) . ' instances.'); } @@ -673,7 +673,15 @@ class DibiConnection extends DibiObject */ public function __sleep() { - throw new DibiNotSupportedException('You cannot serialize or unserialize ' . $this->getClass() . ' instances.'); + throw new DibiNotSupportedException('You cannot serialize or unserialize ' . get_class($this) . ' instances.'); + } + + + protected function onEvent($arg) + { + foreach ($this->onEvent ?: [] as $handler) { + call_user_func($handler, $arg); + } } } diff --git a/src/Dibi/Object.php b/src/Dibi/Object.php index eaa15e06..960b48ee 100644 --- a/src/Dibi/Object.php +++ b/src/Dibi/Object.php @@ -7,43 +7,7 @@ /** - * DibiObject is the ultimate ancestor of all instantiable classes. - * - * DibiObject is copy of Nette\Object from Nette Framework (https://nette.org). - * - * It defines some handful methods and enhances object core of PHP: - * - access to undeclared members throws exceptions - * - support for conventional properties with getters and setters - * - support for event raising functionality - * - ability to add new methods to class (extension methods) - * - * Properties is a syntactic sugar which allows access public getter and setter - * methods as normal object variables. A property is defined by a getter method - * and optional setter method (no setter method means read-only property). - * - * $val = $obj->label; // equivalent to $val = $obj->getLabel(); - * $obj->label = 'Nette'; // equivalent to $obj->setLabel('Nette'); - * - * Property names are case-sensitive, and they are written in the camelCaps - * or PascalCaps. - * - * Event functionality is provided by declaration of property named 'on{Something}' - * Multiple handlers are allowed. - * - * public $onClick; // declaration in class - * $this->onClick[] = 'callback'; // attaching event handler - * if (!empty($this->onClick)) ... // are there any handlers? - * $this->onClick($sender, $arg); // raises the event with arguments - * - * - * Adding method to class (i.e. to all instances) works similar to JavaScript - * prototype property. The syntax for adding a new method is: - * - * MyClass::extensionMethod('newMethod', function (MyClass $obj, $arg, ...) { ... }); - * $obj = new MyClass; - * $obj->newMethod($x); - * - * + * Object is the ultimate ancestor of all instantiable classes. * @package dibi */ abstract class DibiObject @@ -52,76 +16,24 @@ abstract class DibiObject private static $extMethods; - /** - * Returns the name of the class of this object. - * @return string - */ - final public /*static*/ function getClass() - { - return /*get_called_class()*/ /**/get_class($this)/**/; - } - - - /** - * Access to reflection. - * @return \ReflectionObject - */ - final public function getReflection() - { - return new ReflectionObject($this); - } - - /** * Call to undefined method. - * @param string method name - * @param array arguments - * @return mixed - * @throws \LogicException + * @throws LogicException */ public function __call($name, $args) { - $class = get_class($this); - - if ($name === '') { - throw new LogicException("Call to class '$class' method without name."); - } - - // event functionality - if (preg_match('#^on[A-Z]#', $name)) { - $rp = new ReflectionProperty($class, $name); - if ($rp->isPublic() && !$rp->isStatic()) { - $list = $this->$name; - if (is_array($list) || $list instanceof Traversable) { - foreach ($list as $handler) { - /**/if (is_object($handler)) { - call_user_func_array([$handler, '__invoke'], $args); - - } else /**/{ - call_user_func_array($handler, $args); - } - } - } - return NULL; - } - } - - // extension methods - if ($cb = self::extensionMethod("$class::$name")) { + if ($cb = self::extensionMethod(get_class($this) . '::' . $name)) { // back compatiblity array_unshift($args, $this); return call_user_func_array($cb, $args); } - + $class = method_exists($this, $name) ? 'parent' : get_class($this); throw new LogicException("Call to undefined method $class::$name()."); } /** * Call to undefined static method. - * @param string method name (in lower case!) - * @param array arguments - * @return mixed - * @throws \LogicException + * @throws LogicException */ public static function __callStatic($name, $args) { @@ -131,177 +43,84 @@ abstract class DibiObject /** - * Adding method to class. - * @param string method name - * @param mixed callback or closure - * @return mixed + * Access to undeclared property. + * @throws LogicException */ - public static function extensionMethod($name, $callback = NULL) + public function &__get($name) { - if (self::$extMethods === NULL || $name === NULL) { // for backwards compatibility - $list = get_defined_functions(); - foreach ($list['user'] as $fce) { - $pair = explode('_prototype_', $fce); - if (count($pair) === 2) { - self::$extMethods[$pair[1]][$pair[0]] = $fce; - self::$extMethods[$pair[1]][''] = NULL; - } - } - if ($name === NULL) { - return NULL; - } + if ((method_exists($this, $m = 'get' . $name) || method_exists($this, $m = 'is' . $name)) + && (new ReflectionMethod($this, $m))->isPublic() + ) { // back compatiblity + $ret = $this->$m(); + return $ret; } - - $name = strtolower($name); - $a = strrpos($name, ':'); // search :: - if ($a === FALSE) { - $class = strtolower(get_called_class()); - $l = & self::$extMethods[$name]; - } else { - $class = substr($name, 0, $a - 1); - $l = & self::$extMethods[substr($name, $a + 1)]; - } - - if ($callback !== NULL) { // works as setter - $l[$class] = $callback; - $l[''] = NULL; - return NULL; - } - - // works as getter - if (empty($l)) { - return FALSE; - - } elseif (isset($l[''][$class])) { // cached value - return $l[''][$class]; - } - $cl = $class; - do { - $cl = strtolower($cl); - if (isset($l[$cl])) { - return $l[''][$class] = $l[$cl]; - } - } while (($cl = get_parent_class($cl)) !== FALSE); - - foreach (class_implements($class) as $cl) { - $cl = strtolower($cl); - if (isset($l[$cl])) { - return $l[''][$class] = $l[$cl]; - } - } - return $l[''][$class] = FALSE; - } - - - /** - * Returns property value. Do not call directly. - * @param string property name - * @return mixed property value - * @throws \LogicException if the property is not defined. - */ - public function & __get($name) - { $class = get_class($this); - - if ($name === '') { - throw new LogicException("Cannot read a class '$class' property without name."); - } - - // property getter support - $uname = ucfirst($name); - $m = 'get' . $uname; - if (self::hasAccessor($class, $m)) { - // ampersands: - // - uses & __get() because declaration should be forward compatible (e.g. with Nette\Web\Html) - // - doesn't call & $this->$m because user could bypass property setter by: $x = & $obj->property; $x = 'new value'; - $val = $this->$m(); - return $val; - } - - $m = 'is' . $uname; - if (self::hasAccessor($class, $m)) { - $val = $this->$m(); - return $val; - } - - throw new LogicException("Cannot read an undeclared property $class::\$$name."); - } - - - /** - * Sets value of a property. Do not call directly. - * @param string property name - * @param mixed property value - * @return void - * @throws \LogicException if the property is not defined or is read-only - */ - public function __set($name, $value) - { - $class = get_class($this); - - if ($name === '') { - throw new LogicException("Cannot assign to a class '$class' property without name."); - } - - // property setter support - $uname = ucfirst($name); - if (self::hasAccessor($class, 'get' . $uname) || self::hasAccessor($class, 'is' . $uname)) { - $m = 'set' . $name; - if (self::hasAccessor($class, $m)) { - $this->$m($value); - return; - - } else { - throw new LogicException("Cannot assign to a read-only property $class::\$$name."); - } - } - - throw new LogicException("Cannot assign to an undeclared property $class::\$$name."); - } - - - /** - * Is property defined? - * @param string property name - * @return bool - */ - public function __isset($name) - { - return $name !== '' && self::hasAccessor(get_class($this), 'get' . ucfirst($name)); + throw new LogicException("Attempt to read undeclared property $class::$$name."); } /** * Access to undeclared property. - * @param string property name - * @return void - * @throws \LogicException + * @throws LogicException */ - public function __unset($name) + public function __set($name, $value) { $class = get_class($this); - throw new LogicException("Cannot unset the property $class::\$$name."); + throw new LogicException("Attempt to write to undeclared property $class::$$name."); } /** - * Has property an accessor? - * @param string class name - * @param string method name * @return bool */ - private static function hasAccessor($c, $m) + public function __isset($name) { - static $cache; - if (!isset($cache[$c])) { - // get_class_methods returns private, protected and public methods of Object (doesn't matter) - // and ONLY PUBLIC methods of descendants (perfect!) - // but returns static methods too (nothing doing...) - // and is much faster than reflection - // (works good since 5.0.4) - $cache[$c] = array_flip(get_class_methods($c)); + return FALSE; + } + + + /** + * Access to undeclared property. + * @throws LogicException + */ + public function __unset($name) + { + $class = get_class($this); + throw new LogicException("Attempt to unset undeclared property $class::$$name."); + } + + + /** + * @param string method name + * @param callabke + * @return mixed + */ + public static function extensionMethod($name, $callback = NULL) + { + if (strpos($name, '::') === FALSE) { + $class = get_called_class(); + } else { + list($class, $name) = explode('::', $name); + $class = (new \ReflectionClass($class))->getName(); + } + $list = & self::$extMethods[strtolower($name)]; + if ($callback === NULL) { // getter + $cache = & $list[''][$class]; + if (isset($cache)) { + return $cache; + } + + foreach ([$class] + class_parents($class) + class_implements($class) as $cl) { + if (isset($list[$cl])) { + return $cache = $list[$cl]; + } + } + return $cache = FALSE; + + } else { // setter + $list[$class] = $callback; + $list[''] = NULL; } - return isset($cache[$c][$m]); } } diff --git a/tests/dibi/Object.phpt b/tests/dibi/Object.phpt index 58da69a1..ef30319c 100644 --- a/tests/dibi/Object.phpt +++ b/tests/dibi/Object.phpt @@ -7,6 +7,11 @@ require __DIR__ . '/bootstrap.php'; class TestClass extends DibiObject { + public function callParent() + { + parent::callParent(); + } + public function getBar() { return 123; @@ -29,17 +34,22 @@ Assert::exception(function () { TestClass::undeclared(); }, 'LogicException', 'Call to undefined static method TestClass::undeclared().'); +Assert::exception(function () { + $obj = new TestClass; + $obj->callParent(); +}, 'LogicException', 'Call to undefined method parent::callParent().'); + // writing Assert::exception(function () { $obj = new TestClass; $obj->undeclared = 'value'; -}, 'LogicException', 'Cannot assign to an undeclared property TestClass::$undeclared.'); +}, 'LogicException', 'Attempt to write to undeclared property TestClass::$undeclared.'); // property getter $obj = new TestClass; -Assert::true(isset($obj->bar)); +Assert::false(isset($obj->bar)); Assert::same(123, $obj->bar); Assert::false(isset($obj->foo)); Assert::same(456, $obj->foo); @@ -49,14 +59,14 @@ Assert::same(456, $obj->foo); Assert::exception(function () { $obj = new TestClass; $val = $obj->undeclared; -}, 'LogicException', 'Cannot read an undeclared property TestClass::$undeclared.'); +}, 'LogicException', 'Attempt to read undeclared property TestClass::$undeclared.'); // unset/isset Assert::exception(function () { $obj = new TestClass; unset($obj->undeclared); -}, 'LogicException', 'Cannot unset the property TestClass::$undeclared.'); +}, 'LogicException', 'Attempt to unset undeclared property TestClass::$undeclared.'); Assert::false(isset($obj->undeclared));