Hack74.Build Extensible Processing with Chains


Hack 74. Build Extensible Processing with Chains

Use the Chain of Responsibility pattern to create a Plug and Play (PnP) processing framework in your code.

Watching football with programmers is fun. Even in the fourth quarter, when the game is 33 to 7 with 1:30 to go, you still get a range of options when you ask who is going to win the game. That's because programmers are trained to think of every potential situation, no matter how unlikely (and generally ludicrous). And I've found that most programmers, including myself, hate closing the door on any question. It's always better to write code that handles 100 possible scenarios, even when your manager swears up and down there will only ever be one.

That's why I think the Chain of Responsibility pattern is so appealing. Imagine walking into a room full of people with a box of donuts. You flip open the box and pull out a jelly donut. One by one you ask people, "You want a jelly donut?" until you find someone that does. Then you continue with your varied box of donut flavors until the box is empty.

That's the Chain of Responsibility pattern; each person in the room registers himself in advance with you, the donut vendor. Then, as new donuts come in, you see who wants them by looking through your list of registered people. The advantage is that you, as the vendor, don't care about how many people want donuts; you don't even care what they do with the donuts. You just manage the registry and the vending.

In this hack, I'll write some code that turns URLs into donuts. Actually, it just vends URLs to a bunch of handlers that will potentially remap the URL. If nobody handles the URL, it will just fall through and be ignored.

Figure 7-8 shows how this is going to work. URLMapper is the donut vendor. He has a box full of URLs that he is going to hand to any object that shows up presenting the URLHandler interface. In this case, ImageURLHandler handles mapping image URL requests to an image-handling script. In the same vein, DocumentURLHandler will redirect any document requests to the correct PHP page. This allows the URLs to be sent without any special handling code, but to still be modified as needed by your application.

Figure 7-8. The URLHandler interface, the mapper, and two handlers


7.9.1. The Code

Save the code in Example 7-10 as chain.php.

Example 7-10. An example of the Chain of Responsibility pattern in PHP
 <?php abstract class URLHandler {   abstract function getRealURL( $url ); } class URLMapper {   private $handlers = array();   private function URLMapper()   {   }   public function addHandler( $handler )   {     $this->handlers []= $handler;   }   public function mapURL( $url )   {     foreach( $this->handlers as $h ) {   $mapped = $h->getRealURL( $url );   if ( isset( $mapped ) ) return $mapped; } return $url;   }   public static function instance()   {     static $inst = null; if( !isset( $inst ) ) { $inst = new URLMapper(); } return $inst;   } } class ImageURLHandler extends URLHandler {   private $base;   private $imgurl;   public function ImageURLHandler( $base, $imgurl )   {     $this->base = $base; $this->imgurl = $imgurl;   }   public function getRealURL( $url )   {     if ( preg_match( "|^".$this->base."(.*?)$|", $url, $matches ) ) { return $this->imgurl.$matches[1]; } return null;   } } class StoryURLHandler extends URLHandler {   private $base;   private $story_url;   public function StoryURLHandler( $base, $story_url )   {     $this->base = $base; $this->story_url = $story_url;   }      public function getRealURL( $url )   {     if ( preg_match( "|^".$this->base."(.*?)/(.*?)/(.*?)$|", $url, $matches ) ) {   return $this->story_url.$matches[1].$matches[2].$matches[3]; } return null;   } } $ih = new ImageURLHandler( "http://mysite.com/images/",   "http://mysite.com/image.php?img=" ); URLMapper::instance()->addHandler( $ih ); $ih = new StoryURLHandler( "http://mysite.com/story/",   "http://mysite.com/story.php?http://mysite.com/index.html"; $testurls []= "http://mysite.com/images/dog"; $testurls []= "http://mysite.com/story/11/05/05"; $testurls []= "http://mysite.com/images/cat"; $testurls []= "http://mysite.com/image.php?img=lizard"; foreach( $testurls as $in ) {   $out = URLMapper::instance()->mapURL( $in );   print "$in\n --> $out\n\n"; } ?> 

7.9.2. Running the Hack

Run the chain.php script with the command-line PHP interpreter:

 % php chain.php http://mysite.com/index.html  --> http://mysite.com/index.html http://mysite.com/images/dog  --> http://mysite.com/image.php?img=dog http://mysite.com/story/11/05/05  --> http://mysite.com/story.php?id=110505 http://mysite.com/images/cat  --> http://mysite.com/image.php?img=cat http://mysite.com/image.php?img=lizard  --> http://mysite.com/image.php?img=lizard  % 

Each URL that comes in is sent through URLMapper, which returns the mapped URL. In the first case, the URL is not remapped, so it just drops through. In the second case, ImageURLHandler sees that this is an image URL, so it remaps the URL to the image.php script. The third URL is recognized by the document mapper, which maps that URL to the story.php script.

The great value of the Chain of Responsibility pattern is that you can extend it without changing your core application code. As long as the vendor object has a robust enough API for the registered objects, it can handle almost any situation.

Perhaps the most recognizable instantiation of the Chain of Responsibility pattern is the Apache Web Server. Apache functions as one big donut vendor, delegating various requests to all of its registered handlers.

This pattern is not always easy to use. In fact, this pattern has some serious issues! It's hard to debug, and it's often not very clear how to use the pattern correctly. It also comes in two variants: one where the request stops if a match is found and another where processing continues regardless of a match. It's not always clear which version is being used. Further, the second variation, where all handlers are called on all occasions, can be extremely tricky to debug (even more so than the pattern normally is). Just keep in mind that flexibility in computer programs always comes at a complexity and performance cost.




PHP Hacks
PHP Hacks: Tips & Tools For Creating Dynamic Websites
ISBN: 0596101392
EAN: 2147483647
Year: 2006
Pages: 163

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net