A Procedural Version of Comb

I think this is the third time I’ve re-tooled the guts of Comb. The main idea is still the same, but the default behavior is a little different, and it’s not object-oriented at all. My goal in doing it this totally procedural way is to make it possible to build it as a PHP extension, thus configuring its default behavior via httpd.conf, .htaccess, or php.ini. Not that making it an extension means that I can’t use objects. I just didn’t really feel the need at this point. Unsurprisingly, I may change my mind later :-/

Setup & Configuration

Comb still runs using the same overall lifecycle process as before, and at this point you still need to auto_prepend_file that bootstrap script. So your vhost would look pretty basic:

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /sites/example.com/www
 
    <Directory /sites/example.com/www>
        Options -Indexes, FollowSymLinks
        AllowOverride All
    </Directory>
</VirtualHost>

/sites/example.com/www/.htaccess would contain:

php_value auto_prepend_file "/sites/example.com/bootstrap.php"

and /sites/example.com/bootstrap.php looks like:

<?php
    // comb_* functions are all defined in here...
    require '/www/example.com/comb.php';
 
    /**
     * set config values via php
     * ...or in .htaccess using php_value, if comb was 
     * built as an extension. (someday!)
     */
    $opt = array();
    comb_init($opt);
 
    // find includes, run REQUEST_METHOD function, eg, get(), post()
    comb_run();
 
    // render templates if enabled
    comb_render();
 
    // send headers, flush buffer
    comb_send();

That first comb.php that’s being included is the library itself, and would ideally just get loaded as an extension. Even this bootstrap script would be written into the extension and run implicitly if it’s enabled for the vhost. But until I get brave enough to learn how in the world to translate this stuff into C, it’s going to have to exist in PHPland.

Application code

So now we write a “web application” that runs on Comb. I’ll create the following file in the document root called index.php:

<h1>hello, world!</h1>

That will do exactly what you think it does: a big bold howdy. As will this:

<?php
    echo "<h1>hello, world!</h1>";

And actually, this will too:

<?php
function get()
{
    echo "<h1>hello, world!</h1>";
}

And this is where it gets interesting; that get() function was defined, but I obviously didn’t call it explicitly. That’s done automatically in the comb_run() function. If a function exists that matches the incoming request method, it’ll get called, otherwise any output that’s sent will either get buffered and plugged into a template during rendering (if buffering is enabled), or immediately sent back to the client (if buffering is disabled).

Templating

Templating is also automatic. You can change index.php to be:

<?php
function get()
{
    // assign a var to be extracted into the template
    comb_set('title', 'hello, world!');
}

then create a new file beside it in the document root called index.tpl.php:

<h1><?=$title?></h1>

That template file gets detected automatically in the comb_render() function, basically by looking for a file whose name is {script_basename}{template_extension}. The default template extension is set to .tpl.php but that’s configurable, so you can set it to .tpl or .phtml or whatever you’re accustomed to.

For shared templates (e.g., header & footer), a common practice is to explicitly include them in each template that uses them, like:

<?php include "header.tpl.php"; ?>
 
<h1><?=$title?></h1>
 
<?php include "footer.tpl.php"; ?>

which makes you end up with a lot of code duplication if you have many templates using that header/footer combo. I’m personally fond of the layout approach, i.e., a single wrapper template that plugs the wrapped content into place. To accomplish that, output buffering is critical, and there has to be some mechanism in place in the layout template to grab the contents of the buffer. For that, there’s the comb_buffer() function. It’s used like so, in a new template file (also in the document root) called layout.tpl.php:

<html>
<body>
Header stuff
 
<div><?php echo comb_buffer(); ?></div>
 
Footer stuff
</body>
</html>

This will take the output rendered by index.tpl.php and drop it into that div. No need to include anything within the requested template. The comb_render() function not only auto-detects the requested template based off the requested script’s basename, but it also auto-detects the layout. If {layout_name}{template_extension} doesn’t exist (both configurable, “layout” and “.tpl.php” are the defaults), it simply won’t get wrapped.

Directory-Based Filtering

One of my primary goals with Comb is to keep the application simple by relying on as much web server work as I reasonably can. Organizing the application in simple, reasonably-named directories makes this very easy, because there’s no need for an explicit dispatch method that 1) parses the request URI, 2) processes user-defined routes to find a matching controller/handler, 3) instantiates and runs the controller/handler.

To get around this explicit routing/dispatching, Comb simply looks for any prepend or append files in each directory leading down to the requested script. In other words, a request for /account/index.php will actually end up running the following scripts in this order, if they exist:

/prepend.php
/account/prepend.php
/account/index.php
/account/append.php
/append.php

I’ll refer to this as the execution chain. The prepend and append filenames are also configurable. This enables you to organize your code in such a way that any scripts that require common pre- or post-filtering can simply be placed in the same directory. For example, if the /account directory can only be accessed by authenticated users, your /account/prepend.php script may look like:

<?php
    // assume $user was created in /prepend.php
    if ( !$user->isLoggedIn ) 
    {
        comb_redirect('/login.php');
    }

The comb_redirect() function does three things:

  1. Adds a Location: header pointing to the specified URL to the internal headers array
  2. Halts the execution chain, and
  3. turns off rendering.

This causes none of the proceeding script in the execution chain to get included, so the comb_run() function will return to the bootstrap script. When comb_render() gets called, it first checks to see if rendering is enabled, but it’s not anymore thanks to comb_halt(), so it returns to the bootstrap immediately. Then comb_send() is called, which first sends out any user-specified headers (e.g., Location), then checks to see if rendering is enabled. If so it flushes the buffer, but it’s not enabled so comb_send() thus returns and the lifecycle is complete.

That’s a crash course in what I’ve got so far. There’s a bit more that I’ll cover in my next post, but in the mean time if you’ve got any feedback on this approach, please let me know. I’m interested to hear other folks’ opinions on this approach.