Explore Aspect Oriented Programming with CodeIgniter, Part 3

This entry is part 3 of 3 in the series Explore Aspect Oriented Programming with CodeIgniter

Explore Aspect Oriented Programming with CodeIgniter

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

Explore Aspect Oriented Programming with CodeIgniter

<< Explore Aspect Oriented Programming with CodeIgniter, Part 2

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Ventus

    Thanks for this short series! I wasn’t aware of AOP before. Now I find it interesting. You kept your post clean, which made it easy to read and understand. Thanks again!

    Best regards,
    Ventus

  • http://flow3.typo3.org Robert Lemke

    Just a hint for those who’d like to try a PHP framework with full-featured AOP support: FLOW3 (flow3.typo3.org) is a mature solution with extensive documentation about the topic.

    Full disclosure: I am the project founder of FLOW3

    • Alex Gervasio

      Hey Robert,
      As the project’s founder, it’s really nice to see you took the time to promote the nifty AOP features bundled with FLOW3. I think it’s worth to point out as well that FLOW3 not only provides robust AOP support, but embraces DDD from a committed standpoint. Just for the record, I already posted something similar here: http://phpmaster.com/explore-aspect-oriented-programming-with-codeigniter-2/
      Good work indeed :)

    • http://www.innovativephp.com Rakhitha Nimesh

      Hello Robert

      Thanks for taking time to mention about FLOW3. I am a big fan of Java SPRING Framework and AspectJ. I hope your framework will provide all the features supported by these frameworks in the future.

      Lokking forward to learning FLOW3 soon.:)

  • SD

    The whole point of this, it would seem for the most part anyway, is to reduce interdependencies in the code so that’s it’s clean, manageable, and separable. I really liked the idea of this and was particularly excited by the choice of CI as that’s my framework of choice but as soon as I got to the end of the last post I was concerned. This may be an interesting proof of concept, but I don’t think this kind of implementation in CI could be considered for production. We may have separated our logic and made it look clean, but the final script has the whole functionality of the app resting on URI segments and a couple of regex’s. One change to the routing/.htaccess or even just nesting controllers in folders would break this. URI’s have a single role too: to provide access points for the users of business logic, not to support business logic, especially in such a mission-critical way.

  • Roel

    Thanks, I am learning to use ZF2 currently and because of this read I realised that the framework itself uses AOP. This makes perfect sense once you think about it, but I never realised how everything was being managed behind the scenes. The fact that all these event are being triggered somewhat invisibly is my main concern with this approach, other than that it seems like a very promising way to code. Thanks for this article!

    • http://www.innovativephp.com Rakhitha Nimesh

      Hi Roel

      Thank you very much for the detailed response.

      As developers we tend to work on top of frameworks and sometimes don’t have any idea on how these things are actually implemented. So I thought I could share somethings I have learned.

  • Copas

    Error =(, in $uriSegments = $this->CI->uri->segment_array();
    A PHP Error was encountered
    Severity: Notice
    Message: Trying to get property of non-object
    Filename: hooks/AOPCodeigniter.php
    Line Number: 15

    what is this??..
    Version: 2.1.3