I think I've come up with a relatively simple (and hopefully elegant) way to combine a FrontController using Intercepting Filters for handling basic pre/post-processing logic common to all pages (output buffering, script timing, page caching, logging, etc.) and URL-based PageControllers (1 URL = 1 Command/Action) for page-specific logic.
Code posted below. Hopefully this will be of use to someone out there.
example_page.php:
(You could also leave out the include_once('frontcontroller.inc.php') part if you are using Apache and instead use the auto_prepend_file directive in a .htaccess file.)PHP Code:<?php
include_once('frontcontroller.inc.php');
// do page specific stuff (e.g. PageController), for example:
$user =& new User($_GET['id']);
?>
<html>
<head><title>User Info</title></head>
<body>
<h1><?=$user->getName()?></h1>
<p><?=$user->getProfile()?></p>
</body>
</html>
Yes, I am combining the Controller and the View (which is perfectly acceptable). This is on purpose. You could put the $user =& new User($_GET['id']); line (and anything else) in its own UserController class if you'd like to acheive better separation, but simplicity is one of my design goals here.
frontcontroller.inc.php:
Notice the exit; line? PageControllerFilter (see below) will include() the originally requested page. This facilitates having Filters that have pre-processing AND post-processing.PHP Code:$t = microtime();
require_once('FilterChain.class.php');
$fc =& new FilterChain();
require_once('OutputBufferingFilter.class.php');
$fc->addFilter(new OutputBufferingFilter());
require_once('TimingFilter.class.php');
$fc->addFilter(new TimingFilter($t));
require_once('PageControllerFilter.class.php');
$fc->addFilter(new PageControllerFilter());
$fc->process();
exit;
You can put any other stuff that should be global to your application in frontcontroller.inc.php (like define()'s, ini_set()'s, etc.)
FilterChain.class.php:
InterceptingFilter.class.php:PHP Code:class FilterChain
{
var $filters;
function FilterChain()
{
$this->filters = array();
}
function addFilter(&$filter)
{
$this->filters[] =& $filter;
}
function next()
{
$f =& next($this->filters);
if (is_a($f, 'InterceptingFilter'))
{
$f->run($this);
}
}
function process()
{
$f =& reset($this->filters);
$f->run($this);
}
}
OutputBufferingFilter.class.php:PHP Code:class InterceptingFilter
{
function InterceptingFilter()
{
}
function run(&$filterChain)
{
$filterChain->process();
}
}
TimingFilter.class.php:PHP Code:class OutputBufferingFilter extends InterceptingFilter
{
function OutputBufferingFilter()
{
parent::InterceptingFilter();
}
function run(&$fc)
{
ob_start();
$fc->next();
ob_end_flush();
echo '<div id="outputbufferingfilter">buffered</div>';
}
}
PageControllerFilter.class.php:PHP Code:class TimingFilter extends InterceptingFilter
{
var $t;
function TimingFilter($t = NULL)
{
parent::InterceptingFilter();
$this->t = $t;
}
function run(&$fc)
{
$t0 = explode(' ', (is_null($this->t) ? microtime() : $this->t));
$fc->next();
$t1 = explode(' ', microtime());
$t = sprintf('%.6f', ($t1[1]-$t0[1])+($t1[0]-$t0[0]));
echo '<div id="timingfilter">', $t, '</div>';
}
}
PageController include()'s the originally requested page, then the post-processing for each of the filters occurs (the stuff after $fc->next() in the run() methods).PHP Code:class PageControllerFilter extends InterceptingFilter
{
function PageControllerFilter()
{
parent::InterceptingFilter();
}
function run(&$fc)
{
include($_SERVER['PATH_TRANSLATED']);
}
}
BTW, the echo lines in the run() methods are just for testing and can be commented out or deleted.
That's it, there's not much to it (less than there appears anyway), any feedback would be greatly appreciated.






Some great idea here folks... Keep them flowing



Bookmarks