Applying Arbitrary Custom Filters To A Collection: Chain Of Responsibility

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?

Published: January 21 2005