Created
August 30, 2011 13:55
-
-
Save CHH/1180947 to your computer and use it in GitHub Desktop.
PHP does Meta Programming too! (Requires PHP 5.4)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace CHH; | |
trait MetaObject | |
{ | |
protected static $__metaClass; | |
static function setMetaClass(MetaClass $metaClass) | |
{ | |
static::$__metaClass = $metaClass; | |
} | |
static function getMetaClass() | |
{ | |
if (null === static::$__metaClass) { | |
static::$__metaClass = new MetaClass; | |
} | |
return static::$__metaClass; | |
} | |
function __call($method, array $argv = array()) | |
{ | |
$metaClass = static::getMetaClass(); | |
if (!$metaClass->respondsTo($method)) { | |
throw new \BadMethodCallException(sprintf( | |
'Call to undefined method %s', $method | |
)); | |
} | |
return $metaClass->send($method, $argv, $this); | |
} | |
function __get($property) | |
{ | |
$metaClass = static::getMetaClass(); | |
if (property_exists($metaClass, $property)) { | |
return $this->$property = $metaClass->$property; | |
} | |
} | |
function __isset($property) | |
{ | |
return property_exists(static::getMetaClass(), $property); | |
} | |
} | |
class MetaClass | |
{ | |
protected $methods = array(); | |
function extend($methods) | |
{ | |
if ($methods instanceof MetaClass) { | |
$methods = $methods->getMethods(); | |
} | |
foreach ($methods as $method => $body) { | |
$this->method($method, $body); | |
} | |
return $this; | |
} | |
function getMethods() | |
{ | |
return $this->methods; | |
} | |
function method($name, \Closure $body) | |
{ | |
$this->methods[$name] = $body; | |
return $this; | |
} | |
function property($name, $default = null) | |
{ | |
$this->{$name} = $default; | |
return $this; | |
} | |
function respondsTo($method) | |
{ | |
return isset($this->methods[$method]); | |
} | |
function send($method, array $argv = array(), $context = null) | |
{ | |
if (!$this->respondsTo($method)) { | |
throw new \BadMethodCallException("Call to undefined Method $method"); | |
} | |
$body = $this->methods[$method]; | |
if (null !== $context) { | |
$body = $body->bindTo($context, get_class($context)); | |
} | |
return call_user_func_array($body, $argv); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace CHH\Test; | |
require_once __DIR__ . "/MetaObject.php"; | |
use CHH\MetaObject; | |
class Animal | |
{ | |
use MetaObject; | |
} | |
class Dog extends Animal | |
{ | |
public $name; | |
protected $favouriteToy = "Stick"; | |
function __construct($name = null) | |
{ | |
$this->name = $name; | |
} | |
} | |
class MetaClassTest extends \PHPUnit_Framework_TestCase | |
{ | |
/** | |
* Registers an Instance Meta Property at runtime and initializes it | |
* with a default value. | |
*/ | |
function testRegisterMetaProperty() | |
{ | |
$beethoven = new Dog; | |
Dog::getMetaClass()->property('no'); | |
$this->assertTrue(isset($beethoven->no)); | |
} | |
function testRegisterMetaMethod() | |
{ | |
$beethoven = new Dog("Beethoven"); | |
Dog::getMetaClass()->method('speak', function() { | |
return "Hello I'm {$this->name}"; | |
}); | |
$this->assertTrue(is_callable(array($beethoven, 'speak'))); | |
$this->assertEquals("Hello I'm Beethoven", $beethoven->speak()); | |
} | |
function testExtendWithArray() | |
{ | |
$beethoven = new Dog("Beethoven"); | |
Dog::getMetaClass()->extend([ | |
"bark" => function() { | |
return "Woof! Woof!"; | |
}, | |
"isNamed" => function($name) { | |
return $this->name === $name; | |
} | |
]); | |
$this->assertFalse($beethoven->isNamed('Johnny')); | |
$this->assertEquals('Woof! Woof!', $beethoven->bark()); | |
} | |
function testProtectedMemberAccess() | |
{ | |
$beethoven = new Dog("Beethoven"); | |
Dog::getMetaClass()->method("getFavouriteToy", function() { | |
return $this->favouriteToy; | |
}); | |
$this->assertEquals("Stick", $beethoven->getFavouriteToy()); | |
} | |
// I've to figure out this later. It's a quirk by PHP's | |
// behaviour with static inheritance. | |
function testInheritanceAndMetaClass() | |
{ | |
$beethoven = new Dog("Beethoven"); | |
Animal::getMetaClass()->method("foo", function() { | |
return "bar"; | |
}); | |
Dog::getMetaClass()->method("bar", function() { | |
return "bar"; | |
}); | |
$this->assertFalse(Animal::getMetaClass()->respondsTo("bar")); | |
$this->assertFalse(is_callable(array($beethoven, 'foo'))); | |
$this->assertFalse(Animal::getMetaClass() === Dog::getMetaClass()); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Interesting, its good to know that PHP is smarter than we thought. XD With proper work, PHP can do a lot of things that are supposedly only achievable in more object oriented languages like Java, C# and Ruby.