Même si le nouveau framework de formulaires de symfony 1.1 n'est pas encore documenté, certains développeurs commence déjà à l'utiliser. Tout cela me fait très plaisir, non seulement parce que c'est l'une des fonctionnalités majeures de symfony 1.1 mais également parce que c'est l'une des fonctionnalités dont je suis le plus fier.
Récemment, un utilisateur de symfony a posé quelques questions très intéressantes sur la mailing-list utilisateurs de symfony à propos du nouveau framework de formulaires et notamment sur son adéquation avec le pattern MVC.
J'ai passé plus d'un an et demi à réfléchir et à coder ce nouveau framework de formulaires et j'ai passé la plupart de ce temps à être sûr qu'il respecte le pattern MVC. J'ai donc été un peu déçu quand j'ai lu les questions de cet utilisateur. Ce billet est donc une tentative d'explication de la philosophie MVC que j'ai adopté pour le nouveau framework de formulaires.
Ce billet n'est pas un tutoriel sur le nouveau framework de formulaires mais plutôt pourquoi il respecte le pattern MVC.
Avant de commencer, vous avez certainement remarqué que j'appelle le nouveau système un "framework". Eh oui, c'est un framework en soi. Vous pouvez l'utilisez en dehors de symfony. Il n'y a strictement aucune dépendance entre le framework de formulaires et symfony. Hartym a d'ailleurs concocté un plugin pour symfony 1.0. Cela veut également dire que vous pouvez l'utiliser avec le Zend Framework ou dans un script PHP. La seule limitation est d'utiliser PHP 5.1.3 ou ultérieur.
Un formulaire symfony (sfForm) est un objet capable de valider des données en entrée et d'être affiché en HTML.
Mais le formulaire lui-même ne fait rien d'autre que déléguer ces deux tâches à des objets embarqués : un schéma de validation (sfValidatorSchema) et un schéma de widgets (sfWidgetSchema) :
class DemoForm extends sfForm { function configure() { $this->setValidatorSchema(new sfValidatorSchema(/* configuration des validateurs */)); $this->setWidgetSchema(new sfWidgetFormSchema(/* configuration des widgets */)); } }
Dans votre action, voici un exemple d'utilisation de DemoForm :
function executeIndex($request) { $this->form = new DemoForm(array(/* valeurs initiales */)); if ($request->isMethod('post')) { $this->form->bind($request->getParameter('demo')); if ($this->form->isValid()) { // faites quelque chose d'utile avec les données validées $values = $this->form->getValues(); $this->redirect('@somewhere'); } } }
Avant de réinventer la roue, j'ai lu énormément de code et de documentation sur l'existant : principalement des bibliothèques en PHP, Perl et Python.
Le framework de formulaires de symfony est très largement inspiré de deux bibliothèques Python que j'apprécie particulièrement : FormEncode et Django newforms.
Ces deux bibliothèques ont accompli un énorme travail de découplage mais je pense que symfony va encore un peu plus loin en respectant complètement les principes MVC. J'ai passé beaucoup de temps pour être sûr que chaque ligne de code appartienne à la bonne couche et que toutes les couches sont réellement découplées et utilisables sans le reste du framework.
Voici un extrait de la définition du pattern MVC sur Wikipedia:
In complex computer applications that present a large amount of data to the user, a developer often wishes to separate data (model) and user interface (view) concerns, so that changes to the user interface will not affect data handling, and that the data can be reorganized without changing the user interface. The model-view-controller solves this problem by decoupling data access and business logic from data presentation and user interaction, by introducing an intermediate component: the controller.
Et voici ma version annotée de la description qui est faite sur Wikipedia :
The domain-specific representation of the information that the application operates. Domain logic adds meaning to raw data (e.g., calculating whether today is the user's birthday, or the totals, taxes, and shipping charges for shopping cart items).
Les classes de validation gèrent la logique utilisée pour valider les données brutes et les convertir en des données validées et consistentes (convertir les dates en timestamps ou les données de $_FILES en objets sfValidatedFile). De plus, ce sous-framework de validation étant complètement découplé du reste de symfony, vous pouvez l'utiliser en soi. Vous pouvez l'utiliser pour valider n'importe quelle source de données : un fichier XML, des objets métiers, ...
Renders the model into a form suitable for interaction, typically a user interface element. Multiple views can exist for a single model for different purposes.
Les classes de widgets interprètent le formulaire (en HTML par défaut) pour pouvoir l'afficher à l'utilisateur. Comme pour le sous-framework de validation, le sous-framework de widget est complètement indépendant du reste de symfony... et à ce titre utilisable en soi. D'ailleurs, dans un futur proche, symfony l'utilisera pour gérer des widgets qui ne sont pas des widgets de formulaires.
Processes and responds to events, typically user actions, and may invoke changes on the model.
Les classes de formulaire créent le formulaire, répondent aux requêtes GET ou POST de l'utilisateur, invoquent la validation (->bind() ->isValid()) et font appel au modèle (en fonction de ce que vous souhaitez faire des données validées).
Le formulaire est le contrôleur, ou le liant entre les validateurs (le modèle) et les widgets (la vue).
Essayons de détailler l'exemple d'action que nous avons vu précédemment.
Cette action crée un formulaire et le passe à la vue (grâce à la convention de nommage $this->) :
$this->form = new DemoForm(array(/* valeurs initiales */));
On attache ensuite les données de la requête au formulaire et la validation est alors invoquée automatiquement. La validation elle-même n'est pas gérée par le formulaire mais par le schéma de validation.
$this->form->bind($request->getParameter('demo'));
Si le formulaire est valide, c'est au développeur de décider ce qu'il veux faire des données validées :
$values = $this->form->getValues();
L'action contrôle également le flux de la requête. Dans cet exemple, la validation est uniquement invoquée sur les requêtes en POST :
if ($request->isMethod('post')) { }
Et si le formulaire est valide, l'action redirige l'utilisateur :
$this->redirect('@somewhere');
L'action contient le minimum de code pour pouvoir contrôler le flux de la requête et passer les données utilisateurs au modèle (validateurs) et à la vue (widgets). Toute la logique est encapsulée dans la classe
sfFormelle-même.
Le formulaire est affiché dans la template. Le plus simple pour cela est :
<?php echo $form ?>
Mais il faut garder à l'esprit que echo $form est juste un raccourci sympathique. Il est très pratique pour créer un prototype rapide mais la plupart du temps, vous voudrez avoir un contrôle plus important sur le rendu du formulaire et sur la disposition des différents widgets dans la page. Et c'est là que le nouveau système est vraiment puissant. Le travail des intégrateurs est grandement simplifié. Comparons l'écriture de l'affichage d'un champ input en symfony 1.0 et 1.1 :
// symfony 1.0 <?php echo input_tag('author[first_name]', $sf_params->get('author[first_name]', 'initial data'), array('class' => 'foo')) ?> // symfony 1.0 avec le filtre fill-in activé <?php echo input_tag('author[first_name]', 'initial data', array('class' => 'foo')) ?> // symfony 1.1 (le fill-in et les données initiales fonctionnent sans rien faire) <?php echo $form['author']['first_name'] ?> // symfony 1.1 si la classe n'a pas été configurée dans le widget <?php echo $form['author']['first_name']->render(array('class' => 'foo')) ?>
Je pense vraiment que les intégrateurs vont adorer ce nouveau système.
Voici quelques exemples supplémentaires pour démontrer la flexibilité du système :
// affichier les errors dans une liste <?php echo $form['author']['first_name']->renderError() ?> // afficher le tag label pour un champ (avec l'attribut for) <?php echo $form['author']['first_name']->renderLabel() ?> // afficher une ligne complète pour un champ (comprenant le label, le tag, le message d'aide et les erreurs) <?php echo $form['author']['first_name']->renderRow() ?>
J'espère avoir bien décrit la philosophie de ce nouveau framework de formulaire et vous avoir démontré en quoi il respecte le pattern MVC.
Un dernier mot sur le framework de formulaire. Comme un formulaire est une classe, vous héritez de tous les avantages des systèmes orientés objets, et notamment :
Discussion