Monday, March 6. 2006
Aspect Oriented Programming in PHP as a contrast to other languages.
It is often said that when you travel the world, you will learn more about your home country then you will of the host country. I feel that the same is true of programming languages. Learning a new language is like visiting a country for awhile, and sometimes you do more then just visit, you move. By jumping across the boundary of languages, you can really gain some insight on some very basic (to borrow the term...) cross-cutting concerns with programming languages. How does the language handle data and code abstraction? What kind of reflective qualities does it have?
One of the biggest eye openers for me is when I made the jump from PHP to Scheme. Trying to work with concepts I was familiar with and learnt from PHP inside of Scheme illustrated some monumentally flawed assumptions that I had made about programming in general. Essentially I had become the ugly Canadian, who shouts, carries on, and runs roughshod over the local customs. It doesn't always have to be like this, sometimes the returning traveller imports the foreign culture with excellent results; consider the effect Disneys animation had on the Japanese and how out of that style grew Anime.
Okay, so what does this have to do with Aspect Oriented Programming you might ask? Well, importing concepts like Aspect Oriented Programming is a lot like importing Buddhism from the East. You can try to import Tibetan Buddhism, or you can try to import Zen Buddhism. Imported Tibetan Buddhism feels very foreign to me, and seems more like a trans-plant. Imported Zen Buddhism seems a much closer fit.
Okay, enough ranting of foreign culture and religion, lets talk about what Aspect Oriented Programming is, and talk about its implementations, especially inside of PHP. Aspect Oriented Programming is, in its basest form, adding extra behaviour to a unit of abstracted code (a function, a method, what-have-you) seperate from the definition of that behaviour.
Generally, there are 3 major concepts of AOP: The pointcut, advice, and a weaving. The pointcut defines which unit of code is being modified, the advice describes the extra behaviour, and how it is applied, and the weave is simply the combination of a pointcut with a specific piece of advice (thus binding extra behaviour to a particular unit).
Now in Java, a strongly statically typed compiled language, aspects are woven at compiletime. You weave in extra behaviour on the class level. For instance, one of the chief uses (or examples at least) of AOP is to add logging to a class. So in Java, once we have woven the logging advice to the class, every object that is instantiated from that class will have that advice.
In Javascript however, things are MUCH different. First of all, Javascript is a dynamically weakly typed interpreted language. Second of all, functions inside of Javascript are first class entities, and third of all, objects and classes don't play the same role as a fundamental unit of code that they do inside of Java. Implementing an "aspect system" inside of Javascript is much different, and in fact, the guts of a Javascript AOP implementation are mind-bogglingly simple, mostly due to Javascripts first class functions.
The Current State of AOP in PHP
Yoinked from the venerable Mr. Bergmann
This subtitle is yoinked from Sebastian Bergmanns blog. He too has been ruminating about AOP inside of PHP, and he is poised to present quite an implementation, if his work on PHPUnit is any indication. He will be using the Runkit extension, which is a different (and stronger) implementation then my own, but carries the weight of depending on an experimental extension.
So now, lets talk about PHP. PHP, like Javascript is a dynamically weakly typed interpreted language. However, like Java, classes (and in this case functions) are special entities within the language, they can't be directly referenced. There are already a few different AOP implementations for PHP, but all of them require pre-processing (that is, running your PHP source through a processor first) or patches against the engine.
So what is wrong with the first case of pre-processing code? Well there are two main issues. Preprocessing adds an extra step to your development: it's no longer code then test; but has become code, preprocess then test. The benifit of php being an interpreted language is lost. The second issue is that you have stopped wirting PHP, but have started writing a dialect of PHP; your new dialect won't run natively on any interpreter. There seem to be a few preprocessors out there that move the step of preprocessing out of the programmers hands and into the hands of apache, so that apache will preprocess the PHP before handing it off to the Zend engine, but we're talking about Pre-processing the PHP Hypertext PreProcessor language. This seems highly redundant and very slow.
Now, when it comes to patching the engine, the problems associated with that should be blatently obvious: you loose code portability. This isn't bad if you are just concerned with your application on your server, but once you start getting into the world of class libraries, frameworks, or applications... well you start having problems. And you are going to be in for a surprise if you ever need to switch servers, or upgrade versions of PHP.
The problem here is that the implementors seem to have looked at the Java method of weaving aspects (which admitedly is quite powerful and full of mojo) and trying to apply that to PHP. In fact, this is really indicative of a deeper problem inside of the PHP community: some of us are trying to hard to be Java. Now this is not a slam against PHP5's object system (in fact, I think it is about the sexiest thing added to PHP, second only to Runkit.) Nor is this a slam against Java—Javas bondage and dicepline makes for some very strong, and robust code. what this is really a slam on is trying to turn PHP into Java. It is, as Alan Watts loves to say, like putting legs on a snake.
An AOP implementation in PHP
So what does a proper PHP implementation of aspect oriented programming look like? Well first of all, there should be no extra steps between writing the code, and the execution of said code; this means no pre-processing. Second of all, it must utilize the existing qualities and abilities of the language, this means that weaving an aspect should be a dynamic action, rather then a static one. Again, when the Japanese imported Disney, they re-purposed it into Anime; they didn't just re-animate snow-white.
AOP for procedures in PHP?
It can be done, but it isn't easy, or particularly graceful. The first method is to refer to each function as a variable, instead of by its name. So to use nl2br(), you would use $nl2br(), which may be bound to the string 'nl2br' and calling the proper function, it might instead be bound to a the name of a function containing the weaving of a particular set of advice with nl2br, or it might just be bound to 'exit'. You also have to either chose a reasonable subset of the function list, both the php function list and your own, that are referenced by variable name instead of directly by name, or spend an inordinate amount of time binding each function to a variable of the same name. Ugh.
Another method would be to write a simple meta-circular evaluator. Instead of calling functions directly, you call them through an intercessory function, which could then handle the weaving for you.
As you can see, each solution is possible, but neither are desireable.
There is no way to effectively weave aspects with procedures in PHP (see the right sidebar). About the best we can do is with objects. However, as you will soon see, there is a lot of power and flexability that we are about to gain. The linchpin to the implementation of a very basic AOP system inside of PHP is in the form of the magic method __call(). You can even see the germ of this idea inside of my Fluenter class. Instead of aspects being woven to classes, they will be woven to objects instead. Now, this might not seem as powerful, it is however, just in a different way.
BunnyAspects is my implementation of AOP inside of pure PHP5. You do not need any extensions and it uses the existing qualities and abilities of the language—which carries its own set of problems, as we will see later. You can download bunny aspects Here, and its software (wiki) page is Here
The essence of BunnyAspects is to wrap a BunnyAspect object around our target object. This BunnyAspect object keeps track of what is being woven into it, and then with the magic __call method, intercedes between all of the method calls, and calls before or after advice as needed.
Because you are weaving aspects to objects (versus classes), you can weave much closer to the focus of your weaving. For instance, say you wanted to log the output of a particular "RequestProcessor" object, you simply weave the logging advice to the required instance, rather then all instances across your application. However, this also has its negative implications, in that you cannot weave an aspect to a class.
Enough talk, here is an example:
include('warren/lang/BunnyAspect.php'); //tihs might change depending on where you have it
class MyFoo
{
function Bar($a)
{
return $a * $a;
}
}
function double($method, $retval)
{
return $retval * 2;
}
$foo = new MyFoo();
// Prep the foo object to be wowen
$fooAspected = BunnyAspect($foo);
// $foo = new BunnyAspect($foo); works just as well here
// Weave the function "double" after the Bar method of the foo object
$fooAspected->weave(array('after','Bar'), 'double');
$foo->Bar(5);
// Returns 25
$fooAspected->Bar(5);
// Returns 50
One of the things you'll notice is that I have renamed the aspected object to $fooAspected. This is to show off the fact that the object you have aspected can become a different object from the original. As you can see in the comment, we can bind $foo to the apsected object instead. Which method you choose will depend on your needs.
The weaving is performed by the weave method of BunnyAspect, which takes two arguments. The first argument is the point-cut, or the exact location that the advice is to be applied. Currently a pointcut is an array, with the first element containing the location of the weaving ('before' or 'after') and the second element containing the method to weave. The second argument is the piece of advice to weave, which is a 'callable' php pseudo type (it can be a string naming a procedure, or an array containing: an object or class name, and a method). You might wonder why I opted to refer to the pointcut as an array, rather then seperate it out, and pass 3 arguments to weave. I want a point-cut to be a first-class entity. Because a pointcut has at least 2 pieces of information: where the advice is woven, and what it is woven to; pointcuts would lose their first-class-ness if that information was separated. I would also have a harder time refactoring the class later to handle different pointcuts. In fact, there is no technical reason why BunnyRegex cannot handle pointcuts containing regular expressions, or pointcuts that are applied to the getting, setting, unsetting, or even testing of object properties (using the __get, __isset, __unset, and __isset magic methods). It just needs to be implemented
Bunny Aspects and Types
There is a major issue here: because you are wrapping around the object, its associated type information (class, subclass, and interfaces) is lost. I do not believe that typecasting the class would work (either using Runkit or the serialize-<unserialize trick) because once the aspected class has been typecast, it would inherit the methods that were previously over-ridden, and therefore the __call magic method wouldn't be called, bypassing the aspect system.
Really what I would love to see is a __instance_of() magic method that allows us to override the type checking. Some might think it is dangerous. Really, PHP is a dynamically typed language, so lets get some of those dynamics back, and give it back to the programmer. Right now, PHP is in this weird state where arrays, resources and scalar values are dynamically typed, but objects are in a state where they can be dynamically typed, or implicitly typed but not casted.
Runkit. SexXxy PHP.
Runkit can change all of this. Using Runkit_method_rename to rename the original method to a new (unique) name, and then use Runkit_method_add to add a new method that calls the old one with the applied advice would give us static weavings (that is aspects woven into classes versus objects) that would not affect object type. using the runkit_function_rename and runkit_function_add, we can do the same with procedures.
As I mentioned earlier, Sebastian Bergmann is already working on an AOP system for PHP using Runkit, and I am eagerly awaiting his invention. As soon it comes out, I'll post a trackback to this article, so stay tuned.




