September 5, 2013

Classes
Tags blog

Redirection and Controller Workflow

<p>A common problem when developing controller functionality is how to structure the workflow of the page. You have to m

A common problem when developing controller functionality is how to structure the workflow of the page. You have to make sure that the different scenarios are accounted for and make sense to the end user, but you also want to make sure that your code stays neat and easy to maintain.

A good solution to this common problem is to break up your functionality into two discrete categories and use header redirection to navigate between controller actions that only belong to one category or the other. One type of action is going to show information to the user. This includes tables, forms, reports, etc. that send information from the application out, usually to a user's web browser. The second type of action is to take data in, usually from a form submission. The top-level process flow is greatly simplified when these two basic categories of task are kept discrete.

I'll use a generic example. We needed a new management interface for resources. The way these resources work is that each resource has a date range during which the resource is in use. It is important that we do not modify any history, or change dates for resources that have already gone into use.

NOTE: This makes some assumptions about our strategy and architecture. If, for example, we were using a heavy client-side technology to make our pages single-page applications, this would be less relevant. For our architecture, however, this solution can simplify our controller logic and make our code efficient and simple.

Category One: Display Actions

Start by designing what your user is going to look at. In our case we have three basic displays: a listing page, a form to create/edit a resource, and a form to edit a resource that is already in progress. In this third display we will have a very limited set of information we can change. So our controller will start with three actions, one for each display: manage, edit, and limitedEdit.

Once you have those actions defined, it should be pretty simple to teach each action how to pull the information you need and then pass control to the template you want to display. I'll add some fake code (shorter than the real code) that will illustrate this step.

public function manage()
{
    $resources = $this->getResourceInformation();
    $this->renderTempalte('manage.tpl.php');
}
 
public function edit($id)
{
    $resource = $this->getResource($id);
    $this->renderTemplate('edit.tpl.php');
}
 
public function limitedEdit($id)
{
    $resource = $this->getResource($id);
    $this->renderTemplate('limitedEdit.tpl.php');
}

Category Two: Data Submission

Next, figure out how data will get into your system. In our case, we really need two actions to take data in, one to save changes to a record or a new record entirely and an action to cancel an already created record. Make sure to use enough different types of actions so that each one is fairly straightforward.

public function save()
{
    $this->updateRecordFromData($_POST);
}
 
public function cancel($id)
{
    if ($this->resourceAlreadyInUse($id)) {
        $this->endAlreadyInUseResource($id);
    }
    else {
        $this->deleteResourceRecord($id);
    }
}

Final Step - Piecing it Together

This is great, but we have a problem: our save and cancel don't do anything or send the user anywhere useful. This is easy to fix. We also have a new tool to help us have good user interaction when using this strategy, the setPersistentMessage() method in the controller class. What this new method allows us to do is to tell the controller to display a message after our next redirect. That way we can perform data updates in a save() method, tell the system to display a message after a redirect, then do that redirect. The user will then see a message on the listing page which will tell them their operation succeeded. The setPersistentMessage() calls in this code example will be real and usable in your own code.

public function save()
{
    $this->updateRecordFromData($_POST);
 
    // note how we are using the action we are about to redirect to, not the action we are in right now for the message key
    $this->setPersistentMessage('Resource/manage', 'Your data was saved successfully.');
    $this->headerRedirect('manage');
}
 
public function cancel($id)
{
    if ($this->resourceAlreadyInUse($id)) {
        $this->endAlreadyInUseResource($id);
    }
    else {
        $this->deleteResourceRecord($id);
    }
 
    // note how we are using the action we are about to redirect to, not the action we are in right now for the message key
    $this->setPersistentMessage('Resource/manage', 'Your resource was cancelled successfully.');
    $this->headerRedirect('manage');
}

Most, if not all, developers have seen this pattern even if they were not quite aware how to categorize it. Especially with the new setPersistentMessage() helper method this pattern should be easy to implement and facilitates targeted quality controllers that are easy to implement and maintain.

-Tony