Development

Documentation/fr_FR/book/1.0/trunk/06-Inside-the-Controller-Layer

You must first sign up to be able to contribute.

Version 17 (modified by ladenise, 10 years ago)
--

Cette partie de la documentation est en cours de traduction. Cela signifie qu'elle est traduite de manière soit incomplète, soit inexacte. En attendant que cette traduction soit terminée, vous pouvez consulter la version en anglais pour des informations plus fiables.

Chapitre 6 - La couche contrôleur en détail
===========================================

Dans Symfony, la couche contrôleur contient le code liant la partie logique et la présentation, elle est séparée en plusieurs composants ayant chacun une utilité différente : 

  * Le contrôleur principal (front controller) est l'unique point d'entrée de l'application, il charge la configuration en cours et détermine les actions à effectuer.
  * Les actions contiennent le code applicatif. Elles vérifient l'intégrité des requêtes et préparent les données à passer à la couche de présentation.
  * Les requêtes, les réponses et les objets de sessions permettent l'accès à des informations particulières comme les headers des réponses et les données persistantes. Ces informations sont très souvent utilisées par le contrôleur principal.
  * Les filtres sont des portions de codes exécutées à chaque requête, avant ou après l'action. Par exemple, on utilise des filtres pour gérer la sécurité ou les processus d'authentification d'applications web. Vous pouvez rajouter vos propres filtres au framework. 
 
Ce chapitre décrit l'ensemble de ces composants, mais ne soyez pas impressionné par leur nombre. Pour créer une simple page l'ajout de quelques lignes dans la classe action suffiront. Les autres composants de la couche contrôleur ne seront utilisés que dans des cas bien spécifiques.

Le contrôleur principal
-------------------

Toutes les requêtes web sont prises en charge par le contrôleur principal qui est l'unique point d'accès à l'intégralité de l'application dans un environnement déterminé.

Quand le contrôleur principal reçoit une requête il utilise le système de routing afin de faire correspondre le nom de l'action et le nom du module avec l'URL saisie (ou cliquée) par l'utilisateur. Par exemple, la requête suivante appelle le script `index.php` (qui est le contrôleur principal) ce qui ce traduira par un appel à l'action `myaction` du module `mymodule`:

* * *
    http://localhost/index.php/mymodule/myaction
    http://localhost/index.php/mymodule/myaction
    
Si vous n'êtes pas intéressé par le fonctionnement interne de Symfony, alors vous en savez assez sur le contrôleur principal. c'est certes un élément indispensable de l'architecture MVC de Symfony mais vous n'aurez besoin que de très rarement le modifier. Vous pouvez donc passer à la section suivante à moins que vous ne désiriez vraiment en savoir plus sur les entrailles du contrôleur principal.

### Le fonctionnement du contrôleur principal en détail

Le contrôleur principal s'occupe de gérer les requêtes, mais cela signifie plus que simplement déterminer une action à exécuter. En fait, il exécute le code qui est commun à chaque action, soit les étapes suivantes :

  1. Définir les constantes.
  2. Déterminer les chemins des bibliothèques Symfony.
  3. Charger et initialiser les classes du coeur du framework.
  4. Charger la configuration.
  5. Décoder la requête URL afin d'identifier les actions à effectuer et les paramètres de la requête.
  6. Si l'action n'existe pas, faire une redirection sur l'action 404 error.
  7. Activer les filtres (par exemple, si les requêtes nécessitent une authentification).
  8. Exécuter les filtres une première fois.
  9. Exécuter l'action et générer la présentation. 
  10. Exécuter les filtres une seconde fois.
  11. Renvoyer la réponse.
  
### Le contrôleur principal par défaut

Le contrôleur principal par défaut s'appelle `index.php`, il est situé dans le repertoire `web/` du projet, et il s'agit d'un simple fichier PHP comme le montre son contenu dans le Listing 6-1.

*Listing 6-1 - Le Contenu du contrôleur principal par défaut*

    [php]
    <?php

    define('SF_ROOT_DIR',    realpath(dirname(__FILE__).'/..'));
    define('SF_APP',         'myapp');
    define('SF_ENVIRONMENT', 'prod');
    define('SF_DEBUG',       false);

    require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php');

    sfContext::getInstance()->getController()->dispatch();

La définition des constantes correspond à la première étape décrite dans la section précédente. Ensuite le contrôleur principal appelle le `config.php` de l'application qui prend en charge les étapes 2 à 4. L'appel de la méthode `dispatch()` du `sfController` object (qui est le contrôleur objet du coeur de l'architecture MVC de Symfony) expédie la requête effectuant les étapes 5 à 7. Les dernières étapes sont prises en charge par les chaînes de filtres comme expliqué plus loin dans ce chapitre.

### Faire appel à un autre contrôleur principal pour permuter d'environnement

Un seul contrôleur principal existe par environnement en fait, c'est l'existence même d'un contrôleur principal qui détermine un environnement. L'environnement est défini par la constante `SF_ENVIRONMENT`.

Pour changer l'environnement dans lequel s'exécute votre application, il suffit juste de choisir un autre contrôleur principal. Le contrôleur principal disponible par défaut quand vous créez une nouvelle application avec la commande `symfony init-app` est `index.php` pour  un environnement de production et `myapp_dev.php` pour un environnement de développement (sous-entendue que votre application ce nomme `myapp`). La configuration par défaut du `mod_rewrite` utilisera `index.php` si l'url ne contient pas de nom de script contrôleur principal. Ainsi ces deux URL vont afficher la même page (mymodule/index) dans un environnement de production:
* * * 
    http://localhost/index.php/mymodule/index
    http://localhost/mymodule/index
* * *
et cette URL affiche la même page dans un environnement de développement
* * *
    http://localhost/myapp_dev.php/mymodule/index
* * *

Créer un nouvel environnement est aussi facile que de créer un nouveau contrôleur principal. Par exemple vous aurez peut être besoin d'un environnement temporaire pour autoriser vos clients à tester votre application avant de mettre celle-ci en production. Pour créer cet environnement temporaire (staging), copier simplement `web/myapp_dev.php` dans `web/myapp_staging.php`, et changer la valeur de la constante `SF_ENVIRONMENT` à `staging`. A partir de maintenant dans tous les fichiers de configuration vous pouvez ajouter une nouvelle section intermédiaire : cela vous permettra de préciser certaines valeurs spécifiques à cet environnement, comme le démontre le Listing 6-2.

*Listing 6-2 - Extrait de `app.yml` avec des paramètres spécifiques à l'environnement intermédiaire (staging)*

    staging:
      mail:
        webmaster:    dummy@mysite.com
        contact:      dummy@mysite.com
    all:
      mail:
        webmaster:    webmaster@mysite.com
        contact:      contact@mysite.com

Pour observer comment réagit l'application dans ce nouvel environnement, appeler le contrôleur principal associé:
* * *
    http://localhost/myapp_staging.php/mymodule/index
* * *
### Fichiers batch

Vous pouvez exécuter des scripts en ligne de commande (ou par l'intermédiaire d'une tache cron) accédant à l'ensemble des classes symfony y compris vos propres classes, afin par exemple d'envoyer des mails pré-programmés ou bien de mettre à jour votre modèle (ce qui nécessite un calcul intense). Pour créer un tel script il faut lui inclure les même premières lignes que celle d'un contrôleur principal. Le Listing 6-3 est un début d'exemple d'un tel script.

*Listing 6-3 - Extrait d'un script batch*

    [php]
    <?php

    define('SF_ROOT_DIR',    realpath(dirname(__FILE__).'/..'));
    define('SF_APP',         'myapp');
    define('SF_ENVIRONMENT', 'prod');
    define('SF_DEBUG',       false);

    require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php');

    // ajouter votre code ici

Comme vous pouvez le constater la seule ligne manquante est l'appel à la méthode `dispatch()` de l'objet `sfController`, qui ne peut être utilisé que dans le cadre d'un serveur web, pas dans un processus batch. Le fait de définir une application et un environnement vous donne accès à une configuration spécifique. L'appel du `config.php` de l'application initialise le contexte et l'auto-chargement de l'application.


>**TIP**
>Le CLI (interface en ligne de commande) de Symfony propose une commande `init-batch` qui crée automatiquement un squelette similaire à celui décrit dans le Listing 6-3 dans le repertoire `batch/`. Il suffit juste de passer en arguments à cette commande, le nom de l'application, celui de l'environnement, et le nom du batch.

Actions
-------

Les actions représentent le coeur d'une application, c'est elles qui contiennent la logique des applications. Elles utilisent le modèle et définissent des variables pour la présentation. Quand vous faites une requête web dans une application Symfony, l'URL définit l'action à entreprendre ainsi que les paramètres de requête.

### La classe action

Les actions sont des méthodes nommées `executeactionName` de la classe nommée `moduleNameactions` qui hérite de la classe `sfactions`, et groupées par modules. La classe action d'un module est stockée dans le fichier `actions.class.php`, lui même situé dans le répertoire du module `actions/`.

Le listing 6-4 montre un exemple de fichier `actions.class.php` avec seulement une action d'indexation `index` pour tout le module `mymodule`.

*Listing 6-4 - Extrait de la classe action de `apps/myapp/modules/mymodule/actions/actions.class.php`*

    [php]
    class mymoduleactions extends sfactions
    {
      public function executeIndex()
      {

      }
    }

>**CAUTION**
>Les noms de méthodes ne sont pas sensibles à la casse en PHP, mais elle le sont avec Symfony. N'oubliez pas que les méthodes actions doivent débuter par `execute` en minuscule, puis le nom exact du module avec sa première lettre en capitale.

Pour exécuter une action, il est nécessaire d'appeler le script de contrôleur principal avec le nom du module et le nom de l'action en paramètres. Par défaut, cela est réalisé en ajoutant au script le nom du module et de l'action. Cela signifie que l'action définie au Listing 6-4 peut être appelée par cette URL:
* * *
    http://localhost/index.php/mymodule/index
* * *
Ajouter plus d'actions signifie ajouter plus de méthodes `execute` à l'objet `sfactions` comme le montre le Listing 6-5.

*Listing 6-5 -Classe action avec 2 actions, dans `myapp/modules/mymodule/actions/actions.class.php`*

    [php]
    class mymoduleactions extends sfactions
    {
      public function executeIndex()
      {
        ...
      }

      public function executeList()
      {
        ...
      }
    }

Si la taille d'une classe action grandit trop vite, il vous faudra probablement faire le refactoring de celle-ci et déplacer une partie de son code dans la couche modèle. Les actions doivent être tout le temps réduite au strict minimum (pas plus de quelques lignes), toute la partie concernant la logique d'application doit être contenue dans la modèle.

Toutefois, le nombre d'actions dans un module peut être si important que vous soyez amené à le partager en deux modules distincts. 

>**SIDEBAR**
>Convention de codage de Symfony
>
>Dans les codes d'exemples donnés dans ce livre, vous aurez probablement remarqué que les parenthèses, crochets ouvrant ou fermant ({ et }) occupent chacun une ligne. Cette standardisation du code augmente sa lisibilité.
>
>Parmi les autres standards de codage du framework, l'indentation est toujours réalisées avec deux espaces blancs, les tabulations ne sont pas utilisées. Ceci est dû au fait les tabulations ne correspondent pas au même nombres d'espaces d'un éditeur de texte à l'autre et également parce que les sources dans lesquels un mélange de tabulations et d'espaces servent à l'indentation, sont illisibles.
>
>Les fichiers PHP systèmes et ceux générés par Symfony ne se terminent pas avec l'habituelle balise de fermeture ?>. La raison est que cette balise n'est pas obligatoire, mais aussi qu'elle peut créer des problèmes d'output si vous laissez des espaces blancs derrière cette balise.
>
>Et si vous êtes très attentif, vous noterez qu'aucune ligne ne se termine par un espace blanc dans Symfony. Cette fois la raison est plus prosaïque: les lignes se terminant pas une espace blanc ont un rendu horrible dans l'éditeur de texte de Fabien.

### Syntaxe alternative de la classe action

Il existe une syntaxe alternative pour la classe action permettant de dispatcher les actions dans des fichiers séparés, un fichier par action. Dans ce cas, chaque action étends `sfaction` (au lieu de `sfactions`) et ce nomme `actionNameaction`. La présente méthode action se nomme simplement `execute`. Le nom du fichier est le même que celui de la classe. Cela signifie que l'équivalent du Listing 6-5 peut s'écrire comme les 2 fichiers présents dans les Listings 6-6 et 6-7. 

* Listing 6-6 - Fichier action seul, dans `myapp/modules/mymodule/actions/indexaction.class.php`*

    [php]
    class indexaction extends sfaction
    {
      public function execute()
      {
        ...
      }
    }

*Listing 6-7 - Fichier action seul, dans `myapp/modules/mymodule/actions/listaction.class.php`*

    [php]
    class listaction extends sfaction
    {
      public function execute()
      {
        ...
      }
    }

###Récupérer des informations via la classe action

La classe action permet d'obtenir des informations provenant de son contrôleur et des objets du noyaux de Symfony. Le Listing 6-8 explique comment les utiliser.

*Listing 6-8 - Les méthodes communes de `sfactions`*

    [php]
    class mymoduleactions extends sfactions
    {
      public function executeIndex()
      {
        // Récupère les paramètres de la requête 
        $password    = $this->getrequestParameter('password');

        // Récupère les informations du Controller
        $moduleName  = $this->getModuleName();
        $actionName  = $this->getactionName();

        // Récupère les objet du noyau
        $request     = $this->getrequest();
        $userSession = $this->getUser();
        $response    = $this->getResponse();
        $Controller  = $this->getController();
        $context     = $this->getContext();

	// Instancie les variables ??action?? afin de passer des informations au gabarit
        $this->setVar('foo', 'bar');
        $this->foo = 'bar';            // Version courte

      }
    }

>**SIDEBAR**
>Le singleton de contexte
>
>Vous avez déjà vu dans le contrôleur principal, un appel à sfContext::getInstance(). Dans une classe action,la méthode getContext() renvoie le même singleton. C'est un objet très pratique qui stocke les références de tous les objets du noyau de Symfony correspondant à une requête donnée, et propose des accès à chacun d'eux:
>
>`sfController`: l'objet contrôleur (`->getController()`) `sfRequest`: l'objet requêtes (`->gêtrequest()`) `sfResponse`: l'objet réponse (`->getResponse()`) `sfUser`: l'onjet session utilisateur (`->getUser()`) `sfDatabaseConnection`: la connexion à la base de données (`->getDatabaseConnection()`) `sfLogger`: l'objet de log (`->getLogger()`) `sfI18N`: l'objet d'internationalisation (`->getI18N()`)
>
>Vous pouvez appeler le singleton `sfContext::getInstance()` depuis n'importe quelle section du code.

### Terminaison d'une action

Plusieurs comportements sont possible en fin d'exécution d'une action. La valeur retournée par la méthode action va déterminer la présentation qui sera générée. Les constantes de la classe `sfView` sont utilisées pour spécifier quel gabarit doit être utilisé pour afficher le résultat d'une action.

Si il y a une présentation par défaut à appeler (ce qui est le cas le plus fréquent), la classe action devrait se terminer comme suit:

    [php]
    return sfView::SUCCESS;

Symfony va maintenant rechercher un gabarit appelé `actionNameSuccess.php`. Ce comportement est défini par défaut, donc si vous omettez la valeur de `return` dans une méthode d'action, Symfony ira quand même à la recherche du gabarit `actionNameSuccess.php`. Une action vide reproduira également ce comportement. Voyez le Listing 6-9 pour des exemples de fin d'actions réussies.

*Listing 6-9 - Classe action qui va appeler les templates `indexSuccess.php` et `listSuccess.php`.*

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

    public function executeList()
    {
    }

S'il faut une présentation symbolisant une erreur, l'action devrait se terminer comme ceci:

    [php]
    return sfView::ERROR;

Symfony recherchera donc un gabarit appelé `actionNameError.php`.

Pour appeler une présentation personnalisée, terminez votre fichier comme suit:

    [php]
    return 'MyResult';

Symfony recherchera alors un gabarit appelé `actionNameMyResult.php`.

Si il n'y a pas de présentation à appeler (par exemple dans le cas d'une action exécutée à partir d'un processus batch) la classe action doit se terminer comme suit:

    [php]
    return sfView::NONE;

Aucun gabarit ne sera exécuté dans ce cas de figure. Cela signifie que vous pouvez complètement court-circuiter la couche de présentation et renvoyer directement du HTML en sortie depuis une classe action. Comme vu dans le Listing 6-10, Symfony utilise une méthode `renderText()` spécifique dans ce cas là. Cela peut être très pratique si vous nécessitez une classe action très réactive, comme des interactions ajax, qui seront discutées au chapitre 11. 

*Listing 6-10 - Court-circuiter la couche présentation en retournant la réponse sous forme d'echo avec la constante `sfView::NONE`.*

    [php]
    public function executeIndex()
    {
      echo "<html><body>Hello, World!</body></html>";

      return sfView::NONE;
    }

    // Est équivalent à
    public function executeIndex()
    {
      return $this->renderText("<html><body>Hello, World!</body></html>");
    }

Parfois, vous aurez besoin de renvoyer une réponse vide ne comprenant que les headers ( en particulier les headers `X-JSON`). Pour ce faire, définissez les headers avec l'objet `sfResponse`, qui sera vu dans le prochain chapitre, et retournez la constante `sfView::HEADER_ONLY` comme le montre le Listing 6-11.

*Listing 6-11 - Éviter de générer une présentation et envoyer seulement les headers*

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

      return sfView::HEADER_ONLY;
    }

Si la classe option doit générer un gabarit spécifique, ignorez la déclaration `return` et utilisez à la place la méthode `setTemplate()`

    [php]
    $this->setTemplate('myCustomTemplate');

### Passer à une autre action

Parfois, l'exécution d'une action se termine par une requête d'exécution d'une nouvelle action. Par exemple, une action initiée par un formulaire de soumission via une requête POST est habituellement re-dirigée vers une autre action après avoir mis à jour la base de données. Un autre exemple, une action nommée `index`  est souvent un moyen d'afficher une liste, et suit habituellement une action `list`.

La classe d'action fournit deux méthodes pour exécuter une autre action:

  * Si l'action précède l'appel à la nouvelle action

        [php]
        $this->forward('otherModule', 'index');

  * Si le résultat de l'action est une redirection web:

        [php]
        $this->redirect('otherModule/index');
        $this->redirect('http://www.google.com/');

>**NOTE**
>Le code situé après un redirect ou un forward dans une action n'est jamais exécuté. Vous pouvez considérer que ces appels sont équivalents à l'instruction `return`. Ils jettent un `sfStopException` qui stoppe l'exécution d'une action. Cette exception est ensuite catchée par Symfony puis simplement ignorée.

Le choix à faire entre un redirect et un forward est parfois piégeur. Pour choisir la meilleure solution il faut se souvenir qu'un forward agit de façon interne à l'application et qu'il est transparent pour l'utilisateur. Tant que l'utilisateur agit, il conserve la même URL que celle équivalente à sa premiere requête. A l'inverse, un redirect est un message envoyé au navigateur de l'utilisateur, ce qui implique une nouvelle requête de celui ci et au final un changement d'URL. 

Si l'action provient d'un formulaire de soumission avec la méthode `method="post"`,il vaut mieux faire toujours une redirection. Le principal avantage est que si l'utilisateur rafraîchit la page du résultat, le formulaire ne sera pas soumis une nouvelle fois, en complément, le bouton revenir en arrière marchera comme prévu en affichant le formulaire sans le message d'erreur demandant à l'utilisateur si il veut re-soumettre sa requête POST.

Il existe un certain type de forward qui est fréquemment employé. La méthode `forward404()` redirige vers une action provoquant une "page not found". Cette méthode est souvent appelée quand un paramètre nécessaire à l'exécution de l'action est manquant dans la requête (elle est détectée comme une URL mal écrite). Le Listing 6-12 décrit un exemple d'une action `show` nécessitant un paramètre `id`.

*Listing 6-12 - Usage de la méthode `forward404()`*

    [php]
    public function executeShow()
    {
      $article = ArticlePeer::retrieveByPK($this->gêtrequestParameter('id'));
      if (!$article)
      {
        $this->forward404();
      }
    }

>**TIP**
>Si vous recherchez l'action error 404 et son template, vous les trouverez dans le répertoire `$sf_symfony_ data_dir/modules/default/`. Vous pouvez customiser cette page en ajoutant un nouveau module `default` dans votre application ainsi qu'une action `error404` et un template error404Success à l'intérieur, cela surchargera celle présente dans le framework. Autrement vous pouvez paramétrer les constantes _404_module et error_404_ action dans le fichier `settings.yml` pour l'utiliser dans une action déjà existante.

L'expérience montre, la plupart du temps, qu'une action effectue toujours un redirect ou un forward après avoir testé quelque chose, comme dans le Listing 6-12. C'est pourquoi, la classe sfactions possède quelques méthodes en plus `forwardIf()`, `forwardUnless()`, `forward404If()`, `forward404Unless()`, `redirectIf()`, et `redirectUnless()`. Ces méthodes requièrent simplement un paramètre additionnel qui représente la condition  d'une exécution si elle est testée true ( pour la méthode `xxxIf()`) ou false (pour la méthode  `xxxUnless()`) comme décrit dans le Listing 6-13.

*Listing 6-13 - Usage de la méthode  `forward404If()`*

    [php]
    // Cette action est la même que celle présente dans le Listing 6-12
    public function executeShow()
    {
      $article = ArticlePeer::retrieveByPK($this->gêtrequestParameter('id'));
      $this->forward404If(!$article);
    }

    // Si c'est celle-là
    public function executeShow()
    {
      $article = ArticlePeer::retrieveByPK($this->gêtrequestParameter('id'));
      $this->forward404Unless($article);
    }

Utiliser ces méthodes vous permettra de garder un code restreint, ce qui le rendra plus lisible.

>**TIP**
>Quand une action appelle une méthode forward404() ou une méthode similaire, Symfony lance une sfError404Exception qui s'occupe de générer une réponse 404. Cela signifie que si vous voulez afficher une page 404 depuis un endroit ou vous n' avez pas accès au contrôleur, vous pouvez juste lancer une exception similaire.

### Répéter du code pour plusieurs actions d'un module

Les conventions de nommage `executeactionName()` (dans le cas d'une classe `sfactions`) ou `execute()` (dans le cas d'une classe sfaction) garantissent que Symfony trouvera ces méthodes d'action. Vous pouvez alors ajouter vos propres méthodes qui ne seront pas considérées comme des actions, dans la mesure où elles ne commencent pas par `execute`.

Il existe une autre convention de nommage très pratique lorsque vous avez besoin de répéter plusieurs déclarations dans chaque action avant la classe exécution. Vous pouvez extraire ces déclarations grâce à la méthode `preExecute()` de votre classe action. Vous avez probablement deviné comme répéter ces déclarations après avoir effectué une action exécution : entourez les d'une méthode `postExecute()`. Les syntaxes de ces méthodes sont décrites dans le Listing 6-14.

*Listing 6-14 - Utiliser vos propres méthodes ainsi que les méthodes `preExecute` et `postExecute` dans une classe action*

    [php]
    class mymoduleactions extends sfactions
    {
      public function preExecute()
      {
	// Le code à insérer ici est executer au début, à chaque appel de l'action
        ...
      }

      public function executeIndex()
      {
        ...
      }

      public function executeList()
      {
        ...
        $this->myCustomMethod();  // Les méthodes de la classe action sont accessibles
      }

      public function postExecute()
      {
	// Le code inséré ici est exécuté à la fin de chaque appel de l'action
        ...
      }

      protected function myCustomMethod()
      {
	//Vous pouvez aussi ajouter vos propres méthodes, à condition qu'elles ne débutent pas par le mot "execute"
	// Dans ce cas, il vaut mieux les déclarer protected ou private
        ...
      }
    }

##Accéder aux valeurs des requêtes

Vous êtes maintenant familier avec la méthode `getRequestParameter('myparam')`, utilisée pour récupérer la valeur des paramètres d'une requête en fonction de son nom. De ce fait, cette méthode est un intermédiaire pour une chaîne d'appel mémorisant les paramètrès de la requête `getRequest()->getParameter('myparam')`. La classe action a accès à l'objet requête appelé `sfWebRequest` dans Symfony, et à toutes ses méthodes, via la méthode `getRequest()`. La Table 6-1 liste les méthodes les plus communes de `sfWebRequest`



*Table 6-1 -Les méthodes de l'objet `sfWebRequest`*

Nom                              | Fonction                                       |  Extrait retourné
-------------------------------- | -----------------------------------------------|-----------------------------------------------------
**Information sur la requête**   |                                                |
`getMethod()`                    | méthode requête                                | Retourne les constantes `sfRequest::GET` ou `sfRequest::POST`
`getMethodName()`                | Nom de la méthode requête                      | `'POST'`
`getHttpHeader('Server')`        | Valeur d'une entête http donnée                | `'Apache/2.0.59 (Unix) DAV/2 PHP/5.1.6'`
`getCookie('foo')`               | Valeur d'un cookie donné                       | `'bar'`
`isXmlHttpRequest()`*            | Est-ce une requête AJAX?                       | `true`
`isSecure()`                     | Est-ce une requête SSL?                        | `true`
**paramètrès de la requête**     |                                                |
`hasParameter('foo')`            | Y a-t-il un paramètre présent dans la requête? | `true`
`getParameter('foo')`            | Quelle est la valeur d'un paramètre donné?     | `'bar'`
`getParameterHolder()->getAll()` | Tableau de tous les paramètres de la requête   |
**URI-Related Information**      |                                                |
`getUri()`                       | URI entière                                    | `'http://localhost/myapp_dev.php/mymodule/myaction'`
`getPathInfo()`                  | information sur le path                        | `'/mymodule/myaction'`
`gêtreferer()`**                 | Référence                                      | `'http://localhost/myapp_dev.php/'`
`getHost()`                      | Nom de l'host                                  | `'localhost'`
`getScriptName()`                | path du Front Controller et son nom            | `'myapp_dev.php'`
**Client Browser Information**   |                                                |
`getLanguages()`                 | Tableau des langages acceptés                  | `Array( ` ` [0] => fr ` ` [1] => fr_FR ` ` [2] => en_US ` ` [3] => en )`
`getCharsets()`                  | Tableau des charsets appelés                   | `Array( ` ` [0] => ISO-8859-1 ` ` [1] => UTF-8 ` ` [2] => * )`
getAcceptableContentTypes()      | Tableau des content-types acceptés             | `Array( [0] => text/xml [1] => text/html`

**Ne marche qu'avec prototype*

***Parfois bloqué par des proxies*

La classe `sfactions` accepte quelques intermédiaires pour accéder aux méthodes de requêtes plus rapidement, comme le montre le Listing 6-15.

*Listing 6-15 - Accéder aux méthodes de l'objet `sfactions` depuis une action*

    [php]
    class mymoduleactions extends sfactions
    {
      public function executeIndex()
      {
        $hasFoo = $this->gêtrequest()->hasParameter('foo');
        $hasFoo = $this->hasRequestParameter('foo');  // Shorter version
        $foo    = $this->gêtrequest()->getParameter('foo');
        $foo    = $this->gêtrequestParameter('foo');  // Shorter version
      }
    }

Pour des requêtes en plusieurs partie (multi-part) pour lesquelles un utilisateur attache un fichier, l'objet `sfWebRequest` fournit une façon d'accéder et de déplacer ces fichiers, comme le décrit le Listing 6-16.

*Listing 6-16 - L'objet `sfWebRequest` sait comment s'occuper des fichier attachés*

    [php]
    class mymoduleactions extends sfactions
    {
      public function executeUpload()
      {
        if ($this->gêtrequest()->hasFiles())
        {
          foreach ($this->gêtrequest()->getFileNames() as $fileName)
          {
            $fileSize  = $this->gêtrequest()->getFileSize($fileName);
            $fileType  = $this->gêtrequest()->getFileType($fileName);
            $fileError = $this->gêtrequest()->hasFileError($fileName);
            $uploadDir = sfConfig::get('sf_upload_dir');
            $this->gêtrequest()->moveFile('file', $uploadDir.'/'.$fileName);
          }
        }
      }
    }

Vous n'avez pas à vous préoccuper de savoir si votre serveur supporte les variables `$_SERVER` ou `$_ENV`, ou même des valeurs par défaut ou des problèmes de compatibilité de serveurs, la méthode `sfWebRequest` fait ça pour vous. De plus leurs noms sont tellement évidents que vous n'aurez plus à parcourir la documentation PHP pour savoir comment retirer des informations sur la requête.


##Session Utilisateur


Symfony gère automatiquement les sessions utilisateurs par défaut et peut gérer des données persistantes entre différentes requêtes pour les utilisateurs. Il s'appuie sur le système de session interne à PHP et l'améliore afin qu'il soit plus configurable et facile à utiliser.

### Accéder aux paramètres Session d'un utilisateur

L'objet session pour l'utilisateur courant, est accessible dans l'action par la méthode `getUser()` qui est une instance de la classe `sfUser`. Cette classe contient des paramètres en mémoire qui vous autorisent à stocker n'importe quel attribut utilisateur. Ces données seront disponibles pour les autres requêtes jusqu'à la fin de la session utilisateur, comme le décrit le Listing 6-17. Les attributs des utilisateurs peuvent être stockés sous n'importe quel format de données (strings, tableaux, tableaux associatifs). Ils peuvent être positionnés pour n'importe quel utilisateur individuel, même si cet utilisateur n'est pas identifié.

*Listing 6-17 - L'objet `sfUser` peut mémoriser plusieurs attributs utilisateurs existant entre plusieurs requêtes*

    [php]
    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()
      {
	// Récupère des données depuis la session utilisateur avec une valeur par défaut
        $nickname = $this->getUser()->getAttribute('nickname', 'Anonymous Coward');
      }
    }

>**CAUTION**
>Vous pouvez stocker des objets dans une session utilisateur, mais ceci est fortement déconseillé. En effet, l'objet session est sérialisé entre deux requêtes, puis stocker dans un fichier. Quand la session est de-sérialisée, la classe des objets stockés doit être déjà chargée, or ce n'est pas toujours le cas. En conséquence, il peut y avoir des objets "bloqué", si vous stockez des objets Propel. 

Comme beaucoup d'attributs passent par la méthode get, la méthode `getAttribute()` accepte un second argument qui spécifie la valeur par défaut lorsqu'il n'est pas défini. Pour vérifier si un attribut a été défini par un utilisateur utilisez la méthode `hasAttribute()`. Les attributs sont stockés dans un paramètre qui peut être accessible par la méthode `getAttributeHolder()`. Ce qui permet un nettoyage facile des attributs utilisateur avec la méthode retenant les paramètres, décrit dans le Listing 6-18.

*Listing 6-18 - Effacer des données d'une session utilisateur*

    [php]
    class mymoduleactions extends sfactions
    {
      public function executeRemoveNickname()
      {
        $this->getUser()->getAttributeHolder()->remove('nickname');
      }

      public function executeCleanup()
      {
        $this->getUser()->getAttributeHolder()->clear();
      }
    }

Les attributs d'une session utilisateur sont aussi disponibles depuis le template par défaut grâce à la variable `$sf_user`, qui stocke l'objet actuel  `sfUser`, comme le décrit le Listing 6-19.

*Listing 6-19 - Les Templates peuvent aussi accéder aux attributs de session utilisateurs*

    [php]
    <p>
      Hello, <?php echo $sf_user->getAttribute('nickname') ?>
    </p>

>**NOTE**
>Si vous désirez stocker des informations juste le temps de la requête comme par exemple pour passer des informations à travers une chaîne d'action, appelez de préférence la classe `sfRequest`, qui prend également comme attributs les méthodes `getAttribute()` et `setAttribute()`. Seul les attributs de l'objet `sfUser` persistent entre différentes requêtes.

### Attributs Flash

Un problème récurrent avec les attributs utilisateurs est le nettoyage de ceux ci une fois qu'ils ne sont plus utiles. Par exemple, il se peut que vous vouliez afficher une confirmation après avoir mis à jour des données via un formulaire. Comme le gestionnaire de formulaire effectue une redirection, le seul moyen de faire passer l'attribut de cette action à l'action qu'il redirige est de stocker l'information dans la session utilisateur. Mais après que le message de confirmation a été affiché, vous devrez vider cet attribut, sinon il sera toujours en mémoire jusqu à la fin de la session.

L'attribut Flash est un attribut éphémère qui peut être défini puis oublié, sachant qu'il disparaîtra après la toute prochaine requête et laissera la session utilisateur propre pour le futur. Dans votre action, définissez un attribut Flash comme suis:

    [php]
    $this->setFlash('attrib', $value);

Le template sera généré et délivré à l'utilisateur, qui va maintenant faire une nouvelle requête pour exécuter un autre action. Dans cette seconde action, récupérer la valeur de l'attribut flash de cette façon:

    [php]
    $value = $this->getFlash('attrib');

Plus besoin de s'en soucier. Après avoir fourni la deuxième page, l'attribut flash `attrib` sera vidé. Et même si vous le nécessitez pas après cette seconde action, l'attribut flash disparaîtra de la session quand même.

Si vous avez besoin d'accéder à un attribut flash depuis le template, utilisez l'objet  `$sf_flash`:

    [php]
    <?php if ($sf_flash->has('attrib')): ?>
      <?php echo $sf_flash->get('attrib') ?>
    <?php endif; ?>

ou juste:

    [php]
    <?php echo $sf_flash->get('attrib') ?>

Les attributs flash sont une bonne façon de faire passer des informations entre deux requêtes consécutives.

### Gérer sa session

Le système de gestion de session amélioré par symfony masque complètement le session ID de stockage entre le client et le serveur aux développeurs. Toutefois si vous voulez modifier le comportement par défaut des mécanismes de gestion des sessions, il est possible de le faire. Ceci est principalement réservé aux utilisateurs avancés.

Coté client, les sessions sont prises en charge par les cookies. Le cookie de session de symfony est appelé `symfony`, mais vous pouvez changer son nom en éditant le fichier de configuration `factories.yml`, comme l'indique le Listing 6-20.

*Listing 6-20 - Modifier le nom du cookie de session dans `apps/myapp/config/factories.yml`*

    all:
      storage:
        class: sfSessionStorage
        param:
          session_name: my_cookie_name

>**TIP**
>La session est démarrée (avec la fonction PHP `session_start()`) seulement si le paramètre `auto_start` est précisé à true dans le fichier `factories.yml` ( ce qui est le cas par défaut). Si vous voulez gérer le démarrage de session manuellement, désactivez ce paramètre.

Le système chargé de la gestion des sessions de Symfony est basé sur celui de PHP session. Cela signifie que si vous voulez que la gestion des sessions côté client s'effectue via des paramètres passés dans l'URL plutôt que dans des cookies, vous devez changer le paramètre use_trans_sid présent dans le php.ini. Soyez averti que ceci n'est pas recommandé.

    session.use_trans_sid = 1

Du coté serveur, Symfony stocke les sessions utilisateurs dans un fichier par défaut. Vous pouvez les stocker dans une base de données en changeant la valeur du paramètre `class` dans le fichier `factories.yml`, comme cela est indiqué dans le chapitre 6-21.

*Listing 6-21 - Modifier la façon de stoker les sessions coté serveur, dans le fichier `apps/myapp/config/factories.yml`*

    all:
      storage:
        class: sfMySQLSessionStorage
        param:
          db_table: SESSION_TABLE_NAME      # Nom de la table stockant les sessions
          database: DATABASE_CONNECTION     # Nom de la base à laquelle se connecter

Les classes de stockage de sessions sont, `sfMySQLSessionStorage`, `sfPostgreSQLSessionStorage`, et `sfPDOSessionStorage`. La dernière est recommandée. Le paramètre optionnel `database` permet de définir la connection à la base devant être utilisée, Symfony utilisera alors `databases.yml` (voir chapitre 8) qui détermine les paramètres de connexion (hôte, nom de la base, utilisateur et mot de passe) pour cette connexion.

L'expiration de la session apparaît automatiquement après  sf_timeout secondes. Cette constante est de 30 min par défaut mais vous pouvez la modifier pour chaque environnement dans le fichier de configuration `settings.yml`, voir le Listing 6-22.

*Listing 6-22 - Changer la durée de vie d'une session , dans `apps/myapp/config/settings.yml`*

    default:
      .settings:
        timeout:     1800           # durée de vie de la session en secondes

##Classe action sécurité

La possibilité d'exécuter une action peut être réservée aux utilisateurs ayant certains privilèges. Les outils mis à la disposition par Symfony dans cette optique autorisent la création d'applications sécurisées, où les utilisateurs doivent être identifiés avant d'accéder aux nouveautés ou aux parties spécifiques de l'application. Sécuriser une application nécessite deux étapes : déclarer le niveau de sécurité pour chaque action et autoriser à se logguer les utilisateurs disposant des privileges nécessaires pour accéder à ces actions sécurisées.

### Condition d'accès

Avant d'être exécutée, chaque action passe par un filtre spécial qui vérifie si l'utilisateur actuel dispose des privilèges nécessaires pour l'action demandée. Dans Symfony, les privilèges sont composés de deux parties:

  * Les actions sécurisées nécessitent que les utilisateurs soient identifiés.
  * Les autorisations sont nommées selon des privilèges de sécurité ce qui vous permet d'organiser la sécurité par groupe.

Restreindre l'accès à une action se fait simplement en créant et éditant un fichier de configuration en YAML appeler `security.yml` dans le répertoire `config/` du module. Dans ce fichier vous pouvez spécifier les conditions de sécurité nécessaires que les utilisateur devront pleinement remplir pour chaque action ou pour `all` (toutes) les actions.

*Listing 6-23 - paramètres des conditions d'accès, dans apps/myapp/modules/mymodule/config/security.yml`*

    read:
      is_secure:   off       # Tout les utilisateur peuvent appeler l'action read

    update:
      is_secure:   on        # L'action update n'est disponible que pour les utilisateurs authentifiés

    delete:
      is_secure:   on        # Que pour les utilisateurs authentifiés
      credentials: admin     # Avec l'autorisation admin

    all:
      is_secure:  off        # off est la valeur par défaut partout ailleurs

Les actions ne sont pas sécurisées par défaut, donc quand il n y a pas de `security.yml` ou aucune mention à une action dans ce fichier, les actions sont accessibles à tout le monde. Si il y a un fichier `security.yml`, Symfony regarde d'abord le nom de l'action demandée, puis si elle existe, vérifie l'intégralité des conditions de sécurité. Ce qui se produit quand un utilisateur essaye d'accéder à une action protégée dépend de ses autorisations :

  * Si l'utilisateur est authentifié et possède les bonnes autorisations, l'action est executée
  * Si l'utilisateur n'est pas authentifié, il sera re-dirigé sur la page par défaut d'authentification.
  * Si l'utilisateur est identifié mais ne possède pas les autorisations nécessaires, il sera re-diriger vers l'action de sécurité par défaut, décrite dans la Figure 6-1.

Les pages par défaut d'authentification et de sécurité sont assez simples, vous voudrez probablement les customiser. Vous pouvez configurer quelles actions seront appelées en cas de privilèges insuffisants dans le fichier `settings.yml` de l'application en changeant les valeurs des propriétés décrites dans le Listing 6-24.

*Figure 6-1 - La page par défaut de l'action sécurité*

![La page par défaut de l'action sécurité](http://www.symfony-project.com/images/book/1_0/F0601.jpg "La page par défaut de l'action sécurité")

*Listing 6-24 - Les actions de sécurité par défaut sont définies dans `apps/myapp/config/settings.yml`*

    all:
      .actions:
        login_module:           default
        login_action:           login

        secure_module:          default
        secure_action:          secure

### Donner des autorisations d'accès

Pour accéder à une action protégée, les utilisateurs ont besoin d'être authentifiés et/ou de posséder les bonnes autorisations. Les privilèges d'un utilisateur peuvent être alloués en appelant des méthodes de l'objet `sfUser`. Le statut d'authentification d'un utilisateur s'obtient par la méthode  `setAuthenticated()`. Le Listing 6-25 décrit un exemple simple d'authentification pour un utilisateur.

*Listing 6-25 - Positionner le statut d'authentification d'un utilisateur*

    [php]
    class myAccountactions extends sfactions
    {
      public function executeLogin()
      {
        if ($this->gêtrequestParameter('login') == 'foobar')
        {
          $this->getUser()->setAuthenticated(true);
        }
      }

      public function executeLogout()
      {
        $this->getUser()->setAuthenticated(false);
      }
    }

Le fait de pouvoir vérifier, ajouter, enlever ou effacer des autorisations, les rendent un peu plus complexes à manipuler. Le Listing 6-26 décrit les méthodes d'autorisations de la classe `sfUser`

*Listing 6-26 - Manier les autorisations utilisateurs dans une action*

    [php]
    class myAccountactions extends sfactions
    {
      public function executeDoThingsWithCredentials()
      {
        $user = $this->getUser();

	//ajouter une ou plusieurs autorisations
        $user->addCredential('foo');
        $user->addCredentials('foo', 'bar');

	//vérifier si l'utilisateur dispose d'une autorisation
        echo $user->hasCredential('foo');                      =>   true

	//Vérifier si l'utilisateur possède toutes les autorisations
        echo $user->hasCredential(array('foo', 'bar'));        =>   true

	//vérifier si l'utilisateur possède l'une des autorisations
        echo $user->hasCredential(array('foo', 'bar'), false); =>   true

	//enlever une autorisation
        $user->removeCredential('foo');
        echo $user->hasCredential('foo');                      =>   false

	//enlever toutes les autorisations (utile pour le processus de logout)
        $user->clearCredentials();
        echo $user->hasCredential('bar');                      =>   false
      }
    }

Si un utilisateur a les autorisations `'foo'`, cet utilisateur sera capable d'accéder aux actions que nécessite le fichier `security.yml`. Les autorisations peuvent aussi être utilisées afin d'afficher seulement les contenus autorisés dans un template, comme le décrit le Listing 6-27.

*Listing 6-27 - Manier les autorisations utilisateurs dans un Template*

    [php]
    <ul>
      <li><?php echo link_to('section1', 'content/section1') ?></li>
      <li><?php echo link_to('section2', 'content/section2') ?></li>
      <?php if ($sf_user->hasCredential('section3')): ?>
      <li><?php echo link_to('section3', 'content/section3') ?></li>
      <?php endif; ?>
    </ul>

Comme pour le statuts d'authentification, les autorisations sont souvent données aux utilisateurs durant le processus d'identification. C'est pourquoi, l'objet `sfUser` est souvent autorisé à ajouter des méthodes login et logout, pour mettre le statut de la sécurisation des utilisateurs au premier plan.

>**TIP**
>Parmi les PlugIns Symfony, le `sfGuardPlugin` ajoute à la classe session des fonctionnalités rendant le login et logout plus facile. Reportez vous au chapitre 17 pour plus de détails.

### Autorisations complexes

Le syntaxe YAML utilisée dans le fichier `security.yml` permet de jouer avec les autorisations des utilisateurs grâce à des combinaisons basées sur les types AND et OR. Avec de telles combinaisons vous pouvez construire des workflow très complexes avec des systèmes de gestion de privilège, pour par exemple des CMS (content management system). Ainsi le back-office est accessible seulement aux utilisateurs disposant des autorisations `admin`, les articles peuvent être édités seulement par les utilisateurs disposant des autorisations `editor` et publiés uniquement par ceux possédant les autorisations `publisher`. Le Listing 6-28 illustre cet exemple.

*Listing 6-28 - Syntaxe proposant une combinaison d'autorisations*

    editArticle:
      credentials: [ admin, editor ]              # admin and editor

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

    userManagement:
      credentials: [[ admin, superuser ]]         # admin or superuser

A chaque fois que vous rajoutez un jeu de parenthèses, l'union logique peut basculer entre AND et OR. Ce qui fait que vous pouvez obtenir des combinaisons d'associations très complexes comme celle ci :


    credentials: [[root, [supplier, [owner, quasiowner]], accounts]]
                 # root OR (supplier AND (owner OR quasiowner)) OR accounts

##Méthodes de validation et de gestion des erreurs

Valider les actions, et plus particulièrement les paramètres des requêtes est une tâche fastidieuse. Symfony propose un système intégré de validation de requêtes, qui utilise les méthodes de la classe action.

Examinons ceci avec un exemple. Quand un utilisateur fait une requête pour une action `myaction`, Symfony regarde toujours en premier lieu si une méthode  `validateMyaction()` est présente. Si il l'a trouvée, Symfony l'exécute. La valeur retournée par cette méthode de validation determine quelle méthode sera prochainement exécutée: si elle retourne `true` alors `executeMyaction()`sera exécutée, autrement `handleErrorMyaction()` sera exécutée. Et dans l'ultime cas ou handleErrorMyaction() n'existe pas, Symfony propose la méthode générique handleError(). Si cette Méthode n'existe pas non plus, `sfView::ERROR` sera simplement la valeur de retour afin de générer le template `myactionError. php`. La figure 6-2 décrit ce processus.

*Figure 6-2 -Le processus de validation*

![Le processus de validation](http://www.symfony-project.com/images/book/1_0/F0602.png "Le processus de validation")

Ainsi la clé du processus de validation est de respecter les conventions de nommages pour les méthodes d'actions:

  * `validateactionName` est une méthode de validation retournant `true` ou `false`. C'est la premiere méthode examinée quand une action `actionName` est requise. Si elle n'existe pas la méthode de l'action est directement exécutée.
  * `handleErroractionName` est la méthode appelée quand la méthode validation échoue. Si elle n'existe pas, le template `Error` est affiché.
  * `executeactionName` est la méthode d'action. Elle doit exister pour toutes les actions.
  

*Listing 6-29 - Extrait des méthodes de validation*

    [php]
    class mymoduleactions extends sfactions
    {
      public function validateMyaction()
      {
        return ($this->gêtrequestParameter('id') > 0);
      }

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

        return sfView::SUCCESS;
      }

      public function executeMyaction()
      {
        $this->message = "The parameters are correct";
      }
    }

Vous pouvez mettre le code que vous voulez dans le méthode `validate()`. Assurez vous juste qu'elle retournera soit `true` ou `false`. Comme c'est une méthode de la classe `sfactions`, elle a accès aux objets `sfRequest` et `sfUser` aussi, ce qui peut être vraiment pratique pour les input et les contextes de validation.

Vous pouvez mettre en oeuvre ces mécanismes dans le cadre d'implémentation de formulaire de validation (ce qui contrôlera les valeurs entrées par l'utilisateur dans le formulaire avant de le traiter), mais c'est le type de tâche répétitive pour lesquelles symfony prodigue des outils automatisés, comme décrit dans le chapitre 10. 

##Filtres

Le processus de sécurité peut être compris comme un filtre par lequel toute les requêtes doivent passer avant d'exécuter l'action. Après avoir exécuté quelques tests dans le filtre, le processus de la requête est modifié par exemple, en changeant l'action executer (la requête d'action passe de défaut à secure dans le cas d'un filtre de sécurité). Symfony développe cette idée de filtre de classe. Vous pouvez spécifier n'importe quelle nombre de filtres de classe à exécuter avant une exécution d'action ou avant le rendu de la réponse, et faire ceci pour chaque requête. Vous pouvez voir les filtres comme une façon de créer un ensemble de code, similaire à  `preExecute()` et `postExecute()`, mais à un plus haut niveau (pour une entière application plutôt que pour un module). 

### La chaîne filtre

Symfony décrypte actuellement le processus d'une requête comme une chaîne de filtre. Quand une requête est reçue par le framework, le premier filtre (qui est toujours `sfRenderingFilter`) est exécuté. Puis, il appelle le filtre suivant dans la chaîne et ainsi de suite. Quand le dernier filtre ( qui est toujours `sfexecutionFilter`) est exécuté, le filtre d'avant peut se terminer et ainsi de suite jusqu'au filtre rendering. La Figure 6-3 illustre cette idée avec un diagramme de séquence, utilisant une petite chaîne de filtre ( la vraie utilise beaucoup plus de filtres).

*Figure 6-3 - Extrait de la chaîne de filtre*

![Extrait de la chaîne de filtre](http:/www.symfont-project.com/images/book/1_0/F0603.png "Extrait de la chaîne de filtre")

Ce processus justifie la structure des filtres de classes. Ils étendent tous la classe `sfFilter`, et contiennent une méthode `execute()`, nécessitant un objet `$filterChain` en paramètre.
Quelque part dans cette méthode, le filtre passe au filtre suivant dans la chaîne en appelant `$filterChain->execute()`. Voir le Listing 6-30 comme exemple. Les filtres sont donc divisés simplement en deux parties:

  * Le code avant l'appel `$filterChain->execute()` exécuté avant l'exécution de l'action
  * le code après l'appel à `$filterChain->execute()` exécuté après l'exécution de l'action et avant le rendering.

*Listing 6-30 -Structure de la classe filtre*

    [php]
    class myFilter extends sfFilter
    {
      public function execute ($filterChain)
      {
	//Code à executer avant l'exécution de l'action
        ...

	// Execute le prochain filtre de la chaîne
        $filterChain->execute();

	// Code à exécuter après l'exécution de l'action, avant le rendering
        ...
      }
    }

La chaîne de filtre par défaut est définie dans un fichier de configuration d'une application appelé `filters.yml`, et décrit dans le Listing 6-31. Ce fichier liste les filtres qui doivent être exécutés pour toutes les requêtes sans exceptions.

*Listing 6-31 - Fichier chaîne de filtres par défaut , dans `myapp/config/filters.yml`*

    rendering: ~
    web_debug: ~
    security:  ~

    # En principe, vous insérerez vos propres filtres ici

    cache:     ~
    common:    ~
    flash:     ~
    exécution: ~

Ces déclarations n'ont pas de paramètres (le caractère tilde, `~`, signifie "null" en YAML) , car elles héritent de paramètres présents dans le noyau de symfony. Dans le noyau, symfony définit des paramètres `class` et `param` pour chacun de ces filtres. Par exemple, le Listing 6-32 illustre les paramètres par défaut du filtre `rendering`.

*Listing 6-32 - paramètres par défaut du filtre rendering, dans   $sf_symfony_data_dir/config/filters.yml*

    rendering:
      class: sfRenderingFilter   # classe filtre
      param:                     # paramètres du filtre
        type: rendering

En laissant une valeur vide (`~`) dans l'application  `filters.yml`, vous spécifiez à symfony d'appliquer ce filtre avec les paramètres par défaut définis dans le noyau.

Vous pouvez customiser vos chaînes de filtres de plusieurs façons :

  * Désactivez quelques filtres de la chaîne en ajoutant un paramètre `enabled: off`. Par exemple pour désactiver le filtre web_debug, écrire:

        web_debug:
          enabled: off

  
  * Ne pas effacer d'entrée dans `filters.yml` pour désactiver un filtre, Symfony générerait une exception dans ce cas.
  * Ajouter vos propres déclarations quelque part dans la chaîne (habituellement après le filtre `security`) pour ajouter un filtre spécial (ceci est discuté dans la section précédente). Soyez prévenus que le filtre `rendering` doit être declaré en premier, et le filtre `exécution` doit être listé comme la dernière entrée de la chaîne.
 * Remplacer les class et paramètres par défaut des filtres standards(notamment afin de changer le système de sécurité et utiliser votre propre filtre de sécurité.
	
>**TIP**
>Le paramètre `enabled: off` marche bien pour désactiver vos propres filtres, mais vous pouvez aussi désactiver les filtres par défaut dans le fichier `settings.yml`, en modifiant la valeur des paramètres `web_debug`, `use_security`, `cache`, et `use_flash`.

### Construire votre propre filtre

C'est assez simple de construire son propre filtre. Créez une définition de classe similaire à celle décrite dans le Listing 6-30, et placez la dans un des répertoires lib/ du projet, afin qu'il prenne le pas sur le comportement auto-charger.

Comme une action peut effectuer un forward ou un redirect vers une autre action et par conséquent relancer l'entière chaîne de filtre, vous voudrez restreindre l'exécution de vos propres filtres à la première action. La méthode `isFirstCall()` de la classe `sfFilter` renvoie un boolean à cet effet. Cet appel n'est sensé qu'avant l'exécution de l'action.

Ces concepts sont plus parlants avec un exemple. Le Listing 6-33 décrit un filtre servant à auto-enregistrer des utilisateurs avec un cookie spécial `MyWebSite` qui est supposé être créé durant l'action d'authentification. Il est rudimentaire mais c'est une façon d'implémenter la fonctionnalité "remember me" dans les formulaires de login.

*Listing 6-33 - Extrait du fichier classe filtre, sauvegardé dans `apps/myapp/lib/rememberFilter.class.php`*

    [php]
    class rememberFilter extends sfFilter
    {
      public function execute($filterChain)
      {
	// Exécuter ce filtre une seule fois
        if ($this->isFirstCall())
        {
	  // Les filtres n'ont pas d'accès direct aux requêtes et aux objets de l'utilisateur
	  // Vous devez employer les objets de contexte pour les obtenir
          $request = $this->getContext()->getRequest();
          $user    = $this->getContext()->getUser();

          if ($request->getCookie('MyWebSite'))
          {
	    // s'enregistrer
            $user->setAuthenticated(true);
          }
        }

	// Exécuter le filtre
        $filterChain->execute();
      }
    }

Quelquefois, au lieu de continuer l'exécution de la chaîne de filtre, vous souhaiterez faire un forward vers une action spécifique à la fin du filtre. `sfFilter` ne possède pas de méthode `forward()` mais `sfController` en possède une, donc vous pouvez faire cela simplement via le code suivant:

    [php]
    return $this->getContext()->getController()->forward('mymodule', 'myaction');

>**NOTE**
>La classe `sfFilter` possède une méthode `initialize()`, qui est exécutée quand le filtre objet est créé. Vous pouvez l'outrepasser dans votre filtre personnel si vous voulez gérer les paramètres du filtre (définis dans `filters.yml`, comme décrit après) à votre propre façon.

### Activation et paramètres des filtres

Il n'est pas suffisant de créer un filtre pour l'activer. Si vous devez ajouter un filtre à votre chaîne de filtre, vous devez le déclarer dans la classe filtre dans le fichier `filters.yml`, situé dans l'application ou dans le répertoire `config/` du module, comme le décrit le Listing 6-34.

*Listing 6-34 - Extrait d'un fichier d'activation de filtre , sauvé dans `apps/myapp/config/filters.yml`*

    rendering: ~
    web_debug: ~
    security:  ~

    remember:                 #L'identifiant des filtres doit être unique
      class: rememberFilter
      param:
        cookie_name: MyWebSite
        condition:   %APP_ENABLE_REMEMBER_ME%

    cache:     ~
    common:    ~
    flash:     ~
    exécution: ~

Une fois activé, le filtre s'exécute à chaque requête. Le fichier de configuration du filtre peut contenir une ou plusieurs définitions de paramètres sous la clé `param`. La classe filtre a la possibilité d'obtenir la valeur de ses paramètres avec la méthode getParameter(). Le Listing 6-35 explique comment obtenir la valeur d'un paramètre de filtre.

*Listing 6-35 - Obtenir la valeur d'un paramètre, dans `apps/myapp/lib/rememberFilter.class.php`*

    [php]
    class rememberFilter extends sfFilter
    {
      public function execute ($filterChain)
      {
          ...
          if ($request->getCookie($this->getParameter('cookie_name')))
          ...
      }
    }

Le paramètre `condition` est testé par la chaîne de filtre afin de déterminer si il doit être exécuté. Donc la déclaration de votre filtre peut dépendre de la configuration de votre application, comme celle présente dans le Listing 6-34. Le filtre remember sera exécuté seulement si le `app.yml` contient :

    all:
      enable_remember_me: on

### Extraits de fichiers filtres

L'utilisation de filtres est très pratique pour NE PAS répéter du code à chaque action. Par exemple, si vous utilisez un système d'analyse distant, vous vous servez probablement d'une portion de code (snippet) qui ce charge d'appeler ce script distant dans chaque page. Vous pouvez mettre ce code dans la couche globale, mais il serait actif pour toute l'application. En revanche, vous pouvez placer ce code dans un filtre comme dans celui explicité dans le Listing 6-36 et l'activer 

*Listing 6-36 – Filtre Google Analytics*

    [php]
    class sfGoogleAnalyticsFilter extends sfFilter
    {
      public function execute($filterChain)
      {
        // Rien à faire avant l'action
        $filterChain->execute();

        // Agrémenter la réponse avec le code distant
        $googleCode = '
    <script src="http://www.google-analytics.com/urchin.js"  type="text/javascript">
    </script>
    <script type="text/javascript">
      _uacct="UA-'.$this->getParameter('google_id').'";urchinTracker();
    </script>';
        $response = $this->getContext()->gêtrèsponse();
        $response->setContent(str_ireplace('</body>', $googleCode.'</body>',$response->getContent()));
       }
    }

Soyez prévenus que ce filtre n'est pas parfait, puisqu'il ne peut ajouter de tracker sur des réponses qui ne sont pas en HTML.

Un autre exemple pourrait être un filtre qui bascule une requête en ssl si elle ne l'est pas déjà, afin de sécuriser la communication comme le décrit le Listing 6-37.

*Listing 6-37 - Filtre pour sécuriser la communication*

    [php]
    class sfSecureFilter extends sfFilter
    {
      public function execute($filterChain)
      {
        $context = $this->getContext();
        $request = $context->getRequest();

        if (!$request->isSecure())
        {
          $secure_url = str_replace('http', 'https', $request->getUri());

          return $context->getController()->redirect($secure_url);
	  // On interromps la chaîne de filtre
        }
        else
        {
	  // La requête est deja sécurisée, donc on ne peut pas continuer
          $filterChain->execute();
        }
      }
    }

Les Filtres sont extrêmement utilisés par les plug-in, ce qui vous permet de développer les capacités de votre application de manière globale. Reportez vous au chapitre 17 pour en connaître davantage sur les PlugIns, et pour obtenir plus d'exemples de scripts rendez-vous sur le wiki (http://www.symfony-project.com/trac/wiki).

##Configuration des modules

Certains comportements des modules dépendent de leur fichier de configuration. Pour les modifier, vous devez créer un fichier `module.yml` dans le répertoire `config/` du module et définir des paramètres spécifiques à chaque environnement (ou alors via le header `all:` appliquer à tous les environnements). Le Listing 6-38 illustre un exemple d'un fichier `module.yml` pour le module `mymodule`.

*Listing 6-38 - Configuration d'un module, dans `apps/myapp/modules/mymodule/config/module.yml`*

    all:                 # Pour tous les environnements
      enabled:     true
      is_internal: false
      view_name:   sfPHP

Le paramètre enabled vous permet de désactiver toutes les actions du module. Toutes les actions sont re-dirigées vers l'action `module_disabled_module`/`module_disabled_action` (définie dans `settings.yml`).

Le paramètre is_internal vous permet de restreindre l'exécution de toutes les actions d'un module uniquement à des appels internes. Ceci est pratique, pour par exemple, une action mail (capable d'envoyer des e-mails) qui doit être joignable depuis d'autres actions mais pas de l'extérieur. Le paramètre view_name définit la classe view. Il doit hériter de `sfView`. Changer cette valeur vous permet d'utiliser différents systèmes de view, avec d'autres moteurs de template comme Smarty.

Résumé
------

Dans symfony, la couche contrôleur est divisée en deux parties : le contrôleur principal, qui est l'unique point d'accès de l'application dans un environnement donné, et les actions, qui contiennent le code logique de la page. Une action a la possibilité de déterminer de quelle façon la présentation sera générée via la constante `sfView` de son choix. A l'intérieur d'une action, vous pouvez manipuler les différents éléments du contexte, comprenant l'objet request (`sfRequest`) et l'objet courant de session (`sfUser`).

En combinant le pouvoir des objets session, l'objet action et la configuration de la sécurité, on obtient un système complètement sécurisé, contrebalancé par des  restrictions d'accès et des autorisations. Les méthodes spéciales `validate()` et `handleError()` des actions permettent d'attester la validation d'une requête. Et si les méthodes `preExecute()` et `postExecute()`sont faites pour ré-utiliser du code à l'intérieur d'un module, les filtres assurent quant à eux la même fonctionnalité mais pour l'intégrité d'une application en exécutant le code Controller à chaque nouvelle requête.