Interceptors with PHP

June 23, 2010, (updated on September 6, 2014), 2 comments, Software Development

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 
*/
Tweet about this on TwitterShare on FacebookEmail this to someoneShare on TumblrShare on LinkedIn

Tags: ,

2 responses to “Interceptors with PHP”

  1. Reader says:

    Something on your code is not correct and also you need to remove “_” in _myMethod2().

Leave a Reply

Your email address will not be published. Required fields are marked *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax