Actions
As actions são o coração da aplicação, porque elas contém toda a lógica de uma aplicação. Elas usam o model e definem variáveis para a view. Quando você faz uma requisição web na aplicação symfony, a URL define uma action e a os parâmetros da requisição.
The Action Class
Actions são métodos nomeados executeNomedaAction de uma classe nomeada moduleNomedaAction herdando da classe sfActions, e agrupadas por módulos. A classe action de um módulo é armazenada em um arquivo actions.class.php, e os módulos no diretório actions/.
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
<?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
<?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.
*SIDEBAR* Symfony coding standards In the code examples given in this book, you probably noticed that the opening and closing curly braces ({ and }) occupy one line each. This standard makes the code easier to read. Among the other coding standards of the framework, indentation is always done by two blank spaces; tabs are not used. This is because tabs have a different space value according to the text editor you use, and because code with mixed tab and blank indentation is impossible to read. Core and generated symfony PHP files do not end with the usual ?> closing tag. This is because it is not really needed, and because it can create problems in the output if you ever have blanks after this tag. And if you really pay attention, you will see that a line never ends with a blank space in symfony. The reason, this time, is more prosaic: lines ending with blanks look ugly in Fabien's text editor.
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
<?php class indexAction extends sfAction { public function execute() { ... } } ?>
Listing 6-7 - Single Action File, in myapp/modules/mymodule/actions/listAction.class.php
<?php class listAction extends sfAction { public function execute() { ... } } ?>
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
<?php 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 } } ?>
**SIDEBAR** The context singleton You already saw, in the front controller, a call to sfContext::getInstance(). In an action, the getContext() method returns the same singleton. It is a very useful object that stores a reference to all the symfony core objects related to a given request, and offers an accessor for each of them: sfController: The controller object (->getController()) sfRequest: The request object (->getRequest()) sfResponse: The response object (->getResponse()) sfUser: The user session object (->getUser()) sfDatabaseConnection: The database connection (->getDatabaseConnection()) sfLogger: The logger object (->getLogger()) sfI18N: The internationalization object (->getI18N()) You can call the sfContext::getInstance() singleton from any part of the code.
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:
<?php 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
<?php public function executeIndex() { return sfView::SUCCESS; } public function executeList() { } ?>
If there is an error view to call, the action should end like this:
<?php return sfView::ERROR; ?>
Symfony will then look for a template called actionNameError.php.
To call a custom view, use this ending:
<?php 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 output 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() { echo "<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');
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-12 shows an example of a show action expecting an id parameter.
Listing 6-12 - Use of the forward404() Method
public function executeShow() { $article = ArticlePeer::retrieveByPK($this->getRequestParameter('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_ data_dir/modules/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-13.
Listing 6-13 - Use of the forward404If() Method
// 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); }
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.
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-14.
Listing 6-14 - Using preExecute, postExecute, and Custom Methods in an Action Class
<?php 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 ... } } ?>