Skip to content

Instantly share code, notes, and snippets.

@dg
Created July 2, 2011 04:20
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dg/1059734 to your computer and use it in GitHub Desktop.
Save dg/1059734 to your computer and use it in GitHub Desktop.
Nette\ObjectTrait
<?php
/**
* This file is part of the Nette Framework (http://nette.org)
*
* Copyright (c) 2004, 2011 David Grudl (http://davidgrudl.com)
*
* For the full copyright and license information, please view
* the file license.txt that was distributed with this source code.
*/
namespace Nette;
use Nette;
/**
* Nette\Object is the common trait for all instantiable classes.
*
* 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
* or 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>
*
* @author David Grudl
*
* @property-read string $class
* @property-read Nette\Reflection\ClassType $reflection
*/
trait ObjectTrait
{
/** @var array */
private static $methods;
/**
* Access to reflection.
* @return Nette\Reflection\ClassType
*/
public /**/static/**/ function getReflection()
{
return new Reflection\ClassType(/*5.2*$this*//**/get_called_class()/**/);
}
/**
* Call to undefined method.
* @param string method name
* @param array arguments
* @return mixed
* @throws MemberAccessException
*/
public function __call($name, $args)
{
$class = new Reflection\ClassType($this);
if ($name === '') {
throw new MemberAccessException("Call to class '$class->name' method without name.");
}
// event functionality
if ($class->hasEventProperty($name)) {
if (is_array($list = $this->$name) || $list instanceof \Traversable) {
foreach ($list as $handler) {
callback($handler)->invokeArgs($args);
}
}
return NULL;
}
// extension methods
if ($cb = $class->getExtensionMethod($name)) {
array_unshift($args, $this);
return $cb->invokeArgs($args);
}
throw new MemberAccessException("Call to undefined method $class->name::$name().");
}
/**
* Call to undefined static method.
* @param string method name (in lower case!)
* @param array arguments
* @return mixed
* @throws MemberAccessException
*/
public static function __callStatic($name, $args)
{
throw new MemberAccessException("Call to undefined static method $class::$name().");
}
/**
* Adding method to class.
* @param string method name
* @param mixed callback or closure
* @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 Reflection\ClassType($class);
if ($callback === NULL) {
return $class->getExtensionMethod($name);
} else {
$class->setExtensionMethod($name, $callback);
}
}
/**
* Returns property value. Do not call directly.
* @param string property name
* @return mixed property value
* @throws MemberAccessException if the property is not defined.
*/
public function &__get($name)
{
$class = get_class($this);
if ($name === '') {
throw new MemberAccessException("Cannot read a class '$class' property without name.");
}
if (!isset(self::$methods[$class])) {
// get_class_methods returns ONLY PUBLIC methods of objects
// but returns static methods too (nothing doing...)
// and is much faster than reflection
// (works good since 5.0.4)
self::$methods[$class] = array_flip(get_class_methods($class));
}
// property getter support
$name[0] = $name[0] & "\xDF"; // case-sensitive checking, capitalize first character
$m = 'get' . $name;
if (isset(self::$methods[$class][$m])) {
// ampersands:
// - uses &__get() because declaration should be forward compatible (e.g. with Nette\Utils\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' . $name;
if (isset(self::$methods[$class][$m])) {
$val = $this->$m();
return $val;
}
$type = isset(self::$methods[$class]['set' . $name]) ? 'a write-only' : 'an undeclared';
$name = func_get_arg(1);
throw new MemberAccessException("Cannot read $type property $class::\$$name.");
}
/**
* Sets value of a property. Do not call directly.
* @param string property name
* @param mixed property value
* @return void
* @throws MemberAccessException if the property is not defined or is read-only
*/
public function __set($name, $value)
{
$class = get_class($this);
if ($name === '') {
throw new MemberAccessException("Cannot write to a class '$class' property without name.");
}
if (!isset(self::$methods[$class])) {
self::$methods[$class] = array_flip(get_class_methods($class));
}
// property setter support
$name[0] = $name[0] & "\xDF"; // case-sensitive checking, capitalize first character
$m = 'set' . $name;
if (isset(self::$methods[$class][$m])) {
$this->$m($value);
return;
}
$type = isset(self::$methods[$class]['get' . $name]) || isset(self::$methods[$class]['is' . $name])
? 'a read-only' : 'an undeclared';
$name = func_get_arg(1);
throw new MemberAccessException("Cannot write to $type property $class::\$$name.");
}
/**
* Is property defined?
* @param string property name
* @return bool
*/
public function __isset($name)
{
if ($name === '') {
return FALSE;
}
$class = get_class($this);
if (!isset(self::$methods[$class])) {
self::$methods[$class] = array_flip(get_class_methods($class));
}
$name[0] = $name[0] & "\xDF";
return isset(self::$methods[$class]['get' . $name]) || isset(self::$methods[$class]['is' . $name]);
}
/**
* Access to undeclared property.
* @param string property name
* @return void
* @throws MemberAccessException
*/
public function __unset($name)
{
$class = get_class($this);
throw new MemberAccessException("Cannot unset the property $class::\$$name.");
}
}
@dg
Copy link
Author

dg commented Jul 2, 2011

Usage:

<?php

require 'ObjectTrait.php';

class Circle
{
    use Nette\ObjectTrait;

    private $radius;

    public function getRadius()
    {
        return $this->radius;
    }

    public function setRadius($radius)
    {
        $this->radius = max(0.0, (float) $radius);
    }

    public function getArea()
    {
        return $this->radius * $this->radius * M_PI;
    }

}

$circle = new Circle;
$circle->radius = 10;
echo $circle->area; 

Circle::extensionMethod('getCircumference', function (Circle $that) {
    return $that->radius * 2 * M_PI;
});

echo $circle->getCircumference(); // ? 62.8

echo $circle->getReflection()->hasMethod('getArea'); // existuje metoda test?
echo $circle->getReflection()->getName(); // vrací název třídy, tj. 'Circle'

@Majkl578
Copy link

Majkl578 commented Jul 2, 2011

Pěkné! Kdypak se dočkáme podpory 5.4 přímo v Nette?

@dg
Copy link
Author

dg commented Jul 2, 2011

Nette v 5.4 bezproblémů funguje.

@Majkl578
Copy link

Majkl578 commented Jul 2, 2011

Špatně jsem se vyjádřil, měl jsem na mysli právě třeba traity přímo v Nette.

@dg
Copy link
Author

dg commented Jul 3, 2011

Až nadejde správný čas.

@karmi
Copy link

karmi commented Jul 5, 2011

Properties is a syntactic sugar which allows access public getter and setter methods as normal object variables.

LOL! Mně skutečně fascinuje, jak se jedna část PHP komunity snaží udělat z PHP Ruby, a druhá bastardní Javu :)

@dg
Copy link
Author

dg commented Jul 15, 2011

Chtěl jsi říct Delphi (property) a .NET (extension method), ne?

@karmi
Copy link

karmi commented Jul 15, 2011

Ne, mně to spíš evokovalo attr_accessor (http://www.ruby-doc.org/core/classes/Module.html#M000478)

@dg
Copy link
Author

dg commented Jul 15, 2011

Jasně, existuje to v každém druhém jazyce.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment