Rico Suter's blog.
 


Interceptors are used for aspect oriented programming (AOP). With the given interceptor, method calls can be extended or suppressed. PHP interceptors are implemented using the _call magic method.

Problems

A big problem implementing interceptors in PHP is the fact that method calls of the same class will not be intercepted. To solve the problem, simply call a method with $this->intercepted->myMethod() instead of $this->myMethod(). Methods which are called on intercepted will be intercepted.

Another problem is that the AbstractInterceptor’s methods (e.g. callMethod()) must not be called on the intercepted object from outside.

Implementation

Important: Within the interceptor methods, all calls on the original object must be done on the $object variable (first parameter of the before, around and after method), not on $this. If you use $this, this might omit some interceptors in the interceptor stack.

class AbstractInterceptor {
    public $_object;
    public $_rootObject; 

    public function __construct($object) {
        $this->_object = $object;

        if (is_a($object, "AbstractInterceptor"))
            $this->_rootObject = $object->_rootObject;
        else
            $this->_rootObject = $object;

        $object->intercepted = $this; 
    }

    public function callMethod($method, $args){
        return call_user_func_array(array($this->_object, $method), $args);
    }

    public function __isset($name) {
        return isset($this->_rootObject->$name);
    }

    public function __unset($name) {
        unset($this->_rootObject->$name);
    }

    public function __set($name, $value) {
        $this->_rootObject->$name = $value;
    }

    public function __get($name) {
        return $this->_rootObject->$name;
    }

    public function __call($method, $args) {
        if ($method[0] == "_")
            $method = substr($method, 1);

        if (method_exists($this, "before"))
            $this->before($this->_rootObject, $method, $args);

        if (method_exists($this, "around"))
            $value = $this->around($this->_rootObject, $method, $args);
        else
            $value = $this->callMethod($method, $args); 

        if (method_exists($this, "after"))
            $this->after($this->_rootObject, $method, $args);

        return $value; 
    }
}

class LogInterceptor extends AbstractInterceptor {
    function around($object, $method, $args){
        print "before $method <br />";
        $value = $this->callMethod($method, $args);
        print "after $method <br />";
        return $value; 
    }
}

class MyObject {
    function myMethod1(){
        print "myMethod1 called <br />";
        $this->intercepted->myMethod2(); // called with "around" code
        $this->myMethod3(); // called directly
    }

    function myMethod2(){
        print "myMethod2 called <br />";
    }

    function myMethod3(){
        print "myMethod3 called <br />";
    }
}

$object = new MyObject();
$object = new LogInterceptor($object);
$object->myMethod1(); 

/* Output: 
before myMethod1
myMethod1 called
before myMethod2
myMethod2 called
after myMethod2
myMethod3 called
after myMethod1 
*/


Discussion