1
0
mirror of https://github.com/dg/dibi.git synced 2025-08-11 08:34:59 +02:00

DibiObject: simplified to minimum (BC break)

Removed support for setters, onEvent(), getClass() & getReflection().
Retained support for getters and extension methods.
This commit is contained in:
David Grudl
2015-10-06 12:44:37 +02:00
parent ae68965710
commit 76396ab250
3 changed files with 87 additions and 250 deletions

View File

@@ -664,7 +664,7 @@ class DibiConnection extends DibiObject
*/ */
public function __wakeup() 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() 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);
}
} }
} }

View File

@@ -7,43 +7,7 @@
/** /**
* DibiObject is the ultimate ancestor of all instantiable classes. * Object 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).
* <code>
* $val = $obj->label; // equivalent to $val = $obj->getLabel();
* $obj->label = 'Nette'; // equivalent to $obj->setLabel('Nette');
* </code>
* 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.
* <code>
* 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
* </code>
*
* Adding method to class (i.e. to all instances) works similar to JavaScript
* prototype property. The syntax for adding a new method is:
* <code>
* MyClass::extensionMethod('newMethod', function (MyClass $obj, $arg, ...) { ... });
* $obj = new MyClass;
* $obj->newMethod($x);
* </code>
*
* @package dibi * @package dibi
*/ */
abstract class DibiObject abstract class DibiObject
@@ -52,76 +16,24 @@ abstract class DibiObject
private static $extMethods; 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. * Call to undefined method.
* @param string method name * @throws LogicException
* @param array arguments
* @return mixed
* @throws \LogicException
*/ */
public function __call($name, $args) public function __call($name, $args)
{ {
$class = get_class($this); if ($cb = self::extensionMethod(get_class($this) . '::' . $name)) { // back compatiblity
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")) {
array_unshift($args, $this); array_unshift($args, $this);
return call_user_func_array($cb, $args); 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()."); throw new LogicException("Call to undefined method $class::$name().");
} }
/** /**
* Call to undefined static method. * Call to undefined static method.
* @param string method name (in lower case!) * @throws LogicException
* @param array arguments
* @return mixed
* @throws \LogicException
*/ */
public static function __callStatic($name, $args) public static function __callStatic($name, $args)
{ {
@@ -131,177 +43,84 @@ abstract class DibiObject
/** /**
* Adding method to class. * Access to undeclared property.
* @param string method name * @throws LogicException
* @param mixed callback or closure
* @return mixed
*/ */
public static function extensionMethod($name, $callback = NULL) public function &__get($name)
{ {
if (self::$extMethods === NULL || $name === NULL) { // for backwards compatibility if ((method_exists($this, $m = 'get' . $name) || method_exists($this, $m = 'is' . $name))
$list = get_defined_functions(); && (new ReflectionMethod($this, $m))->isPublic()
foreach ($list['user'] as $fce) { ) { // back compatiblity
$pair = explode('_prototype_', $fce); $ret = $this->$m();
if (count($pair) === 2) { return $ret;
self::$extMethods[$pair[1]][$pair[0]] = $fce;
self::$extMethods[$pair[1]][''] = NULL;
}
}
if ($name === NULL) {
return NULL;
}
} }
$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); $class = get_class($this);
throw new LogicException("Attempt to read undeclared property $class::$$name.");
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));
} }
/** /**
* Access to undeclared property. * Access to undeclared property.
* @param string property name * @throws LogicException
* @return void
* @throws \LogicException
*/ */
public function __unset($name) public function __set($name, $value)
{ {
$class = get_class($this); $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 * @return bool
*/ */
private static function hasAccessor($c, $m) public function __isset($name)
{ {
static $cache; return FALSE;
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 * Access to undeclared property.
// (works good since 5.0.4) * @throws LogicException
$cache[$c] = array_flip(get_class_methods($c)); */
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]);
} }
} }

View File

@@ -7,6 +7,11 @@ require __DIR__ . '/bootstrap.php';
class TestClass extends DibiObject class TestClass extends DibiObject
{ {
public function callParent()
{
parent::callParent();
}
public function getBar() public function getBar()
{ {
return 123; return 123;
@@ -29,17 +34,22 @@ Assert::exception(function () {
TestClass::undeclared(); TestClass::undeclared();
}, 'LogicException', 'Call to undefined static method 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 // writing
Assert::exception(function () { Assert::exception(function () {
$obj = new TestClass; $obj = new TestClass;
$obj->undeclared = 'value'; $obj->undeclared = 'value';
}, 'LogicException', 'Cannot assign to an undeclared property TestClass::$undeclared.'); }, 'LogicException', 'Attempt to write to undeclared property TestClass::$undeclared.');
// property getter // property getter
$obj = new TestClass; $obj = new TestClass;
Assert::true(isset($obj->bar)); Assert::false(isset($obj->bar));
Assert::same(123, $obj->bar); Assert::same(123, $obj->bar);
Assert::false(isset($obj->foo)); Assert::false(isset($obj->foo));
Assert::same(456, $obj->foo); Assert::same(456, $obj->foo);
@@ -49,14 +59,14 @@ Assert::same(456, $obj->foo);
Assert::exception(function () { Assert::exception(function () {
$obj = new TestClass; $obj = new TestClass;
$val = $obj->undeclared; $val = $obj->undeclared;
}, 'LogicException', 'Cannot read an undeclared property TestClass::$undeclared.'); }, 'LogicException', 'Attempt to read undeclared property TestClass::$undeclared.');
// unset/isset // unset/isset
Assert::exception(function () { Assert::exception(function () {
$obj = new TestClass; $obj = new TestClass;
unset($obj->undeclared); unset($obj->undeclared);
}, 'LogicException', 'Cannot unset the property TestClass::$undeclared.'); }, 'LogicException', 'Attempt to unset undeclared property TestClass::$undeclared.');
Assert::false(isset($obj->undeclared)); Assert::false(isset($obj->undeclared));