Chained Request Handlers with Closures

One of the problems with naming request handlers to match the incoming request method, e.g., GET, POST, PUT, DELETE, is that there’s an obvious potential for accidentally re-declaring that function. At this point in time, there are no pre-existing functions in PHP core that are named get(), post(), put(), delete(), head(), options(), trace(), or connect(), but that’s no guarantee that there won’t be in the future, or in a 3rd party extension/library that you may be using.

To get around that potential name collision, I could do a few things:

  • Have a fixed prefix for those functions, e.g., doget(), dopost(), etc
  • Have a user specified prefix that defaults to “”, so you could write your app with get(), post(), etc, or specify “do” in the init and use doget(), dopost(), or maybe specify “_” and use _get(), _post(), etc.
  • Require the requested script to define a specific class with static methods for those HTTP verbs (with or without prefixes).

I wasn’t really satisfied with any of these options though. Prefixing the function names makes it less likely that a fatal function redeclaration would occur down the line, but technically it’s not impossible. That’s where the dynamic prefixing comes in: it makes it easier to sort of move those functions out of the line of fire, but they’re still on the battlefield. I’d rather extract them from combat altogether, so to speak.

The cleaner solution of those three options, IMO, would be to define a single class in every single one of the requested scripts. Something like:

<?php
class comb_handler 
{
    public static function get()    { ... }
    public static function post()   { ... }
    public static function put()    { ... }
    public static function delete() { ... }
}

Then Comb would just check to see of the requested method exists, and run it if so. While that would pretty much solve the function name collision problem, it feels very hackish to me. You’d essentially be using a class for nothing more than its namespace. After considering that for a bit, I remembered that PHP 5.3 introduced closures. So instead of the request script looking like this:

<?php
function get() 
{
    echo "hi";
}

I could do this:

<?php
comb_handle('get', function() 
{
    echo "hi";
});

What I like about this approach is that instead of defining a specific request handler, I can define a bunch of them as closures and have comb_handle() just stick them all in an array, called $method_chains. Then when it comes time to run the request methods, I can just loop over that array of closures and run them one after another. comb_handle() would look something like:

 
function comb_handle($http_method, $closure)
{
    /**
     *  if comb were an extension, this variable would 
     *  only be accessible within the extension itself.
     *
     * $method is one of GET, POST, PUT, DELETE, XHR
     */
    global $method_chains;
    $method_chains[strtolower($http_method)][] = $closure;
}

So imagine a GET request comes in for /session/login.php. The following scripts would get included (if they all exist):

/prepend.php
/session/prepend.php
/session/login.php
/session/append.php
/append.php

In each of those scripts, you can define handlers for any request method you want. To illustrate this, you could do the following in each of the scripts above:

/* /prepend.php */
<?php
comb_handle('get', function() {
    echo "one! ";
});
 
/* /session/prepend.php */
<?php
comb_handle('get', function() {
    echo "two! ";
});
 
/* /session/login.php */
<?php
comb_handle('get', function() {
    echo "three! ";
});
 
/* /session/append.php */
<?php
comb_handle('get', function() {
    echo "four! ";
});
 
/* /append.php */
<?php
comb_handle('get', function() {
    echo "five! ";
});

Then after Comb includes those scripts the $method_chains array will look like:

array(1) {
  ["get"]=> array(5) {
    [0]=> object(Closure)#1 (0) {}
    [1]=> object(Closure)#2 (0) {}
    [2]=> object(Closure)#3 (0) {}
    [3]=> object(Closure)#4 (0) {}
    [4]=> object(Closure)#5 (0) {}
  }
}

Comb would simply do a foreach over $method_chains[‘get’] calling each closure, and you’d see this in your browser:

one! two! three! four! five!

So that’s the direction I’m heading now. That obviously means that Comb will require PHP 5.3 from this point forward, but another advantage of doing so is using namespaces to clean up these ugly comb_* prefixed functions. Some folks might not agree, but to me, this:

<?php
use comb as c;
c\handle('get',  function() {});
c\handle('post', function() {});

feels cleaner (though weirder) than:

<?php
comb_handle('get',  function() {});
comb_handle('post', function() {});

Might as well start getting used to using namespaces though. They’ll be getting much more common over the next couple of years.