Front controller pattern

The current project which is breaking my mind is commonly called the Front Controller pattern[MF en.WP], which unfortunately is one of those simple phrases which is implemented in all kinds of ways for all kinds purposes.

from Martin Fowler’s P of EAA catalogue | Front Controller

When it comes to web site development it basically means all requests to the site arrive at one place, which figures out what part of the site is delegated the task. Seems pretty straightforward, right?

But the simpler the task, the harder it is to get it right. This is one of many possible ways; it is not right (or wrong) but it accomplishes the task.

$request = explode( '/', $_SERVER['PATH_INFO'] );
$resource = array_shift( $request );
 
// Types of resources managed by the api
$resources = [
    'page',
    'user',
];
 
if ( ! array_key_exists( $resource, $resources ) ) {
    http_response_code( 404 );
    exit;
}
 
$method = strtoupper( $_SERVER['REQUEST_METHOD'] );
switch( $method ) {
    case 'DELETE':
    case 'GET':
    case 'POST':
    case 'PUT':
    case 'OPTIONS':
        if ( method_exists( $resource, $method )) {
            call_user_func( [ $resource, $method ], $request );
            break;
        }
        // fall through as method not implemented for this resource
    default:
        // unimplemented method
        http_response_code( 405 );
}

This should work as an unabstracted Front Controller; it is plagiarized with only the slightest of modifications for purpose from the PHP Cookbook 3rd Edition which I very strongly recommend.

GOTCHA#1: this will not work from the cli; it relies on $_SERVER[ ] which is not populated from the cli (naturally this means it is not testable.) It must be abstracted to be testable from the command line.

$request = explode( '/', $_SERVER['PATH_INFO'] );
$resource = array_shift( $request );

First we extract the request from the client to an array of elements. This array is positional, the first element MUST be the resource being requested, which we pop off to a variable.

$resources = [
    'page',
    'user',
];

This is hard-coded configuration, and should not be here, but the front controller does need to know what resources will be managed. These two options are for example purposes; a well-rounded API would also have the plurals – pages, users – to generate indexes, etc.

if ( ! array_key_exists( $resource, $resources ) ) {
    http_response_code( 404 );
    exit;
}

If the resource requested is not managed by this script, return a 404 “Resource not found” error.

$method = strtoupper( $_SERVER['REQUEST_METHOD'] );
switch( $method ) {
    case 'DELETE':
    case 'GET':
    case 'POST':
    case 'PUT':
    case 'OPTIONS':
        if ( method_exists( $resource, $method )) {
            call_user_func( [ $resource, $method ], $request );
            break;
        }
        // fall through as method not implemented for this resource
    default:
        // unimplemented method
        http_response_code( 405 );
}

This Switch-Case control structure is the dispatcher portion of the Front Controller. It captures requests which used the HTTP methods delete, get, post, put, or options, which are the top four methods used plus the ‘options’ method (which should return information about the API.) It then checks if a controller exists for the given method within the resource model, and if so it passes control to that controller.

If the controller has not yet been implemented, it falls through to generate a 405 “Method Not Allowed” error. This is not prejudicial; it is the current standard response (although one would like to have a parallel to 451 “Unavailable for Legal Reasons”, perhaps 450 “Unavailable Now” indicating it may or should be implemented in the future.)

GOTCHA#2: The controller(s) are implied to be simple methods of the resource model classes which in turn pass control on to a view. This is intentional for this project’s purpose, but likely would not be appropriate in other cases. It should also be possible to dispatch to a family of controllers within much the same structure.