woensdag 9 september 2009

Decorators in PHP

Introduction

In this blog I will try to explain what an decorator is and when to use it. After this I will show you how to make an decorator in PHP. Then how to write an general abstract decorator class and a useful concrete class based on the abstract class.

What is a decorator?

A decorator is a design pattern, which can dynamically add and remove functionality from an existing class.

When to use it?

You usually use this in two situations.

The first one is that you cannot change the behaviour of the original class. This is very uncommon in php, but sometimes licenses won't permit changing some classes, which exhibits some annoying behaviour. By writing a decorator for this specific class, we can change or even remove the behaviour.

The second case is more common. We want to change the behaviour of our class dynamically depending on the situation. For example we write an class with outputs xml data. This class can have an getOutput method. By creating a decorator for this class, we can change the output provided by the getOutput method. For example we can create an decorator, which captures the getOutput from the original object and transform it to html.

How to create our xml decorator.

First we need a class, which outputs some xml for us. We create a very simple class for this:

class Bookoutput {
public function getOutput(){
return '<books>
<book>
<title>Booktitle</title>
<abstract>Abstract</abstract>
<author>Author</author>
</book>
</books>';
}
}


Bookoutput knows one method and that is getOutput, which returns some hardcoded xml. To create a decorator, we have to define a class, which accepts an Bookoutput object in its constructor and stores it, so we can call it later in our methods. The class must also have the getOutput method like the Bookoutput class to preserve its interface:

class BookToHTMLDecorator {
protected $xml;
public function __construct(Bookoutput $xml){
$this->xml = $xml;
}
public function getOutput(){
$output = $this->xml->getOutput();
$xml = simplexml_load_string($output);
return $this->transform($xml);  

}
protected function transform($xml){
$out = '<table><tr><th>Title</th><th>Author</th><th>Abstract</th></tr>';
foreach($xml as $book){
$out .=  "<tr><td>" . $book->title ."</td><td>"  . $book->author . "</td><td></tr>";
}
$out .= "</table>";
return $out;
}
}


We can use the class as follow:

$p = new BookToHTMLDecorator(new Bookoutput);
print $p->getOutput();
/* Output:
<table><tr><th>Title</th><th>Author</th><th>Abstract</th></tr><tr><td>Booktitle</td><td>Author</td></tr></table>
*/


It is important to choose good names for your decorators, because once you got the concept, their number will grow fast.

How to generalise the concept?

Writing decorators in the above way is no problem, when the number of methods in a class is small. But when I want to decorate a huge class with a billion methods, I probably want to do it another way.

Fortunately we can create an abstract decorator class, which propagate method calls magically to the decorated object, if the method doesn't exist in the original class:

abstract class GeneralDecorator {
/* decorated object */
protected $object;
public function __construct($object){
if(!is_object($object)){
throw new Exception('Needs to be an object');
}
$this->object = $object;
}
public function __call($method, $args){
call_user_func_array(array($this->object, $method), $args);
}
public function __set($key, $val){
$this->object->$key = $val;
}
public function __get($key){
return $this->object->$key;
}

}


We use the magic methods (__get, __call and __set) php is providing to let the GeneralDecorator class behave exactly the same as the object we put in. Now we can implement some concrete classes. In this case I will implement an decorator, which add ArrayAccess to any class:

class ArrayAccessDecorator extends GeneralDecorator  implements ArrayAccess {
public function offsetSet($key, $value){
$this->__set($key, $value);
}
public function offsetGet($key){
return $this->__get($key);
}
public function offsetUnset($key){
$this->__set($key, null);
}
public function offsetExists($offset){

$test = $this->object->__get($offset);
if($test !== null){
return true;
} else {
return false;
}
}


}


I have use a similiar class when I had a controller which passed array's to its templates. So the page title was accessible like: $parameters['title']. After some time I changed the template parameters into objects, so I could add methods to it, if I wanted. But this created a problem. I had to change all the templates (boring), add array access to the object (unwanted, in newer templates I don't use the old syntax) or create a decorator which does this. Which was the perfect solution. I can load it on demand and I can see there is something funky going on (the code warns about the old syntax the templates use.

Example usage of the class:



$template = (object)array('test' => 'bla', 'bla' => '1', 'option' => '3');
$template = new ArrayAccessDecorator($template);
print $template['bla'] . "\n";
print $template['test'] . "\n";
print $template['option'] . "\n";
/* 1 
3
bla 
*/


Next time I will show you a couple of handy decorators for php and how to implement the concept in javascript (also oo-oriented, but prototype based, not class-based).