PHPonTrax
[ class tree: PHPonTrax ] [ index: PHPonTrax ] [ all elements ]
Prev Next
ActionController

ActionController

Table of Contents

Introduction

The ActionController base class does the following:

  1. Accepts a URL as input
  2. Translates the URL into a controller and action
  3. Creates the indicated controller object (which is a subclass of ActionController) and calls its action method
  4. Redirects to another URL or renders the output of the action method

URL Processing

When Apache receives an HTTP request addressed to a Trax application, Apache mod_rewrite is invoked and rewrites the request to invoke Trax file dispatch.php. At this time the URL which was input to the rewrite rules is in $_SERVER['REDIRECT_URL']. dispatch.php creates a new Dispatcher object and calls its dispatch() method. dispatch() restores the state of the session identified by a cookie in the request, or creates a session if none exists. Then it creates a new ActionController object and calls its process_route() method.

The word "route" is used in Trax to describe a rule which translates some URL into a particular controller object and method. When process_route() receives control, it calls recognize_route() to parse the URL into controller, action and id components. recognize_route() calls load_router() to load the "routing table", which is a list of one or more $router->connect() calls, from config/routes.php. This list of calls define the rules for translating a URL into a controller and action.

The translation rules work as follows: Starting with the first rule in routes.php, each rule is tested against the URL to see whether the rule matches. If a rule does not match, then the next rule in the table is tested in turn, until a rule matches or the table is exhausted. If no matching rule is found, recognize_route() tests the last route in the table to see whether it is the default route :controller/:action/:id . If the last route is the default route, then recognize_route() returns it as a match, even if it does not in fact match. But if there is no matching route and the last route in the table is not the default route, then recognize_route() returns 'failure' which is equivalent to HTTP code '404 Not found'.

Each entry in the route table contains two parts:

  1. A path, which is a character string to test against the URL.
  2. Parameters, which are not tested against the URL and aren't involved unless the path part of the entry matches the URL. Parameters are optional (and frequently omitted).
A path is a series of substrings separated by '/' (forward slash) characters. Each of these substrings can contain any character except '/'. The path does not begin or end with '/'. A substring may not be the null (no characters) string, but it is legal for the entire path to be the null string. Each substring is one of the following: The following are legal path values:
  • :controller/:action/:id This is the default path. It matches URLs like word1/word2/word3
  • catalog/product/:action/:id Remember that catalog is a Perl regular expression that matches catalog, and product is a Perl regular expression that matches product, so this path matches URLs like catalog/product/word1/word2
  • '' matches '' (the empty string as a path value matches the empty string as a URL).
  • member/name=.* matches URLs like member/name= or member/name=Tom.Jones or member/name=Smith,J/since=1987/type=full etc.
:controller, :action and :id may each appear at most once in a path.

After the URL has been matched to a path, the next step is to extract the name of the controller and action to be invoked on this URL. These must be valid names in the PHP language consisting only of lower-case alphameric characters and '_' (underscore), because the controller name will translate directly into a file name and a class name, and the action name will be used as the name of a method in that class. The controller and action names come from the route that matches the URL.

There are two places that a route can specify a controller or action name: as part of the path, or in the parameters. The parameters are the optional second part of a route. The value of parameters is an array with key values that may be :controller or :action. The following are legal parameters values:

  • array(':controller' => 'new_product')
  • array(':action' => 'enter')
  • array(':controller' => 'membership', ':action => 'new')

When a URL matches a route, the controller name is extracted as follows: First, if the parameters array exists and has an element whose key is :controller, then the value of that element is used as the controller name. If no :controller is specified by the parameters, then the path is tested for a substring whose value is :controller. If found, then the part of the URL which matched that substring is used as the controller value. A controller value must be specified by either the parameters or the path. The action name is extracted by the same process, substituting :action for :controller. If the path has a substring :id, then the part of the URL which matched that substring is forced to lower case and the result assigned to $_REQUEST['id'].

If routes.php contains the following:

router->connect('',array(':controller' => 'home'));
router->connect('product\?.*',
                array(':controller' => 'catalog', ':action' => 'find'));
router->connect(':controller/:action/:id');
Then URLs will match routes as follows:
  • URL '' (no characters) will select controller home, action not specified.
  • URL product?item=4317 will select controller catalog, action find
  • URL cart/add/4317 will select controller cart, action add

Action Call

When the names of the controller and action have been successfully determined from the URL, the associated filesystem paths are constructed and relevant files are loaded, and any parameters and their values are stored in ActionController::action_params. First file app/controllers/application.php is loaded if it exists. This file contains the definition of the ApplicationController class, which extends ActionController. ApplicationController contains properties and methods used by all the controller classes, which should extend ApplicationController . Then the controller name is used to find the file and class containing the selected controller. By Trax naming conventions, if the controller name is controller name then the controller file name is controller_name_controller.php and the controller class name is ControllerName . So for a "catalog item" controller, the controller file name is catalog_item_controller and the controller class name is CatalogItem. The controller file is loaded and a new object of the controller class is created.

Next any needed helper files are loaded. Helper files contain PHP code which helps prepare the output of an action method for viewing. If file application_helper.php exists, it is loaded. application_helper.php contains helpers that apply to every controller in the application. Then the controller-specific helper file controller_name_helper.php is loaded if it exists. Finally any extra helper files, as specified by calls to ActionController::add_helper(), are loaded.

When controller and helper files have been loaded, the before filters are executed (FIXME: We should check return but don't). Next the controller object is tested for the presence of a method with the name of the action as determined from the URL. If such a method exists, it is called; if no such method exists, then the controller object is tested for the presence of a method named index(). If such a method exists it is called, otherwise the request fails with 404 Unknown action. If an action method was found and called, the after filters are executed.

Helper Loading

Helpers are classes that provide view logic. They exist to hold view logic that would otherwise need to be added to a template or controller. Helper services that are applicable to the entire application go into application_helper.php, while controller-specific helper functions go into a helper file named after the controller, as controller_name_helper.php . Helper classes are written as subclasses of class Helpers, which has a number of methods widely used by helper subclasses. You can add a helper to an ActionController object by calling its add_helper() method, passing the name of the helper as an argument.

A number of predefined helper classes are distributed with Trax:

These classes are not automatically loaded, you have to load them explicitly.


Filters

Filters enable controllers to run shared pre and post processing code for its actions. These filters can be used to do authentication, caching, or auditing before the intended action is performed. Or to do localization or output compression after the action has been performed.

Filters have access to the request, response, and all the instance variables set by other filters in the chain or by the action (in the case of after filters). Additionally, it's possible for a pre-processing before_filter to halt the processing before the intended action is processed by returning false or performing a redirect or render. (FIXME: we don't implement this) This is especially useful for filters like authentication where you're not interested in allowing the action to be performed if the proper credentials are not in order.

Filter inheritance

Controller inheritance hierarchies share filters downwards, but subclasses can also add new filters without affecting the superclass. For example:

class BankController extends ActionController
{
    $this->before_filter = audit();

    private function audit() {
        // record the action and parameters in an audit log
    }
}

class VaultController extends BankController
{
    $this->before_filter = verify_credentials();

    private function verify_credentials() {
        // make sure the user is allowed into the vault
    }
}

Now any actions performed on the BankController will have the audit method called before. On the VaultController, first the audit method is called, then the verify_credentials method. If the audit method returns false, then verify_credentials and the intended action are never called. FIXME: This is currently broken.


Filter types

A filter can take one of three forms: method reference (symbol), external class, or inline method (proc). The first is the most common and works by referencing a protected or private method somewhere in the inheritance hierarchy of the controller by use of a symbol. In the bank example above, both BankController and VaultController use this form.

Using an external class makes for more easily reused generic filters, such as output compression. External filter classes are implemented by having a static +filter+ method on any class and then passing this class to the filter method. Example:

class OutputCompressionFilter
{
    static functionfilter(controller) {
        controller.response.body = compress(controller.response.body)
    }
}

class NewspaperController extends ActionController
{
    $this->after_filter = OutputCompressionFilter;
}

The filter method is passed the controller instance and is hence granted access to all aspects of the controller and can manipulate them as it sees fit.

The inline method (using a proc) can be used to quickly do something small that doesn't require a lot of explanation. Or just as a quick test. It works like this:

class WeblogController extends ActionController
{
    before_filter { |controller| false if controller.params["stop_action"] }
}

As you can see, the block expects to be passed the controller after it has assigned the request to the internal variables. This means that the block has access to both the request and response objects complete with convenience methods for params, session, template, and assigns. Note: The inline method doesn't strictly have to be a block; any object that responds to call and returns 1 or -1 on arity will do (such as a Proc or an Method object).


Filter chain skipping

Some times its convenient to specify a filter chain in a superclass that'll hold true for the majority of the subclasses, but not necessarily all of them. The subclasses that behave in exception can then specify which filters they would like to be relieved of. Examples

class ApplicationController extends ActionController
{
    $this->before_filter = authenticate();
}

class WeblogController extends ApplicationController
{
    // will run the authenticate() filter
}

Filter conditions

Filters can be limited to run for only specific actions. This can be expressed either by listing the actions to exclude or the actions to include when executing the filter. Available conditions are +:only+ or +:except+, both of which accept an arbitrary number of method references. For example:

class Journal extends ActionController
{
    // only require authentication if the current action is edit or delete
    before_filter :authorize, :only => [ :edit, :delete ]
    
    private function authorize() {
        // redirect to login unless authenticated
    }
}

When setting conditions on inline method (proc) filters the condition must come first and be placed in parentheses.

class UserPreferences extends ActionController
{
    before_filter(:except => :new) { ? some proc ... }
    ...
}


Redirect Browser or Render Output

After the controller object's action method has returned to ActionController::process_route() and the after filters have been executed, the controller object is examined for a property named redirect_to. If this property exists and has a value, it means that the action method has decided to redirect the user's browser to a different URL. The value of the redirect_to property is passed to redirect_to() which outputs a header redirecting the browser, then calls exit .

If the action didn't redirect the browser, it should have provided output to send to the browser. This is in the form of explicit output produced by calls to echo, print or printf , plus any properties of the controller object that are referenced in the layout. ActionController::process_route() collects all output produced by the controller's action method in the output buffer, for presentation within a layout.

If the controller object has a property render_text which contains a string, then this string is sent directly to the browser and all output and view files are ignored.

If render_text is undefined or empty, then the saved output of the controller's action method is to be rendered. A view file determined by the action is found and included. The view file for an action is app/views/controller_name/action_name.phtml . This file contains HTML, which goes to the output buffer after the action method's output. The output buffer is now assigned to $content_for_layout. Finally the layout file is loaded. The view file and layout file both contain HTML with embedded PHP expressions to present action method output to the user.

Prev   Next
ApplicationController ActiveRecord

Documentation generated on Thu, 04 May 2006 19:46:55 -0600 by phpDocumentor 1.3.0RC4