The actions are the heart of an application, because they contain all the application's logic. They use the model and define variables for the view. When you make a web request in a symfony application, the URL defines an action and the request parameters.

6.2.1. The Action Class

Actions are methods named executeActionName of a class named moduleNameActions inheriting from the sfActions class, and grouped by modules. The action class of a module is stored in an actions.class.php file, in the module's actions/ directory.

Listing 6-3 shows an example of an actions.class.php file with only an index action for the whole mymodule module.

Listing 6-3 - Sample Action Class, in apps/frontend/modules/mymodule/actions/actions.class.php

class mymoduleActions extends sfActions
{
  public function executeIndex()
  {
    // ...
  }
}

Caution Even if method names are not case-sensitive in PHP, they are in symfony. So don't forget that the action methods must start with a lowercase execute, followed by the exact action name with the first letter capitalized.

In order to request an action, you need to call the front controller script with the module name and action name as parameters. By default, this is done by appending the couple module_name/action_name to the script. This means that the action defined in Listing 6-4 can be called by this URL:

http://localhost/index.php/mymodule/index

Adding more actions just means adding more execute methods to the sfActions object, as shown in Listing 6-4.

Listing 6-4 - Action Class with Two Actions, in frontend/modules/mymodule/actions/actions.class.php

class mymoduleActions extends sfActions
{
  public function executeIndex()
  {
    // ...
  }

  public function executeList()
  {
    // ...
  }
}

If the size of an action class grows too much, you probably need to do some refactoring and move some code to the model layer. Actions should often be kept short (not more than a few lines), and all the business logic should usually be in the model.

Still, the number of actions in a module can be important enough to lead you to split it in two modules.

6.2.2. Alternative Action Class Syntax

An alternative action syntax is available to dispatch the actions in separate files, one file per action. In this case, each action class extends sfAction (instead of sfActions) and is named actionNameAction. The actual action method is simply named execute. The file name is the same as the class name. This means that the equivalent of Listing 6-4 can be written with the two files shown in Listings 6-5 and 6-6.

Listing 6-5 - Single Action File, in frontend/modules/mymodule/actions/indexAction.class.php

class indexAction extends sfAction
{
  public function execute($request)
  {
    // ...
  }
}

Listing 6-6 - Single Action File, in frontend/modules/mymodule/actions/listAction.class.php

class listAction extends sfAction
{
  public function execute($request)
  {
    // ...
  }
}

6.2.3. Retrieving Information in the Action

The action class offers a way to access controller-related information and the core symfony objects. Listing 6-7 demonstrates how to use them.

Listing 6-7 - sfActions Common Methods

class mymoduleActions extends sfActions
{
  public function executeIndex($request)
  {
    // Retrieving request parameters
    $password    = $request->getParameter('password');

    // Retrieving controller information
    $moduleName  = $this->getModuleName();
    $actionName  = $this->getActionName();

    // Retrieving framework core objects
    $userSession = $this->getUser();
    $response    = $this->getResponse();
    $controller  = $this->getController();
    $context     = $this->getContext();

    // Setting action variables to pass information to the template
    $this->setVar('foo', 'bar');
    $this->foo = 'bar';            // Shorter version
  }
}

6.2.4. Action Termination

Various behaviors are possible at the conclusion of an action's execution. The value returned by the action method determines how the view will be rendered. Constants of the sfView class are used to specify which template is to be used to display the result of the action.

If there is a default view to call (this is the most common case), the action should end as follows:

return sfView::SUCCESS;

Symfony will then look for a template called actionNameSuccess.php. This is defined as the default action behavior, so if you omit the return statement in an action method, symfony will also look for an actionNameSuccess.php template. Empty actions will also trigger that behavior. See Listing 6-8 for examples of successful action termination.

Listing 6-8 - Actions That Will Call the indexSuccess.php and listSuccess.php Templates

public function executeIndex()
{
  return sfView::SUCCESS;
}

public function executeList()
{
}

If there is an error view to call, the action should end like this:

return sfView::ERROR;

Symfony will then look for a template called actionNameError.php.

To call a custom view, use this ending:

return 'MyResult';

Symfony will then look for a template called actionNameMyResult.php.

If there is no view to call — for instance, in the case of an action executed in a batch process — the action should end as follows:

return sfView::NONE;

No template will be executed in that case. It means that you can bypass completely the view layer and set the response HTML code directly from an action. As shown in Listing 6-9, symfony provides a specific renderText() method for this case. This can be useful when you need extreme responsiveness of the action, such as for Ajax interactions, which will be discussed in Chapter 11.

Listing 6-9 - Bypassing the View by Echoing the Response and Returning sfView::NONE

public function executeIndex()
{
  $this->getResponse()->setContent("<html><body>Hello, World!</body></html>");

  return sfView::NONE;
}

// Is equivalent to
public function executeIndex()
{
  return $this->renderText("<html><body>Hello, World!</body></html>");
}

In some cases, you need to send an empty response but with some headers defined in it (especially the X-JSON header). Define the headers via the sfResponse object, discussed in the next chapter, and return the sfView::HEADER_ONLY constant, as shown in Listing 6-10.

Listing 6-10 - Escaping View Rendering and Sending Only Headers

public function executeRefresh()
{
  $output = '<"title","My basic letter"],["name","Mr Brown">';
  $this->getResponse()->setHttpHeader("X-JSON", '('.$output.')');

  return sfView::HEADER_ONLY;
}

If the action must be rendered by a specific template, ignore the return statement and use the setTemplate() method instead.

$this->setTemplate('myCustomTemplate');

6.2.5. Skipping to Another Action

In some cases, the action execution ends by requesting a new action execution. For instance, an action handling a form submission in a POST request usually redirects to another action after updating the database. Another example is an action alias: the index action is often a way to display a list, and actually forwards to a list action.

The action class provides two methods to execute another action:

  • If the action forwards the call to another action:
$this->forward('otherModule', 'index');
  • If the action results in a web redirection:
$this->redirect('otherModule/index');
$this->redirect('http://www.google.com/');

Note The code located after a forward or a redirect in an action is never executed. You can consider that these calls are equivalent to a return statement. They throw an sfStopException to stop the execution of the action; this exception is later caught by symfony and simply ignored.

The choice between a redirect or a forward is sometimes tricky. To choose the best solution, keep in mind that a forward is internal to the application and transparent to the user. As far as the user is concerned, the displayed URL is the same as the one requested. In contrast, a redirect is a message to the user's browser, involving a new request from it and a change in the final resulting URL.

If the action is called from a submitted form with method="post", you should always do a redirect. The main advantage is that if the user refreshes the resulting page, the form will not be submitted again; in addition, the back button works as expected by displaying the form and not an alert asking the user if he wants to resubmit a POST request.

There is a special kind of forward that is used very commonly. The forward404() method forwards to a "page not found" action. This method is often called when a parameter necessary to the action execution is not present in the request (thus detecting a wrongly typed URL). Listing 6-11 shows an example of a show action expecting an id parameter.

Listing 6-11 - Use of the forward404() Method

public function executeShow($request)
{
  $article = ArticlePeer::retrieveByPK($request->getParameter('id'));
  if (!$article)
  {
    $this->forward404();
  }
}

Tip If you are looking for the error 404 action and template, you will find them in the $sf_symfony_ lib_dir/controller/default/ directory. You can customize this page by adding a new default module to your application, overriding the one located in the framework, and by defining an error404 action and an error404Success template inside. Alternatively, you can set the error_404_module and error_404_action constants in the settings.yml file to use an existing action.

Experience shows that, most of the time, an action makes a redirect or a forward after testing something, such as in Listing 6-12. That's why the sfActions class has a few more methods, named forwardIf(), forwardUnless(), forward404If(), forward404Unless(), redirectIf(), and redirectUnless(). These methods simply take an additional parameter representing a condition that triggers the execution if tested true (for the xxxIf() methods) or false (for the xxxUnless() methods), as illustrated in Listing 6-12.

Listing 6-12 - Use of the forward404If() Method

// This action is equivalent to the one shown in Listing 6-11
public function executeShow($request)
{
  $article = ArticlePeer::retrieveByPK($request->getParameter('id'));
  $this->forward404If(!$article);
}

// So is this one
public function executeShow()
{
  $article = ArticlePeer::retrieveByPK($request->getParameter('id'));
  $this->forward404Unless($article);
}

Using these methods will not only keep your code short, but it will also make it more readable.

Tip When the action calls forward404() or its fellow methods, symfony throws an sfError404Exception that manages the 404 response. This means that if you need to display a 404 message from somewhere where you don't want to access the controller, you can just throw a similar exception.

6.2.6. Repeating Code for Several Actions of a Module

The convention to name actions executeActionName() (in the case of an sfActions class) or execute() (in the case of an sfAction class) guarantees that symfony will find the action method. It gives you the ability to add other methods of your own that will not be considered as actions, as long as they don't start with execute.

There is another useful convention for when you need to repeat several statements in each action before the actual action execution. You can then extract them into the preExecute() method of your action class. You can probably guess how to repeat statements after every action is executed: wrap them in a postExecute() method. The syntax of these methods is shown in Listing 6-13.

Listing 6-13 - Using preExecute(), postExecute(), and Custom Methods in an Action Class

class mymoduleActions extends sfActions
{
  public function preExecute()
  {
    // The code inserted here is executed at the beginning of each action call
    ...
  }

  public function executeIndex($request)
  {
    ...
  }

  public function executeList($request)
  {
    ...
    $this->myCustomMethod();  // Methods of the action class are accessible
  }

  public function postExecute()
  {
    // The code inserted here is executed at the end of each action call
    ...
  }

  protected function myCustomMethod()
  {
    // You can also add your own methods, as long as they don't start with "execute"
    // In that case, it's better to declare them as protected or private
    ...
  }
}