General OO practise is to extend classes to implement new behaviour. But sometimes that’s just not possible, for instance when you want to change how a class behaves during runtime. One of the solutions in such a case is to code a decorator (see wikipedia).
Here is a short example of a decorator implemented in PHP, as a small reminder. (If you want to understand more about the decorator pattern I recommend you to read the wikipedia pages on the topic). It’s a very basic text generator for a magazine company. Sometimes the editor of the magazine wants texts to be printed in capital letters. At such a time a decorator comes in handy:
class Text { protected $text = "TestText"; public function get() { return $this->text; } } // An extension for more specific texts class Article extends Text { protected $text = "ArticleText"; } // This is the actual decorator. It decorates the // Text class to only print in upper case class UpperCaseText extends Text { protected $text = null; public function __construct(Text $text) { $this->text = $text; } public function get() { return strtoupper($this->text->get()); } } $text = new Text(); // $text->get() == "TestText" $upText = new UpperCaseText($text); // $upText->get() == "TESTTEXT" $article = new Article(); // $article->get() == "ArticleText" $upArticle = new UpperCaseText($article); // $upArticle->get() == "ARTICLETEXT"
I recently used the decorator pattern to decorate the sfWidgetFormSchemaFormatter class of the symfony framework. This class is responsible for formatting forms and I wanted to change the behaviour of the ‘->generateLabel()’ method. Using a decorator modified the behaviour of the method so that it doesn’t output a label (like ‘<label>—the label—</label>’) but it outputs an input for the label (like ‘<input type=”text”>—the label—</input>’).
There’s one big problem though when coding a decorator: you need to forward the full public api of a class. When decorating core classes of a framework, this ends up to be really hard and not easily maintainable. Take for instance the sfWidgetFormSchemaFormatter class which has 27 public methods, a lot of work and very error prone!
Luckily the reflection library of PHP comes to help here. As the Reflectionclass::getMethods() is able to produce an array of all methods inside a class, it’s possible to write a decorator class by script. This is exactly what I did and is now available as a symfony plugin called sfDecoratorPlugin.
The plugin gives you a cli-task to generate a decorator class in your lib-dir and (more important) adds an autoloader which generates decorators on the fly. The autoloader watches for classes being instantiated which fit the ‘<<class to decorate>>Decorator’ string pattern. When it finds such a class, it analyses the class you want to decorate and writes a full decorator class to a cache PHP file. This allows you to easily decorate even the most complex core classes.
As a final example a snippet of my decorator to the sfWidgetFormSchemaFormatter class:
class sfWidgetFormSchemaFormatterDecoratorArray extends sfWidgetFormSchemaFormatterDecorator { public function generateLabel($name, $attributes = array()) { $labelName = parent::generateLabelName(); $input = new sfWidgetFormInputText(); return $input->render('_key['.$name.']', $labelName, array(), $attributes); } }
The autoloader of sfDecoratorPlugin recognised that extending the sfWidgetFormSchemaFormatterDecorator class meant that it had to produce a decorator. In the cache the sfWidgetFormSchemaFormatterDecorator.class.php file now contains:
/** * Decorator to class sfWidgetFormSchemaFormatter, automatically generated by sfDecorator */ abstract class sfWidgetFormSchemaFormatterDecorator extends sfWidgetFormSchemaFormatter { protected $object = null; function __construct(sfWidgetFormSchemaFormatter $object) { $this->object = $object; } /** forwards to class sfWidgetFormSchemaFormatter (autocode by generate:decorator) */ public function translate($subject, $parameters = array()) { $result = $this->object->translate($subject, $parameters); return $result===$this->object ? $this : $result; } /** forwards to class sfWidgetFormSchemaFormatter (autocode by generate:decorator) */ public function generateLabel($name, $attributes = array()) { $result = $this->object->generateLabel($name, $attributes); return $result===$this->object ? $this : $result; } /** forwards to class sfWidgetFormSchemaFormatter (autocode by generate:decorator) */ public function generateLabelName($name) { $result = $this->object->generateLabelName($name); return $result===$this->object ? $this : $result; } // ... AND SO ON for all 27 methods... function __clone() { $this->object = clone($this->object); } }
Good luck decorating yourself! Thanks for reading my post.
That’s clever! I take.