Development

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

You must first sign up to be able to contribute.

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 (->getRequest()) sfResponse: l'objet réponse (->getResponse()) sfUser: l'objet 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->getRequestParameter('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 404module 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->getRequestParameter('id'));
  $this->forward404If(!$article);
}

// Si c'est celle-là
public function executeShow()
{
  $article = ArticlePeer::retrieveByPK($this->getRequestParameter('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->getRequest()->hasParameter('foo');
    $hasFoo = $this->hasRequestParameter('foo');  // Shorter version
    $foo    = $this->getRequest()->getParameter('foo');
    $foo    = $this->getRequestParameter('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->getRequest()->hasFiles())
    {
      foreach ($this->getRequest()->getFileNames() as $fileName)
      {
        $fileSize  = $this->getRequest()->getFileSize($fileName);
        $fileType  = $this->getRequest()->getFileType($fileName);
        $fileError = $this->getRequest()->hasFileError($fileName);
        $uploadDir = sfConfig::get('sf_upload_dir');
        $this->getRequest()->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 stocké 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és", 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é

*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->getRequestParameter('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

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->getRequestParameter('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

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.