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.
Ethan McCallum grew from curious child to curious adult, turning his passion for technology into a career.
Return to the PHP DevCenter.
You must be logged in to the O'Reilly Network to post a talkback.
Showing messages 1 through 10 of 10.
-
ErrorDocument
2005-02-04 04:31:49 spdyvkng [Reply | View]
I haven't tried this on Apache 2.x, but in Apache 1.3.x it works wonderfully.
Don't use any php files in the document root. Set the apache configuration to use an error document which is an PHP file in an out of place way (ie. not document root or below).
Then let the error document work as the handler for the application.
It adds a layer of redirection, but it has the wonderful upside of letting you use plain media and directories for those in the system, as well as maitain a "cached" hierarchy of data by adding pre- and post-processing php scripts to html documents.
This averts the problem of adding ever more complex rewrites to take into account lots of special cases (look into eZ publish 2.x (http://ez.no) series to get an idea of what I mean; I worked for eZ and I'm not mentioning this to detract from eZ publish in anyway, just an easily accessible example).
Paul K Egell-Johnsen
-
Problem with forms
2004-10-22 05:48:32 lamyFranck [Reply | View]
I try your code and all is OK.
But, when i put a form in the content file, there is a problem with the validation because i don't try to retrieve the form's data.
So how to make it works in order to retrieve form's data after the validation.
-
Why use a controller paradigm?
2004-07-14 00:25:36 gaeldesign [Reply | View]
I don't want to sound like I'm criticizing this article, because I'm not. The "front controller" design pattern is well-known in Web app circles. But, from my perspective, I have never understood any reason to use it. No article I've read on the matter, and no PHP app I've inspected, has shown me any good reason for it. I've seen good PHP apps that don't use it (even while being very OO in nature), and my own apps don't use it. Honestly, what's the point? The argument seems to be that you can run the same kind of "set up the app framework" stuff on every page. But, er, that can be done anyway. See here in a fake code example:
"page1.php"
$myapp = new MyWebApp();
$myapp->setupWhateverYouNeed();
// program logic
Page1LogicGoesHere();
// include page template
includePage1Template();
// Optional: clean up code
$myapp->cleanUp();
"page2.php"
$myapp = new MyWebApp();
$myapp->setupWhateverYouNeed();
// program logic
Page2LogicGoesHere();
// include page template
includePage2Template();
// Optional: clean up code
$myapp->cleanUp();
Honestly, there's no reason you'd have to duplicate any code other than a very small handful of lines in each file. The separate objects in their own code files would handle 99% of the work.
The benefit of this approach is that you can use real files for real URLs. Using the above example, you get:
http://www.mydomain.com/page1.php
http://www.mydomain.com/subfolder/page2.php
And so on. Real pages! Real files! No fake stuff. I hate fake stuff!
So there you go. Maybe there's something I've overlooked, but I can't think of anything. Like I said, no Web app I've come across seems like it HAS to use the front controller paradigm to be easy to design and manage.
Regards,
Jared -
Why use a controller paradigm?
2004-08-04 14:37:30 thomaskostecki [Reply | View]
There are a few reasons that I've found that make front controllers better than the way you are doing. I'll give you the first two that popped into my head.
One: You gave this example.
"page2.php"
$myapp = new MyWebApp();
$myapp->setupWhateverYouNeed();
// program logic
Page2LogicGoesHere();
// include page template
includePage2Template();
// Optional: clean up code
$myapp->cleanUp();
---------------------------------------
What if this structure needed to change for some reason? Lets say you now need to do
$myapp = new MyWebApp($someParam);
instead of
$myapp = new MyWebApp();
You now need to change this in many places instead of just one!
Two:
php paths are based on the file in the url. If you use your example of
http://www.mydomain.com/page1.php
http://www.mydomain.com/subfolder/page2.php
then it gets really complicated. The relative paths of all the requires in all the files used in page2.php will need to be different than all the requires for all the files used in page1.php. There are ways around this, but I think it's cleaner to use a controller
-
PAS a front controler 100% PHP
2004-07-13 12:10:52 PhL [Reply | View]
Did you looked at PAS ?
The PAS developper have implemented a Front Controller 100% in PHP.
It involve 3 type of classes, Event, Display, EventControler and Event Actions.
The Event and Display class built the Request for the EventControler Class. The request can include Parameter and on or multiple Event Actions.
You get the same benefits, without dependance in Apache.
You can find more on http://www.sqlfusion.org the developer documentation describe in detail how it work.
The never talk about the Front Controller partner but its an implementation of it.
Phil
-
Not really seeing the point...
2004-07-11 15:00:02 ionrock [Reply | View]
I am not sure I totally understand the use here. I have made sites using a index page that accepts all the requests but I always find myself feeling as though there are just too many includes and it gets out of control very quickly as options are created. Is this method a solution for this kind of thing?
I have been making an attempt to use a factory method for frontend logic in hopes of reducing the complexity some but it still has felt a bit overwhelming. I could just be thinking that these kinds of things should be avoidable by talented programming when in fact it is inevitable that mundane complexity will come with large projects. In any sense I did enjoy the article and the solution did seem very effective for some cms type uses. Thanks! -
Not really seeing the point...
2004-07-13 09:02:58 Ethan McCallum |
[Reply | View]
Thanks for the nod.
As for your situation with "too many includes": you don't say what those includes are, or where they're being made. Any time you can factor out common actions -- such as including the same content in several places -- then it's likely using a front controller would help.
-
Not really seeing the point...
2004-07-13 11:25:10 ionrock [Reply | View]
The main reason I end using includes is for different actions. The obvious example would be inputting data into a database.
switch ($_GET['do']) {
case "forms":
include "myforms.php";
break;
case "db":
include "handleinfo.php";
break;
}
The issue is that for each and every action we are creating a file. The better solution is of course to make a common means of creating forms and a common means of handling form data using a class. My issue is that while a class to handle all types of data input and a class for creating forms seems to be useful and the most efficient, it also becomes very confusing if you needed to change forms. You edit a large class that might be difficult to understand after a few months or years. On the other hand finding the file with the forms and database input seems very simple. Where then is the advantage in using a class for this kind of action?
I understand this probably strays a bit from the article regarding patterns and a front controller but I think the root of the article and coding in general lies in creating easy to manage and efficient code. So really, I think the question is really how is the best way to handle a huge tree of options that a web app often takes.
Your comment does make a lot of sense with regard to producing content to the end user. I think my issues are more associated with creating the admin interface for adding content. That is where things seem to become much more complex (at least in my experience).
Thanks again for writing the article and if this is not the right forum for my questions/thoughts please don't hesitate to let me know. While I have worked with php for a while I am by no means an expert. I am really trying to learn more about large scale projects that are just too large to be handled using the same concepts as the smaller projects I have worked with in th past.



this code didn't come close to working for me "out of the box".
Running php5 on Apache 1.3, on Windows XP, I had to:
-Change lots of var references that looked like: ${var} to this: {$var}.
-Remove the redirect on /index.site. This seems to do bad stuff for me.
Other than that, I quite like this article as a "low cost" basic MVC framework. I looked at Seagull and lots of other stuff, but they were
all too complicated.