Cgiapp2 Tutorial 2: Pluggable Applications

This is the second in a series of Cgiapp2 Tutorials. In this tutorial, I cover creating what I call 'Pluggable Applications', applications that can be distributed and customized to work with other sites.

Background

In several positions I've held over the years, I've needed to use the same application with very slight modifications on multiple sites. These include article applications, galleries, contact forms, and more. The underlying code typically remains the same — but I may need to plug the content into a different sitewide template, or require authentication.

When following MVC well, many of these tasks are already easy. You can create a new instance of the controller at will, and slip in new templates to customize the look and feel. However, things like a sitewide template fall out of the scope of the application templates; they are designed for a suite of applications. Authentication is also typically very site dependent; sure, your application may require a logged in user, but should you really dictate what credentials are used? shouldn't that be left up to the site owner?

A good Controller can provide this kind of flexibility. With Cgiapp2's callback hook system, you actually don't need to develop the application to do some of these tasks; instead, you can leave it up to the end-user developer to implement. This provides strong portability and incredible flexibility in your applications.

Example 1: Add content to a sitewide template

One of the most common tasks I need to undertake is to add the content generated by an application to a sitewide template. In Cgiapp, you can do this via cgiapp_postrun(). cgiapp_postrun() is executed after the application logic is done, and receives the generated content as its first argument:

    public function cgiapp_postrun($body, $cgiapp)
    {
        $cgiapp->tmpl_assign('content', $body);
        return $cgiapp->load_tmpl('site.phtml');
    }

However, if you do this in your application logic, you're deciding things for later users of the application. Leave it out. Instead, let the end-user developer register their own postrun hook to do this. As an example:

require_once 'Cgiapp2.class.php';
require_once 'My/Article.php';

class OurSite
{
    public static function postrun($body, Cgiapp2 $cgiapp)
    {
        $cgiapp->tmpl_assign('content', $body);
        return $cgiapp->load_tmpl('site.phtml');
    }
}
Cgiapp2::add_callback('postrun', array('OurSite', 'postrun'), 'My_Article');

$app = new My_Article($options);
$app->run();

The developer has now registered a 'postrun' hook with the My_Article application. When the application flow hits the postrun event, it will trigger OurSite::postrun(), which can then manipulate the content, throwing it into the developer's sitewide template.

Example 2: Adding authentication

What if an article application doesn't require authentication, but you, as the end-user developer, want to require it for the edit, add, and delete views? Register a prerun action. Using our example from above:

require_once 'Cgiapp2.class.php';
require_once 'My/Article.php';

class OurSite
{
    protected static function _validate($user, $pass)
    {
        // ... validate user ...
    }

    public static function prerun($action, Cgiapp2 $cgiapp)
    {
        if (in_array($action, array('add', 'edit', 'delete')) {
            // Need to authenticate...
            require_once 'Phly/Auth.php';
            $auth = new Phly_Auth(array(self, '_validate'));
            $auth->start();
            if (!$auth->isValid) {
                // reset to the default entry for the app
                $cgiapp->prerun_mode($cgiapp->start_mode());
                return;
            }
        }
    }

    public static function postrun($body, Cgiapp2 $cgiapp)
    {
        $cgiapp->tmpl_assign('content', $body);
        return $cgiapp->load_tmpl('site.phtml');
    }
}
Cgiapp2::add_callback('prerun', array('OurSite', 'prerun'), 'My_Article');
Cgiapp2::add_callback('postrun', array('OurSite', 'postrun'), 'My_Article');

$app = new My_Article($options);
$app->run();

The example above uses Phly_Auth, but could as easily use another authentication mechanism.

The prerun hook is executed just prior to running the requested action, and receives the requested action as its first argument. In the code above, we check to see if the action requires authentication, and if so, check the user's credentials. If they are not authenticated, we then reset the action to the default entry action.

Summary

Cgiapp2's callback hook system allows incredible opportunities for end-user developers to customize applications on a per-instance basis. This tutorial covered two hooks, prerun and postrun, but several more are available, and Cgiapp2 provides the means for creating additional hooks in your applications.

For more information on this topic, see the callback hook documentation.