You are hereHome / Development / Applying Arbitrary Custom Filters To A Collection: Chain Of Responsibility

Applying Arbitrary Custom Filters To A Collection: Chain Of Responsibility


By Gerd Riesselmann - Posted on 21 January 2005

I faced some annoyance of my Randomized Blogroll Plugin the last days: My own blogroll contains some subscriptions to ego searches that I don't want to publicize on my webpage, since they are

  • depressing, and
  • not of any public interest.

Getting tired of removing them by hand everytime I update my blogroll, I started to implement filtering. Here's what I came up with. See my mind at work.

My first sketch was to create a simple class called Filter, containing just one function that gets passed a collection, iterates through it and does what needs to be done. This is PHP 4.x code:

class Filter
{
  function filter(&$collection)
  {
    // Iterate and apply filter
  }
}

However, if I want to apply an additional filter (renaming root category "Subscriptions" to "General", for example), the collection is stepped through a second time: ugly.

So the next step was to extract the iteration from Filter and move it into the collection class:

class Collection
{
  function filter(&$filter)
  {
    foreach ([myvalues] as $item)
      $filter->filter($item, $this);
  }
}
  
class Filter
{
  function filter(&$item, &$collection)
  {
    // apply filter on $item
  }
}

However, this doesn't solve the problem of applying more than one filter, since collection->filter() still only takes one Filter instance. Maybe I should make it accepting an array, too?

No. There is a nice little design pattern at hand that solves this problem: chain of responsibility. This pattern can be applied if

the set of objects that can handle a request should be specified dynamically.

This is excatly what I want to do. Applying this pattern the collection can be left untouched, while I extend the Filter class:

class FilterChain
{
  /**
  * The next chain mamber
  */
  var $nextInChain = false;
  
  /**
  * Append new member to the end of the chain
  */
  function appendToChain($member)
  {
    if ($this->nextInChain == false)
      $this->nextInChain = $member;
    else
      $this->nextInChain->appendToChain($member);
  }
  
  /**
  * Filter
  */
  function filter(&$item, &$collection)
  {
    if ( $this->doFilter($item, $collection) && $this->nextInChain )
      $this->nextInChain->filter($item, $collection);
  }
  
  /**
  * Does filtering. Overridden in subclasses.
  * Returns TRUE if next filter can be applied, FALSE if not (the item has been removed)
  */
  function doFilter(&$item, &$collection)
  {
    return false; // default. Override this.
  }
}
  
class Filter extends FilterChain
{
  function doFilter(&$item, &$collection)
  {
    // apply filter on $item
    // return FALSE if item was removed, otherwise TRUE
  }
}

The code is simple. FilterChain delegates filtering to doFilter() which would be virtual protected in C++ or comparable languages. appendToChain() find the last member of the chain and sets its next element property.

The usage is simple, too:

$filter = new SomeFilter();
$filter->appendToChain( new SomeOtherFilter() );
$someCollection->filter($filter);

Reflecting the state of affairs for this plugin, I have customizable input, customizable output, customizable filtering and I'm starting on customizable sorting. Maybe it neds some renaming? What about The most customizable blogroll plugin ever?

How about using the PHP5 FilterIterator to provide the filter functionality