Skip to content

Instantly share code, notes, and snippets.

@CHH
Created August 30, 2011 13:55
Show Gist options
  • Save CHH/1180947 to your computer and use it in GitHub Desktop.
Save CHH/1180947 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, get_class($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;
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());
}
}
@solankin1576
Copy link

Thanks for Contribute it!

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