Rico Suter's blog.
 

ads via Carbon Squarespace has all the tools you need to keep visitors coming back. ads via Carbon

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