News & Info

Daily Updates and Tech Chatter

Meta-Classes

I recently came across an interesting take on object-oriented programming in PHP, in which the author (Dennis Hotson) implemented meta-classes. When you have a meta-class system, that means that your classes are themselves objects on another class. As you may imagine, this leads to circular definitions — if classes are objects of another class, what do those classes come from? Other objects?

Yes—usually. If you want a great text on the subject, try to get a hold of the book, “The Art of the Meta-Object Protocol,” which pretty much defined the concepts and related practices. A lot of modern object-oriented languages support meta-classes. PHP is a notable exception to that trend. However, since PHP 5.3 gives us anonymous functions, we can actually implement meta-classes. It turns out that anonymous functions and closures are all we need to implement an object-oriented system, but that’s another email for another day.

Let’s look at how we can create our own object system within PHP’s standard one. And then we’ll see what we gain as a result. First we will need a class to represent our objects. For this system to work, it needs to be rather flexible.

Our Metaclass

class Object {
    public $methods = array();
    public function __construct($methods = array()) {
        $this->methods = $methods;
    }

    // Returns a method with the given name.
    public function method($name) {
        if (isset($this->methods[$name]) === false) {
            throw new BadMethodCallException();
        }

        return $this->methods[$name];
    }

    // Assigns a function $fn to the given name.
    public function fn($name, $fn) {
        $this->methods[$name] = $fn;
        return $this;
    }

    public function __call($name, $args) {
        return call_user_func_array(
            $this->method($name),
            array_merge(array($this), $args)
        );
    }
}

// By creating a function that calls the constructor,
// we can chain calls off of it.
function Object($methods = array()) {
    return new Object($methods);
}

Basically we have an Object class that stores a hash of methods. We are going to use an object of this class to represent *classes*. That is, we will create an object that describes the structure we want for classes, and then we’ll use *that* object to create other classes. Let’s look at how to do that.

$class = Object()
// Our meta-class has a new() methods, which is the constructor.
// It creates a new class, also giving it a constructor called
// new(), which will take any number of arguments and then call
// the classes init() method with those arguments.  We will
// implement class-specific constructor behavior by implementing
// init() methods for our own classes.
->fn('new', function ($class) {
    $new_class = Object($class->methods)->fn('new', function($class) {
        $new_object = Object($class->methods);
        $args = func_get_args();
        array_shift($args);
        call_user_func_array(array($new_object, 'init'), $args);
        return $new_object;
    });
    return $new_class;
})

// Remember that fn() returns our meta-class, which is how we can
// chain these calls to fn().
//
// Here we define a def() method for our meta-class.  We will use
// it to add methods to our own classes.  The $t here is $this.
->fn('def', function ($t, $name, $fn) {
    $t->methods[$name] = $fn;
    return $t;
})

// The extend() method will return a clone of our class so that we
// can add more methods to it.  It is how we will handle
// sub-classes.
->fn('extend', function ($t) {
    return clone $t;
});

This $class variable is an object, but we will use it to create new classes. It provides the basic functionality that we need for creating classes:

  1. It returns a class with a constructor we can manipulate.
  2. It provides a way to define methods for the class we create.
  3. It gives us a way to create sub-classes from our classes.

Of course, keep in mind that when I say ‘class’ above, that class is an object. Remember, meta-programming in the object-oriented means that our classes are objects; we blur the line between those two concepts. Given our $class above, let’s see how we can use it to define classes and create instances of them.

$person = $class->new()
->def('init', function($t, $name) {
    $t->name = $name;
})

->def('greet', function($t) {
    echo "Hello from $t->name
";
});

// A sub-class of $person.
$lobster = $person->extend()
->def('greet', function($t) {
    echo "NOM NOM NOM
";
})

->def('is_drunk', function($t) {
    return true;
});

// Instances of our classes.
$eric = $person->new('Eric');
$eric->greet();

$lobby = $lobster->new('Lobby C. Jones');

if ($lobby->is_drunk()) {
    $lobby->greet();
}

The syntax may look a little strange, but once we get to creating instances of our classes, we see that very little is different from the normal PHP object system. Which leads us to an important question:

What Are the Benefits of This?

Initially it may seem like all we have done is created a convoluted way to make classes. But the key here is that our classes are objects, and that means they are stored like any other PHP variable.

This provides some interesting benefits that are not possible with PHP’s standard object system:

  1. Our classes can be lexically scoped. All class definitions in PHP are global, and we cannot do something like define inner classes. But with our meta- class approach we can. Objects can be lexically scoped, and since our classes are objects, that means are classes can be lexically scoped. This is a more powerful form of controlling visibility than namespaces provides.
  2. Our classes are ‘open classes’. This means we can come back and add methods to classes later on. In PHP’s system, everything we want to define for a class has to be done solely within the class { … } construct. But since our classes here are objects, we can pass them around and call def() on them at any time to add methods as needed.
  3. Our classes can be garbage collected. Since we can create and modify our classes in lexical scope, those modifications can be GC’ed once the scope ends. In this case of PHP this actually isn’t a benefit at all (see below), but in other languages this can be a gain in memory usage.

The Reality of This Code

It is, unfortunately, entirely impractical. The performance hit we take from doing this is unacceptable for production code, just like when I implemented overloaded methods based on argument types (ala C++). Entirely possible, but not at all usable for real-world programs that we have to release to the public.

This code does, however, demonstrate that we can implement a flexible meta-class system in PHP. If you think this type of thing is interesting, you should check out the language Smalltalk. And if its syntax seems a little too weird, you could also check out Ruby, which implements these same concepts in the guise of more familiar looking syntax.

Tags: ,

1 Awesome Comments So Far

Don't be a stranger, join the discussion by leaving your own comment
  1. Dennis Hotson
    April 6, 2011 at 8:08 AM #

    Thanks for the write up. You’ve explained the concept much better than I did. :-)