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-4 shows an example of an actions.class.php file with only an index action for the whole mymodule module.

Listing 6-4 - Sample Action Class, in apps/myapp/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-5.

Listing 6-5 - Action Class with Two Actions, in myapp/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-5 can be written with the two files shown in Listings 6-6 and 6-7.

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

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

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

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

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-8 demonstrates how to use them.

Listing 6-8 - sfActions Common Methods

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

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

    // Retrieving framework core objects
    $request     = $this->getRequest();
    $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-9 for examples of successful action termination.

Listing 6-9 - 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-10, 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-10 - 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-11.

Listing 6-11 - 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. code.955dc8a3ab05445345101984f1f27820b7260628php public function executeShow() { $article = ArticlePeer::retrieveByPK($this->getRequestParameter('id')); if (!$article) { $this->forward404(); } } code.ec0ed0790c343fb55ef9279a5c4b933999fd64ccphp // This action is equivalent to the one shown in Listing 6-12 public function executeShow() { $article = ArticlePeer::retrieveByPK($this->getRequestParameter('id')); $this->forward404If(!$article); }

// So is this one public function executeShow() { $article = ArticlePeer::retrieveByPK($this->getRequestParameter('id')); $this->forward404Unless($article); } code.28776dc3b859d7fad27081f569f83e7aacb5170cphp class mymoduleActions extends sfActions { public function preExecute() { // The code inserted here is executed at the beginning of each action call ... }

public function executeIndex() { ... }

public function executeList() { ... $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 ... } } code.4337b4566eb2a59f87352bb68699a03a4db44a5bphp class mymoduleActions extends sfActions { public function executeIndex() { $hasFoo = $this->getRequest()->hasParameter('foo'); $hasFoo = $this->hasRequestParameter('foo'); // Shorter version $foo = $this->getRequest()->getParameter('foo'); $foo = $this->getRequestParameter('foo'); // Shorter version } } code.4fddeb0b6eee18d3fdc46fc1bdef5670156cb00dphp class mymoduleActions extends sfActions { public function executeUpload() { if ($this->getRequest()->hasFiles()) { foreach ($this->getRequest()->getFileNames() as $uploadedFile) { $fileName = $this->getRequest()->getFileName($uploadedFile); $fileSize = $this->getRequest()->getFileSize($uploadedFile); $fileType = $this->getRequest()->getFileType($uploadedFile); $fileError = $this->getRequest()->hasFileError($uploadedFile); $uploadDir = sfConfig::get('sf_upload_dir'); $this->getRequest()->moveFile($uploadedFile, $uploadDir.'/'.$fileName); } } } } code.bb8d384689eeb731aa0169a9915b7b2f959269e1php class mymoduleActions extends sfActions { public function executeFirstPage() { $nickname = $this->getRequestParameter('nickname');

// Store data in the user session
$this->getUser()->setAttribute('nickname', $nickname);

}

public function executeSecondPage() { // Retrieve data from the user session with a default value $nickname = $this->getUser()->getAttribute('nickname', 'Anonymous Coward'); } } code.76d7f94d0dadd18d035ee60b9c20386dd2d7a9e7php class mymoduleActions extends sfActions { public function executeRemoveNickname() { $this->getUser()->getAttributeHolder()->remove('nickname'); }

public function executeCleanup() { $this->getUser()->getAttributeHolder()->clear(); } } code.4cbb0dd8360d9efe6ac24be6f758c9de8d3882d6php

Hello, getAttribute('nickname') ?>

code.0755d867bad110c06b5800d3aa69ee2575572874php $this->setFlash('attrib', $value); code.41870bb93b29928f33aded5f65cf5dbcbb0b8104php $value = $this->getFlash('attrib'); code.aa92c750bfc7bda8f0cff52b7dc4635169c7cab2php has('attrib')): ?> get('attrib') ?> code.494f29c4445b390a673da64c4536be13876da922php get('attrib') ?> code.1ba5d3f60f0191647ef58aa4af25b21d02cd2415yaml all: storage: class: sfSessionStorage param: session_name: my_cookie_name code.9ff68c870dc962fecaf6061c7b9a39cc94df5c92ini session.use_trans_sid = 1 code.6e230d49473485c6e7c510d4cce4e7ed297ddfe2yaml all: storage: class: sfMySQLSessionStorage param: db_table: SESSION_TABLE_NAME # Name of the table storing the sessions database: DATABASE_CONNECTION # Name of the database connection to use code.1c6ade8a66177d746fd6a5e55cde544de1e75289yaml default: .settings: timeout: 1800 # Session lifetime in seconds code.65b8cffed96da942efcd31938d31176d3582ac31yaml read: is_secure: off # All users can request the read action

update: is_secure: on # The update action is only for authenticated users

delete: is_secure: on # Only for authenticated users credentials: admin # With the admin credential

all: is_secure: off # off is the default value anyway code.6557d1dc7c371f52188f1aa7c03432818391efe1yaml all: .actions: login_module: default login_action: login

secure_module:          default
secure_action:          secure

code.87b7e015d5f61f544f921059f6d2531d34505a2aphp class myAccountActions extends sfActions { public function executeLogin() { if ($this->getRequestParameter('login') == 'foobar') { $this->getUser()->setAuthenticated(true); } }

public function executeLogout() { if ($this->getUser()->isAuthenticated()) { $this->getUser()->setAuthenticated(false); } } } code.6ce563b9e8f3ee926ab7095fc6fb1f2780f72918php class myAccountActions extends sfActions { public function executeDoThingsWithCredentials() { $user = $this->getUser();

// Add one or more credentials
$user->addCredential('foo');
$user->addCredentials('foo', 'bar');
// Check if the user has a credential
echo $user->hasCredential('foo');                      =>   true
// Check if the user has both credentials
echo $user->hasCredential(array('foo', 'bar'));        =>   true
// Check if the user has one of the credentials
echo $user->hasCredential(array('foo', 'bar'), false); =>   true
// Remove a credential
$user->removeCredential('foo');
echo $user->hasCredential('foo');                      =>   false
// Remove all credentials (useful in the logout process)
$user->clearCredentials();
echo $user->hasCredential('bar');                      =>   false

} } code.89e72c82d48d63b9c1e43a37f613126bad1866ecphp

  • hasCredential('section3')): ?>

code.fab51a015c456eacef634f5ba7841d7e0483d8efyaml editArticle: credentials: [ admin, editor ] # admin AND editor

publishArticle: credentials: [ admin, publisher ] # admin AND publisher

userManagement: credentials: [[ admin, superuser ]] # admin OR superuser code.b3d4ffe9e6a5e1efd185062b273430c9a017f674yaml credentials: [[root, [supplier, [owner, quasiowner]], accounts]] # root OR (supplier AND (owner OR quasiowner)) OR accounts code.6c3e9e7bd43f4f55920d0162f32eb40b8aedd087php class mymoduleActions extends sfActions { public function validateMyAction() { return ($this->getRequestParameter('id') > 0); }

public function handleErrorMyAction() { $this->message = "Invalid parameters";

return sfView::SUCCESS;

}

public function executeMyAction() { $this->message = "The parameters are correct"; } } code.41419dc78fd598c0a993cf93311f24735541bd08php class myFilter extends sfFilter { public function execute ($filterChain) { // Code to execute before the action execution ...

// Execute next filter in the chain
$filterChain->execute();
// Code to execute after the action execution, before the rendering
...

} } code.12de94ec60a08627f2ac33c2e6487655eacce319yaml rendering: ~ web_debug: ~ security: ~