Source for file action_controller.php
Documentation is available at action_controller.php
* File containing ActionController class
* @version $Id: action_controller.php 284 2007-02-18 20:04:07Z john $
* @copyright (c) 2005 John Peterson
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* <p>The ActionController base class operates as follows:</p>
* <li>Accept a URL as input</li>
* <li>Translate the URL into a controller and action</li>
* <li>Create the indicated controller object (which is a subclass
* of ActionController) and call its action method</li>
* <li>Render the output of the action method</li>
* <li>Redirect to the next URL</li>
* {@tutorial PHPonTrax/ActionController.cls class tutorial}
* Name of the controller (without the _controller.php)
* Set by {@link recognize_route()} by parsing the URL and the
* routes in {@link routes.php}. The value of this string is set
* before any attempt is made to find the file containing the
* Name of the action method in the controller class
* Set by {@link recognize_route()}
* Value of :id parsed from URL then forced to lower case
* Set by {@link recognize_route()}
* Path to add to other filesystem paths
* Set by {@link recognize_route()}
* Parameters for the action routine
* Set by {@link recognize_route()}, passed as arguments to the
* controller's action routine.
* Filesystem path to ../app/controllers/ directory
* Set by {@link recognize_route()}
* Filesystem path to ../app/helpers/<i>extras</i> directory
* Set by {@link recognize_route()}, {@link set_paths()}
* Filesystem path to ../app/helpers/ directory
* Set by {@link recognize_route()}
* Filesystem path to ../app/views/layouts/<i>extras</i> directory
* Set by {@link recognize_route()}, {@link set_paths()}
* Filesystem path to ../app/views/layouts/ directory
* Set by {@link recognize_route()}
* User's URL in components
* Contains user's URL stripped of TRAX_URL_PREFIX and leading
* and trailing slashes, then exploded into an array on slash
* Filesystem path to the controllername_helper.php file
* Set by {@link recognize_route()}
* Filesystem path to application.php file
* Set by {@link recognize_route()}
* Filesystem path to application_helper.php file
* Set by {@link recognize_route()}
* URL recognized, paths resoved, controller file found
* Set by {@link recognize_route()}
* Whether a Router object was loaded
* <li>true => $router points to the Router object</li>
* <li>false => no Router object exists</li>
* @todo <b>FIXME:</b> No declaration of $router so no place to hang
* List of additional helper files for this controller object
* Set by {@link add_helper()}
* List of filters to execute before calling action method
* Set by {@link add_before_filters()}
* List of filters to execute after calling action method
* Set by {@link add_after_filters()}
* @todo Document this attribute
* @todo Document this attribute
* @todo Document this attribute
* @todo Document this attribute
* Filesystem path to the PHP program file for this controller
* Set by {@link recognize_route()}
* @see $application_controller_file
* Filesystem path to the view file selected for this action
* Set by {@link process_route()}
* Filesystem path to the ../app/views/ directory
* Set by {@link recognize_route()}
* Class name of the controller
* Set by {@link recognize_route()}.
* Derived from contents of {@link $controller}.
* Instance of the controller class
* Set by {@link process_route()}
* @todo Document this attribute
* @todo <b>FIXME:</b> Not referenced in this class - is it used
* by subclasses? If so, for what?
* Render controllers layout
* Can be overridden in the child controller to false
* Whether to keep flash message after displaying it
* Build a Router object and load routes from config/route.php
if(!isset ($this->router) || !is_object($this->router)) {
* @todo Document this method
* @uses add_after_filter()
* @uses add_before_filter()
function __set($key, $value) {
//error_log("__set($key, $value)");
if($key == "before_filter") {
} elseif($key == "after_filter") {
} elseif($key == "helper") {
} elseif($key == "render_text") {
} elseif($key == "redirect_to") {
} elseif($key == "layout") {
* @todo Document this method
* Implement before_filter(), after_filter(), helper()
function __call($method_name, $parameters) {
# If the method exists, just call it
if($method_name == "before_filter") {
$result = call_user_func(array($this, 'add_before_filter'), $parameters);
} elseif($method_name == "after_filter") {
$result = call_user_func(array($this, 'add_after_filter'), $parameters);
} elseif($method_name == "helper") {
* Load routes from configuration file config/routes.php
* Routes are loaded by requiring {@link routes.php} from the
* configuration directory. The file routes.php contains
* statements of the form "$router->connect(path,params);" where
* (path,params) describes the route being added by the
* statement. Route syntax is described in
* {@tutorial PHPonTrax/Router.cls the Router class tutorial}.
require (Trax::$config_path. "/routes.php");
if(is_object($this->router)) {
* Convert URL to controller, action and id
* http://www.php.net/manual/en/reserved.variables.php#reserved.variables.server $_SERVER}['REDIRECT_URL']
* Compute filesystem paths to the various components used by the
* URL and store the paths in object private variables.
* Verify that the controller exists.
* @uses $application_controller_file
* @uses $controller_class
* @uses $controllers_path
* <li>true => route recognized, controller found.</li>
* <li>false => failed, route not recognized.</li>
if(isset ($_SERVER['REDIRECT_URL']) && !stristr($_SERVER['REDIRECT_URL'], 'dispatch.php')) {
$browser_url = $_SERVER['REDIRECT_URL'];
} elseif(isset ($_SERVER['REQUEST_URI'])) {
$browser_url = strstr($_SERVER['REQUEST_URI'], "?") ?
substr($_SERVER['REQUEST_URI'], 0, strpos($_SERVER['REQUEST_URI'], "?")) :
//error_log('browser url='.$browser_url);
# strip off url prefix, if any
$browser_url = str_replace(Trax::$url_prefix, "", $browser_url);
# strip leading slash (if any)
if(substr($browser_url, 0, 1) == "/") {
$browser_url = substr($browser_url, 1);
# strip trailing slash (if any)
if(substr($browser_url, - 1) == "/") {
$browser_url = substr($browser_url, 0, - 1);
if($this->router->routes_count > 0) {
$route = $this->router->find_route($browser_url);
// find_route() returns an array if it finds a path that
// matches the URL, null if no match found
// Matching route found. Try to get
// controller and action from route and URL
$route_path = explode("/",$route['path']);
$route_params = $route['params'];
// Find the controller from the route and URL
// ':controller' in route params overrides URL
// Set controller from URL if that field exists
//error_log('controller='.$this->controller);
// Find the action from the route and URL
// ':action' in route params overrides URL
$this->action = $route_params[':action'];
// Get action from URL if that field exists
//error_log('action='.$this->action);
// FIXME: RoR uses :name as a keyword parameter, id
// is not treated as a special case.
// Do we want to do the same?
// Parameters for the action routine.
// FIXME: make more general than just id
// For historical reasons, continue to pass id
$_REQUEST['id'] = $this->id;
* Parse URL, extract controller and action and execute them
* @uses $application_controller_file
* @uses $application_helper_file
* @uses $controller_class
* @uses $controller_object
* @uses determine_layout()
* @uses execute_after_filters()
* @uses $helpers_base_path
* @uses recognize_route()
* @uses ScaffoldController
* @uses Session::unset_var()
# First try to load the routes and setup the paths to everything
$this->raise("Failed to load any defined routes",
//error_log('process_route(): controller="'.$this->controller
// .'" action="'.$this->action.'" id="'.$this->id.'"');
# Include main application controller file
# If controller is loaded then start processing
Trax::$current_controller_name = $this->controller;
Trax::$current_action_name = $this->action;
# Which layout should we use?
# Check if there is any defined scaffolding to load
$render_options['scaffold'] = true;
# the generic scaffold layout
# Include main application helper file
# Include helper file for this controller
# Include any extra helper files defined in this controller
# Include the helper file
include($helper_path_with_file);
//error_log('started capturing HTML');
# Call the controller method based on the URL
$controller_layout = null;
//error_log('method '.$this->action.' exists, calling it');
//error_log('calling action routine '
// . get_class($this->controller_object)
// .'::'.$action.'() with params '
// .var_export($this->action_params,true));
//error_log('views file "'.$this->action.'"');
//error_log('calling action routine '
// . get_class($this->controller_object)
// .'::index() with params '
// .var_export($this->action_params,true));
//error_log('no action');
$this->raise("No action responded to ". $this->action, "Unknown action", "404");
# layout was set in the action need to redetermine the layout file to use.
# Find out if there was a redirect to some other page
# execution will end here redirecting to new page
# If render_text was defined as a string render it
# execution will end here rendering only the text no layout
# If defined string render_action use that instead
# Render the action / view
isset ($render_options) ? $render_options : null )) {
$this->raise("No view file found $action ($this->view_file).", "Unknown view", "404");
# Grab all the html from the view to put into the layout
//error_log("captured ".strlen($content_for_layout)." bytes\n");
$locals['content_for_layout'] = $content_for_layout;
//error_log("rendering layout: ".$this->controller_object->layout_file);
# No layout template so just echo out whatever is in $content_for_layout
echo $content_for_layout;
# Can't find any layout so throw an exception
# $this->raise("No layout file found.", "Unknown layout", "404");
# No layout template so just echo out whatever is in $content_for_layout
//error_log("no layout found: ".$this->controller_object->layout_file);
echo $content_for_layout;
$this->raise("Failed to instantiate controller object \"". $this->controller. "\".", "ActionController Error", "500");
$this->raise("No controller found.", "Unknown controller", "404");
// error_log('keep flash='.var_export($this->keep_flash,true));
unset ($_SESSION['flash']);
} // function process_route()
* Extend the search path for components
* On entry, $url_path is set according to the browser's URL and
* $controllers_path has been set according to the configuration
* in {@link environment.php config/environment.php} . Examine
* the $controllers_path directory for files or directories that
* match any component of the URL. If one is found, add that
* component to all paths. Replace the contents of $url_path
* with the list of URL components that did NOT match any files
* @uses $controllers_path
* @todo <b>FIXME:</b> Creating a file or directory in
* app/controllers with the same name as a controller, action or
* other URL element will hijack the browser!
$test_path = (is_null($test_path) ? $path : "$test_path/$path");
if(isset ($extra_path) && is_array($extra_path)) {
$extra_path = implode("/", $extra_path);
* Execute the before filters
if(false === $this->$filter_function()) {
//error_log("execute_before_filters(): returning false");
* Append a before filter to the filter chain
* @param mixed $filter_function_name String with the name of
* one filter function, or array of strings with the names of
* several filter functions.
//error_log("adding before filter: $filter_function_name");
if(is_string($filter_function_name) && !empty($filter_function_name)) {
} elseif(is_array($filter_function_name)) {
* Execute the after filters
$this->$filter_function();
* Append an after filter to the filter chain
* @param mixed $filter_function_name String with the name of
* one filter function, or array of strings with the names of
* several filter functions.
if(is_string($filter_function_name) && !empty($filter_function_name)) {
} elseif(is_array($filter_function_name)) {
* Add a helper to the list of helpers used by a controller
* @param $helper_name string Name of a helper to add to the list
* @uses $controller_object
* Renders the content that will be returned to the browser as the response body.
function render($options = array(), $locals = array(), $return_as_string = false) {
$options['locals'] = $options['locals'] ? $options['locals'] : array();
$options['use_full_path'] = !$options['use_full_path'] ? true : $options['use_full_path'];
} elseif($options['file']) {
$this->render_file($options['file'], $options['use_full_path'], $options['locals']);
} elseif($options['partial']) {
} elseif($options['nothing']) {
# Safari doesn't pass the headers of the return if the response is zero length
* Rendering of text is usually used for tests or for rendering prepared content.
* By default, text rendering is not done within the active layout.
* # Renders the clear text "hello world"
* render(array("text" => "hello world!"))
* # Renders the clear text "Explosion!"
* render(array("text" => "Explosion!"))
* # Renders the clear text "Hi there!" within the current active layout (if one exists)
* render(array("text" => "Explosion!", "layout" => true))
* # Renders the clear text "Hi there!" within the layout
* # placed in "app/views/layouts/special.phtml"
* render(array("text" => "Explosion!", "layout" => "special"))
$locals['content_for_layout'] = $text;
* Action rendering is the most common form and the type used automatically by
* Action Controller when nothing else is specified. By default, actions are
* rendered within the current layout (if one exists).
* # Renders the template for the action "goal" within the current controller
* render(array("action" => "goal"))
* # Renders the template for the action "short_goal" within the current controller,
* # but without the current active layout
* render(array("action" => "short_goal", "layout" => false))
* # Renders the template for the action "long_goal" within the current controller,
* # but with a custom layout
* render(array("action" => "long_goal", "layout" => "spectacular"))
$this->layout = $options['layout'];
if($options['scaffold']) {
//error_log(get_class($this)." - render_action() view_file: $this->view_file");
* Renders according to the same rules as render, but returns the result in a string
* instead of sending it as the response body to the browser.
return $this->render($options, $locals, true);
* File rendering works just like action rendering except that it takes a filesystem path.
* By default, the path is assumed to be absolute, and the current layout is not applied.
* # Renders the template located at the absolute filesystem path
* render(array("file" => "/path/to/some/template.phtml"))
* render(array("file" => "c:/path/to/some/template.phtml"))
* # Renders a template within the current layout
* render(array("file" => "/path/to/some/template.rhtml", "layout" => true))
* render(array("file" => "c:/path/to/some/template.rhtml", "layout" => true))
* # Renders a template relative to app/views
* render(array("file" => "some/template", "use_full_path" => true))
function render_file($path, $use_full_path = false, $locals = array()) {
# Renders a template relative to app/views
//error_log("render_file() path:$path");
# Pull all the class vars out and turn them from $this->var to $var
foreach($locals as $tmp_key => $tmp_value) {
$ {$tmp_key} = $tmp_value;
* <p>Partial rendering is most commonly used together with Ajax
* calls that only update one or a few elements on a page without
* reloading. Rendering of partials from the controller makes it
* possible to use the same partial template in both the
* full-page rendering (by calling it from within the template)
* and when sub-page updates happen (from the controller action
* responding to Ajax calls). By default, the current layout is
* <li><samp>render_partial("win");</samp><br>
* located at app/views/controller/_win.phtml</li>
* <li><samp>render_partial("win",
* array("locals" => array("name" => "david")));</samp><br>
* Renders the same partial but also makes a local variable
* <li><samp>render_partial("win",
* array("collection" => array(...)));</samp><br>
* Renders a collection of the same partial by making each
* element of the collection available through the local variable
* "win" as it builds the complete response </li>
* <li><samp>render_partial("win", array("collection" => $wins,
* "spacer_template" => "win_divider"));</samp><br>
* Renders the same collection of partials, but also renders
* the win_divider partial in between each win partial.</li>
* @param string $path Path to file containing partial view
* @param string[] $options Options array
$file_with_path = Trax::$views_path. "/". $path. "/_". $file. ".". Trax::$views_extension;
$file_with_path = $this->views_path. "/_". $file. ".". Trax::$views_extension;
if(file_exists($file_with_path)) {
$spacer_path = $options['spacer_template'];
if(strstr($spacer_path, "/")) {
$spacer_file_with_file = Trax::$views_path. "/". $spacer_path. "/_". $spacer_file. ".". Trax::$views_extension;
$spacer_file = $spacer_path;
$spacer_file_with_file = $this->views_path. "/_". $spacer_file. ".". Trax::$views_extension;
if(file_exists($spacer_file_with_file)) {
foreach($options['collection'] as $tmp_value) {
$locals[$file] = $tmp_value;
$locals[$file. "_counter"] = $ {$file. "_counter"};
if($add_spacer && ($ {$file. "_counter"} < count($options['collection']))) {
$this->render_file($spacer_file_with_file, false, $locals);
* Select a layout file based on the controller object
* @uses $controller_object
* @uses $layouts_base_path
* @return mixed Layout file or null if none
* @todo <b>FIXME:</b> Should this method be private?
// If the controller defines $layout and sets it
// to NULL, that indicates it doesn't want a layout
if(isset ($this->layout) && is_null($this->layout)) {
//error_log('controller->layout absent');
# $layout will be the layout defined in the current controller
# or try to use the controller name for the layout
$layout = (isset ($this->layout)
# Check if a method has been defined to determine the layout at runtime
$layout = $this->$layout();
$layouts_base_path = Trax::$layouts_path;
$default_layout_file = $layouts_base_path . "/application." . Trax::$views_extension;
if(!$full_path && $layout) {
# Is this layout for from a different controller
if(strstr($layout, "/")) {
$layout = $layouts_base_path. "/". $path. "/". $file. ".". Trax::$views_extension;
} elseif(file_exists($this->layouts_path. "/". $layout. ".". Trax::$views_extension)) {
# Is there a layout for the current controller
$layout = $this->layouts_path. "/". $layout. ".". Trax::$views_extension;
$layout = $layouts_base_path. "/". $layout. ".". Trax::$views_extension;
if(file_exists($layout)) {
# No defined layout found so just use the default layout
# app/views/layouts/application.phtml
if(!isset ($layout_file)) {
$layout_file = $default_layout_file;
$this->layout_file = $layout_file;
* Redirect the browser to a specified target
* Redirect the browser to the target specified in $options. This
* parameter can take one of three forms:
* <li>Array: The URL will be generated by calling
* {@link url_for()} with the options.</li>
* <li>String starting with a protocol:// (like http://): Is
* passed straight through as the target for redirection.</li>
* <li>String not containing a protocol: The current protocol
* and host is prepended to the string.</li>
* <li>back: Back to the page that issued the request. Useful
* for forms that are triggered from multiple
* places. Short-hand for redirect_to(request.env["HTTP_REFERER"])
* <li>redirect_to(array(":action" => "show", ":id" => 5))</li>
* <li>redirect_to("http://www.rubyonrails.org")</li>
* <li>redirect_to("/images/screenshot.jpg")</li>
* <li>redirect_to("back")</li>
* @param mixed $options array or string url
* @todo <b>FIXME:</b> Make header configurable
$url = $_SERVER["HTTP_REFERER"];
echo "<html><head><META HTTP-EQUIV=\"REFRESH\" CONTENT=\"0; URL=". $url. "\"></head></html>";
* Raise an ActionControllerError exception
* @param string $error_message Error message
* @param string $error_heading Error heading
* @param string $error_code Error code
* @throws ActionControllerError
function raise($error_message, $error_heading, $error_code = "404") {
* Generate an HTML page describing an error
$error_code = $exception->error_code;
$error_heading = $exception->error_heading;
$error_message = $exception->error_message;
$trace = $exception->getTraceAsString();
header('HTTP/1.0 {$error_code} {$error_heading}');
header('status: {$error_code} {$error_heading}');
# check for user's layout for errors
include(Trax::$layouts_path. "/trax_error.". Trax::$views_extension);
# use default layout for errors
echo "<font face=\"verdana, arial, helvetica, sans-serif\">\n";
echo "<h1>$error_heading</h1>\n";
echo "<p>$error_message</p>\n";
echo "<pre style=\"background-color: #eee;padding:10px;font-size: 11px;\">";
echo "<code>$trace</code></pre>\n";
echo "<font face=\"verdana, arial, helvetica, sans-serif\">\n";
echo "<h2>Application Error</h2>Trax application failed to start properly";
// -- set Emacs parameters --
// c-hanging-comment-ender-p: nil
|