DI52 decorator pattern support 01

Thinking about decorator support for the DI52 package.

A PHP 5.2 compatible Dependency Injection container

The DI52 library was born out of my frustration of not being able to find a PHP 5.2 compatible package I could use to leverage the dependency injection container pattern in WordPress plugins.
The PHP 5.2 back-compatibility requirement sure limits the access to many libraries and I needed that.
Heavily inspired by the Laravel DI container the library exposes a similar syntax but, I guess, a different implementation.
While the library has covered all my needs up to this point I’ve hit a wall while trying to implement the fairly simple decorator pattern.

Decorators

A code example of what the decorator pattern might be in a PHP and WordPress application is one where a basic class is in charge of filtering a post title:

class TitleFilter implements TitleFilterInterface{
    public function filterTheTitle($title = '', $id = null){
        return ucwords($title);
    }
}

In its basic implementation the class is simply uppercasing the title words and returning it.
As the decorator pattern goes I might want to modify that behaviour prepending something before the title if the post is categorized under the “archived” category:

class ArchiveTitleFilter implements TitleFilterInterface{

    private $titleFilter;

    public function __construct(TitleFilterInterface $titleFilter){
        $this->titleFilter = $titleFilter;
    }

    public function filterTheTitle($title = '', $id = null){
        $post = get_post($id);
        $categories = wp_get_post_terms($post->ID, 'category', array('fields'=>'names'));
        $prefix = in_array('archive', $categories) ? 'From the archive - ' : '';

        return $prefix . ($this->titleFilter->filterTheTitle($title, $id));
    }
}

On top of that I might have another decorator that’s appending something to the title when it’s tagged as “outdated”

class OutdatedTitleFilter implements TitleFilterInterface{

    private $titleFilter;

    public function __construct(TitleFilterInterface $titleFilter){
        $this->titleFilter = $titleFilter;
    }

    public function filterTheTitle($title = '', $id = null){
        $post = get_post($id);
        $tags = wp_get_post_terms($post->ID, 'post_tag', array('fields'=>'names'));
        $postfix = in_array('outdated', $tags) ? ' (outdated)' : '';

        return $postfix . ($this->titleFilter->filterTheTitle($title, $id));
    }
}

If I had to manually build and hook the decorator chain I would do something like this:

$titleFitler = new OutdatedTitleFilter(new ArchiveTitleFilter(new TitleFilter()));

add_filter('the_title', array($titleFilter, 'filterTheTitle'), 10, 2);

Now, how can I do that using the DI52 container? Short answer: I can’t at the moment.

Closures to the rescue

The decorator pattern relies upon the basic class and its decorators exposing a common interface, the TitleFilterInterface in this case, and that’s going to be a problem when dealing with a DI container:

$container = new tad_DI52_Container();

$container->bind('TitleFilterInterface','TitleFilter');
$container->bind('TitleFilterInterface','ArchiveTitleFilter');
$container->bind('TitleFilterInterface','OutdatedTitleFilter');

$titleFilter = $container->make('TitleFilterInterface');

add_filter('the_title', array($titleFilter, 'filterTheTitle'), 10, 2);

The call to the make method on the container will result in an infinite loophole of calls as only the last bind call is in effect and the container will hence try to provided the OutdatedTitleFilter class with an instance of itself.
The solution is to use a closure. The library is PHP 5.2 “compatible” and not “limited”:

$container = new tad_DI52_Container();

$container->bind('TitleFilterInterface', function($container){
    $titleFilter = $container->make('TitleFilter');

    return new OutdatedTitleFilter(new ArchiveTitleFilter($titleFilter));
});

$titleFilter = $container->make('TitleFilterInterface');

add_filter('the_title', array($titleFilter, 'filterTheTitle'), 10, 2);

But 5.2 back compatibilty…

As I’ve reinvented the wheel to this point I do not see why I should stop.
The next iteration of DI52 will add the following method to deal with this problem exactly:

$container = new tad_DI52_Container();

$titleFilterDecorators = array('OutdatedTitleFilter', 'ArchiveTitleFilter', 'TitleFilter');

$container->bindDecorators('TitleFilterInterface', $titleFilterDecorators );

$titleFilter = $container->make('TitleFilterInterface');

add_filter('the_title', array($titleFilter, 'filterTheTitle'), 10, 2);

Next

Down to the code.