Building a PHP Front Controller
Pages: 1, 2
What About Apache 2.x?
Users of Apache 2 will notice that simple.php does not work as
a content handler. For Apache 2.0.x, AddHander works only for
existing files. According to reports (specifically, Bugzilla bug ID
8431), Apache 1.x misused the term "handler" so this changed in 2.x.
There is hope. Per that bug report, Apache 2.1 may add a configuration
directive to relax the restriction, such that AddHandler can refer
to nonexistent files just as it did in Apache 1.x.
Putting It Together: An Overview of the Sample Site
This article's sample web site uses a Front Controller to separate content from layout. The controller maps request URIs to content files, then merges the content with a layout template at request time.
php_include/classes.php contains the helper classes that
simplify the process:
AppConfigwraps an array of common application settings such as page background color.SitePagerepresents a page of content. It wraps a title (for the browser title bar) and the path to a content file.PageMapis the backbone of the controller's work. It associatesSitePageobjects with URIs and holdsSitePages for HTTP errors (such as 404 and 500).AliasMapholds aliases for the controller-managed URIs.- Content pages use
AliasMap, viaUtil::aliasLink(), to refer to one another without hard-coding paths in<a>tags. (This is not required, but certainly eases maintenance.)Utilexposes a second function,literalLink(), to create anchor tags to offsite resources.
SitePage, PageMap, and AliasMap could
be replaced by an XML document; but they are straight code here for the sake of
simplicity.
php_include/mappings.php configures the page mappings and
populates AppConfig with some values. End-users can adjust site
settings here without touching the support classes or controller code.
Controller.php, the controller, weighs in around 30 lines of code
because the helper classes do the heavy lifting:
<?php
require_once( 'php_include/classes.php' );
require_once( 'php_include/mappings.php' );
$daysToCache = 1.5;
$cacheMaxAge = ${daysToCache}*24*(60*60);
$layoutFile = 'php_include/layout_main.php';
$requestedURI = ereg_replace(
$config->getValue( 'URISuffix' ) . '$' ,
"" ,
$_SERVER["REQUEST_URI"]
);
$page = $pageMappings->getPage( $requestedURI );
if( is_null( $page ) )
{
header( 'HTTP/1.0 404 Not Found' );
$page = $pageMappings->getNotFoundPage();
}
if( ! headers_sent() )
{
header(
'Cache-Control: max-age=' . ${cacheMaxAge} . ',
must-revalidate'
);
header(
'Last-Modified: ' . gmdate('D, d M Y H:i:s' ,
$page->getLastModified() ) . ' GMT'
);
}
include( $layoutFile );
?>
The actual content files in the content directory are HTML
fragments with no layout. The include() function merges them with
the layout template. Adding a new page to the site requires placing the file
under content, updating the PageMap, and (optionally)
updating AliasMap.
The content and php_include directories should
never allow direct access, so .htaccess files restrict normal web
requests.
.htaccess in the base of the document root contains various
Apache settings. Since virtual resources cannot be index documents,
RedirectMatch forwards requests for the root URI to
/main/index.site. (Change the host and port statements to make the
sample code to work on your computer.) AddHandler directives
associate Controller.php with requests ending in
.site. ErrorDocument directives map HTTP status
codes to controller-managed error pages.
The stub directories main and errors permit us to
call controller-managed resources with directory paths, for logical grouping.
(Recall Apache's handling of nonexistent directories, described above.)
Request Flow
When the site receives a request for a controller-managed file:
- The controller loads its helper classes and the mappings file which creates
$config, the app-wide configuration object. - The controller strips the custom file extension (as declared in
mappings.php) from the request URI and pulls the matchingSitePageobject from thePageMap. - If the content file exists and is readable, the
$pagevariable will contain theSitePageobject. Otherwise,$pagerefers to the designated 404-error page. - The controller sets headers — in this case, a
Last-Modifieddate — and passes control to the layout template. - The layout template (php_include/layout_main.php) uses
$configto set the header bar's background color and pulls the name of the content file from$page. - The
include()call inserts the content page in the middle of the template to complete the view. The server will interpret content files as PHP documents, so they may contain any valid PHP logic.
At this point, the server can return a complete HTML document to the client browser.
Is It Appropriate?
Few techniques are suitable for everyone. If your web site updates involve adding new pages in-flight, then the overhead of the map maintenance may work against you. However, if the dynamic nature of your site depends on something outside of the raw pages (such as a database) or if your changes involve carefully-tested migrations, then the Front Controller technique may benefit you long-term.
Conclusion
The Front Controller demonstrated in this article separates the request from
the response, the layout from the content, and the content pages from each
other. Such decoupling offers significant flexibility over both static HTML
pages and duplicated, include()-based template pages.
The sample code is just a starting point. Certainly, the controller could do
more in terms of logging or authentication checks. The SitePage
class could also provide more information, such as per-page
<META> tags. Finally, it would be trivial to implement
automatic generation of a site map through a helper class that logically groups
internal aliases.
For more detail on the Front Controller design pattern, refer to the texts listed in the Resources section.
Resources
- The sample code download includes
simple.php, the full sample site, and a minimal Apachehttpd.confto demonstrate web host requirements. - Patterns of Enterprise Application Architecture (Fowler)
- Core J2EE Patterns (Alur, Crupi, Malks)
- Writing Apache Modules with Perl and C (Stein, MacEachern)
- Several concepts in this article (including
PageMapandUtil::aliasLink()) were inspired by Java'sRequestDispatcherand the Tiles extension of the Jakarta Struts framework.
Q Ethan McCallum grew from curious child to curious adult, turning his passion for technology into a career.
Return to the PHP DevCenter.
-
Code doesn't work for me
2005-10-07 12:46:52 scottp3318 [View]
- Trackback from http://programmin.prim8.net/archives/16-Getting-started-with-PEAR-Part-2-Apologies-to-Whitman.html
Getting started with PEAR - Part 2: Apologies to Whitman
2005-06-29 12:50:19 [View]
-
ErrorDocument
2005-02-04 04:31:49 spdyvkng [View]
-
Problem with forms
2004-10-22 05:48:32 lamyFranck [View]
-
Why use a controller paradigm?
2004-07-14 00:25:36 gaeldesign [View]
-
Why use a controller paradigm?
2004-08-04 14:37:30 thomaskostecki [View]
-
PAS a front controler 100% PHP
2004-07-13 12:10:52 PhL [View]
-
Not really seeing the point...
2004-07-11 15:00:02 ionrock [View]
-
Not really seeing the point...
2004-07-13 09:02:58 Q Ethan McCallum |
[View]
-
Not really seeing the point...
2004-07-13 11:25:10 ionrock [View]
-
mod_rewrite
2004-07-09 13:39:11 christopherbowland [View]