Wednesday, May 18, 2011

Considering a MetaObject Protocol for PHP

The purpose of a "MetaObject Protocol" is to allow for defining the very nature of how an object system functions. This means the ability to add traits/mixins, aspects, multiple inheritance, "Generics" etc. w/o requiring compilation or low level tinkering. One major advantage is that this means not having to wait for a revision of your language to express certain design patterns.

As usual php is so close, yet so far away. However ab/utiliz/ing the magic methods you can actually get pretty close to something resembling the CLOS system. To accomplish this it requires a few building blocks which I have abstracted into my Phutility library. Primarily the MetaArg and Node which provide the building blocks for composing declarative domain specific languages.

The majority of the magic occurs via macro expansion in CLOS. We are going to make up for that via declaring an abstract syntax tree (AST) which we will expand and weave into our method dispatch mechanism.

Enough talk, let's code. First our goal is be able to declare classes and methods.


defineClass('Shape',
slot('width'),
slot('height'),
method('getHeight', function($self) {
return $self->height;
}),
method('getArea', function($self) {
return $self->width * $self->height;
})
);

$shape = new Shape(
width, 20,
height, 50
);

echo $shape->getHeight();
echo $shape->getArea();

defineGeneric('beforeGetArea',
before(getArea),
arity(Shape),
function($self) {
echo "Called before get area!\n";
}
);

echo $shape->getArea(); //outputs "Called before get area!" and then area


Looks like a class and acts like a class. But most definitely not a raw class. How does this work? Principally we keep the meta information about each class in a Registry. Each class defined results in a simple declaration of "class X extends \phmop\StandardClass{}" which allows the usual "new Class" syntax. It writes this to a cache directory and does a require_once on it so as to avoid eval.

The base Obj class looks something like:

class StandardClass {
public $slots = array();

public function __call($slot, $args) {
return Registry::dispatchMethod($this, $slot, $args);
}

public function &__get($slot) {
return Registry::getSlot($this, $slot);
}

public function __set($slot, $value) {
Registry::setSlot($this, $slot, $value);
}

public function __construct() {
foreach(Appos(func_get_args()) as $key => $val) {
$this->$key = $val;
}
}
}


So pretty clearly the real magic is taking place in the way that defineClass, defineGeneric, and friends interoperate with the Registry.

I am going to end things here for now as I need to make a few posts about some of the inner workings of phutility to really get into the thick of things.

No comments: