A simple Filter Chain example in PHP 5
Filter chains can be really useful when you want to pass an object (or several objects) through a series of tests in a scalable fashion. An example may be performing some request validation/filtering before your application starts to do anything too specific with it.
The idea behind a filter chain is that you create a container (the chain) which holds a series of strategies (filters). Each filter is called in turn and is passed a reference to the chain so that it can tell the chain to continue to the next filter. Anything one filter does to the object(s) it is passed should be available in subsequent filters.
I’m going to demonstrate an example by passing a pseudo request and response object along a chain during or before the routing phase in a controller.
test.php
define("BASE_DIR", dirname(__FILE__));
require_once BASE_DIR . "/FilterChain.php";
require_once BASE_DIR . "/Filter.php";
require_once BASE_DIR . "/Filter/Foo.php";
require_once BASE_DIR . "/Filter/Bar.php";
require_once BASE_DIR . "/Filter/Zip.php";
require_once BASE_DIR . "/Request.php";
require_once BASE_DIR . "/Response.php";
$filterChain = new FilterChain();
$filterChain->addFilter(new Filter_Foo());
$filterChain->addFilter(new Filter_Bar());
$filterChain->addFilter(new Filter_Zip());
$request = new Request();
$response = new Response();
$filterChain->doFilter($request, $response);
FilterChain.php
class FilterChain {
protected $filters = array();
public function addFilter(Filter $filter) {
$this->filters[] = $filter;
}
public function doFilter(Request $request, Response $response) {
$filter = array_shift($this->filters);
if (!$filter) {
return; //End of chain
}
$filter->doFilter($request, $response, $this);
}
}
Filter/Foo.php
class Filter_Foo implements Filter {
public function doFilter(Request $request, Response $response, FilterChain $filterChain) {
echo __CLASS__ . " running…" . PHP_EOL;
$filterChain->doFilter($request, $response);
}
}
Filter/Bar.php
class Filter_Bar implements Filter {
public function doFilter(Request $request, Response $response, FilterChain $filterChain) {
echo __CLASS__ . " running…" . PHP_EOL;
$filterChain->doFilter($request, $response);
}
}
Filter/Zip.php
class Filter_Zip implements Filter {
public function doFilter(Request $request, Response $response, FilterChain $filterChain) {
echo __CLASS__ . " running…" . PHP_EOL;
$filterChain->doFilter($request, $response);
}
}
The Request/Response classes I’ve used are only pseudo, but the code is available at the end of this article if you need to see them.
When we run that code we see this:
Filter_Foo running…
Filter_Bar running…
Filter_Zip running…
Each filter simply asked the chain to keep going, which is merely a case of array_shift()’ing the next filter and running it.
Now lets take a scenario where one of our filters is looking to break the chain on a certain condition.
Filter/Bar.php
class Filter_Bar implements Filter {
public function doFilter(Request $request, Response $response, FilterChain $filterChain) {
echo __CLASS__ . " running…" . PHP_EOL;
if ($request->getAttribute("module") != "good") {
echo "The requested module is no good. Breaking chain!" . PHP_EOL;
return;
}
$filterChain->doFilter($request, $response);
}
}
Let’s test the theory this filter chain will go no further than the “Filter_Bar” stage if we set “module” to anything other than “good”.
Here’s the modified test.php.
test.php
define("BASE_DIR", dirname(__FILE__));
require_once BASE_DIR . "/FilterChain.php";
require_once BASE_DIR . "/Filter.php";
require_once BASE_DIR . "/Filter/Foo.php";
require_once BASE_DIR . "/Filter/Bar.php";
require_once BASE_DIR . "/Filter/Zip.php";
require_once BASE_DIR . "/Request.php";
require_once BASE_DIR . "/Response.php";
$filterChain = new FilterChain();
$filterChain->addFilter(new Filter_Foo());
$filterChain->addFilter(new Filter_Bar());
$filterChain->addFilter(new Filter_Zip());
$request = new Request();
$request->setAttribute("module", "bad");
$response = new Response();
$filterChain->doFilter($request, $response);
Low and behold, the chain stops as we expect!
Filter_Foo running…
Filter_Bar running…
The requested module is no good. Breaking chain!
Now let’s see the beauty of the fact this is a chain. Maybe one of our other filters is there to try and correct problems with badly requested modules? It’s not a realistic example, but here goes anyway.
Filter/Foo.php
class Filter_Foo implements Filter {
public function doFilter(Request $request, Response $response, FilterChain $filterChain) {
echo __CLASS__ . " running…" . PHP_EOL;
if ($request->getAttribute("module") == "bad") {
echo "Module looks bad, but I can fix this…. ";
$request->setAttribute("module", "good");
echo "Fixed!" . PHP_EOL;
}
$filterChain->doFilter($request, $response);
}
}
And the result now:
Filter_Foo running…
Module looks bad, but I can fix this…. Fixed!
Filter_Bar running…
Filter_Zip running…
It’s a pretty simple pattern really, but one that comes in handy at times. You’ll see this exact same pattern used in Java servlet containers before the requested servlet is actually dispatched :)
* Here’s the Request/Response classes for those who wanted to see them (pseudo):
Request.php
class Request {
protected $attributes = array();
public function setAttribute($key, $value) {
$this->attributes[$key] = $value;
}
public function getAttribute($key) {
if (array_key_exists($key, $this->attributes)) {
return $this->attributes[$key];
}
}
}
Response.php
class Response {
}
Bramus! [ 20Aug07]
Yet again an awesome article Chris! Thanks!
Jeffery Fernandez [ 08Sep07]
Hey Chris,
Just wondering what real world example can this pattern be used in? Can you suggest some? Thanks
chris [ 08Sep07]
Yes, sure :)
Back to my FrontController example. You may want to switch from using the unclean form index.php?module=aModule&action=myAction in the URL, to parsing out something like index.php/aModule/myAction and populating the request with the appropriate values.
For the FrontController to work without any prior knowledge of this aesthetic modification you need to ensure the request has been modified as early as possibe. The best way to do this is the place your request mapping logic in a filter chain which runs before the FrontController is ever reached.
Imagine I change the FilterChain to allow something to explictly run at the end of the chain (I’ll pass it to the constructor here).
$chain->addFilter(new RequestMapper());
$chain->doFilter(new Request(), new Response());
Now our RequestMapper class can read the REQUEST_URI value and determine what parameters to put in the request.
This is a typical use of the filter chain pattern and one you’ll see in some PHP frameworks (and in J2EE as standard).
Ideally you’d have a configuration file which allows filters to be specified loosely :)
chris [ 08Sep07]
I should point out also that this particluar example is run-once, because it uses array_shift which actually reduces the array size as it goes. If you need to repeatedly run the same filter chain you’d have to use iteration with a little extra logic.
Matthijs [ 12Apr08]
Wow, that’s a pretty cool piece of code. And excellent write-down, thanks. Makes me want to use this somewhere immediately.