A PHP5 pluggable framework
based on the
POSA2 interceptor design
pattern
Lee Provoost
Software Technology Group
Department of Information and
Computing Sciences
University of Utrecht, P.O. Box
80089 3508 TB, Utrecht, The Netherlands
{l.provoost} @students.uu.nl
Abstract
The POSA2 interceptor design
pattern by Schmidt et. al allows us to plug in extra functionality in a
framework using predefined hooks. The original implementation is programmed in
C++ and can also be easily done in Java. With the growing popularity of dynamic
typed scripting languages like PHP5, it is interesting to see if such languages
are mature enough to implement complex design patterns from the object-oriented
world. The goal of this paper is twofold; first of all demonstrate how the
interceptor pattern works, but also to test PHP5Õs new object-oriented support.
I want to show that we can build complex applications using the former ÒtoyÓ
scripting language. Several language specific issues are taken in account, like
the lack of language built-in support for events in PHP5, dynamic typing, etc.
To make the framework more dynamic (and interesting), I implemented the
implicit interceptor pattern variant, which frees developers from the burden of
explicitly registering the plug-ins.
Keywords
Interceptor design pattern,
PHP5, framework
Introduction
The book ÒPattern-oriented
software architecture, Volume 2Ó by Schmidt et. al [1] is quite famous among
developers of distributed systems. Originally implemented in C++, there are
several implementations in Java available. With the growing popularity and
maturity of dynamic-typed scripting languages, it is interesting to see how
well we can implement complex patterns in these languages. IÕll take for this
project, PHP5 as an example, but it could have been Ruby on Rails or Python
also.
In this paper I will discuss
the implementation of the interceptor design pattern in PHP5. The interceptor
pattern allows applications and frameworks to be extended with extra services
without requiring changes to the core implementation. These services can be
added through predefined hooks. Because we can easily plug new services in our
framework, I will often refer to these extra services as plug-ins.
This project wants to address
two aspects. First of all trying to find out whether these dynamic-typed
scripting languages are suitable for developing complex frameworks, but also
showing people how you can implement your own framework that dynamically loads
plug-ins based on the interceptor pattern.
1. POSA2 INTERCEPTOR
PATTERN
1.1 Problem
We want to build an
extensible framework with the following three aspects:
á
Extending the framework
with extra services shouldnÕt require modifications to the core architecture.
á
Adding new services to
the framework should not affect or rely on other services.
á
The added services
should be able to access and modify the frameworkÕs internals.
1.2 Solution
We will make a framework
where extra services can be registered through predefined interfaces or hooks.
Services have to register themselves with a dispatcher who will notify them
when certain events occur. Services should be independent and should not be
aware of or take in account that other services might be registered with the
same dispatcher. To open up our implementation and allow changes, we will
provide context objects to the services that can be read and manipulated. These
context objects contain the data that is flowing in our framework.
1.3 Implicit interceptor
pattern variant
The original interceptor
pattern requires that when you extend the application with some concrete
interceptors, you have to register these interceptors with the dispatcher. This
makes sense at first, but limits the dynamic behavior of your
framework/application. It would be nice to just drop some plug-in into a
directory and let the application find them and register them automatically.
This can be accomplished with the implicit interceptor pattern variant.
A lot of famous applications
that allow plug-ins like Adobe Photoshop and Eclipse have dedicated plug-in or
extension directories where you can copy your plug-in or extension and they
will be detected and loaded at application load-time. But this dynamic behavior
comes at a cost: performance. Everyone can agree that starting up Photoshop or
Eclipse takes a while. However this performance issue is only present at
starting-up and once up and running, the program performs like a breeze.
Developing web applications,
however, requires a total different approach. Languages like PHP are stateless
and are interpreted at every invocation. When you apply the dynamic approach as
earlier described with Photoshop and Eclipse, then your web application will
detect and load each time your plugin directory. It is not hard to see that
this causes a serious performance penalty.
To minimize the load time
while still allowing dynamic loading of plug-ins, I opted for an intermediate
approach. I reserved a special plug-in directory where plugin writers can drop
their code, but they have to additionally configure a configuration file.
Instead of scanning a directory and figuring out how to use the plug-ins, I
require plug-in writers to provide all the information immediately in the
config.xml file.
<plugin>
<class>Logging</class>
<method>onPreSubmit</method>
<hook>onPreSubmit</hook>
<interceptor>Post</interceptor>
<filters>
<filter>BlogEntry</filter>
<filter>BlogComment</filter>
</filters>
</plugin>
Example of plug-in
configuration
The semantics of this configuration
file will be explained further in the paper when IÕll discuss the implmentation
details.
Although this intermediate
solution is quite nice, it still puts some overhead in the execution of the
scripts. Parsing XML files is usually an expensive operation. Some developers
shifted from XML configuration files to code configuration files. This means
that you require the plug-in writers to supply the information in PHP code.
This has the advantage that no parsing is needed anymore. The downside of this approach
is that such solutions are usually a little harder to understand and more
error-prone. If you develop your web application with performance in mind, IÕd
suggest using this code configuration file approach or just making your
application less dynamic. As I said before, everything comes at a costÉ
<?PHP
$plugin[0]["class"] =
"Logging";
$plugin[0]["method"] =
"onPreSubmit";
$plugin[0]["hook"] =
"onPreSubmit";
$plugin[0]["interceptor"] =
"Post";
$plugin[0]["filters"][0] =
"BlogEntry";
$plugin[0]["filters"][1] =
"BlogComment";
?>
Example of code
configuration file
1.4 Interceptor pattern !=
Aspect-Oriented Programming
An often-made mistake is that
the interceptor pattern is confused with aspect-oriented programming. At first
sight, they look very similar, but they have some significant differences.
1.4.1 How does
intercepting take place?
Both the intercepting pattern
and AOP are talking about intercepting, the former about events and the latter
about calls. The interceptor pattern requires some predefined hooks in the
framework where we can attach plug-ins too. So your whole application is made
with the goal of allowing other people to attach code and changing and
retrieving values to and from your context objects. AOP has a whole different
approach. AOP does not require being ÒAOP readyÓ. We can easily apply AOP
techniques on existing software, which were never written with AOP in mind.
IÕll give an example from the AspectJ project [2].
public class Demo {
static Demo d;
public static void main(String[] args){
new Demo().go();
}
void go(){
// implementation
details left out
}
}
Example of a standard Java
class
aspect GetInfo {
static
final void println(String s){ System.out.println(s); }
pointcut
goCut(): cflow(this(Demo) && execution(void go()));
pointcut
demoExecs(): within(Demo) && execution(* *(..));
Object
around(): demoExecs() && !execution(* go()) && goCut() {
println("Intercepted message:
" + thisJoinPointStaticPart.getSignature().getName());
printParameters(thisJoinPoint);
println("Running original
method: \n" );
Object result = proceed();
println(" result: " + result );
return result;
}
static
private void printParameters(JoinPoint jp) {
// implementation details left
out
}
}
Example of an aspect that
weaves around the go method
As you can easily see in the
above example, the class Demo is just a plain old java class with a method void go(). The aspect GetInfo wraps around the void go() method as you can see in the bold line.
1.4.2 When does
intercepting take place?
Another big difference
between the interceptor pattern and AOP is when intercepting kicks in. the
POSA2 interceptor pattern definition [1] states that intercepting takes place
at run-time. This makes sense because interceptors are triggered by certain
events in the system. Interceptors in AOP are usually woven into the system
right in the source code at compile-time or sometimes at startup-time of the
application. AspectJ for instance has a dedicated compiler that ÒinjectsÓ the
aspects in the class files.
The AOPHP project [3] aims to
bring aspect-oriented programming to the PHP world. They have a different
approach. They canÕt inject the aspects in some compiled PHP files because PHP
scripts are interpreted and not compiled. (LetÕs not consider caching systems
like the Zend Optimizer.) Their approach is to make use of the Apache
mod_rewrite module to redirect calls to PHP files to the AOPHP Java program
that does some code weaving. The authors are aware of the performance overhead
caused by this Java approach and are working on native C module for Apache2.
2. PHP5
2.1 New language features
When PHP5 was introduced in
2004, it came with an impressive new object model. It was finally possible to
program decent object-oriented programs with PHP. The predecessor, version 4,
has only very basic support for object-oriented programming. Basically it has
just the concept of classes and the most advanced OO operation you can do with
PHP4 is extending from a base class.
PHP5 introduced interfaces,
abstract classes, destructors, private and protected members and methods and
much more. Although it still doesnÕt reach the same level as Java, it is a very
big leap forward and we can finally build complex object-oriented applications.
My interceptor framework
would have been very hard to implement with PHP4. It would have been possible
but then we wouldnÕt have a generic and easy to extend framework.
For an extensive overview of
the new language features IÕd like to refer to the website of Zend [4].
2.2 Dynamic typing
There are huge debates going
on whether dynamic typing is a good thing or not. Scripting languages like
Ruby, Perl and PHP tend to have dynamic typing, but also compiled languages
like Visual Basic. A big difference with static typing is that type checking
takes place at runtime because variables can acquire different types depending
on the execution path. There is no explicit defining of types while programming
and this can lead sometimes to hard to find bugs. The PHP engine for instance
converts variables from string to integer type and vice versa where necessary.
Although this loosely typed
aspect works fine, in an enterprise environment you prefer to have a mechanism
to enforce stricter type checking to be 100 % sure. The new PHP5 introduces a
feature called Òclass-type hintsÓ where you can declare the expected class of
objects that are passed as parameters to a method. I applied this technique as
much as possible and while developing my framework I discovered a small bug in
my code when I introduced class-type hints.
Interface PostInterceptor {
static function onPreSubmit (Event_Notification $notification);
static function onPostSubmit (Event_Notification $notification);
}
Example of class-type
hints
In the above example you can
see that we have an interface PostInterceptor with two static
methods onPreSubmit and onPostSubmit, which require both
one argument. Although PHP programmers usually just give a variable name, I
prefer to use the class-type hint Event_Notification to
indicate that this method requires an Event_Notification
object. This is checked at run-time.
However I couldnÕt apply this
class-type hinting to all my methods. Unfortunately this does not work with
built-in types like strings and integers. This would have saved me some time
because at a certain moment I was sure that I passed a string while I was
actually passing a SimpleXMLElement object. An example when dynamic typing can
get nasty is when I printed that SimpleXMLElement object to my console for
testing purpose. Because my SimpleXMLElement object only contained a string,
PHP automatically converted it to a string, because the echo function of PHP triggered the conversion to a string type.
foreach
($plugin->filters->filter as $filter) {
if (in_array($filter, $allowed)) {
// register the allowed
interceptors
$this->_bd->register(
(string)
$plugin->class,
(string) $plugin->method,
(string) $plugin->hook);
echo
$plugin->class;
var_dump($plugin->class);
}
}
Example where dynamic
typing can go wrong
Logging
object(SimpleXMLElement)#14
(1) {
[0]=>
string(7) "Logging"
}
Output of the previous
code
In the above example you can
see that echo
$plugin->class nicely prints the
string value due to the conversion by the PHP engine. When you however use the var_dump() function, then you see what $plugin->class really contains. As the code snippet shows, we can
easily solve this by explicitly casting the type of $plugin->class from SimpleXMLElement to the built-in string
primitive. So putting type hinting of primitive built-in types would be a nice
thing to put on the PHP6 feature wish listÉ
2.3 Event_Dispatcher
One of the key aspects of the
interceptor pattern is that services are triggered automatically when certain
events occur. Unfortunately PHP does not have built in support for events like
Java. Luckily the Frenchman
Bertrand Mansion has released an Event_Dispatcher package in the PEAR
repository [5] that adds support for events in PHP. Although still in beta
version (version 0.9.1 at moment of writing), it is quite complete and most
important bug free.
Basically the
Event_Dispatcher package was implemented with the observer design pattern or
publisher/subscriber design pattern in mind. The Event_Dispatcher acts like a
notification dispatch table where client objects can register themselves as
observers of specific notifications by other objects.
The nice thing about the
package is that it acts more or less as the dispatcher described in the
interceptor pattern. It provides methods to register and remove observers and
the Event_Dispatcher object contains already a list of the interested parties,
if I may describe it like that. So this package really helped me in
implementing the PHP based interceptor framework and saved me some headaches.
Event_Dispatcher::getInstance() -- Create a new
Event_Dispatcher object
Event_Dispatcher::getName() -- Get the name of the
dispatcher.
Event_Dispatcher::addObserver() -- Add a new
observer.
Event_Dispatcher::removeObserver() -- Remove an
observer.
Event_Dispatcher::setNotificationClass() -- Set
the class that is used as notification.
Event_Dispatcher::post() -- Post a
notification.
Event_Dispatcher::addNestedDispatcher() -- Add a
nested dispatcher.
Event_Dispatcher::removeNestedDispatcher() -- Remove
a nested dispatcher.
Event_Notification -- Container class for
notifications.
Event_Notification::Event_Notification() -- Create
a new notification object.
Event_Notification::getNotificationName() -- Get
the name of the notification.
Event_Notification::getNotificationObject() -- Get
the object that sent the notification.
Event_Notification::getNotificationInfo() -- Get
additional information from the notification.
Event_Notification::getNotificationCount() -- Get
number of observers notified.
Event_Notification::cancelNotification() -- Cancel
the notification.
Event_Notification::isNotificationCancelled() -- Check,
whether notification has been cancelled.
Event_Dispatcher and
Event_Notification methods available in version 0.9.1
In the above list you can
find an overview of the available functionality as of version 0.9.1 of the
Event_Dispatcher PEAR package. I only needed four of the methods: Event_Dispatcher::getInstance() to get a singleton instance of the Event_Dispatcher
object. Event_Dispatcher::addObserver() and Event_Dispatcher::removeObserver() to
add and remove interceptors and Event_Dispatcher::post() to
define the interceptor points.
To make the integration of
the Event_Dispatcher package in my framework more transparent and to stick more
to the definition of the interceptor design pattern as described in the POSA2
book by Schmidt et. al, I wrapped this up in a Dispatcher class in my
framework. More details can be found in the next chapter that takes a closer
look at the implementation.
3. FRAMEWORK IMPLEMENTATION
3.1 Overview
I tried to stick as much as possible to the original POSA2
interceptor pattern definition. However I reorganized the code structure a
little bit to make it more clear and extensible. My PHPInterceptor framework
takes a simple blog post action as a case study to demonstrate how the
framework works. I should note, however, that this code is specific to my case
study. You should adapt the code to your own needs, because it is impossible to
make a generic interceptor pattern that can be used for every single
application.
The class diagram from my framework is more or less the same
as the general POSA2 definition class diagram. However I adapted the definition
a little bit to my needs and here is the directory structure that perfectly
represents the internal structure of my framework.
phpinterceptor
\--
ContextObjects
\-- post.php
\--
Dispatchers
\-- post.php
\-- dispatcher.php
\--
Interceptors
\-- post.php
\-- Library
\-- Event
\-- Dispatcher.php
\-- Notification.php
\-- library.php
\-- Plugins
\-- config.xml
\-- logging.php
\-- mailer.php
\-- preprocessor.php
\--
blogentry.php
\--
blogframework.php
Framework directory
structure
The reason why I have all these directories is that you can
group common functionality together. In this case you can group the
ContextObjects, Dispatchers and Interceptors together. The concrete
interceptors are in the plug-in directory together with the XML based
configuration file. The Library directory contains helper functions and the
Event_Dispatcher library. In the root level, we have our framework with one or
more specific applications.
Class diagram PHPInterceptor framework
The above class diagram gives a good overview on the relations
between the components in my framework. There are a few things that I want to
add. First of all, next to Logging and PreProcessor, we also have a Mailer
interceptor, but this was so trivial that I left it out. Then three things that
I could not do with the UML tool: The $_plugins variable in the BlogFramework
is of the type SimpleXML. The attachInterceptors method in the same class
should not have just a string argument, but an array of strings. Last but not
least, the Dispatcher class has a variable $_event_dispatcher. This should be
of the type Event_Dispatcher
3.2 blogframework.php & blogentry.php
The blogframework.php and
blogentry.php act as the Concrete Framework and Application from the POSA2
definition. The purpose of this distinction is to put common code and functionality
in the blogframework.php and the application specific code in blogentry.php.
PHP does not know the concept
of packages like Java. We have to explicitly include files with other code if
we want to access that code. The framework takes care of loading all the files.
On the one hand it loads specific files using the include_once function, but it also loads whole directories using
the static Library::includeDir function from the frameworkÕs library.
include_once("Library/library.php");
include_once("Library/Event/Dispatcher.php");
Library::includeDir("Interceptors");
Library::includeDir("Plugins");
Library::includeDir("Dispatchers");
Library::includeDir("ContextObjects");
Loading the files in the
framework
Two aspects are important
here. First of all the loading of single files. In PHP you can load files in
four different ways: include, include_once, require and require_once. The difference
between include and require is that the former loads the files in a lazy
fashion, i.e. only when it needs them. The latter loads the files eagerly, so
immediately at the declaration of the require function. I opted for the lazy
loading, because at initialization, my framework reads in already the XML
configuration file, so that would add more loading time. The difference between
the normal loading functions and the functions with the _once attached is that include_once and require_once load files only one
time, no matter how many times it is loaded. This is very useful when you
include several files that can include other files. The problem is that when
youÕre not using the _once version, you can load the files twice, resulting in
conflicts. PHP will sometimes complain that you try to define twice the same
variable or creating twice the same object. The second aspect is the loading of
whole directories with the Library::includeDir function.
Instead of explicitly loading all the files separately, my function scans the
whole directory for proper files. This adds of course much to the dynamic
behavior of the framework.
The blogframework.php
contains also a constructor that initializes the dispatcher and context objects
and loads the configuration file.
function __construct() {
//
create message dispatcher
$this->_bd = new PostDispatcher();
//
create message context
$this->_bc = new PostContext();
//
read in the plugins config file
$this->_plugins = simplexml_load_file('Plugins/config.xml');
}
Framework constructor that
initializes objects and configuration file
An interesting change between
PHP5 and PHP4 is the __construct() keyword. When you want to use a constructor in PHP4,
you have to use a function with the same name as the class name. So in this
case, the class name is BlogFramework, so my constructor had to be defined as: function BlogFramework().
The application BlogEntry
extends the BlogFramework and can hereby use the functionality provided in the
BlogFramework. In the application you take care of configuring the interceptor
attaching mechanism and calling the methods from your application.
function main () {
$this->attachInterceptors(array("BlogEntry"));
//
put info in message context
$this->_bc->setOwner("admin");
$this->_bc->setMessage("<b>HELLO
WORLD</b>");
//
preSend stuff
$this->_bd->preSubmit($this->_bc);
//
send message
$this->submitEntry($this->_bc);
//
postReceive stuff
$this->_bd->postSubmit($this->_bc);
}
Main execution code from
the application
The $this->attachInterceptors(array("BlogEntry")) function allows you to load the interceptors for this
application, while supplying some filters as argument. As earlier described,
you can supply some filters in the XML configuration file. A filter can be very
useful to limit the appliance of interceptors. Imagine that you have a mailer
interceptor and you only want certain applications to access this. Then you can
define in the configuration file the allowed classes. It can also work the
other way around. You can specify a filter ÒmailerÓ and say in your application
that you are only interested in mailer interceptors. Then you would be using
something like: $this->attachInterceptors(array("mailer")). The reason why you can supply an array, is that you
can specify more filters. You can say that you are interested in the logging
and mailer interceptors instead of only the mailer interceptor. This can be
very powerful to limit on the on hand the usage of the interceptors, but on the
other hand we can define a whole library of interceptors and just say in the
application what interceptors we want to use.
3.3 ContextObjects
An important way to open up
your implementation for the interceptors is the usage of context objects. These
are objects, containing data from the framework that can be passed to and can
be manipulated by the interceptors. Basically you can compare it with a plain
old Java object with getters and setters.
class PostContext {
private $_owner;
private $_message;
//
omitted some code
function getMessage () {
return
$this->_message;
}
function setMessage ($message) {
$this->_message =
$message;
}
}
Context object containing
information about the owner and content of a post.
3.4 Dispatchers
Our dispatcher configures and
triggers the concrete interceptors (or plug-ins). We have to define a
dispatcher for each type of interceptor. If we have a user centric interceptor,
then we have to define a user dispatcher. If we have a post centric
interceptor, then we have to define a dispatcher. Do not confuse with the
concrete interceptors! You do not have to define a dispatcher for each concrete
interceptor or plug-in!
In the PHPInterceptor
framework, the dispatchers share some common functionality like the constructor
and the register/remove functions. Therefore we put these three functions in a
base class and the specific dispatcher extends from this base class and adds
its own specific code to it.
class Dispatcher {
protected $_event_dispatcher
function
__construct() {
$this->_event_dispatcher = Event_Dispatcher::getInstance();
}
function
register ($target_classname, $target_methodname, $hook) {
$this->_event_dispatcher->addObserver(array($target_classname,$target_methodname),$hook);
}
function
remove ($target_classname, $target_methodname, $hook) {
$this->_event_dispatcher->removeObserver(array($target_classname,$target_methodname),$hook);
}
}
Dispatcher base class with
common functionality
Our dispatcher is, as you can
see, some kind of wrapper class around the Event_Dispatcher class from Bertrand
Mansion. The constructor gets a singleton instance from the Event_Dispatcher::getInstance() method. The register and remove methods call the
appropriate addObserver and removeObserver methods from the
Event_Dispatcher package.
Each specific dispatcher
extends this base class now and adds the hooks for the interceptors.
class PostDispatcher extends Dispatcher {
function preSubmit (PostContext $pc) {
$this->_event_dispatcher->post($pc, 'onPreSubmit');
}
function postSubmit (PostContext $pc) {
$this->_event_dispatcher->post($pc, 'onPostSubmit');
}
}
Concrete PostDispatcher
that extends from the Dispatcher base class
The preSubmit and postSubmit methods are called in
the application. Every time these methods are called, we notify the observers
who are interested in the onPreSubmit and onPostSubmit event. As you can
see, we supply also the PostContext object with more information about the
message. In PHP4 we have to use the ampersand (&) sign to say that we want
a Òpass-by-referenceÓ. In PHP5, objects are passed around by default Òby
referenceÓ. If you want to pass-by-value, then you have to make use of the
object cloning mechanism of PHP5 to pass a copy of the object. Because this
framework is fully targeted at PHP5 environments, we omit the & sign. The
usage of & is still allowed, but is discouraged because this will be
deprecated in the future.
3.5 Interceptors and
concrete interceptors
The POSA2 interceptor pattern
definition requires us to define an interceptor interface that the concrete
interceptors have to implement. For our PostInterceptor, it looks like this:
Interface PostInterceptor {
static function onPreSubmit (Event_Notification $notification);
static function onPostSubmit (Event_Notification $notification);
}
Interceptor interface as
required by POSA2
Each plug-in has to implement
these two methods. As you might have noticed, what we define is here what
happens at what kind of events.
class PreProcessor implements PostInterceptor {
static function onPreSubmit (Event_Notification $notification) {
$postContext =
$notification->getNotificationObject();
// convert dangerous
characters like < to <
$postContext->setMessage( htmlentities( $postContext->getMessage()
) );
}
static function onPostSubmit (Event_Notification $notification) {
$postContext =
$notification->getNotificationObject();
}
}
PreProcessor plug-in
The above example is a
preprocessor plug-in that transforms dangerous HTML characters. The lower then
Ô<Õ sign will be transformed to Ô<Õ. Instead of parsing the HTML
code, the tags will be displayed Òas isÓ. The onPreSubmit
method requires an Event_Notification object as argument and also forces this with
the class-type hinting mechanism. Because objects are passed by-reference by
default, we can manipulate the original content of the object. The get our
PostContext object out of the Event_Notification object, we need to execute the
getNotificationObject() on the Event_Notification object. Then we can execute
the methods available in our context object (usually the getters and setters).
Here we transform the message content with the PHP htmlentities() function and overwrite the original message with our transformed
message.
Now that we have made a
concrete interceptor or plug-in, we still need to configure it in our
framework. As earlier described, this can be done with the confix.xml file
located in the Plugins directory.
<plugin>
<class>Logging</class>
<method>onPreSubmit</method>
<hook>onPreSubmit</hook>
<interceptor>PostInterceptor</interceptor>
<filters>
<filter>BlogEntry</filter>
<filter>Logging</filter>
</filters>
</plugin>
Entry in our config.xml
file
The configuration file is
quite straightforward, but very powerful. First of all you have to define the
location of the plug-in or concrete interceptor. You supply the class name and
the method name. You need to associate the plug-in with an event also. This
event that we are interested in, is the hook. In this case I opted to use the
same name for the method and hook because this is the easiest way, but this
does not need to be the same. The interceptor defines, as the name implies, to
which interceptor you want to associate this concrete interceptor. In your
application code, you extend from this interceptor then.
Last but not least we have
the filters that we mentioned already earlier. These are very powerful and you
can do almost anything you want with it. I choose here to provide filters that
tell what application can use the filter and what kind of filter it is. The
reason is that at plug-in load time I can for instance say that I want to
register all the plug-ins allowed for this application, or that IÕm just
interested in the logging plug-ins. You can define here as much filters as you
want.
3.6 How to get started
with the PHPInterceptor framework?
Of course it makes sense that
you first try to understand the POSA2 interceptor pattern. Once you reached
that point, basically you just need to understand how to make plug-ins and
configure them properly. The easiest way is to piggyback on the current code
base. Once plug-ins are done deal, itÕs quite straightforward to make your own
framework based on my code base and on this paper.
4. FUTURE WORK
I have now a fully working
version of the framework available. I still need to add decent documentation
using the phpDoc standards and supply unit tests. The current code base could
be extended with some nice user examples. It would be interesting to see what
kind of applications will be built on top of the framework. I am a little
worried about the performance in a high-load web environment, but IÕm pretty
sure that the PHPInterceptor framework would operate perfectly in a PHP+GTK
desktop application environment. Because I believe that this code can be useful
for other people, I will be released soon on sourceforge.net.
(http://sourceforge.net/projects/phpinterceptor)
5. CONCLUSION
Applying the interceptor
pattern in a framework is very interesting if you want to support plug-ins.
With some adaptations, as in the PHPInterceptor, you can make it really dynamic
and extensible. This project/paper shows that developing complex applications
is possible with PHP5, although it requires a thorough knowledge of the
language and object-oriented programming. I believe that this framework has a
future in desktop-based PHP+GTK applications. If you are willing to sacrifice
some of the dynamic behaviors of the framework, then it can be applied also in
a high-load web application environment.
References
[1] Pattern Oriented Software
Architecture, patterns for concurrent and networked objects, Volume 2. Douglas
Schmidt, Michael Stal, Hans Rohnert, Frank Buschman. ISBN 0471606952. John
Wiley & Sons. 2000. 101-125
[2] Eclipse AspectJ project. http://eclipse.org/aspectj/
[3] Aspect Oriented PHP: A
PHP extension for aspect oriented programming. http://www.aophp.net/
[4] Changes in PHP5/Zend
Engine II by php.net. http://www.zend.com/php5/articles/engine2-php5-changes.php
[5] Bertrand Mansion. PEAR
Event_Dispatcher package. http://pear.php.net/manual/en/package.event.event-dispatcher.php