In a few words, Transparent PHP AOP is a set of classes that work together to emulate AOSD (Aspect Oriented Software Development).
Supporting PHP 4.3.0+ installations, the simplicity is a strong point in Transparent AOP; everything is controlled in a human-readable and easily understandable XML Aspect
Definition (XAD) file.
This document explain all the resources available to you with this package. It is a short document, because of class simplicity; however, it
is still a very powerful tool.
This is the best part! Look: It will not have more than one line! Here it comes:
Unzip the content of this package inside your project's folder.
Difficult, huh? ;)
This chapter is like "Learn Transparent PHP AOP in 5 minutes" tutorials. It is simple, do not cover all features, but it is enough to
developers use the basic features of this class without any problems.
In this tutorial, we will setup a class, will define an Aspect to be appplied and finally will apply it. The "Testing your installation"
section will explain how to check if it worked well, and "Troubleshooting" will
Suppose we have a class names Test. In our UML Class Diagram, we have this methods and properties:
+-------------------------------+ | Test | +-------------------------------+ | - message: string | +-------------------------------+ | + Test(m: String): Test | | + setMessage(m: string): void | | + getMessage(): string | | + display(): void | +-------------------------------+
So, we create the following PHP Code:
<?php class Test { var $message; function Test($m) { $this->setMessage($m); } function setMessage($m) { $this->message = $m; } function getMessage() { return $this->message; } function display() { echo $this->message; } } ?>
Finally, save this file in this location: AOP root folder + tutorial/class.Test.php
Suppose we have to track method calls, in a basic approach of a Debugger System; consider we want to include a Debugger Aspect.
To track calls, we just print the name of the function and if is in the beginning or ending of it.
The XML Aspect Definition allows you to write PHP Code to be applied in the class (Advice).
We will write 4 custom pieces of code to be merged with class Test. The XML syntax uses a simple hierarchy that the root element is an
Aspect and supports one or more Pointcuts. Each Pointcut contains some attributes that define the location to be applied and as a child
it comes with a CDATA node containing the PHP code.
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE aspect SYSTEM "../aop.dtd"> <aspect> <pointcut auto="after" function="setMessage"> <![CDATA[ echo "End of " . __FUNCTION__ ."(\"" . $m . "\") call.<br />"; ]]> </pointcut> <pointcut auto="after" class="Test" nfunction="display"> <![CDATA[ echo "End of " . __FUNCTION__ ." call.<br />"; ]]> </pointcut> <pointcut auto="before" nfunction="display"> <![CDATA[ echo "Beginning of " . __FUNCTION__ ." call.<br />"; ]]> </pointcut> <pointcut auto="around" function="display" class="Test"> <![CDATA[ echo "<b>Message: </b>"; proceed(); echo "<br />"; ]]> </pointcut> </aspect>
The first pointcut is an AutoPointcut (detailed later) specific to setMessage method. Its Advice prints the name of the function and the
$m local variable content.
The second pointcut apply the same Advice to all end of calls inside class Test, except in method "display".
The third pointcut is also an AutoPointcut and its Advice prints a message containing the name of the function, and it is applicable in
the beginning of all methods (or functions) defined in the desirable document, but it will not be applyed to a function/method called
"display".
The last pointcut, another AutoPointcut will be applyed in method display inside of class Test. It will display the first line before the
method commands, will execute them and after it will print the new line HTML code. The proceed(); method means the whole method content!
Save this file as debugger.xml inside the AOP root folder + tutorial directory.
The last part of this quick tutorial explains supperficially how to apply the XAD into Original class.
Transparent PHP AOP defines a function called require_aop. It is a require_once style method that either compiles the Aspects into Class files or load already compiled files. If the compiled file does not exist, the script compiles and try to save the compiled content into a unique named file. Since version 1.0 RC3, the compiled file name is more readable, allowing the programmer to connect the original class file with the compiled class file (they will have the same name, except that the compiled will have a md5 hash after it).
Our script loader is our application. It is reduced into a minimum possible code, to show exactly only the AOP functionality.
<?php require_once "../func.require_aop.php"; // Loading compiled class or compiling and saving require_aop("class.Test.php", "debugger.xml"); // Testing... $test = new Test("George Bush"); if ($test->getMessage() != "Author") { $test->setMessage("Guilherme Blanco"); } $test->display(); ?>
Save this file into AOP root folder + tutorial with the index.php file name.
Try running the index.php into your server and you will see the defined Advices begin displayed in screen, as defined. If not, proceed to Troubleshooting section.
Last topic we exposed a quick tutorial. It compiled the class Test with the debugger.xml aspect. As a result, it should generate a compiled file, probably named "class.Test.4f207ed29fbb1b4b06f233a9fa05c471.php". If not, it will probably have a file with a name that is the original file name plus a md5 hash plus the PHP extension. Open it in your favourite PHP editor.
If everything went ok, you'll see the defined debugger.xml code merged with class.Test.php code. For our example, it should be something like:
<?php class Test { var $message; function Test($m) { /* AOP "before" Auto Code */ echo "Beginning of " . __FUNCTION__ ." call.<br />"; $this->setMessage($m); /* AOP "after" Auto Code */ echo "End of " . __FUNCTION__ ." call.<br />"; } function setMessage($m) { /* AOP "before" Auto Code */ echo "Beginning of " . __FUNCTION__ ." call.<br />"; $this->message = $m; /* AOP "after" Auto Code */ echo "End of " . __FUNCTION__ ."(\"" . $m . "\") call.<br />"; echo "End of " . __FUNCTION__ ." call.<br />"; } function getMessage() { /* AOP "before" Auto Code */ echo "Beginning of " . __FUNCTION__ ." call.<br />"; /* AOP "after" Auto Code */ echo "End of " . __FUNCTION__ ." call.<br />"; return $this->message; /* AOP "after" Auto Code */ echo "End of " . __FUNCTION__ ." call.<br />"; } function display() { /* AOP "before" Auto Code */ echo "<b>Message: </b>"; echo $this->message; /* AOP "after" Auto Code */ echo "<br />"; } } ?>
Next section explains commom mistakes. If the compiled file exist and if its code does not match with above code, notify me via email (guilhermeblanco [at] hotmail [dot] com), messenger (same as email) or at PHP Classes.org Forums.
1. It appears some errors while using require_aop function?
This is probably because of your PHP version module installed in your server. Currently, Transparent PHP AOP supports versions since
4.3.0+, basically because of tokenizer functions and Regular Expressions functions. Also, it uses internally the Expat XML functions,
that is built-in with your PHP module (and if you don't want it, you remove it manually).
These are the main issues that can happen. If is everything fine and you still experience errors, feel free to contact me at
guilhermeblanco [at] hotmail [dot] com.
2. Every call to require_aop, appears this error: [Aspect Error]: File XXX [From Original: YYY] could not be created/loaded for write!
This happens when you do not assign permissions to read/write in the directory that compiled file is generated. Please, assign a valid CHMOD value and everything will work. If not, check if you are not exceeding your disk quota.
1. It appears some E_STRICT errors while using the package?
That's true. In PHP5 there is a E_STRICT error, that notifies commom differences between PHP4 and PHP5. This does not means that your code will not work; it just mention that the notifyed code has changed its declaration. To fix this, disable this type of error reporting.
New Troubleshooting itens will be added if encountered new user mistakes.
Several places around Web provide more complete guides to AOSD. This Manual explains the basic idea of usage and the terminology, to help introducing interested people in AOP that want to use this package. For more explanations and references, please refer to: http://en.wikipedia.org/wiki/Aspect-oriented_programming.
AOSD came to solve the existent problems of OOP approach. Imagine building a website, and you need to incorporate a debugging system on it.
In commom OOP approach, you have to create a Debbuger class, and trace your code by editting the class code, right? So, you have a single
class merging the whole system. That is not OOP, that is AOP. The only point I wrote here that is wrong, is to edit the class code. In AOP,
you do not edit code, you apply what we call Advices and they are aplied without change the original class code.
You still do not understand? Ok, I will try to explain better. Instead of create a set of classes and embed their usage code inside your
application module, you merge different aspects of your system in your application module, and everything will be fine.
Still don't understand? Check out WikiPedia text.
AOP has a special naming convention, and knowing them will help you to understand how does this approach work. Read carefully this section and the "Conventions", that will explain how does this package works based in the AOP theory.
To be very formal, an Aspect is a set of Pointcuts. Explaining better, an Aspect is a perspective of your system, for example, Logging, Debugging, Benchmarking, etc.
An Advice is a piece of code. AOP theory divagates a lot in this point, but this is a piece of code that can be applied in a Join Point.
This is a place inside original file where additionally code (Pointcut) can be defined.
A set of Advices consists in a Pointcut. It is this code that is applied in a Join Point.
Weave is the whole behavior of your system. Technically speaking, a Weave is a set of Aspects.
This section tryes to explain the reasons of the existance of this class.
The origin is a conversation between the author of the package and a friend, who came with the idea to break the paradigm that it was
impossible to create a complete AOP approach in PHP language. After some discussion and links popping at MSN, we found several
implementations that uses external extensions, PHP5 dependency or even it's a module that must be installed on server.
Since none of these solutions match our needs, we continued discussing possibilities to solve the paradigm. More references we added,
and we came up with the idea of compiling merged contents and use Tokenizer functions. This solved all our "to-be-defined" questions
and allow me (the author) to start a project. My friend did not onboard it.
While I was developing this package, I assigned some names to help to strict follow my idea. Since I think it's not necessary those
UML diagrams (Use Case, Analisys Class, Implementation Class, Sequence) for small projects, I used some other names (different from
AOP theory) to simplify my work.
Since version 1.0 RC2, I updated the whole system to follow the AOP theory.
Now, what AOP defines as a Weave, we have a class named Weave too. The same for Aspect, Pointcut (not exactly, it is called in my
implementation as AutoPointcut) and Advice. The only missing point is the Join Point.
In AOP, a Join Point is a place where advice can be applied. My implementation does not have this, since XAD can refer to more than
one Join Point. So, this is packed inside Pointcut class.
The idea is simple. I'll draw it using ASCII instead of pic, since I want to make this Manual a unique file, not a package. I hope you understand. Here is the approach I defined:
+-------------------+ | Class File | +-------------------+ /\ || +---------------------+ ||=====================>| Compiled Class File | || +---------------------+ +-----------+-------------------+ | | | +-------+ +-------+ +-------+ | XAD | | XAD | . . . | XAD | +-------+ +-------+ +-------+
You have your class file. There, you can define CustomPointcuts (explained in details later), by adding some comment like lines.
You also define you XAD (Xml Aspect Definition), that is a XML file with a strict DTD. This is converted in a PHP Aspect structure.
This structure stores 1..N Pointcuts. Each Pointcut contains a name (or its repectively auto), a possible se of classes and a set of
possible methods to be applied. Finally, we have the code inside it too.
When you want to get some Advice, each Aspect is checked and is exists any Pointcut that matches with the name/auto, function (optional)
and class (also optional too), its code (an instance of Advice) is returned.
Technically speaking, Transparent PHP AOP is simple.
It starts by calling a custom function, require_aop. Internally, it make some conditions to check if there is at least one Aspect
associated, if a compiled file exists, and, if yes, check for recent changes in original class file or in one of the associated
Aspects.
If this condition is false, then it simply load the compiled class file; otherwise, start the pain!
To compile a file, the package read each XAD file into an Aspect object. This object holds a list of Pointcuts (AutoPointcuts and
CustomPointcuts).
Each instance of Pointcut derived classes holds an Advice object, which contains the source code defined.
The compiler retrieves the Original Class file source and then starts its execution.
The source is then passed to the lexer, which extracts each token. The compiler does a lot of jobs, and them compile first the CustomPointcut.
This is done first bacause a CustomPointcut may also have an "after" JoinPoint, that should be treated by AutoPointcuts.
After the first compilation, another call to curly braces correction is done, to correct possible statements what define a single command block.
The last compilation does the AutoPointcut merging.
The CustomPointcut compilation is done via Regular Expressions. The compiler looks for occurencies of /// Pointcut: XXX and then replace the matched item by the Advice related to that name.
AutoPointcut compilation does the hardest part.
First of all, it split the whole source code into tokens. Also, it defines a level stack, which hold the identation level in the current file.
The compiler then loops through all tokens, and it finds the class declaration, method declaration, function declaration and also function exit
statements (like return).
When it finds a match, it looks for the list of attached Aspects for an Advice related to the JoinPoint in the Pointcut. Then, it replaces one
by the other.
After the whole compilation, the package generated a cache file containing the orginal source file merged with the related aspects.
This compiled file is then loaded, as mentioned earlier.
XAD follows a very simple DTD. It consists in a root element, an aspect. Then, it accepts one or more childs, all pointcuts.
One pointcut can have modifiers, that will be detailed later. The pointcut childen can be the following things: CDATA sections,
that contains PHP code or Advice tags, that loads external PHP source.
There is a DTD available that you can attach to your XAD file to be validated. Include it and in some browsers to check if it is
validated successfully.
A simple structure can be extracted:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE aspect SYSTEM "../aop.dtd"> <aspect> <pointcut ...> <![CDATA[ ... ]]> </pointcut> ... </aspect>
Previous section exposes the basic XAD definition.
To define one pointcut, it must be child of an aspect node. Then, you have its attributes, that are restrictions to be applied (defined
in the next section).
An Advice is the PHP code that is relative to a Pointcut. It must be defined inside the CDATA node, defined as child node of the Pointcut
XML node or an advice node, if you want to load external data.
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE aspect SYSTEM "../aop.dtd"> <aspect> // [CORRECT] This code will work! <pointcut ...> <![CDATA[ echo "Here!<br />"; ]]> </pointcut> // [WRONG] This will not work... <pointcut ...> echo "Here too!<br />"; </pointcut> </aspect> // [WRONG] This will not work too... <pointcut auto="before"> <![CDATA[ echo "And here again!<br />"; ]]> </pointcut>
Since version 1.0 RC2, Transparent PHP AOP enabled you to load external PHP Advice code. To make things happen, you have to define it
in your XAD, using the Advice XAD tag. The usage is really simple, and I do not think you will have troubles on it.
In XAD, to define an external source, do this:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE aspect SYSTEM "../aop.dtd"> <aspect> <pointcut ...> <advice src="../MyAdviceExternalSource.php" /> <![CDATA[ echo "Here!<br />"; ]]> <advice src="../MyAdviceExternalSource2.php" /> <advice src="../MyAdviceExternalSource3.php" asrequire="true" /> </pointcut> </aspect>
By doing this, you will load the file "MyAdviceExternalSource.php", that MUST have the <?php and ?> tags to enclosure PHP code.
This works exactly like any other PHP script, that you can close the PHP tag, print some HTML and then turn back to PHP. Just remember
that PHP code must contains the PHP tags.
The Pointcut instance will, then, contains the merge of all defined Advices (CDATAs and Advices). To retrieve the whole merged code,
you can access this: $advice = & $pointcut->getAdvice();
You will experience that $advice variable will contain an instance of Advice class.
Restriction attributes will filter the occurency of your Advice in compiled code. This is obvious, but there are some restrictions that
must be defined.
These filters enable you to define the Advice exactly in a Join Point or Pointcut. Read the attributes carefully, they have a lot of
meaning in the way of package works.
This is optional, but if not set, the attribute "name" must be defined.
If this attribute is included, this exposes which AutoPointcut will have the following Advice code. Since version 1.0 RC 1, it is
possible to define one of the two possibilities, and version 1.0 RC 3 implements another one:
function getLine() { if ($this->line < 0) { return 0; } // end of if statement return $this->line; } // end of methodA simple code, but can cause some headache, specially if I want to apply this Advice:
<![CDATA[ try { proceed(); } catch ( Exception $e ) { die( $e->getMessage() ); } ]]>When I compile, It will result in this compiled file (I added identation to show you the problem):
function getLine() { /* AOP "before" Auto Code */ try { if ($this->line < 0) { /* AOP "after" Auto Code */ } catch ( Exception $e ) { die( $e->getMessage() ); } return 0; } // end of if statement } catch ( Exception $e ) { die( $e->getMessage() ); } /* AOP "after" Auto Code */ } catch ( Exception $e ) { die( $e->getMessage() ); } return $this->line; } // end of methodAs you can see, the last curly brace is two levels out of scope. Then the compiles file will generate successfully, but when loaded, it will report a fatal error.
This is optional, but if not set, the attribute "auto" must be defined.
If this attribute is included, this exposes which CustomPointcut will have the following Advice code.
<pointcut name="MyJoinPoint1, MyJoinPoint2" ...>...</pointcut>
Optional attribute. By defining it, it restricts the Pointcut to that function. If the compiler code is not inside the defined function,
it is impossible to retrieve Advice code.
Since Release Candidate 2, it is possible to implement a multiple Pointcut Advice code. Like an array, you define the same Advice to
more than one function. Read the note box above.
Optional attribute, but if defined, it restricts the Pointcut to that class.
The implementation of multiple Pointcut Advice code is allowed in this attribute.
Optional attribute. By defining it, it restricts the Pointcut to other function, but not the defined. If the compiler code is inside the
defined function, it is impossible to retrieve Advice code.
This restriction was added in Release Candidate 3, and can deal with array definitions.
Optional attribute, but if defined, it restricts the Pointcut to all classes, except the listed ones.
This restriction was added in Release Candidate 3.
The implementation of multiple Pointcut Advice code is allowed in this attribute too.
The original release of Transparent PHP AOP covered just custom pointcuts. This is the basic - but powerful - functionality of this
package, that solves all your wishes while talking about include special behaviors in your class code.
A Custom Pointcut, usually written in a single word, is a Join Point (AOP theory) that enables you to define points that XAD can
incorporate custom Advice code. XAD must define the "name" pointuct attribute in order to include special Advice code in the
CustomPointcut.
A Custom Pointcut is defined using a PHP special comment, like this: /// Pointcut: XXX
Changing XXX to your pointcut desirable name, you have a Join Point defined. The following 2 codes describe a PHP Class and a XAD:
<?php class Test { ... function someMethod($arg1, $arg2) { // Some stuff here ... /// Pointcut: simpleCustomPointcut ... } ... } ?>
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE aspect SYSTEM "../aop.dtd"> <aspect> <pointcut name="simpleCustomPointcut" class="Test"> <![CDATA[ echo "Here!<br />"; ]]> </pointcut> </aspect>
This is the cute little baby of this package.
An Automatic Pointcut, usually called as AutoPointcut, is a Pointcut that strictly follows AOP theory. You do not need to define a PHP
comment in your class code. All you have to do is to define the Advice code in the XAD file. To be applied in an AutoPointcut, you must
have to use "auto" attribute of pointcut XML node. The "Quick Tutorial" section exposes exactly how to use this feature. Please move to
there and take a look at XAD file.
Simple as take a lolipop from a child. This is how I describe the expertise needed to compile and load the class file using AOP
approach.
Currently, you have your class inclusion using probably this: require_once "class.MyObject.php";
To incorporate the AOSD, you simply have to change this command to: require_aop("class.MyObject.php", "myXAD.xml");
I will explain in detail. the require_aop function takes 2 arguments:
So, I want to incoporate the "myXAD.xml" Aspect Definition file into my Original Class file "class.MyObject.php". First you have to
load the require_aop function (that includes the whole AOP system), by loading the "func.require_aop.php" file.
After that, you can load your class via require_aop function, something like this:
require_aop("class.MyObject.php", "myXAD.xml");
The whole example can be found in "Quick Tutorial" section, described in the beginning of this manual.
People usually ask my about the Compiler Options. I have to tell you that you do not need to modify Compiler Options, as soon as you are really sure of what are you doing. I enabled the Compiler Options just to simplify programmers life, but they maybe not recommended for production usage.
Default value: Original Class file directory. To reset its value, use AOP::CACHE(".");
If you define another directory to AOP using this property, all the compiled class codes will be created there. This is very useful if
you do not want to mantain all your folders with permissions to read/write. The argument to be passed is a valid directory.
Please remember that this property just configure the compiled files directory, it do not rewrite the current directory. For this,
take a look at getcwd and
chdir PHP functions.
Default behavior of this property is false.
If defined as "true", the Advice code will not be compacted, and lines reference between original class file and compiled file can
differ. This is a very useful tool if you are looking for PHP Advice code error, but it is not the ideal thing to use it in production
environment; although it will not overhead your system an will not implicate in load time. It is not recommended just because of the
changes in the lines.
Default behavior is false.
I really do not know why I added this. Maybe to satisfy "build on-the-fly" fans.
If you define this property as "true", every time your scripts runs, it will recompile the AOP compiled file, even if both files (XAD
and Original Class) have not been changed.