Building proxies, decorators and delegates in PHP5
July 3rd, 2006 by Ivo
In PHP5, you can define methods in an object for intercepting calls to methods of an object and for intercepting access to object member variables. These methods (__get, __set and __call) enable the implementation of very generic proxies, decorators and delegators.
(For those unfamiliar with these design patterns, the extensive descriptions at wikipedia are worth the read.)
What proxies, decorators and delegates have in common is that they all change a part of the behaviour of an underlying object by overriding its methods, while forwarding the rest of the calls to the original object.
I wouldn't like this post to be purely theoretical, so let's use a simple example with decorators to demonstrate the concept.
Suppose we have the following class:
class HelloWorld { function sayHello() { return "Hello World"; } function doSomethingElse() { } } $obj = new HelloWorld(); echo $obj->sayHello(); // "Hello World"
We would also like to create classes that decorate the hello-saying. I want to have a decorator that makes the output bold, and another one that makes the output italic. (You could create a class that extends HelloWorld, but more in this later. For the moment, assume that inheritance is not an option.)
In classic code, if I wanted to decorate the sayHello method, I could create a decorator object that looks like this:
class BoldHelloWorld { var $m_object = NULL; function BoldHelloWorld(&$object) { $this->m_object = $object; } function sayHello() { return "<strong>".$this->m_object->sayHello()."</strong>"; } function doSomethingElse() { return $this->m_object->doSomethingElse(); } } $obj = new BoldHelloWorld(new HelloWorld()); echo $obj->sayHello(); // "<strong>HelloWorld</strong>"
This would work like a charm, but the problem is in the doSomethingElse method. Well, not really a problem, it's just that I have to redefine it to forward the call to the original object. What if we had 5 methods, or 10? All of them would need to be forwarded.
What if there are member variables in place that should be accessible? You'd have to forward those too. (I know, exposing member variables is Evil, but sometimes the object in question is not developed by yourself and you just have to deal with it.)
PHP5 has excellent ways to solve this, and fairly easy too. It allows us to build a generic base class for our decorators that takes care of the whole forwarding problem. (Credit for this class goes to my coworkers Martin and Peter).
/** * AutoForward baseclass for automatic forwarding of * method calls and member variables. * * @author Peter C. Verhage * @author Martin Roest */ class AutoForward { var $m_object; /** * Constructor. * * @param Object $object */ function __construct(&$object) { $this->m_object = $object; } /** * Returns the forwarded object. */ function &__getObject() { return $this->m_object; } /** * Forward method calls. * * @param String $method method name * @param Array $args method arguments * @return Unknown method return value */ function __call($method, $args) { return call_user_func_array(array($this->m_object, $method), $args); } /** * Forward property set. * * @param String $name property name * @param Unknown $value property value */ function __set($name, $value) { $this->m_object->$name = $value; } /** * Forward property get. * * @param String $name, property name * @return Unknown */ function __get($name) { return $this->m_object->$name; } }
By overriding __set, __get and __call, any methods to an object of the AutoForward class are automatically forwarded to the contained object.
Let's use this class to create our bold and italic decorators:
class BoldHelloWorld extends AutoForward { function sayHello() { return "<strong>".$this->m_object->sayHello()."</strong>"; } } class ItalicHelloWorld extends AutoForward { function sayHello() { return "<em>".$this->m_object->sayHello()."</em>"; } } $obj = new ItalicHelloWorld(new HelloWorld()); echo $obj->sayHello(); // "<em>Hello World</em>"
Notice how we no longer defined the doSomethingElse method? It is automatically forwarded to the original object. In fact, an object of type ItalicHelloWorld will behave exactly like a HelloWorld object. Any caller wouldn't know the difference. Except ofcourse that sayHello returns a manipulated result.
I promised to explain why inheritance wouldn't be the solution here. By providing both an ItalicHelloWorld and a BoldHelloWorld, I've tried to demonstrate this. What if you needed a string that was both bold and italic? Which of the two would you extend? PHP doesn't have multiple inheritance, so we would be stuck there.
But not in our example. It would simply be a matter of:
$obj = new BoldHelloWorld( new ItalicHelloWorld( new HelloWorld())); echo $obj->sayHello(); // "<strong><em>Hello World</em></strong>"
Using this approach, we've worked around the need to have multiple inheritance.
This technique is also useful if you are a fan of Aspect Oriented Programming. You could have an object that implements a certain aspect and applies that to the underlying object. Instead of overriding a single method, you can override __call and implement an aspect.
Let's take security as an example. Suppose we have an object, but we want to secure access to any of its members. We could use the following class:
class SecurityAspect extends AutoForward { function __call($method, $args) { if (isAllowed($method)) { return call_user_func_array(array($this->m_object, $method), $args); } throw new Exception("Caller not allowed to execute $method on this object."); } } $obj = new SecurityAspect(new HelloWorld()); $obj->sayHello(); // throws error if sayHello not allowed
Another aspect that can easily be implemented like this is caching of resource intensive methods. Just add a list of methods to cache, and a member variable to hold results for cached methods.
With this post I've tried to demonstrate the power of PHP5's __call, __get and __set special functions. Java has similar solutions using reflection, but PHP5 makes it much more easy to create constructs like this. This also makes it possible to easily create patterns with small amounts of code.
Feel free to use the code samples in this post. You can link back to this post from your code to explain how the code works, if you like.
I know Kung Fu
June 17th, 2006 by Ivo
This week I visited the Holland Open Software Conference. It was a nice conference, I will write a review later.
What I want to talk about now though, is what happened to me during this trip.
Before I begin, you should know that I am a die hard PHP fan, and I have been using PHP for 6.5 years now. If there is one thing that I've learned during this time, it's that PHP rules.
For a few months now however, friend and colleague Peter has been nagging me to check out Ruby. While I have been working mostly in PHP, he has used Ruby for the backend of epointment.com. I had looked at his code before, but it didn't catch on. It eyed weird, had funny-looking constructs (Ruby's lack of braces reminded me of Visual Basic even) and it was surrounded by a lot of hype, which often is a bad sign. It didn't even have a '++' operator. Get real.
Then he gave me this book they call the 'Pickaxe'. I tossed it on my desk, near the rest of the things-to-do-which-never-get-done.
But now I was going to this conference, which was a 2.5 hour train trip on both conference days, so I decided to check out the book to kill time while on the train.
The first day I read about 30 pages into the book, and I was intrigued. I read about how everything is an object, how even primitive types are objects. I read about code blocks (which turned out to be the funny constructs I didn't understand when reading Peter's code earlier), about the ability to redefine operators (which I remember from C++), about its regular expression support and about how Ruby encourages application of OO principles like encapsulation and loose coupling.
I had my laptop with me but no Ruby installed, so I wasn't able to try any of the code, but I was intrigued nevertheless.
At night, I decided to install Ruby so the next day I was able to actually hack away a bit. I read more in the book. I delved into collections, found out how they handle looping and how iterators are an intrinsic feature of the language. Around 100 pages into the book, I was hooked. I was writing classes, trying out all these language features, and it was great fun. I hadn't read nearly all of it yet, but at one point, I needed to get something done in my code, and without looking it up in the book, I just typed what I thought was the right way. I thought 'oh well, let's see what this does' and ran the script; and it simply worked. It didn't even generate a parse error. It was handled exactly the way I had expected it. This happened on 2 more occasions. So Ruby seems to be quite intuitive; if you have an idea of how the language is set up, what its approach to object orientation is, you can almost guess how you should accomplish things. So there I was, enjoying a new language. It felt like the first time I used PHP, back in 1999. Ik was astounded with the ease with which you could write programs (something which I never felt with C, C++ or Java).
I nearly missed my stop, and when I bagged my stuff and stepped out of the train onto a sun-engulfed platform, one thought crossed my mind.
"Whoa, I know Kung Fu".
It really felt like that. It was as if in a few hours, a whole new approach to programming was uploaded into my brain. In essence it was just a new scripting language I had just learned, but it merged perfectly with everything I ever learned about object oriented programming. I realised why they call it 'Ruby'. It is this clean language that has a pragmatic approach to object orientation that I haven't encountered before in any language.
And then, during the short walk from the station to the conference premises, I was reflecting on what had just happened. In my mind I was already looking at how I could do certain features of ATK more cleanly in Ruby. I was comparing how things are done in PHP and how the same things are done in Ruby. I marvelled at how Ruby fits ATK's "less code" philosophy.
And then I got scared.
What if I can't find any aspect of PHP that is better than Ruby? What if the only reason to still use PHP is 'that it is more widely used'? Surely that will not be the case, right? I have been using PHP for 6.5 years and it has never let me down. I am not crazy, am I? Ruby seems to be this shiny apple, but will eternal damnation come my way if I eat it? is Ruby the Red Pill? Will this be the beginning of the end? Will I switch from PHP to Ruby? This question has been bothering me since I got back from the conference.
After a good night of sleep, I've concluded that I will use both. PHP feels like this jeans that I have been wearing for years. It's worn out, but it's still my favorite jeans. It just has the right fit. I have other newer jeans that are less worn, but they still are not as comfortable as my old one. Although this Ruby jeans feels damn comfy, too.
But my jeans are not mutually exclusive. Neither are Ruby and PHP. So I'm going to use both. I'll use PHP here, and Ruby there. Let's just see what the future will bring. I will consider making a Ruby version of ATK (on top of the Rails framework), so I have two options when developing web applications. I hope to continue to be part of the PHP community for a long time, and I hope I will become part of the Ruby community too. Surely they'll accept me even if I still use PHP a lot.
In any case, one thing this tale has left me with is a deeper resolve for object orientation. I hope that for PHP 6 and 7, they will look more to their sister Ruby than to their predecessors Java and C++. Because Ruby knows how object oriented programming should, and can, be done.
How a PHP notice revealed a quirk of Norton Internet Security
April 4th, 2006 by Ivo
I had a strange thing just now.
I was working on a website which was working just fine and notice free.
Out of the blue, my website had the following notice:
Notice: Undefined index: HTTP_ACCEPT_ENCODING in /mnt/clusterdata/home/ivo/beta.epointment.nl/atk/ui/class.atkoutput.inc on line 153
I thought 'Que?!', as I have not modified the code in class.atkoutput.inc in weeks, and certainly not tonight. The code in question:
if (atkconfig("output_gzip") &&
phpversion() >= '4.0.4pl1' &&
(strstr($_SERVER["HTTP_USER_AGENT"],'compatible') || strstr($_SERVER["HTTP_USER_AGENT"],'Gecko')) &&
strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')
)
{
header('Content-Encoding: gzip');
echo $this->gzip($res);
}
else
{
echo $res;
}
This code worked in all browsers, for years, without notices, because the HTTP_ACCEPT_ENCODING header is usually set for most major browsers.
Then I realised that simultaneously I was installing Norton Internet Security on my new laptop (a very cool Toshiba Tecra M4 tablet PC, thank you Ibuildings!). It turned out not to be a coincidence. Apparently, Norton Internet Security puts itself between my browser in the network and intercepts headers. It seems to remove the HTTP_ACCEPT_ENCODING header from any browser request.
That explained the sudden appearance of the notice.
The (undocumented?) side effect is that with Norton Internet Security active, no page will be send gzipped. This is a performance penalty I think. They probably do it to be able to scan the text before it arrives in the browser (unzipping, scanning and rezipping would probably take too much time).
I'll be turning off such features in Norton Internet Security anyway, but it was funny how a PHP notice revealed this.
What php search engine would you use?
March 8th, 2006 by Ivo
I'm facing a challenge with a customer running a site we built on a hosting environment that is quite restrictive. We used to use htdig to add search functionality, but the hoster doesn't allow execution of binaries.
I've taken a look at several search engine implementations, but so far, none match all requirements:
- Zend Framework's lucene search: requires PHP5, but we have to run it on PHP4
- phpdig: uses exec() to perform the search, but we are running in safe mode
- google: layout not flexible enough
- htdig: requires execution of binaries, which is not allowed
- perlfect: requires execution of perl, which is not allowed
Maybe isearch is an option but I cannot find a lot of info about it.
The requirements are simple, but restrictive:
- Should be usable/integratable in a PHP4 based site
- Should run with safe mode on
- Layout should be customisable
- Should not require execution of perl or binaries, at least not for the search part (uploading an index that is created on a separate machine is somewhat acceptable although not prefered)
- Should be spider based, so no database query based search engine
Free Software/Open Source/Free as in Free Beer would be nice, but is no strict requirement.
Anybody any suggestion? What are you guys using?
Defensive Programming
January 27th, 2006 by Ivo
A few weeks ago, we had a major problem with software we'd written for a client. It was software for sending mailings to the client's customers. Suddenly there were many reports of clients receiving multiple mailings instead of just one.
The problem appeared to be in our test code. The software had a 'test' mode for testing the mailing by sending it only to the author and a small test team. It appeared that for some reason, all test mails were being mailed to the customers as well.
This problem would not have appeared if we had applied what I would like to call 'defensive programming'. Take a look at this example, which is an oversimplified version of how the software worked:
.... if ($mode=="test"} { $recipients = "testers@somedomain.ext"; } else { $recipients = "customers@somedomain.ext"; }
What happened was that due to some change in the software, this particular piece of code, along with a bunch of other code, was refactored into some other function, where the $mode variable ran out of scope. Result, $mode=="test" is never true, and we send all the test mailings to all our customers.
The defensive programming approach would be like this:
.... if ($mode=="production"} { $recipients = "customers@somedomain.ext"; } else { $recipients = "testers@somedomain.ext"; }
Would the same problem appear, the bug would have been less awkward, as only the testers would receive the mailing. Ofcourse this doesn't solve the actual bug, but it helps fight bad results.
In essence, you have to expect the worse. Write your code with Murphy in mind. Anything that can go wrong, will go wrong at some point. Especially when relying on (global) variables that are defined at some distance from the code where they are used, so it's easy to not notice the problem until it's too late.
P.S. We've released ATK 5.4 this week and, [shameless plug], my little .com adventure epointment.com has gone live this week in a very basic first version [/shameless plug]. For those interested: the epointment site is entirely written in ATK, while the backend server was written using Ruby on Rails, linked together via SOAP. What better way to compare ATK with Ruby on Rails
The danger of Fluent interfaces
December 30th, 2005 by Ivo
After Martin, Mike and Paul have demonstrated the usefulness of Fluent Interfaces, I'd like to take a look at the downside.
Ironically, Martin's original example already demonstrates the problem:
customer.newOrder() .with(6, "TAL") .with(5, "HPK").skippable() .with(3, "LGV") .priorityRush();
newOrder could be implemented in two ways:
a) Create an order and return that new order
b) Add an order to this customer and return the customer
b is the fluid approach, a is more logical. In this example it's a.
This can lead to confusion; the 'with()' method employs method b. If you think that newOrder returns an Order, and hence 'with()' is a method of the Order class, look at 'skippable'. Martin states that order lines are skippable. So what's in front of skippable() is an OrderItem. That means that with() must have returned an OrderItem. If that is true, then with() is a method of OrderLine too!
So this means that in this case, probably both Order and OrderLine have a with() method. Apart from the fact that a method named 'with()' is dangerous as this can mean anything (which might be problematic for the non-fluent programmer), apparently in this case we have to add additional methods (both to Order and OrderLine) in order to be able to use this fluently.
priorityRush further demonstrates this. Where the second with() returned an OrderLine, the third with() now returns the order as priorityRush() is a method of Order.
While this *looks* more readable, it is actually confusing. Or perhaps priorityRush is also a method of OrderLine, that forwards the call to its order. That would only further clutter things up as now OrderLine and Order seem to have a similar API. OrderLine has a priorityRush() method that doesn't belong there, or Order has a skippable() method that doesn't belong there .
It becomes more clear if you write out the API that is needed to create this example:
class Customer public Order newOrder() class Order public OrderLine with(int quantity, String code) public Order priorityRush() class OrderLine public OrderLine with(int quantity, String code) public OrderLine skippable() public Order priorityRush()
To create such an API only to be able to write more english-like sentences in code is, in my opinion, bad practice.
Only use this when it makes sense. If you like, make an add() method return the added item, so you can write stuff like:
item = table.add(new Item()); item.setBorder("red");
But not for readability purposes.