Using Horde/Controller from the command line

Horde/Controller is a new Horde 4/PHP 5 component that builds on Horde/Routes to provide the "C" in an MVC architecture. It's heavily based on the Maintainable framework, which is in turn heavily based on Rails, so it will look similar if you're familiar with either of those. Both Mad and Rails have the concept of tasks that can be run from the command line; I thought it would be nice to use the same routing and controller architecture to allow command line scripts.

After a little hacking, the Horde_Controller package in git now has Cli request and response objects and this script is all you need to give command-line access to a set of controllers:

#!/usr/bin/env php
<?php

require 'Horde/Autoloader.php';

// Set up our request and routing objects
$request = new Horde_Controller_Request_Cli();
$response = new Horde_Controller_Response_Cli();
$mapper = new Horde_Routes_Mapper();
$mapper->connect(':controller/:action');

// Create our controller context.
$context = array(
    'mapper' => $mapper,
    'controllerDir' => dirname(__FILE__) . '/../lib/controllers',
);

// Dispatch.
try {
    $dispatcher = Horde_Controller_Dispatcher::singleton($context);
    $dispatcher->dispatch($request, $response);
} catch (Exception $e) {
    var_dump($e->getMessage());
}

Error handling can obviously be improved, and your controllers need to produce plaintext output for this to be friendly, but it gives you the flexibility to add new commands to your script just by dropping in new controller classes to the controllerDir.

Arguments are parsed with the Horde/Argv package, using a new allowUnknownArgs parameter that auto-creates simple text values for options unknown to the parser. This lets you take arbitrary command line arguments in your controller without having to tell the dispatcher script what arguments you expect ahead of time (of course the trade off is that the dispatcher can't validate the arguments, so the controllers are responsible for that, just like they are with web requests).

Here's an example of invoking the index() method of a MigrateController class:

$ ./dispatch.php migrate

To invoke the currentVersion() method of the same controller:

$ ./dispatch.php migrate/current_version

To pass an argument named target_version with the value "11" to the controller's index() method:

$ ./dispatch.php migrate --target-version=11

It's still a work in progress, but I'm happy with the possibilities.