Skip to content

Instantly share code, notes, and snippets.

@orls
Forked from CHH/MetaObject.php
Created September 3, 2011 20:35
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save orls/1191748 to your computer and use it in GitHub Desktop.
PHP does Meta Programming too! (Requires PHP 5.4)
<?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);
}
return call_user_func_array($body, $argv);
}
}
<?php
namespace CHH\Test;
require_once __DIR__ . "/MetaObject.php";
use CHH\MetaObject;
class Animal
{
use MetaObject;
}
class Dog extends Animal
{
public $name;
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 testExtendWithHashMap()
{
$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());
}
// 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';
});
$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