PHP DevCenter

oreilly.comSafari Books Online.Conferences.

We've expanded our LAMP news coverage and improved our search! Search for all things LAMP across O'Reilly!

Search
Search Tips

advertisement

Listen Print Discuss Subscribe to PHP Subscribe to Newsletters

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:

  • AppConfig wraps an array of common application settings such as page background color.
  • SitePage represents a page of content. It wraps a title (for the browser title bar) and the path to a content file.
  • PageMap is the backbone of the controller's work. It associates SitePage objects with URIs and holds SitePages for HTTP errors (such as 404 and 500).
  • AliasMap holds aliases for the controller-managed URIs.
  • Content pages use AliasMap, via Util::aliasLink(), to refer to one another without hard-coding paths in <a> tags. (This is not required, but certainly eases maintenance.) Util exposes 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:

  1. The controller loads its helper classes and the mappings file which creates $config, the app-wide configuration object.
  2. The controller strips the custom file extension (as declared in mappings.php) from the request URI and pulls the matching SitePage object from the PageMap.
  3. If the content file exists and is readable, the $page variable will contain the SitePage object. Otherwise, $page refers to the designated 404-error page.
  4. The controller sets headers — in this case, a Last-Modified date — and passes control to the layout template.
  5. The layout template (php_include/layout_main.php) uses $config to set the header bar's background color and pulls the name of the content file from $page.
  6. 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 Apache httpd.conf to 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 PageMap and Util::aliasLink()) were inspired by Java's RequestDispatcher and 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.


Have a question about how to apply this or a suggestion you've discovered? Let us know here.
You must be logged in to the O'Reilly Network to post a talkback.
Post Comment
Full Threads Oldest First

Showing messages 1 through 10 of 10.

  • Code doesn't work for me
    2005-10-07 12:46:52  scottp3318 [Reply | View]

    I don't know exactly what env others are using, but
    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.
  • 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 | O'Reilly Author [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.
  • mod_rewrite
    2004-07-09 13:39:11  christopherbowland [Reply | View]

    It seems that for Apache 2.0, mod_rewrite might be an elegant, if complicated for the novice, solution.


Tagged Articles

Post to del.icio.us

This article has been tagged:

php

Articles that share the tag php:

Understanding MVC in PHP (477 tags)

The PHP Scalability Myth (123 tags)

The Dynamic Duo of PEAR::DB and Smarty (53 tags)

PHP Form Handling (43 tags)

Very Dynamic Web Interfaces (39 tags)

View All

designpatterns

Articles that share the tag designpatterns:

Form Your Own Design Pattern Study Group (14 tags)

J2EE Design Patterns (7 tags)

Understanding MVC in PHP (7 tags)

Building a PHP Front Controller (4 tags)

Migrating to Page Controllers (2 tags)

View All

design_patterns

Articles that share the tag design_patterns:

Building a PHP Front Controller (2 tags)

Form Your Own Design Pattern Study Group (2 tags)

View All

Sponsored Resources

  • Inside Lightroom
Advertisement

Sponsored by:

O'Reilly Media

©2009, O'Reilly Media, Inc.
(707) 827-7000 / (800) 998-9938
All trademarks and registered trademarks appearing on oreilly.com are the property of their respective owners.
About O'Reilly
Academic Solutions
Authors
Contacts
Customer Service
Jobs
Newsletters
O'Reilly Labs
Press Room
Privacy Policy
RSS Feeds
Terms of Service
User Groups
Writing for O'Reilly
Content Archive
Business Technology
Computer Technology
Google
Microsoft
Mobile
Network
Operating System
Digital Photography
Programming
Software
Web
Web Design
More O'Reilly Sites
O'Reilly Radar
Ignite
Tools of Change for Publishing
Digital Media
Inside iPhone
O'Reilly FYI
makezine.com
craftzine.com
hackszine.com
perl.com
xml.com

Partner Sites
InsideRIA
java.net
O'Reilly Insights on Forbes.com