Development

/doc/tags/RELEASE_1_1_0_BETA4/book/06-Inside-the-Controller-Layer.txt

You must first sign up to be able to contribute.

root/doc/tags/RELEASE_1_1_0_BETA4/book/06-Inside-the-Controller-Layer.txt

Revision 8340, 55.7 kB (checked in by dwhittle, 9 months ago)

1.1: updated references to front controllers and added about application configuration

Line 
1 Chapter 6 - Inside The Controller Layer
2 =======================================
3
4 In symfony, the controller layer, which contains the code linking the business logic and the presentation, is split into several components that you use for different purposes:
5
6   * The front controller is the unique entry point to the application. It loads the configuration and determines the action to execute.
7   * Actions contain the applicative logic. They check the integrity of the request and prepare the data needed by the presentation layer.
8   * The request, response, and session objects give access to the request parameters, the response headers, and the persistent user data. They are used very often in the controller layer.
9   * Filters are portions of code executed for every request, before or after the action. For example, the security and validation filters are commonly used in web applications. You can extend the framework by creating your own filters.
10
11 This chapter describes all these components, but don't be intimidated by their number. For a basic page, you will probably need to write only a few lines in the action class, and that's all. The other controller components will be of use only in specific situations.
12
13 The Front Controller
14 --------------------
15
16 All web requests are handled by a single front controller, which is the unique entry point to the whole application in a given environment.
17
18 When the front controller receives a request, it uses the routing system to match an action name and a module name with the URL typed (or clicked) by the user. For instance, the following request URL calls the `index.php` script (that's the front controller) and will be understood as a call to the action `myAction` of the module `mymodule`:
19
20     http://localhost/index.php/mymodule/myAction
21
22 If you are not interested in symfony's internals, that's all that you need to know about the front controller. It is an indispensable component of the symfony MVC architecture, but you will seldom need to change it. So you can jump to the next section unless you really want to know about the guts of the front controller.
23
24 ### The Front Controller's Job in Detail
25
26 The front controller does the dispatching of the request, but that means a little more than just determining the action to execute. In fact, it executes the code that is common to all actions, including the following:
27
28   1. Load the project configuration class and the symfony libraries.
29   2. Create an application configuration and a symfony context.
30   3. Load and initiate the core framework classes.
31   4. Load the configuration.
32   5. Decode the request URL to determine the action to execute and the request parameters.
33   6. If the action does not exist, redirect to the 404 error action.
34   7. Activate filters (for instance, if the request needs authentication).
35   8. Execute the filters, first pass.
36   9. Execute the action and render the view.
37   10. Execute the filters, second pass.
38   11. Output the response.
39
40 ### The Default Front Controller
41
42 The default front controller, called `index.php` and located in the `web/` directory of the project, is a simple PHP file, as shown in Listing 6-1.
43
44 Listing 6-1 - The Default Production Front Controller
45
46     [php]
47     <?php
48
49     require_once(dirname(__FILE__).'/../config/ProjectConfiguration.class.php');
50
51     $configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'prod', false);
52     sfContext::createInstance($configuration)->dispatch();
53
54 The front controller includes the application configuration, which takes care of steps 2 through 4. The call to the `dispatch()` method of the `sfController` object (which is the core controller object of the symfony MVC architecture) dispatches the request, taking care of steps 5 through 7. The last steps are handled by the filter chain, as explained later in this chapter.
55
56 ### Calling Another Front Controller to Switch the Environment
57
58 One front controller exists per environment. As a matter of fact, it is the very existence of a front controller that defines an environment. The environment is defined in the `SF_ENVIRONMENT` constant.
59
60 To change the environment in which you're browsing your application, just choose another front controller. The default front controllers available when you create a new application with the `generate:app` task are `index.php` for the production environment and `frontend_dev.php` for the development environment (provided that your application is called `frontend`). The default `mod_rewrite` configuration will use `index.php` when the URL doesn't contain a front controller script name. So both of these URLs display the same page (`mymodule/index`) in the production environment:
61
62     http://localhost/index.php/mymodule/index
63     http://localhost/mymodule/index
64
65 and this URL displays that same page in the development environment:
66
67     http://localhost/frontend_dev.php/mymodule/index
68
69 Creating a new environment is as easy as creating a new front controller. For instance, you may need a staging environment to allow your customers to test the application before going to production. To create this staging environment, just copy `web/frontend_dev.php` into `web/frontend_staging.php`, and change the value of the `SF_ENVIRONMENT` constant to `staging`. Now, in all the configuration files, you can add a new `staging:` section to set specific values for this environment, as shown in Listing 6-2.
70
71 Listing 6-2 - Sample `app.yml` with Specific Settings for the Staging Environment
72
73     staging:
74       mail:
75         webmaster:    dummy@mysite.com
76         contact:      dummy@mysite.com
77     all:
78       mail:
79         webmaster:    webmaster@mysite.com
80         contact:      contact@mysite.com
81
82 If you want to see how the application reacts in this new environment, call the related front controller:
83
84     http://localhost/frontend_staging.php/mymodule/index
85
86 Actions
87 -------
88
89 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.
90
91 ### The Action Class
92
93 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.
94
95 Listing 6-3 shows an example of an `actions.class.php` file with only an `index` action for the whole `mymodule` module.
96
97 Listing 6-3 - Sample Action Class, in `apps/frontend/modules/mymodule/actions/actions.class.php`
98
99     [php]
100     class mymoduleActions extends sfActions
101     {
102       public function executeIndex()
103       {
104
105       }
106     }
107
108 >**CAUTION**
109 >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.
110
111 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:
112
113     http://localhost/index.php/mymodule/index
114
115 Adding more actions just means adding more `execute` methods to the `sfActions` object, as shown in Listing 6-4.
116
117 Listing 6-4 - Action Class with Two Actions, in `frontend/modules/mymodule/actions/actions.class.php`
118
119     [php]
120     class mymoduleActions extends sfActions
121     {
122       public function executeIndex()
123       {
124         ...
125       }
126
127       public function executeList()
128       {
129         ...
130       }
131     }
132
133 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.
134
135 Still, the number of actions in a module can be important enough to lead you to split it in two modules.
136
137 >**SIDEBAR**
138 >Symfony coding standards
139 >
140 >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.
141 >
142 >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.
143 >
144 >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.
145 >
146 >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.
147
148 ### Alternative Action Class Syntax
149
150 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.
151
152 Listing 6-5 - Single Action File, in `frontend/modules/mymodule/actions/indexAction.class.php`
153
154     [php]
155     class indexAction extends sfAction
156     {
157       public function execute($request)
158       {
159         ...
160       }
161     }
162
163 Listing 6-6 - Single Action File, in `frontend/modules/mymodule/actions/listAction.class.php`
164
165     [php]
166     class listAction extends sfAction
167     {
168       public function execute($request)
169       {
170         ...
171       }
172     }
173
174 ### Retrieving Information in the Action
175
176 The action class offers a way to access controller-related information and the core symfony objects. Listing 6-7 demonstrates how to use them.
177
178 Listing 6-7 - `sfActions` Common Methods
179
180     [php]
181     class mymoduleActions extends sfActions
182     {
183       public function executeIndex()
184       {
185         // Retrieving request parameters
186         $password    = $this->getRequestParameter('password');
187
188         // Retrieving controller information
189         $moduleName  = $this->getModuleName();
190         $actionName  = $this->getActionName();
191
192         // Retrieving framework core objects
193         $request     = $this->getRequest();
194         $userSession = $this->getUser();
195         $response    = $this->getResponse();
196         $controller  = $this->getController();
197         $context     = $this->getContext();
198
199         // Setting action variables to pass information to the template
200         $this->setVar('foo', 'bar');
201         $this->foo = 'bar';            // Shorter version
202
203       }
204     }
205
206 >**SIDEBAR**
207 >The context singleton
208 >
209 >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:
210 >
211 >`sfController`: The controller object (`->getController()`)
212 >`sfRequest`: The request object (`->getRequest()`)
213 >`sfResponse`: The response object (`->getResponse()`)
214 >`sfUser`: The user session object (`->getUser()`)
215 >`sfDatabaseConnection`: The database connection (`->getDatabaseConnection()`)
216 >`sfLogger`: The logger object (`->getLogger()`)
217 >`sfI18N`: The internationalization object (`->getI18N()`)
218 >
219 >You can call the `sfContext::getInstance()` singleton from any part of the code.
220
221 ### Action Termination
222
223 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.
224
225 If there is a default view to call (this is the most common case), the action should end as follows:
226
227     [php]
228     return sfView::SUCCESS;
229
230 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.
231
232 Listing 6-8 - Actions That Will Call the `indexSuccess.php` and `listSuccess.php` Templates
233
234     [php]
235     public function executeIndex()
236     {
237       return sfView::SUCCESS;
238     }
239
240     public function executeList()
241     {
242     }
243
244 If there is an error view to call, the action should end like this:
245
246     [php]
247     return sfView::ERROR;
248
249 Symfony will then look for a template called `actionNameError.php`.
250
251 To call a custom view, use this ending:
252
253     [php]
254     return 'MyResult';
255
256 Symfony will then look for a template called `actionNameMyResult.php`.
257
258 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:
259
260     [php]
261     return sfView::NONE;
262
263 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.
264
265 Listing 6-9 - Bypassing the View by Echoing the Response and Returning `sfView::NONE`
266
267     [php]
268     public function executeIndex()
269     {
270       $this->getResponse()->setContent("<html><body>Hello, World!</body></html>");
271
272       return sfView::NONE;
273     }
274
275     // Is equivalent to
276     public function executeIndex()
277     {
278       return $this->renderText("<html><body>Hello, World!</body></html>");
279     }
280
281 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.
282
283 Listing 6-10 - Escaping View Rendering and Sending Only Headers
284
285     [php]
286     public function executeRefresh()
287     {
288       $output = '<"title","My basic letter"],["name","Mr Brown">';
289       $this->getResponse()->setHttpHeader("X-JSON", '('.$output.')');
290
291       return sfView::HEADER_ONLY;
292     }
293
294 If the action must be rendered by a specific template, ignore the `return` statement and use the `setTemplate()` method instead.
295
296     [php]
297     $this->setTemplate('myCustomTemplate');
298
299 ### Skipping to Another Action
300
301 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.
302
303 The action class provides two methods to execute another action:
304
305   * If the action forwards the call to another action:
306
307         [php]
308         $this->forward('otherModule', 'index');
309
310   * If the action results in a web redirection:
311
312         [php]
313         $this->redirect('otherModule/index');
314         $this->redirect('http://www.google.com/');
315
316 >**NOTE**
317 >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.
318
319 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.
320
321 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.
322
323 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.
324
325 Listing 6-11 - Use of the `forward404()` Method
326
327     [php]
328     public function executeShow()
329     {
330       $article = ArticlePeer::retrieveByPK($this->getRequestParameter('id'));
331       if (!$article)
332       {
333         $this->forward404();
334       }
335     }
336
337 >**TIP**
338 >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.
339
340 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.
341
342 Listing 6-12 - Use of the `forward404If()` Method
343
344     [php]
345     // This action is equivalent to the one shown in Listing 6-12
346     public function executeShow()
347     {
348       $article = ArticlePeer::retrieveByPK($this->getRequestParameter('id'));
349       $this->forward404If(!$article);
350     }
351
352     // So is this one
353     public function executeShow()
354     {
355       $article = ArticlePeer::retrieveByPK($this->getRequestParameter('id'));
356       $this->forward404Unless($article);
357     }
358
359 Using these methods will not only keep your code short, but it will also make it more readable.
360
361 >**TIP**
362 >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.
363
364 ### Repeating Code for Several Actions of a Module
365
366 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`.
367
368 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.
369
370 Listing 6-13 - Using `preExecute`, `postExecute`, and Custom Methods in an Action Class
371
372     [php]
373     class mymoduleActions extends sfActions
374     {
375       public function preExecute()
376       {
377         // The code inserted here is executed at the beginning of each action call
378         ...
379       }
380
381       public function executeIndex($request)
382       {
383         ...
384       }
385
386       public function executeList($request)
387       {
388         ...
389         $this->myCustomMethod();  // Methods of the action class are accessible
390       }
391
392       public function postExecute()
393       {
394         // The code inserted here is executed at the end of each action call
395         ...
396       }
397
398       protected function myCustomMethod()
399       {
400         // You can also add your own methods, as long as they don't start with "execute"
401         // In that case, it's better to declare them as protected or private
402         ...
403       }
404     }
405
406 Accessing the Request
407 ---------------------
408
409 You're familiar with the `getRequestParameter('myparam')` method, used to retrieve the value of a request parameter by its name. As a matter of fact, this method is a proxy for a chain of calls to the request's parameter holder `getRequest()->getParameter('myparam')`. The action class has access to the request object, called `sfWebRequest` in symfony, and to all its methods, via the `getRequest()` method. Table 6-1 lists the most useful `sfWebRequest` methods.
410
411 Table 6-1 - Methods of the `sfWebRequest` Object
412
413 Name                             | Function                               |  Sample Output
414 -------------------------------- | -------------------------------------- | -----------------------------------------------------------------------
415 **Request Information**          |                                        |
416 `isMethod($method)`              | Is it a post or a get?                 | true or false
417 `getMethod()`                    | Request method                         | Returns `sfRequest::GET` or `sfRequest::POST` constants
418 `getMethodName()`                | Request method name                    | `'POST'`
419 `getHttpHeader('Server')`        | Value of a given HTTP header           | `'Apache/2.0.59 (Unix) DAV/2 PHP/5.1.6'`
420 `getCookie('foo')`               | Value of a named cookie                | `'bar'`
421 `isXmlHttpRequest()`*            | Is it an Ajax request?                 | `true`
422 `isSecure()`                     | Is it an SSL request?                  | `true`
423 **Request Parameters**           |                                        |
424 `hasParameter('foo')`            | Is a parameter present in the request? | `true`
425 `getParameter('foo')`            | Value of a named parameter             | `'bar'`
426 `getParameterHolder()->getAll()` | Array of all request parameters        |
427 **URI-Related Information**      |                                        |
428 `getUri()`                       | Full URI                               | `'http://localhost/frontend_dev.php/mymodule/myaction'`
429 `getPathInfo()`                  | Path info                              | `'/mymodule/myaction'`
430 `getReferer()`**                 | Referrer                               | `'http://localhost/frontend_dev.php/'`
431 `getHost()`                      | Host name                              | `'localhost'`
432 `getScriptName()`                | Front controller path and name         | `'frontend_dev.php'`
433 **Client Browser Information**   |                                        |
434 `getLanguages()`                 | Array of accepted languages            | `Array( ` ` [0] => fr ` ` [1] => fr_FR ` ` [2] => en_US ` ` [3] => en )`
435 `getCharsets()`                  | Array of accepted charsets             | `Array( ` ` [0] => ISO-8859-1 ` ` [1] => UTF-8 ` ` [2] => * )`
436 getAcceptableContentTypes()      | Array of accepted content types        | `Array( [0] => text/xml [1] => text/html`
437
438 * *Works only with prototype*
439
440 ** *Sometimes blocked by proxies*
441
442 The `sfActions` class offers a few proxies to access the request methods more quickly, as shown in Listing 6-14.
443
444 Listing 6-14 - Accessing the `sfRequest` Object Methods from an Action
445
446     [php]
447     class mymoduleActions extends sfActions
448     {
449       public function executeIndex()
450       {
451         $hasFoo = $this->getRequest()->hasParameter('foo');
452         $hasFoo = $this->hasRequestParameter('foo');  // Shorter version
453         $foo    = $this->getRequest()->getParameter('foo');
454         $foo    = $this->getRequestParameter('foo');  // Shorter version
455       }
456     }
457
458 For multipart requests to which users attach files, the `sfWebRequest` object provides a means to access and move these files, as shown in Listing 6-15.
459
460 Listing 6-15 - The `sfWebRequest` Object Knows How to Handle Attached Files
461
462     [php]
463     class mymoduleActions extends sfActions
464     {
465       public function executeUpload()
466       {
467         if ($this->getRequest()->hasFiles())
468         {
469           foreach ($this->getRequest()->getFileNames() as $fileName)
470           {
471             $fileSize  = $this->getRequest()->getFileSize($fileName);
472             $fileType  = $this->getRequest()->getFileType($fileName);
473             $fileError = $this->getRequest()->hasFileError($fileName);
474             $uploadDir = sfConfig::get('sf_upload_dir');
475             $this->getRequest()->moveFile('file', $uploadDir.'/'.$fileName);
476           }
477         }
478       }
479     }
480
481 You don't have to worry about whether your server supports the `$_SERVER` or the `$_ENV` variables, or about default values or server-compatibility issues--the `sfWebRequest` methods do it all for you. Besides, their names are so evident that you will no longer need to browse the PHP documentation to find out how to get information from the request.
482
483 User Session
484 ------------
485
486 Symfony automatically manages user sessions and is able to keep persistent data between requests for users. It uses the built-in PHP session-handling mechanisms and enhances them to make them more configurable and easier to use.
487
488 ### Accessing the User Session
489
490 The session object for the current user is accessed in the action with the `getUser()` method and is an instance of the `sfUser` class. This class contains a parameter holder that allows you to store any user attribute in it. This data will be available to other requests until the end of the user session, as shown in Listing 6-16. User attributes can store any type of data (strings, arrays, and associative arrays). They can be set for every individual user, even if that user is not identified.
491
492 Listing 6-16 - The `sfUser` Object Can Hold Custom User Attributes Existing Across Requests
493
494     [php]
495     class mymoduleActions extends sfActions
496     {
497       public function executeFirstPage()
498       {
499         $nickname = $this->getRequestParameter('nickname');
500
501         // Store data in the user session
502         $this->getUser()->setAttribute('nickname', $nickname);
503       }
504
505       public function executeSecondPage()
506       {
507         // Retrieve data from the user session with a default value
508         $nickname = $this->getUser()->getAttribute('nickname', 'Anonymous Coward');
509       }
510     }
511
512 >**CAUTION**
513 >You can store objects in the user session, but it is strongly discouraged. This is because the session object is serialized between requests and stored in a file. When the session is deserialized, the class of the stored objects must already be loaded, and that's not always the case. In addition, there can be "stalled" objects if you store Propel objects.
514
515 Like many getters in symfony, the `getAttribute()` method accepts a second argument, specifying the default value to be used when the attribute is not defined. To check whether an attribute has been defined for a user, use the `hasAttribute()` method. The attributes are stored in a parameter holder that can be accessed by the `getAttributeHolder()` method. It allows for easy cleanup of the user attributes with the usual parameter holder methods, as shown in Listing 6-17.
516
517 Listing 6-17 - Removing Data from the User Session
518
519     [php]
520     class mymoduleActions extends sfActions
521     {
522       public function executeRemoveNickname()
523       {
524         $this->getUser()->getAttributeHolder()->remove('nickname');
525       }
526
527       public function executeCleanup()
528       {
529         $this->getUser()->getAttributeHolder()->clear();
530       }
531     }
532
533 The user session attributes are also available in the templates by default via the `$sf_user` variable, which stores the current `sfUser` object, as shown in Listing 6-18.
534
535 Listing 6-18 - Templates Also Have Access to the User Session Attributes
536
537     [php]
538     <p>
539       Hello, <?php echo $sf_user->getAttribute('nickname') ?>
540     </p>
541
542 >**NOTE**
543 >If you need to store information just for the duration of the current request--for instance, to pass information through a chain of action calls--you may prefer the `sfRequest` class, which also has `getAttribute()` and `setAttribute()` methods. Only the attributes of the `sfUser` object are persistent between requests.
544
545 ### Flash Attributes
546
547 A recurrent problem with user attributes is the cleaning of the user session once the attribute is not needed anymore. For instance, you may want to display a confirmation after updating data via a form. As the form-handling action makes a redirect, the only way to pass information from this action to the action it redirects to is to store the information in the user session. But once the confirmation message is displayed, you need to clear the attribute; otherwise, it will remain in the session until it expires.
548
549 The flash attribute is an ephemeral attribute that you can define and forget, knowing that it will disappear after the very next request and leave the user session clean for the future. In your action, define the flash attribute like this:
550
551     [php]
552     $this->getUser()->setFlash('notice', $value);
553
554 The template will be rendered and delivered to the user, who will then make a new request to another action. In this second action, just get the value of the flash attribute like this:
555
556     [php]
557     $value = $this->getUser()->getFlash('notice');
558
559 Then forget about it. After delivering this second page, the `notice` flash attribute will be flushed. And even if you don't require it during this second action, the flash will disappear from the session anyway.
560
561 If you need to access a flash attribute from a template, use the `$sf_user` object:
562
563     [php]
564     <?php if ($sf_user->hasFlash('notice')): ?>
565       <?php echo $sf_user->getFlash('notice') ?>
566     <?php endif; ?>
567
568 or just:
569
570     [php]
571     <?php echo $sf_user->getFlash('notice') ?>
572
573 Flash attributes are a clean way of passing information to the very next request.
574
575 ### Session Management
576
577 Symfony's session-handling feature completely masks the client and server storage of the session IDs to the developer. However, if you want to modify the default behaviors of the session-management mechanisms, it is still possible. This is mostly for advanced users.
578
579 On the client side, sessions are handled by cookies. The symfony session cookie is called `symfony`, but you can change its name by editing the `factories.yml` configuration file, as shown in Listing 6-19.
580
581 Listing 6-19 - Changing the Session Cookie Name, in `apps/frontend/config/factories.yml`
582
583     all:
584       storage:
585         class: sfSessionStorage
586         param:
587           session_name: my_cookie_name
588
589 >**TIP**
590 >The session is started (with the PHP function `session_start()`) only if the `auto_start` parameter is set to true in factories.yml (which is the case by default). If you want to start the user session manually, disable this setting of the storage factory.
591
592 Symfony's session handling is based on PHP sessions. This means that if you want the client-side management of sessions to be handled by URL parameters instead of cookies, you just need to change the use_trans_sid setting in your php.ini. Be aware that this is not recommended.
593
594     session.use_trans_sid = 1
595
596 On the server side, symfony stores user sessions in files by default. You can store them in your database by changing the value of the `class` parameter in `factories.yml`, as shown in Listing 6-20.
597
598 Listing 6-20 - Changing the Server Session Storage, in `apps/frontend/config/factories.yml`
599
600     all:
601       storage:
602         class: sfMySQLSessionStorage
603         param:
604           db_table:    session              # Name of the table storing the sessions
605           database:    propel               # Name of the database connection to use
606           # Optional parameters
607           db_id_col:   sess_id              # Name of the column storing the session id
608           db_data_col: sess_data            # Name of the column storing the session data
609           db_time_col: sess_time            # Name of the column storing the session timestamp
610
611 The `database` setting defines the database connection to be used. Symfony will then use `databases.yml` (see Chapter 8) to determine the connection settings (host, database name, user, and password) for this connection.
612
613 The available session storage classes are `sfMySQLSessionStorage`, `sfPostgreSQLSessionStorage`, and `sfPDOSessionStorage`; the latter is preferred. To disable session storage completely, you can use the `sfNoStorage` class.
614
615 Session expiration occurs automatically after 30 minutes. This default setting and can be modified for each environment in the same `factories.yml` configuration file, but this time in the `user` factory, as shown in Listing 6-21.
616
617 Listing 6-21 - Changing Session Lifetime, in `apps/frontend/config/factories.yml`
618
619     all:
620       user:
621         class:       myUser
622         param:
623           timeout:   1800           # Session lifetime in seconds
624
625 To learn more about factories, refer to Chapter 19.
626
627 Action Security
628 ---------------
629
630 The ability to execute an action can be restricted to users with certain privileges. The tools provided by symfony for this purpose allow the creation of secure applications, where users need to be authenticated before accessing some features or parts of the application. Securing an application requires two steps: declaring the security requirements for each action and logging in users with privileges so that they can access these secure actions.
631
632 ### Access Restriction
633
634 Before being executed, every action passes by a special filter that checks if the current user has the privileges to access the requested action. In symfony, privileges are composed of two parts:
635
636   * Secure actions require users to be authenticated.
637   * Credentials are named security privileges that allow organizing security by group.
638
639 Restricting access to an action is simply made by creating and editing a YAML configuration file called `security.yml` in the module `config/` directory. In this file, you can specify the security requirements that users must fulfill for each action or for `all` actions. Listing 6-22 shows a sample `security.yml`.
640
641 Listing 6-22 - Setting Access Restrictions, in `apps/frontend/modules/mymodule/config/security.yml`
642
643     read:
644       is_secure:   off       # All users can request the read action
645
646     update:
647       is_secure:   on        # The update action is only for authenticated users
648
649     delete:
650       is_secure:   on        # Only for authenticated users
651       credentials: admin     # With the admin credential
652
653     all:
654       is_secure:  off        # off is the default value anyway
655
656 Actions are not secure by default, so when there is no `security.yml` or no mention of an action in it, actions are accessible by everyone. If there is a `security.yml`, symfony looks for the name of the requested action and, if it exists, checks the fulfillment of the security requirements. What happens when a user tries to access a restricted action depends on his credentials:
657
658   * If the user is authenticated and has the proper credentials, the action is executed.
659   * If the user is not identified, he will be redirected to the default login action.
660   * If the user is identified but doesn't have the proper credentials, he will be redirected to the default secure action, shown in Figure 6-1.
661
662 The default login and secure pages are pretty simple, and you will probably want to customize them. You can configure which actions are to be called in case of insufficient privileges in the application `settings.yml` by changing the value of the properties shown in Listing 6-23.
663
664 Figure 6-1 - The default secure action page
665
666 ![The default secure action page](/images/book/F0601.jpg "The default secure action page")
667
668 Listing 6-23 - Default Security Actions Are Defined in `apps/frontend/config/settings.yml`
669
670     all:
671       .actions:
672         login_module:           default
673         login_action:           login
674
675         secure_module:          default
676         secure_action:          secure
677
678 ### Granting Access
679
680 To get access to restricted actions, users need to be authenticated and/or to have certain credentials. You can extend a user's privileges by calling methods of the `sfUser` object. The authenticated status of the user is set by the `setAuthenticated()` method and can be checked with `isAuthenticated()`. Listing 6-24 shows a simple example of user authentication.
681
682 Listing 6-24 - Setting the Authenticated Status of a User
683
684     [php]
685     class myAccountActions extends sfActions
686     {
687       public function executeLogin()
688       {
689         if ($this->getRequestParameter('login') == 'foobar')
690         {
691           $this->getUser()->setAuthenticated(true);
692         }
693       }
694
695       public function executeLogout()
696       {
697         if ($this->getUser()->isAuthenticated())
698         {
699           $this->getUser()->setAuthenticated(false);
700         }
701       }
702     }
703
704 Credentials are a bit more complex to deal with, since you can check, add, remove, and clear credentials. Listing 6-25 describes the credential methods of the `sfUser` class.
705
706 Listing 6-25 - Dealing with User Credentials in an Action
707
708     [php]
709     class myAccountActions extends sfActions
710     {
711       public function executeDoThingsWithCredentials()
712       {
713         $user = $this->getUser();
714
715         // Add one or more credentials
716         $user->addCredential('foo');
717         $user->addCredentials('foo', 'bar');
718
719         // Check if the user has a credential
720         echo $user->hasCredential('foo');                      =>   true
721
722         // Check if the user has both credentials
723         echo $user->hasCredential(array('foo', 'bar'));        =>   true
724
725         // Check if the user has one of the credentials
726         echo $user->hasCredential(array('foo', 'bar'), false); =>   true
727
728         // Remove a credential
729         $user->removeCredential('foo');
730         echo $user->hasCredential('foo');                      =>   false
731
732         // Remove all credentials (useful in the logout process)
733         $user->clearCredentials();
734         echo $user->hasCredential('bar');                      =>   false
735       }
736     }
737
738 If a user has the `'foo'` credential, that user will be able to access the actions for which the `security.yml` requires that credential. Credentials can also be used to display only authorized content in a template, as shown in Listing 6-26.
739
740 Listing 6-26 - Dealing with User Credentials in a Template
741
742     [php]
743     <ul>
744       <li><?php echo link_to('section1', 'content/section1') ?></li>
745       <li><?php echo link_to('section2', 'content/section2') ?></li>
746       <?php if ($sf_user->hasCredential('section3')): ?>
747       <li><?php echo link_to('section3', 'content/section3') ?></li>
748       <?php endif; ?>
749     </ul>
750
751 As for the authenticated status, credentials are often given to users during the login process. This is why the `sfUser` object is often extended to add login and logout methods, in order to set the security status of users in a central place.
752
753 >**TIP**
754 >Among the symfony plug-ins, the `sfGuardPlugin` (http://trac.symfony-project.com/wiki/sfGuardPlugin) extends the session class to make login and logout easy. Refer to Chapter 17 for more information.
755
756 ### Complex Credentials
757
758 The YAML syntax used in the security.yml file allows you to restrict access to users having a combination of credentials, using either AND-type or OR-type associations. With such a combination, you can build a complex workflow and user privilege management system--for instance, a content management system (CMS) back-office accessible only to users with the admin credential, where articles can be edited only by users with the `editor` credential and published only by the ones with the `publisher` credential. Listing 6-27 shows this example.
759
760 Listing 6-27 - Credentials Combination Syntax
761
762     editArticle:
763       credentials: [ admin, editor ]              # admin AND editor
764
765     publishArticle:
766       credentials: [ admin, publisher ]           # admin AND publisher
767
768     userManagement:
769       credentials: [[ admin, superuser ]]         # admin OR superuser
770
771 Each time you add a new level of square brackets, the logic swaps between AND and OR. So you can create very complex credential combinations, such as this:
772
773     credentials: [[root, [supplier, [owner, quasiowner]], accounts]]
774                  # root OR (supplier AND (owner OR quasiowner)) OR accounts
775
776 Validation and Error-Handling Methods
777 -------------------------------------
778
779 >**NOTE**
780 >The features described in this section are deprecated in symfony 1.1 and only work if you enable the `sfCompat10` plugin.
781
782 Validating the action input--mostly request parameters--is a repetitive and tedious task. Symfony offers a built-in request validation system, using methods of the action class.
783
784 Let's start with an example. When a user makes a request for `myAction`, symfony always looks for a method called `validateMyAction()` first. If it is found, then symfony executes it. The return value of this validation method determines the next method to be executed: if it returns `true`, then `executeMyAction()` is executed; otherwise, `handleErrorMyAction()` is executed. And, if in the latter case, handleErrorMyAction() doesn't exist, symfony looks for a generic handleError() method. If that doesn't exist either, it simply returns `sfView::ERROR` to render the `myActionError. php` template. Figure 6-2 depicts this process.
785
786 Figure 6-2 - The validation process
787
788 ![The validation process](/images/book/F0602.png "The validation process")
789
790 So the key to validation is to respect the naming conventions for the action methods:
791
792   * `validateActionName` is the validation method, returning `true` or `false`. It is the first method looked for when the action `ActionName` is requested. If it doesn't exist, the action method is executed directly.
793   * `handleErrorActionName` is the method called when the validation method fails. If it doesn't exist, the `Error` template is displayed.
794   * `executeActionName` is the action method. It must exist for all actions.
795
796 Listing 6-28 shows an example of an action class with validation methods. Whether the validation passes or fails in this example, the `myActionSuccess.php` template will be executed, but not with the same parameters.
797
798 Listing 6-28 - Sample Validation Methods
799
800     [php]
801     class mymoduleActions extends sfActions
802     {
803       public function validateMyAction()
804       {
805         return ($this->getRequestParameter('id') > 0);
806       }
807
808       public function handleErrorMyAction()
809       {
810         $this->message = "Invalid parameters";
811
812         return sfView::SUCCESS;
813       }
814
815       public function executeMyAction()
816       {
817         $this->message = "The parameters are correct";
818       }
819     }
820
821 You can put any code you want in the `validate()` methods. Just make sure they return either `true` or `false`. As it is a method of the `sfActions` class, it has access to the `sfRequest` and `sfUser` objects as well, which can be really useful for input and context validation.
822
823 You could use this mechanism to implement form validation (that is, control the values entered by the user in a form before processing it), but this is the type of repetitive task for which symfony provides automated tools, as described in Chapter 10.
824
825 Filters
826 -------
827
828 The security process can be understood as a filter by which all requests must pass before executing the action. According to some tests executed in the filter, the processing of the request is modified--for instance, by changing the action executed (default/secure instead of the requested action in the case of the security filter). Symfony extends this idea to filter classes. You can specify any number of filter classes to be executed before the action execution or before the response rendering, and do this for every request. You can see filters as a way to package some code, similar to `preExecute()` and `postExecute()`, but at a higher level (for a whole application instead of for a whole module).
829
830 ### The Filter Chain
831
832 Symfony actually sees the processing of a request as a chain of filters. When a request is received by the framework, the first filter (which is always the `sfRenderingFilter`) is executed. At some point, it calls the next filter in the chain, then the next, and so on. When the last filter (which is always `sfExecutionFilter`) is executed, the previous filter can finish, and so on back to the rendering filter. Figure 6-3 illustrates this idea with a sequence diagram, using an artificially small filter chain (the real one contains more filters).
833
834 Figure 6-3 - Sample filter chain
835
836 ![Sample filter chain](/images/book/F0603.png "Sample filter chain")
837
838 This process justifies the structure of the filter classes. They all extend the `sfFilter` class, and contain one `execute()` method, expecting a `$filterChain` object as parameter. Somewhere in this method, the filter passes to the next filter in the chain by calling `$filterChain->execute()`. See Listing 6-29 for an example. So basically, filters are divided into two parts:
839
840   * The code before the call to `$filterChain->execute()` executes before the action execution.
841   * The code after the call to `$filterChain->execute()` executes after the action execution and before the rendering.
842
843 Listing 6-29 - Filter Class Struture
844
845     [php]
846     class myFilter extends sfFilter
847     {
848       public function execute ($filterChain)
849       {
850         // Code to execute before the action execution
851         ...
852
853         // Execute next filter in the chain
854         $filterChain->execute();
855
856         // Code to execute after the action execution, before the rendering
857         ...
858       }
859     }
860
861 The default filter chain is defined in an application configuration file called `filters.yml`, and is shown in Listing 6-30. This file lists the filters that are to be executed for every request.
862
863 Listing 6-30 - Default Filter Chain, in `frontend/config/filters.yml`
864
865     rendering: ~
866     web_debug: ~
867     security:  ~
868
869     # Generally, you will want to insert your own filters here
870
871     cache:     ~
872     common:    ~
873     execution: ~
874
875 These declarations have no parameter (the tilde character, `~`, means "null" in YAML), because they inherit the parameters defined in the symfony core. In the core, symfony defines `class` and `param` settings for each of these filters. For instance, Listing 6-31 shows the default parameters for the `rendering` filter.
876
877 Listing 6-31 - Default Parameters of the rendering Filter, in `$sf_symfony_lib_dir/config/config/filters.yml`
878
879     rendering:
880       class: sfRenderingFilter   # Filter class
881       param:                     # Filter parameters
882         type: rendering
883
884 By leaving the empty value (`~`) in the application `filters.yml`, you tell symfony to apply the filter with the default settings defined in the core.
885
886 You can customize the filter chain in various ways:
887
888   * Disable some filters from the chain by adding an `enabled: off` parameter. For instance, to disable the web debug filter, write:
889
890         web_debug:
891           enabled: off
892
893   * Do not remove an entry from the `filters.yml` to disable a filter; symfony would throw an exception in this case.
894   * Add your own declarations somewhere in the chain (usually after the `security` filter) to add a custom filter (as discussed in the next section). Be aware that the `rendering` filter must be the first entry, and the `execution` filter must be the last entry of the filter chain.
895   * Override the default class and parameters of the default filters (notably to modify the security system and use your own security filter).
896
897 >**TIP**
898 >The `enabled: off` parameter works well to disable your own filters, but you can deactivate the default filters via the `settings.yml` file, by modifying the values of the `web_debug`, `use_security`, and `cache` settings. This is because each of the default filters has a `condition` parameter that tests the value of these settings.
899
900
901 ### Building Your Own Filter
902
903 It is pretty simple to build a filter. Create a class definition similar to the one shown in Listing 6-29, and place it in one of the project's lib/ folders to take advantage of the autoloading feature.
904
905 As an action can forward or redirect to another action and consequently relaunch the full chain of filters, you might want to restrict the execution of your own filters to the first action call of the request. The `isFirstCall()` method of the `sfFilter` class returns a Boolean for this purpose. This call only makes sense before the action execution.
906
907 These concepts are clearer with an example. Listing 6-32 shows a filter used to auto-log users with a specific `MyWebSite` cookie, which is supposedly created by the login action. It is a rudimentary but working way to implement the "remember me" feature offered in login forms.
908
909 Listing 6-32 - Sample Filter Class File, Saved in `apps/frontend/lib/rememberFilter.class.php`
910
911     [php]
912     class rememberFilter extends sfFilter
913     {
914       public function execute($filterChain)
915       {
916         // Execute this filter only once
917         if ($this->isFirstCall())
918         {
919           // Filters don't have direct access to the request and user objects.
920           // You will need to use the context object to get them
921           $request = $this->getContext()->getRequest();
922           $user    = $this->getContext()->getUser();
923
924           if ($request->getCookie('MyWebSite'))
925           {
926             // sign in
927             $user->setAuthenticated(true);
928           }
929         }
930
931         // Execute next filter
932         $filterChain->execute();
933       }
934     }
935
936 In some cases, instead of continuing the filter chain execution, you will need to forward to a specific action at the end of a filter. `sfFilter` doesn't have a `forward()` method, but `sfController` does, so you can simply do that by calling the following:
937
938     [php]
939     return $this->getContext()->getController()->forward('mymodule', 'myAction');
940
941 >**NOTE**
942 >The `sfFilter` class has an `initialize()` method, executed when the filter object is created. You can override it in your custom filter if you need to deal with filter parameters (defined in `filters.yml`, as described next) in your own way.
943
944 ### Filter Activation and Parameters
945
946 Creating a filter file is not enough to activate it. You need to add your filter to the filter chain, and for that, you must declare the filter class in the `filters.yml`, located in the application or in the module `config/` directory, as shown in Listing 6-33.
947
948 Listing 6-33 - Sample Filter Activation File, Saved in `apps/frontend/config/filters.yml`
949
950     rendering: ~
951     web_debug: ~
952     security:  ~
953
954     remember:                 # Filters need a unique name
955       class: rememberFilter
956       param:
957         cookie_name: MyWebSite
958         condition:   %APP_ENABLE_REMEMBER_ME%
959
960     cache:     ~
961     common:    ~
962     execution: ~
963
964 When activated, the filter is executed for each request. The filter configuration file can contain one or more parameter definitions under the `param` key. The filter class has the ability to get the value of these parameters with the getParameter() method. Listing 6-34 demonstrates how to get a filter parameter value.
965
966 Listing 6-34 - Getting the Parameter Value, in `apps/frontend/lib/rememberFilter.class.php`
967
968     [php]
969     class rememberFilter extends sfFilter
970     {
971       public function execute ($filterChain)
972       {
973           ...
974           if ($request->getCookie($this->getParameter('cookie_name')))
975           ...
976       }
977     }
978
979 The `condition` parameter is tested by the filter chain to see if the filter must be executed. So your filter declarations can rely on an application configuration, just like the one in Listing 6-33. The remember filter will be executed only if your application `app.yml` shows this:
980
981     all:
982       enable_remember_me: on
983
984 ### Sample Filters
985
986 The filter feature is useful to repeat code for every action. For instance, if you use a distant analytics system, you probably need to put a code snippet calling a distant tracker script in every page. You could put this code in the global layout, but then it would be active for all of the application. Alternatively, you could place it in a filter, such as the one shown in Listing 6-35, and activate it on a per-module basis.
987
988 Listing 6-35 - Google Analytics Filter
989
990     [php]
991     class sfGoogleAnalyticsFilter extends sfFilter
992     {
993       public function execute($filterChain)
994       {
995         // Nothing to do before the action
996         $filterChain->execute();
997
998         // Decorate the response with the tracker code
999         $googleCode = '
1000     <script src="http://www.google-analytics.com/urchin.js"  type="text/javascript">
1001