The decorator pattern provides us with a mechanism for adding functionality to objects at runtime, as an alternative mechanism to creating additional child classes.

In this article we’ll look at a simple use of the decorator pattern to help with sorting arrays, the example being a directory listing.

1)

Retouching objects

For some reason most discussions of object oriented programming begin with “imagine a car...”, a subject of no relevance to PHP unless perhaps you build a car classifieds website. The result is usually more confusing than helpful.

But just for a moment, this discussion will begin exactly the same way.

Imagine a car. A real car not a class representing a car. Once a car is built, paid for and and standing in the front drive (of our PHP mansion), what do we do if we don’t like the color? Unless we’ve got money to burn, rather than buying a new car we simply re-spray it.

The decorator pattern serves just this purpose. It allows us to do things to objects which have already been built (instantiated).

Enough of cars.

When using inheritance in classes, a general rule of thumb is to avoid going more than two generations deep. If we do, we’ll end up with an increasingly complex heirarchy and stuggle to make changes to to classes at the “top of the tree”.

For example;

<?php
class Parent {
    function foo () {}
}
 
class Child extends Parent {
    function bar () {
        $var=$this->foo();
    }
}
 
class Grandchild extends Child {
    function foobar () {
        $var=$this->bar();
    }
}
 
class Greatgrandchild extends Grandchild {
    function barfoo () {
        $var=$this->foobar();
    }
}
?>

If we make changes to the Parent::foo() method, we’ll need to check every descendant class. With large “family trees” pretty soon we have an unmaintainable nightmare on our hands.

It’s likely that many the of the descendants were created to add little pieces of functionality which couldn’t have been forseen when the parent was origionally defined.

The decoractor patterns offers an alternative approach, allowing us to add this functionality “on the side” without needing to create further children.

Skipping further theory as usual we’ll head straight to an example.

Decorating a list with sorting

Let’s say we have some classes we re-use to fetch a list of files in a directory.

So far we’ve got an abstract class FileList and two children; LocalDir for fetching a list from a directory in the local filesystem and FtpDir to get a list from a remote ftp server’s directory. Both children inherit directly from FileList like so;

/**
*  FileList
*  Abstract class for reading directories
*/
class FileList {
    /**
    * Path to directory in filesystem
    * 
    * @var  string
    */
    var $path;
 
    /**
    * Resource handle for file connection
    * 
    * @var  resource
    */
    var $handle;
 
    /**
    * A list of files in a directory
    * 
    * @var  array
    */
    var $ls;
 
    /**
    * FileList constructor
    *
    * @param string path (filesystem path to directory)
    */
    function FileList ($path) {
        if ( strrpos($path,'/') != 0 ) {
            $this->path=$path.'/';
        } else {
            $this->path=$path;
        }
        $this->ls=array();
    }
 
    /**
    * FileList::read()
    * Abstract
    * Reads the contents of a directory
    *
    * @return void
    */
    function read () {}
 
    /**
    * FileList::getNext()
    * Pops the first element of the file list array
    * and returns it
    *
    * @return string
    */
    function getNext () {
        return array_shift($this->ls);    
    }
 
    /**
    * FileList::close()
    * Abstract
    * Closes the resource handle
    *
    * @return void
    */
    function close () {}
}
 
/**
*  LocalDir
*  Reads the contents of a directory in the local filesystem
*/
class LocalDir extends FileList {
    /**
    * LocalDir constructor
    * Opens local directory
    *
    * @param string path (filesystem path to directory)
    */
    function LocalDir ($path) {
        FileList::FileList($path);
        $this->handle = opendir($this->path);
    }
 
    /**
    * LocalDir::read()
    * Reads the current directory
    *
    * @return void
    */
    function read () {
        while ($file = readdir($this->handle)) {
            if (!is_dir($this->path.$file)) {
                $finfo=array();
                $finfo['name']=$file;
                $fname=explode('.',$file);
                if ( !empty ($fname[1]) ) {
                    $finfo['type']=$fname[1];
                } else {
                    $finfo['type']='???';
                }
                $finfo['size']=filesize($this->path.$file);
                $this->ls[]=$finfo;
            }
        }
    }
 
    /**
    * LocalDir::close()
    * Closes the resource handle
    *
    * @return void
    */
    function close () {
        closedir($this->handle);
    }
}
 
/**
*  FtpDir
*  Opens ftp connection to read a directory
*/
class FtpDir extends FileList {
    /**
    * FtpDir constructor
    * Opens remote ftp directory
    *
    * @param string path (path from INITIAL login directory)
    * @param string host (ftp server hostname)
    * @param string user (ftp username)
    * @param string pass (ftp user password)
    */
    function FtpDir($path,$host,$user,$pass) {
        FileList::FileList($path);
    	$this->handle = 0;	
	    $this->handle = ftp_connect($host,"21");
	    ftp_login ($this->handle,$user,$pass);
	    ftp_chdir($this->handle, $this->path);
    }
 
    /**
    * FtpDir::read()
    * Reads the current directory
    *
    * @return void
    */
    function read () {
        $files=ftp_nlist($this->handle,'.');      
        foreach ( $files as $file ) {
            $size=ftp_size($this->handle,$file);
            if ( $size!=-1) {
                $finfo=array();
                $finfo['name']=$file;
                $fname=explode('.',$file);
                if ( !empty ($fname[1]) ) {
                    $finfo['type']=$fname[1];
                } else {
                    $finfo['type']='???';
                }
                $finfo['size']=$size;
                $this->ls[]=$finfo;
            }
        }
    }
 
    /**
    * FtpDir::close()
    * Closes the resource handle
    *
    * @return void
    */
    function close () {
        ftp_quit($this->handle);
    }
}

Here’s how we might use these classes normally;

<?php
// For a local directory
$dir=new LocalDir('/home/username/www');

// For directory on an ftp server
// $dir=new FtpDir('pub','ftp.server.com','username','password');

$dir->read();

while ($row=$dir->getNext()) {
    print_r($row);
}
?>

Note: for ftp the $path is relative to the directory in which we start when we login.

Now let’s say we need to add the ability to sort the results, so we can display the files listed, ordered by either the filename, the filesize or the filetype.

To do so we extend FileList again but this time with an abstract class called Decorator.

Here’s the code;

/**
*  Decorator
*  Abstract decorator for FileList
*/
class Decorator extends FileList {
    /**
    * An instance FileList (or child)
    * 
    * @var  object
    */
    var $fileList;
 
    /**
    * Decorator constructor
    *
    * @param object fileList (an instance of FileList)
    */
    function Decorator(& $fileList) {
        $this->fileList=& $fileList;
    }
 
    /**
    * Decorator::read()
    * Passes on request to parent
    *
    * @return void
    */
    function read () {
        $this->fileList->read();
    }
 
    /**
    * Decorator::getNext()
    * Passes on request to parent
    *
    * @return string
    */
    function getNext () {
        return $this->fileList->getNext();
    }
 
    /**
    * Decorator::close()
    * Passes on request to parent
    *
    * @return void
    */
    function close () {
        $this->fileList->close();
    }
}

The abstract Decorator is passed in instance of of FileList first then overwrites it’s parents methods to call the methods in the stored instance (note there are other, more optimized ways approach this which we’ll discuss later)

This may seem a little confusing but once we’ve seen it in action it will make more sense.

Now we create children of Decorator which provide the required additional functionality. These children make use of the legitimate children LocalDir and FtpDir’s methods but do “something else” (sorting the list) in addition. Because all are ultimately descended from FileList, everything looks and behaves in the same way at runtime.

/**
*  NameSortDecorator
*  Adds sorting by filename to the file listing
*/
class NameSortDecorator extends Decorator {
    /**
    * NameSortDecorator constructor
    *
    * @param object fileList (an instance of FileList)
    */
    function NameSortDecorator (& $fileList) {
        Decorator::Decorator($fileList);
    }
 
    /**
    * NameSortDecorator::read()
    *
    * @return void
    */
    function read () {
        $this->fileList->read();
        $sizes=array();
        foreach ( $this->fileList->ls as $row ) {
            $names[]=$row['name'];
        }
        array_multisort($names,SORT_ASC,SORT_NUMERIC,$this->fileList->ls);
    }
}
 
/**
*  SizeSortDecorator
*  Adds sorting by filesize to the file listing
*/
class SizeSortDecorator extends Decorator {
    /**
    * SizeSortDecorator constructor
    *
    * @param object fileList (an instance of FileList)
    */
    function SizeSortDecorator (& $fileList) {
        Decorator::Decorator($fileList);
    }
 
    /**
    * SizeSortDecorator::read()
    *
    * @return void
    */
    function read () {
        $this->fileList->read();
        $sizes=array();
        foreach ( $this->fileList->ls as $row ) {
            $sizes[]=$row['size'];
        }
        array_multisort($sizes,SORT_DESC,SORT_NUMERIC,$this->fileList->ls);
    }
}
 
/**
*  TypeSortDecorator
*  Adds sorting by filetype to the file listing
*/
class TypeSortDecorator extends Decorator {
    /**
    * TypeSortDecorator constructor
    *
    * @param object fileList (an instance of FileList)
    */
    function TypeSortDecorator (& $fileList) {
        Decorator::Decorator($fileList);
    }
 
    /**
    * TypeSortDecorator::read()
    *
    * @return void
    */
    function read () {
        $this->fileList->read();
        $sizes=array();
        foreach ( $this->fileList->ls as $row ) {
            $types[]=$row['type'];
        }
        array_multisort($types,SORT_ASC,SORT_STRING,$this->fileList->ls);
    }
}

Notice how the children define their own read() method which first perform the read() method on the “legitimate” instance of FileList then perform a further sorting action.

Taking the NameSortDecorator (which sorts the files by name) we can now use the classes like this;

<?php
// For a local directory
$dir=new NameSortDecorator(new LocalDir('/home/username/www'));

// For directory on an ftp server
/*
    $dir=new NameSortDecorator(
        new FtpDir('pub','ftp.server.com','username','password')
        );
*/

$dir->read();

while ($row=$dir->getNext()) {
    print_r($row);
}
?>

Notice how we’re first creating an instance for LocalDir then passing it to NameSortDecorator.

In terms of UML, the class structure looks like this;

Putting this into action, demonstrating the sort decorations on a local directory list;

<?php
error_reporting (E_ALL ^ E_NOTICE);
 
// Include the decorated classes
require_once('lib/FileList.php');
 
// Specify path to local directory here
$path="/home/username/mydir";
 
// Define an array to use for instantiating the right class
$sort=array(
    'name'=>'NameSortDecorator',
    'size'=>'SizeSortDecorator',
    'type'=>'TypeSortDecorator'
    );
 
if (!isset($_GET['sort'])) {
    $_GET['sort']=false;
}
 
if (isset($sort[$_GET['sort']])) {
    $dir=new $sort[$_GET['sort']](new LocalDir($path));
} else {
    $dir=new LocalDir($path);
}
$dir->read();
?>
<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title> Decorator Pattern Example </title>
<style type="text/css">
body, td, th {
    font-family: verdana;
    font-size: 13px;
}
th {
    font-weight: bold;
}
a {
    color: purple;
}
.alt {
    background-color: #f6f7f8;
}
</style>
</head>
<body>
<table>
<tr>
<th><a href="<?php echo($_SERVER['PHP_SELF']);?>?sort=name">Filename</a></th>
<th><a href="<?php echo($_SERVER['PHP_SELF']);?>?sort=size">FileSize</a></th>
<th><a href="<?php echo($_SERVER['PHP_SELF']);?>?sort=type">FileType</a></th>
</tr>
<?php
while ( $row=$dir->getNext() ) {
    if ( $alt == 'class="alt"' )
        $alt = '';
    else 
        $alt = 'class="alt"';
?>
<tr<?php echo (' '.$alt);?>>
<td><?php echo($row['name']);?></td>
<td><?php echo($row['size']);?></td>
<td><?php echo($row['type']);?></td>
</tr>
<?php
}
$dir->close();
?>
</table>
</body>
</html>

The decision as to which class to instantiate is taken here;

if (isset($sort[$_GET['sort']])) {
    $dir=new $sort[$_GET['sort']](new LocalDir($path));
} else {
    $dir=new LocalDir($path);
}

This takes advantage of PHP‘s ability to instantiate objects dynamically at runtime (saving us needing to use a switch statement). In other words the line;

$dir=new $sort[$_GET['sort']](new LocalDir($path));

Could equally have been;

$dir=new NameSortDecorator(new LocalDir($path));

The finished work looks like;

Refactoring

One thing to be aware of is the Decorator class we used is perhaps a little inefficient. It overwrites all it’s parent methods to make the calls correctly to a local instance. Really we want to re-use the methods defined in FileList unless we specifically need to over-write them in one of the children of decorator.

For the sake of demonstrating the point of the decorator this time, we’ve avoided doing this so far but a sketch example works something like this (note I’ve re-used the class names we’ve used already but this is purely to demonstrate how to avoid having to overwrite the parent methods in Decorator);

<?php
class FileList {
    var $instance;
    var $msg;
    function FileList () {
        // Assign this instance to local member
        $this->instance=$this;
    }
 
    function getModel() {
        return $this->instance;
    }
    function setModel(& $model) {
        $this->instance=& $model;
    }
 
    function setMsg($msg) {
        $this->instance->msg=$msg;
    }
    function getMsg () {
        return $this->instance->msg;
    }
}
 
class Decorator extends FileList {
    function Decorator () {}
}
 
class UpperCaseDecorator extends Decorator {
    function getMsg () {
        return strtoupper($this->instance->getMsg());
    }
}
 
$fileList= new FileList;
$fileList->setMsg('Hello World!');
 
$decorator=new UpperCaseDecorator();
$decorator->setModel($fileList->getModel());
 
echo ($decorator->getMsg());
?>

Now we no longer need to overwrite FileLists methods in Decorator. In a future article we may return to the decorator pattern and show how we can use the refactored version.

Uses

The decorator pattern can be applied to most situations where we have parent and child classes and where circumstances require additional behavoiur from the children but we want to avoid having to extend the children yet again.

One prime example is when rendering output, in particular forms. Say we have some form building classes capable of rendering the following output;

<form>
<input type="text" name="username" />
<input type="password" name="password" />
<input type="submit" name=" Login " />
</form>

That’s fine but the elements of the form aren’t laid out nicely. By using the decorator pattern we could render either of the following without needing to create children of our form classes for each situation;

<form>
<p><input type="text" name="username"></p>
<p><input type="password" name="password"></p>
<p><input type="submit" name=" Login "></p>
</form>

...and as a table...

<form>
<table>
<tr>
<td>
<input type="text" name="username">
</td>
</tr>
<tr>
<td>
<input type="password" name="password">
</td>
</tr>
<tr>
<td>
<input type="submit" name=" Login ">
</td>
</tr>
</table>
</form>

This approach works nicely with the widget approach we’ve looked at in other articles for rendering output.

Further Reading

Design Patterns Explained provides an example of the Decorator pattern in C++ as part of an E-Commerce case study application. Design Patterns of course. Javaworld on the Decorator with some nice examples

Update

[20th August 2003]

This implementation of the decorator pattern is not ideal. As mentioned in the comments below, the use of inheritance is a bad idea. My fault by trying to implement it “by the book” (GoF Design Patterns).

For a real example of decorators, used in PHP, check out PEAR::XML_HTMLSax 2.x +here that PHP5’s overloading might be used to implement a decorator. More on overloading (with PHP4) here


design/decorator_pattern.txt · Last modified: 2005/10/15 21:47