Poor man’s MVC: PHP, auto_prepend_file, and mod_rewrite

I often get the impression that there’s too much unnecessary complexity in the approaches to MVC that are currently in use in PHP, primarily with regard to request handlers and controllers. I’m even guilty of it myself with my ironic attempt at object-oriented simplicity, Camber. So when the mood strikes me, I’ll poke around in forums and articles and look for novel approaches to these problems.

A few years ago, Harry Fuecks posted an entry on his wiki-in-progress regarding PHP and the Front Controller design pattern. I’ve stumbled across this entry a few times before, but I always left it for dead, thinking it was just too simplistic. But recently I decided to try picking up where he left off at the end of his entry to see for myself how far the technique could be taken.

It’s nothing new really. The idea is to use auto_prepend_file and auto_append_file to control the global aspects of your application. For those who are unaware, these two directives allow you to specify scripts that you want to run just before and just after every requested PHP script, either on a per-directory basis, or throughout your entire virtualhost. For example, assume you’ve got a simple personal web site with a page called contact.php that looks like this:

<?php 
    require 'prepend.php'; 
?>
 
<h1>Contact Me!</h1>
<form action="/send_email.php" method="post">
  Send me a message: <input type="text" name="msg" />
  <input type="submit" />
</form>
 
<?php
    require 'append.php';
?>

And for the sake of clarification, prepend.php contains this fragmented HTML as the page header:

<html>
<head><title>My Site!</title></head>
<body>

and append.php has the rest of the fragment for the footer:

</body>
</html>

This “header/content/footer” approach should be familiar to every web developer in the universe since it’s often the first time-saving pattern that beginning developers learn. You can do this a little bit cleaner though; instead of explicitly requiring the header and footer, you could simply change your site’s virtualhost container to have these two directives (assuming /path/to/www is your site’s document root):

<Directory ".">
    php_value auto_prepend_file /path/to/www/prepend.php
    php_value auto_append_file  /path/to/www/append.php
</Directory>

and your contact.php file can be trimmed down to just the contact-specific stuff:

<h1>Contact Me!</h1>
<form action="/send_email.php" method="post">
  Send me a message: <input type="text" name="msg" />
  <input type="submit" />
</form>

This is definitely a better idea than having to require the prepend & append (i.e., header & footer) scripts at the top and bottom of every single script in your web site, but you quickly run into problems when you try to do anything complex. For instance, what happens when a visitor fills out that contact form and submits it to send_email.php:

<?php
    $msg= isset($_POST['msg']) ? $_POST['msg']: 'i got nothin';
    mail([email protected]', 'Some Subject', $msg);
    header('Location: /thanks.php');

This form processing script shouldn’t send any output, other than that header at the end telling the browser to bounce on over to the “thanks” page. The problem here is that prepend.php script is sending output for the top portion of the page, and append.php is closing up all the HTML. That is not only wasteful but also potentially troublesome, especially if that top portion of the page, e.g., the title tag, has to be altered depending on what occurs in the script. You can save a bit of trouble by putting an exit; as the last line of send_email.php to keep append.php from running, but that’s ignoring the fundamental problem: no HTML should be sent at all for this particular request, just the Location header by itself.

The obvious way around this, as Harry points out, is to use Output Buffering. With that in mind, I came up with this:

prepend.php

<?php
    // set some default control values used by append.php
    $app = array(
        'render' => true,
        'redirect_url' => '',
        'template_dir' => '/path/to/www/templates/',
        'layout' => 'layout.tpl'
    );
 
    // start output buffering
    ob_start();
 
    // move into the template dir to make calls to 
    // require() cleaner
    chdir($app['template_dir']);

append.php

<?php
    if ($app['render'])
    {
        // we're rendering, so grab whatever is in the buffer
        $content = ob_get_clean();
        require $app['layout'];
    }
    else
    {
        // screw rendering, erase whatever is in the buffer
        ob_end_clean();
 
        // ...and hopefully we have someplace to redirect to
        header('Location: ' . $app['redirect_url']);
    }

The idea here is to use prepend.php as nothing more than an initialization script. You’d start up the output buffer, maybe start the session, connect to your database, etc. Once this script is done, the requested script (e.g., contact.php) does its thing (e.g., displays the contact form).

The interesting stuff really happens in append.php though. First of all, it checks to see if the $app[‘render’] flag is still set to true. If it is, then the output buffer gets “cleaned” meaning anything that has been echoed thus far is dumped into a string variable, i.e. $content. We then include the specified layout template and everything ends up getting assembled into the same page we originally had.

So contact.php stays the same, but we take all the html that used to be split between the original header and footer scripts, and we combine them into the layout template (layout.tpl) which specifies where that previously-buffered-output should be placed:

<html>
<head><title>My Site!</title></head>
<body>
<?=$content?>
</body>
</html>

Now how about sending that email message? All we’ve got to do is modify send_email.php so that it informs append.php to not bother sending any output:

<?php
    $msg= isset($_POST['msg']) ? $_POST['msg']: 'i got nothin';
    mail([email protected]', 'Some Subject', $msg);
 
    // no need to render any HTML
    $app['render'] = false;
 
    // ...but we need to redirect the visitor here, so that 
    // we can properly thank them for sending their message
    $app['redirect_url'] = '/thanks.php'

That should solve the most basic request handling problems, but this is hardly what anybody would call MVC. Where’s the separation between View and Controller? For that matter, where is the Controller itself supposed to reside? How might clean URLs be implemented, e.g., http://example.com/profile/drew? And why do I ALWAYS ignore the Model?

My answers come in the form of a little bit of discipline, a bucket full of simplicity, and smidgen of reliance on mod_rewrite’s voodoo. But that’s an entry for another day.