I thought I might take a little step back here from my previous entry regarding my simplistic, not-all-that-original MVC using auto_prepend_file. I realized that I didn’t really explain why I occasionally get in that “there’s-got-to-be-a-simpler-way” frame of mind.
As I was building Camber, I made it a point to stick with an object-oriented design as closely as possible. The bootstrap script (i.e., index.php) simply sets up the basic environment, creates the main objects that are needed for the application, and starts things flowing. To re-cap, the whole life-cycle for a request in a Camber application is:
- Create the Application object
- Create the Controller object by passing in the Application
- Call $controller->getResource() which returns a Resource object
- Call $resource->run(), which returns the Response object
- Call $response->send() to send the response back
In general, I like the way the application itself is structured. Although any given Resource class (which is basically just a collection of request handlers, i.e. get(), post(), delete(), put()) might be pretty complex, it’s fairly simple to figure out where each feature of the application should reside. Adding functionality to your application is really just a matter of writing new Resource classes and updating the templates appropriately. The thing that bothers me though is the routing process, i.e., determining which Resource needs to be instantiated for any given request.
Camber’s routing logic is handled within the Controller::getResource() method, but I pretty much ignored building any kind of regular-expression-powered routing language. Instead of having a declarative way of specifying which Resource to create, my neglect requires the author to decide how to perform that instantiation. Maybe a bunch of big, ugly, nested if()s that examine the incoming URL, or a switch() statement that does the same. You could even write your own regular expressions to match the various parts of the URL and decide which Resource needs to be created and run. I know. Shame on me for not building that mechanism into the framework.
Anyway, the reason that this kind of in-framework routing bothers me is that it is implemented in PHP (either pre-built within the framework itself, or (even worse!) by the author of the application). Not only does that seem unnecessarily bulky, but it also requires some kind of loading mechanism that looks up the appropriate request handler. This can be accomplished in a relatively uncomplicated way by putting all of the request handlers in a special directory (e.g., controllers/), then just require()ing the appropriate class file depending on what your routing method returned and instantiating it.
Is that really necessary though? Imagine a simple web application where an HTTP request for:
will result in the following pseudo-code being executed (use your imagination to fill in the gaps):
<?php // ...super regex routing code runs here... // determines the following from the request: // $controller = 'profile' // $action = 'display' // $params['name'] = 'drew' require 'controllers/' . $controller . '.php'; $controller .= 'Controller'; $c = new $controller(); // i.e., $c->display(array('name' => 'drew')); $c->$action($params); // ...finish up, send response...
It’s not a whole bunch of code, but I don’t think it needs to exist at all. Basically, instead of mapping requests to request handler classes, loading them, instantiating them, and running them, I propose simply using mod_rewrite and the natural file system structure in the document root to run the scripts that are being requested. This way, instead of declaring and implementing routing rules in PHP, you’d just use the appropriate mod_rewrite rules. Instead of dynamically instantiating a request handler, you just let Apache serve up the whichever PHP script happens to match the rewrite rule.
Obviously, this would mean that the request handler isn’t an object at all; it’s, for the most part, a regular old PHP script. Is that so bad though? There are definitely many conveniences that object-oriented request handlers can provide, such as using inheritance to specify things like authentication and authorization in the target request handlers, and testability. I believe, however, that it’s possible to abide by the same DRY (Don’t Repeat Yourself) principles for these specific requirements without relying on an unnecessarily complex object-oriented controller design.
The overarching idea here is to use the web server to handle as much of the grunt work of the application as possible before actually handing off control into PHP land.