Thursday, August 25, 2011

Prototype-based programming

class UndefinedException extends Exception {
    public function __construct($msg, $file, $line) {
        $this->message = $msg;
        $this->file = $file;
        $this->line = $line;
    }
}

class Backtrace {
    /**
     * Return caller information from the backtrace
     */
    static public function caller() {
        $bt = debug_backtrace(0);
        $i = count($bt) - 1;
        return $bt[$i];
    }
}

class Prototype {
    public $prototype;

    public function __construct($parent = null) {
        $this->prototype = $parent;
    }

    public function __call($name, $args) {
        // Check closure
        $func = isset($this->$name) ? $this->$name : null;
        if (is_callable($func)) {
            return call_user_func_array($func, $args);
        }
        // Check prototype
        if ($this->prototype) {
            return call_user_func_array(array($this->prototype, $name), $args);
        }        
        $caller = Backtrace::caller();
        throw new UndefinedException('Undefined method: ' . $name, $caller['file'], $caller['line']);
    }

    public function __get($name) {
        if ($this->prototype) {
            return $this->prototype->$name;
        }
        $caller = Backtrace::caller();
        throw new UndefinedException('Undefined property: ' . $name, $caller['file'], $caller['line']);
    }

    final public function begetObject() {
        return new Prototype($this);
    }
}
Here is an example of using it:
$a = new Prototype();
$a->greet = function() {
    echo "Hello World!\n";
};
$b = $a->begetObject();
$b->greet(); // Prints: Hello World!