Explore Aspect Oriented Programming with CodeIgniter, Part 3

Rakhitha Nimesh
Share

In the previous parts of the series we learned about AOP concepts and the need for using AOP in large scale projects and I introduced CodeIgniter’s hooks as a convenient mechanism for creating AOP functionality from scratch. In this part I’ll show you how to use both XML and comment-based techniques to create custom AOP functionality when a dedicated AOP framework is not available.

XML Configuration

Let’s start with the XML method. First, we need a custom XML file with the AOP configuration details. I’m going to define specific tags for the AOP rules as shown in the code below.

<?xml version="1.0" encoding="UTF-8" ?> 
<aopConfig>
 <aopAspect id="LoggingAspect" >
  <aopPointcut expression="execution[*.*]" >
   <aopBefore ref-class="LoggingAspect" method="startLog"/>
   <aopAfter ref-class="LoggingAspect" method="endLog"/>
  </aopPointcut>
 </aopAspect>
 <aopAspect id="ProfilingAspect" >
  <aopPointcut expression="execution[*.*]" >
   <aopBefore ref-class="ProfilingAspect" method="startProfiling"/>
   <aopAfter ref-class="ProfilingAspect" method="endProfiling"/>
  </aopPointcut>
 </aopAspect>
</aopConfig>

Each AOP framework will have its own unique tags. Here I’ve defined <aopAspect> for each type of aspect and the <aopPointcut> tag to reduce the number of joinpoints to match certain criteria. In the <aopPointcut>, I’ve used the expression attribute which will be used to match the joinpoints.

I’ve defined two tags for before and after advice; whenever a method matches the pointcut expression, it will look for before or after advice and call the methods provided in the Aspect class using the ref-class attribute value.

In the above code, the expression [*.*] means the advice will be executed on all the methods of all the controllers. If we wanted to apply some advice to all of the delete methods. the pointcut expression would be [*.delete*]. If we wanted to apply advice to certain type of controllers, it would be [*Service.*]. You can define any custom structure to match classes and methods. The above is the just the most basic way of doing it.

Now let’s look at how we can use this configuration file to apply advices in CodeIgniter. In the previous part I explained how to define pre_controller and post_controller hooks to call custom classes. The following shows the implementation of those classes.

<?php
class AOPCodeigniter
{
    private $CI;

    public function __construct()
        $this->CI = &get_instance();
    }

    public function  applyBeforeAspects() {
        $uriSegments = $this->CI->uri->segment_array();

        $controller = $uriSegments[1];
        $function = $uriSegments[2];

        $doc = new DOMDocument();
        $doc->load("aop.xml");

        $aopAspects = $doc->getElementsByTagName("aopPointcut");

        foreach ($aopAspects as $aspect) {
            $expr = $aspect->getAttribute("expression");
            preg_match('/[(.*?)]/s', $expr, $match);
            if (isset($match[1])) {
                $exprComponents = explode(".", $match[1]);
                $controllerExpr = "/^" . str_replace("*", "[a-zA-Z0-9]+", $exprComponents[0]) . "$/";
                $functionExpr = "/^" . str_replace("*", "[a-zA-Z0-9]+", $exprComponents[1]) . "$/";

            	preg_match($controllerExpr, $controller, $controllerMatch);
            	preg_match($functionExpr, $function, $functionMatch);

            	if (count($controllerMatch) > 0 && count($functionMatch) > 0) {
                    $beforeAspects = $aspect->getElementsByTagName("aopBefore");

                    foreach ($beforeAspects as $beforeAspect) {
                        $refClass = $beforeAspect->getAttribute("ref-class");
                        $refMethod = $beforeAspect->getAttribute("method");

                        $classObject = new $refClass();
                        $classObject->$refMethod();
                    }
                }
            }
        }
    }
}

All the code for before advices are applied inside the applyBeforeAspects() method.

Initially we get the URI segments using CodeIgniter’s URI class. The first parameter is the controller and next parameter is the method name for the current request. Then we load our AOP configuration file using DOMDocument() and get the pointcuts using the <aopPointcut> tag.

While looping through each pointcut we get the expression attribute. Then we get the expression inside the brackets using regex and prepare the controller name and method name by exploding our matched string. I’ve used a basic regex to match the controllers and methods, but in a real AOP framework this would be much more complex.

Then we try to match the controller name and method name with the regular expressions we created. If both method and controller is matched, we have a poincut where we need to apply advices. Since we are applying before advice in this method, we get the <aopBefore> tags for the current pointcut. While looping through each before tag, we apply the advice by calling the method of ref-class.

This is a basic way of creating AOP functionality with CodeIgniter. The same process applies to after advice as well.

Our LoggingAspect class would resemble the following:

<?php
class LoggingAspect
{
	function startLog() {
		echo "Started Logging";
	}

	function endLog(){
	}
}

Now we have an idea how the XML method works, so let’s move on to the comment-based technique.

Document Comment-Based Configuration

In this technique we don’t have any configuration files as we did with the XML approach. Instead, all the advice is inside the aspect classes. We define comments according to a predefined structure and then we can manipulate the comments using PHP reflection classes to apply the advice.

<?php
/**
 * 
 * @Aspect
 */
class LoggingAspect
{
    /**
     * @Before:execution[*.*]
     *
     */
    function startLog() {
        echo "Started Logging";
    }

    /**
     * @After:execution[*.*]
     *
     */
    function endLog() {
    }
}

The LoggingAspect class has changed and is now an Aspect class using the @Aspect comment. At runtime, the AOP framework finds the aspect classes and looks for advice that matches specific pointcuts. Before and after advice is defined using @Before and @After and the specified execution parameter.

Let’s move onto the implementation for the applyBeforeAspects() function for this approach.

<?php
public function applyBeforeAspects() {
    $uriSegments = $this->CI->uri->segment_array();

    $controller = $uriSegments[1];
    $function = $uriSegments[2];

    $aspectClasses = array("LoggingAspect");

    foreach ($aspectClasses as $aspectClass) {
        $ref = new ReflectionClass($aspectClass);
        $methods = $ref->getMethods();

        foreach ($methods as $method) {

            $methodName = $method->name;
            $methodClass = $method->class;
            $reflectionMethod = new ReflectionMethod($methodClass, $methodName);
            $refMethodComment = $reflectionMethod->getDocComment();

            preg_match('/@Before:execution[(.*?)]/s', $refMethodComment, $match);

            if (isset($match[1])) {
                $exprComponents = explode(".", $match[1]);

                $controllerExpr = "/^" . str_replace("*", "[a-zA-Z0-9]+", $exprComponents[0]) . "$/";
                $functionExpr = "/^" . str_replace("*", "[a-zA-Z0-9]+", $exprComponents[1]) . "$/";

                preg_match($controllerExpr, $controller, $controllerMatch);
                preg_match($functionExpr, $function, $functionMatch);

                if (count($controllerMatch) > 0 && count($functionMatch) > 0) {
                    $classObject = new $methodClass();
                    $classObject->$methodName();
                }
            }
        }
    }
}

I’ve hard-coded the aspect class in an array for simplicity, but ideally all the aspect classes will be retrieved at runtime by the AOP framework.

We get the methods of each aspect class using reflection. Then, while looping through each method, we retrieve the document comment defined before the method. We extract the pointcut expression by matching the comment against a specific regular expression. Then we continue the same process as with the XML method for the remaining logic.

Summary

During this series you learned about AOP concepts and how to identify situations where AOP should be applied. And while I hope you have a better understanding about how AOP works now, keep in mind CodeIgniter is not an AOP framework so using hooks will not be the most optimized way to apply AOP in your projects. Rather, CodeIgniter served as a vehicle throughout the series to provide knowledge about AOP from scratch so that you can easily adapt to any new AOP framework. I recommend you to use a well-known AOP framework for large scale projects since it might make your application more complex if not used wisely.

Image via Fotolia