Nous avons vu dans le Chapitre 1 comment créer et afficher un formulaire. Ce chapitre est consacré à la validation des formulaires.
Avant de commencer
Le formulaire de contact que nous avons développé au Chapitre 1 n'est pas encore opérationnel. En effet, que se passerait-il si l'internaute soumettait une adresse email invalide ou si le message était vide ? Dans ce cas, nous souhaiterions afficher à l'utilisateur des messages d'erreurs l'invitant à corriger sa saisie comme l'illustre la Figure 2-1.
Figure 2-1 - Affichage des Messages d'Erreurs
Voici les règles de validation que nous souhaitons mettre en place :
name
: Champ facultatifemail
: Champ obligatoire, la valeur doit être un email validesubject
: Champ obligatoire, la valeur sélectionnée doit être parmi les valeurs proposéesmessage
: Champ obligatoire, la longueur du message doit être d'au moins 4 caractères
note
Pourquoi vouloir valider le champ subject
? En effet, avec un tag <select>
, ne forçons-nous pas déjà l'utilisateur à choisir parmi des valeurs pré-définies ? Pour l'internaute moyen les choix sont contraints par le navigateur. Cependant, il est tout à fait possible de soumettre le formulaire avec d'autres valeurs en utilisant des outils comme la Web Developer Toolbar de Firefox ou en simulant une requête avec des outils comme curl
ou wget
disponibles en standard sur Linux ou Mac OS X.
Dans ce chapitre, nous ne modifierons pas le Template que nous avons utilisée au Chapitre 1 et qui est rappelée dans le Listing 2-1.
Listing 2-1 - Template du Formulaire contact
// apps/frontend/modules/contact/templates/indexSucces.php <form action="<?php echo url_for('contact/index') ?>" method="POST"> <table> <?php echo $form ?> <tr> <td colspan="2"> <input type="submit" /> </td> </tr> </table> </form>
La Figure 2-2 détaille l'interaction entre l'application et l'internaute. La première étape consiste à afficher le formulaire à l'internaute. Lorsque celui-ci le soumet, soit sa saisie est valide et il est redirigé vers la page de remerciements, soit sa saisie comporte des champs invalides et le formulaire est ré-affiché avec des messages d'erreurs.
Figure 2-2 - Interaction entre l'Application et l'Internaute
Les Validateurs
Un formulaire symfony est composé de champs. Nous avons vu au Chapitre 1 que chaque champ était identifié par un nom unique. De la même manière que nous avons associé un widget à chaque champ pour lui permettre d'être affiché à l'utilisateur, voyons maintenant comment lui attacher des règles de validation.
La classe sfValidatorBase
La validation de chaque champ est gérée par des objets héritant de la classe sfValidatorBase
. Pour valider le formulaire de contact, nous devons donc définir des objets validateurs pour les quatre champs : name
, email
, subject
et message
. Le Listing 2-2 montre l'intégration de ces validateurs dans la classe de formulaire en utilisant la méthode setValidators()
.
Listing 2-2 - Ajout de Validateurs à la Classe ContactForm
// lib/form/ContactForm.class.php class ContactForm extends BaseForm { protected static $subjects = array('Subject A', 'Subject B', 'Subject C'); public function configure() { $this->setWidgets(array( 'name' => new sfWidgetFormInputText(), 'email' => new sfWidgetFormInputText(), 'subject' => new sfWidgetFormSelect(array('choices' => self::$subjects)), 'message' => new sfWidgetFormTextarea(), )); $this->widgetSchema->setNameFormat('contact[%s]'); $this->setValidators(array( 'name' => new sfValidatorString(array('required' => false)), 'email' => new sfValidatorEmail(), 'subject' => new sfValidatorChoice(array('choices' => array_keys(self::$subjects))), 'message' => new sfValidatorString(array('min_length' => 4)), )); } }
Nous avons utilisé trois validateurs différents :
sfValidatorString
: Ce validateur permet de valider une chaîne de caractèressfValidatorEmail
: Comme son nom l'indique, ce validateur permet de valider une adresse emailsfValidatorChoice
: Ce validateur permet de s'assurer que la valeur soumise est incluse dans une liste pré-définie
Chaque validateur prend en premier argument une liste d'options. Comme pour les widgets, certaines de ces options sont obligatoires, d'autres facultatives. Le validateur sfValidatorChoice
prend par exemple une option obligatoire, choices
. Tous les validateurs prennent au moins les options facultatives required
et trim
définies par défaut par la classe sfValidatorBase
:
Option | Valeur par défaut | Description |
---|---|---|
required | true |
Permet de définir si le champ est obligatoire |
trim | false |
Supprime automatiquement les blancs en début et fin de chaîne avant la validation |
Voyons maintenant les options disponibles pour les validateurs que nous avons utilisés :
Validateur | Options obligatoires | Options facultatives |
---|---|---|
sfValidatorString | max_length |
|
min_length |
||
sfValidatorEmail | pattern |
|
sfValidatorChoice | choices |
Si vous essayez de soumettre le formulaire avec des valeurs invalides, vous ne verrez aucun changement de comportement. En effet, il faut maintenant modifier le module contact
pour valider les données soumises par l'internaute comme le montre le Listing 2-3.
Listing 2-3 - Intégration de la Validation dans le Module contact
class contactActions extends sfActions { public function executeIndex($request) { $this->form = new ContactForm(); if ($request->isMethod('post')) { $this->form->bind($request->getParameter('contact')); if ($this->form->isValid()) { $this->redirect('contact/thankyou?'.http_build_query($this->form->getValues())); } } } public function executeThankyou() { } }
Le Listing 2-3 introduit de nombreux concepts :
Dans le cas de l'appel initial en
GET
, on initialise le formulaire pour le passer à le Template. Le formulaire est alors dans un état initial :$this->form = new ContactForm();
Lorsque l'internaute soumet le formulaire en
POST
, la méthodebind()
permet de lier le formulaire avec les données saisies et déclenche le mécanisme de validation. Le formulaire passe à un état lié.if ($request->isMethod('post')) { $this->form->bind($request->getParameter('contact'));
Une fois le formulaire à l'état lié, il est possible de tester sa validité avec la méthode
isValid()
:Si la valeur de retour est
true
, le formulaire est valide et on peut rediriger l'internaute vers la page de remerciements :if ($this->form->isValid()) { $this->redirect('contact/thankyou?'.http_build_query($this->form->getValues())); }
Sinon, le Template
indexSuccess
est évaluée comme lors de l'affichage initial. La validation ayant injecté les messages d'erreurs dans le formulaire, ceux-ci sont affichés à l'internaute.
note
Lorsqu'un formulaire est dans l'état initial, la méthode isValid()
renvoie toujours false
et la méthode getValues()
renvoie un tableau vide.
La Figure 2-3 permet de visualiser le code exécuté lors de l'interaction entre l'application et l'internaute.
Figure 2-3 - Visualisation du Code exécuté lors de l'Interaction entre l'Application et l'Internaute
Rôle des validateurs
Lors de la redirection vers la page de remerciements, vous avez peut-être remarqué que nous n'utilisons pas le tableau $request->getParameter('contact')
mais $this->form->getValues()
. En effet, $request->getParameter('contact')
retourne les données saisies par l'internaute alors que $this->form->getValues()
retourne les données validées.
Si le formulaire est valide, pourquoi ces deux constructions de code ne seraient-elles pas équivalentes ? Chaque validateur a en fait deux rôles : un rôle de validation des données mais également un rôle de nettoyage des données. La méthode getValues()
retourne donc les valeurs validées et nettoyées.
Le nettoyage peut avoir deux actions principales : uniformiser et convertir les données saisies.
Nous avons déjà vu un cas d'uniformisation des données avec l'option trim
. Mais l'action d'uniformisation est beaucoup plus importante pour un champ date par exemple. Pour valider une date, on peut utiliser le validateur sfValidatorDate
. Ce validateur accepte comme valeur d'entrée de très nombreux formats (timestamp, format défini par une expression régulière, ...). Au lieu de renvoyer la valeur d'entrée, il la convertit par défaut dans le format Y-m-d H:i:s
. De ce fait, le développeur est sûr d'obtenir un format stable quel que soit le format d'entrée utilisé par l'internaute. Le système offre donc une très grande souplesse dans la saisie de l'internaute et garantit l'uniformité au développeur.
Prenons maintenant un cas de conversion, le téléchargement de fichiers. La validation d'un fichier peut s'effectuer grâce au validateur sfValidatorFile
. Une fois le fichier validé, au lieu de retourner le nom du fichier téléchargé par l'internaute, le validateur retourne un objet sfValidatedFile
qui permet de manipuler facilement le fichier. L'utilisation de ce validateur est détaillée plus loin dans ce chapitre.
tip
La méthode getValues()
renvoie un tableau contenant toutes les valeurs validées et nettoyées. Mais il est parfois utile de récupérer seulement une valeur. Dans ce cas, il est préférable d'utiliser la méthode getValue()
: $email = $this->form->getValue('email')
.
Formulaire invalide
Lorsque le formulaire comporte des champs invalides, le Template indexSuccess
est évaluée. La Figure 2-4 montre ce qu'on obtient en soumettant un formulaire invalide.
Figure 2-4 - Formulaire invalide
Sans rien changer à le Template, l'appel à <?php echo $form ?>
a pris en compte automatiquement les messages d'erreurs associés à chaque champ et a conservé les données saisies par l'internaute.
Lorsqu'on lie le formulaire à des données externes, via la méthode bind()
, le formulaire passe à un état lié, ce qui déclenche les actions suivantes :
La validation est exécutée
Les messages d'erreurs sont stockés dans le formulaire pour qu'ils soient accessibles par le Template
Les valeurs par défaut du formulaire sont remplacées par les données saisies par l'internaute
Toutes les informations nécessaires à l'affichage des messages d'erreurs et au re-peuplement des données saisies par l'internaute sont donc disponibles automatiquement via la variable form
depuis le Template.
caution
Comme nous l'avons vu au Chapitre 1, il est possible de passer des valeurs par défaut au constructeur des classes de formulaire. Dans l'exemple précédent, ces valeurs n'ont pas été conservées après la soumission d'un formulaire invalide au profit des données soumises par l'utilisateur pour lui permettre de corriger ses erreurs. Il ne faut donc jamais utiliser les données saisies par l'utilisateur comme valeurs par défaut comme dans cet exemple : $this->form->setDefaults($request->getParameter('contact'))
.
La Personnalisation des Validateurs
Personnalisation des messages d'erreurs
Comme vous avez pu le remarquer dans la Figure 2-4, les messages d'erreurs ne sont pas très explicites. Nous allons maintenant voir comment les personnaliser.
Chaque validateur peut ajouter des erreurs au formulaire. Une erreur est composée d'un code et d'un message. Tous les validateurs héritent des erreurs required
et invalid
définies dans la classe sfValidatorBase
:
Code | Message | Description |
---|---|---|
required | Required. |
Le champ est obligatoire et la valeur vide |
invalid | Invalid. |
Le champ est invalide |
Voici les codes erreur des validateurs que nous avons utilisés :
Validateur | Codes erreur |
---|---|
sfValidatorString | max_length |
min_length |
|
sfValidatorEmail | |
sfValidatorChoice |
La personnalisation des messages d'erreurs est possible en passant un deuxième argument lors de la création des objets de validation. Le Listing 2-4 personnalise quelques messages d'erreurs et la Figure 2-5 illustre les messages d'erreurs personnalisés en action.
Listing 2-4 - Personnalisation des Messages d'Erreurs
class ContactForm extends BaseForm { protected static $subjects = array('Subject A', 'Subject B', 'Subject C'); public function configure() { // ... $this->setValidators(array( 'name' => new sfValidatorString(array('required' => false)), 'email' => new sfValidatorEmail(array(), array('invalid' => 'L\'adresse email est invalide.')), 'subject' => new sfValidatorChoice(array('choices' => array_keys(self::$subjects))), 'message' => new sfValidatorString(array('min_length' => 4), array('required' => 'Le champ message est obligatoire.')), )); } }
Figure 2-5 - Messages d'Erreurs Personnalisés
La Figure 2-6 montre le message d'erreur que vous obtenez si vous essayez de rentrer un message trop court (nous avons défini une longueur minimum de 4 caractères).
Figure 2-6 - Erreur sur un Message trop court
Le message d'erreur par défaut associé à ce code erreur (min_length
) est différent des messages que nous avons déjà vus puisqu'il intègre deux valeurs dynamiques : la donnée soumise par l'internaute (foo
) et le nombre minimum de caractères autorisés pour ce champ (4
). Le Listing 2-5 personnalise ce message en intégrant ces valeurs dynamiques et la Figure 2-7 illustre le résultat.
Listing 2-5 - Personnalisation de Messages d'Erreurs contenant des Valeurs dynamiques
class ContactForm extends BaseForm { public function configure() { // ... $this->setValidators(array( 'name' => new sfValidatorString(array('required' => false)), 'email' => new sfValidatorEmail(array(), array('invalid' => 'L\'adresse email est invalide.')), 'subject' => new sfValidatorChoice(array('choices' => array_keys(self::$subjects))), 'message' => new sfValidatorString(array('min_length' => 4), array( 'required' => 'Le champ message est obligatoire.', 'min_length' => 'Le message "%value%" est trop court. Il faut au moins %min_length% caractères.', )), )); } }
Figure 2-7 - Messages d'Erreurs Personnalisés avec Valeurs dynamiques
Chaque message d'erreur peut contenir des valeurs dynamiques en indiquant le nom de la valeur entouré par le caractère pourcent (%
). Les valeurs disponibles sont généralement la donnée soumise par l'utilisateur (value
) et les valeurs des options du validateur en relation avec l'erreur.
tip
Pour connaître tous les codes erreur, les options et les messages par défaut d'un validateur, vous pouvez vous reporter à la documentation API en ligne (/api/1_2/). Tous les codes, options et messages y sont décrits, ainsi que les valeurs par défaut (par exemple, l'API du validateur sfValidatorString
est disponible à l'adresse /api/1_2/sfValidatorString).
La Sécurité des Validateurs
Par défaut, un formulaire n'est valide que si tous les champs soumis par l'internaute possèdent un validateur. Cela permet de s'assurer que chaque champ possède ses règles de validation et qu'il n'est pas possible d'injecter des valeurs pour des champs non définis dans le formulaire.
Pour mieux comprendre cette règle de sécurité, prenons l'exemple d'un objet utilisateur comme défini dans le Listing 2-6.
Listing 2-6 - Classe User
class User { protected $name = '', $is_admin = false; public function setFields($fields) { if (isset($fields['name'])) { $this->name = $fields['name']; } if (isset($fields['is_admin'])) { $this->is_admin = $fields['is_admin']; } } // ... }
Un object utilisateur (User
) est composé de deux propriétés, le nom de l'utilisateur (name
) et un boolean permettant de savoir s'il est administrateur (is_admin
). La méthode setFields()
permet de mettre à jour ces deux propriétés. Le Listing 2-7 montre le formulaire associé à la classe User
permettant à l'internaute de modifier uniquement la propriété name
.
Listing 2-7 - Formulaire d'Edition pour la classe User
class UserForm extends BaseForm { public function configure() { $this->setWidgets(array('name' => new sfWidgetFormInputString())); $this->widgetSchema->setNameFormat('user[%s]'); $this->setValidators(array('name' => new sfValidatorString())); } }
Enfin, le Listing 2-8 montre une implémentation du module user
utilisant le formulaire défini précédemment pour permettre à l'internaute de modifier son nom.
Listing 2-8 - Implémentation du Module user
class userActions extends sfActions { public function executeIndex($request) { $this->form = new UserForm(); if ($request->isMethod('post')) { $this->form->bind($request->getParameter('user')); if ($this->form->isValid()) { $user = // récupération de l'utilisateur courant $user->setFields($this->form->getValues()); $this->redirect('...'); } } } }
Sans aucune protection spécifique, si l'internaute soumet un formulaire contenant non seulement une valeur pour le champ name
mais également pour le champ is_admin
, via l'utilisation d'un outil comme Firebug par exemple, notre code est vulnérable. En effet, le champ is_admin
ne possédant pas de validateur dans le formulaire, sa valeur est toujours valide. Quelle que soit sa valeur, la méthode setFields()
va donc mettre à jour non seulement la propriété name
mais également la propriété is_admin
.
Si vous testez ce code en soumettant une valeur pour le champ name
et is_admin
, vous obtiendrez une erreur globale "Extra field name." comme illustré Figure 2-8. Par défaut, le système a généré une erreur car certains champs soumis ne possèdent pas de validateur ; le champ is_admin
n'est pas défini dans le formulaire UserForm
.
Figure 2-8 - Erreur s'il manque un Validateur
Jusqu'à maintenant, nous avons vu que les validateurs généraient des erreurs associées à des champs. D'où peut donc provenir cette erreur globale ? Lorsqu'on utilise la méthode setValidators()
, symfony crée un objet sfValidatorSchema
. La classe sfValidatorSchema
permet de définir une collection de validateurs. L'appel à setValidators()
est équivalent au code suivant :
$this->setValidatorSchema(new sfValidatorSchema(array( 'email' => new sfValidatorEmail(), 'subject' => new sfValidatorChoice(array('choices' => array_keys(self::$subjects))), 'message' => new sfValidatorString(array('min_length' => 4)), )));
La classe sfValidatorSchema
possède des règles de validation par défaut permettant de protéger la collection de validateurs. Ces règles sont paramétrables via deux options : allow_extra_fields
et filter_extra_fields
.
L'option allow_extra_fields
, qui est à false
par défaut, consiste à vérifier que chaque donnée soumise par l'internaute possède un validateur. Si ce n'est pas le cas, une erreur globale "Extra field name." est générée, comme dans l'exemple précédent. Lors du développement, cela permet également d'être alerté si on oublie de valider explicitement un champ.
Revenons au formulaire de contact. Changeons les règles de validation en rendant le champ name
obligatoire. Comme la valeur par défaut de l'option required
est true
, nous pourrions changer le validateur name
par :
$nameValidator = new sfValidatorString();
Ce validateur n'a aucune incidence puisqu'il ne possède ni d'option min_length
, ni d'option max_length
; dans ce cas, nous pourrions également le remplacer explicitement par un validateur vide :
$nameValidator = new sfValidatorPass();
Au lieu de définir un validateur vide, nous pourrions le supprimer, mais la protection par défaut que nous avons vue précédemment nous l'empêche. Le Listing 2-9 montre comment désactiver la protection en utilisant l'option allow_extra_fields
.
Listing 2-9 - Désactivation de la Protection allow_extra_fields
class ContactForm extends BaseForm { public function configure() { // ... $this->setValidators(array( 'email' => new sfValidatorEmail(), 'subject' => new sfValidatorChoice(array('choices' => array_keys(self::$subjects))), 'message' => new sfValidatorString(array('min_length' => 4)), )); $this->validatorSchema->setOption('allow_extra_fields', true); } }
Vous devriez maintenant pouvoir valider le formulaire comme illustré dans la Figure 2-9.
Figure 2-9 - Validation avec allow_extra_fields
à true
En observant la page de remerciements, vous vous apercevrez que même si le formulaire a passé la validation, la valeur du champ name
est toujours vide, quelle que soit la donnée que vous soumettez. En fait, la valeur n'est même pas définie dans le tableau retourné par $this->form->getValues()
. La désactivation de l'option allow_extra_fields
a permis de supprimer l'erreur liée à l'absence de validateur mais l'option filter_extra_fields
, qui est à true
par défaut, filtre ces valeurs en les retirant des valeurs validées. Il est bien évidemment possible de modifier ce comportement comme le montre le Listing 2-10.
Listing 2-10 - Désactivation de la Protection filter_extra_fields
class ContactForm extends BaseForm { public function configure() { // ... $this->setValidators(array( 'email' => new sfValidatorEmail(), 'subject' => new sfValidatorChoice(array('choices' => array_keys(self::$subjects))), 'message' => new sfValidatorString(array('min_length' => 4)), )); $this->validatorSchema->setOption('allow_extra_fields', true); $this->validatorSchema->setOption('filter_extra_fields', false); } }
Vous devriez maintenant pouvoir revalider votre formulaire et récupérer la valeur saisie dans la page de remerciement.
Nous verrons dans le Chapitre 4 que ces protections permettront de sauvegarder simplement et de façon sécurisée les formulaires basés sur des objets Propel.
Les Validateurs Logiques
Il est possible de définir plusieurs validateurs pour un même champ grâce aux validateurs logiques :
sfValidatorAnd
: Pour être valide, le champ doit remplir toutes les conditions de validationsfValidatorOr
: Pour être valide, le champ doit remplir au moins une des conditions de validation
Le constructeur des opérateurs logiques prend une liste de validateurs comme premier argument. Le Listing 2-11 utilise le validateur sfValidatorAnd
pour associer deux validateurs obligatoires au champ name
.
Listing 2-11 - Utilisation du Validateur sfValidatorAnd
class ContactForm extends BaseForm { public function configure() { // ... $this->setValidators(array( // ... 'name' => new sfValidatorAnd(array( new sfValidatorString(array('min_length' => 5)), new sfValidatorRegex(array('pattern' => '/[\w- ]+/')), )), )); } }
En soumettant le formulaire, la donnée saisie pour le champ name
doit être composée d'au moins cinq caractères et elle doit être conforme à l'expression régulière ([\w- ]+
).
Les validateurs logiques étant eux-mêmes des validateurs, il est possible de les combiner pour définir des expressions logiques complexes comme le montre le Listing 2-12.
Listing 2-12 - Combinaisons de plusieurs Opérateurs logiques
class ContactForm extends BaseForm { public function configure() { // ... $this->setValidators(array( // ... 'name' => new sfValidatorOr(array( new sfValidatorAnd(array( new sfValidatorString(array('min_length' => 5)), new sfValidatorRegex(array('pattern' => '/[\w- ]+/')), )), new sfValidatorEmail(), )), )); } }
Les Validateurs Globaux
Tous les validateurs que nous avons vus jusqu'à présent sont attachés à un champ spécifique et ne permettent donc de valider qu'une seule valeur à la fois. Ils agissent isolément des autres données soumises par l'internaute. Mais parfois, la validation d'un champ est contextuelle et dépend d'un ou plusieurs autres champs comme par exemple pour valider que deux mots de passe sont identiques, ou qu'une date de départ est bien antérieure à une date de fin.
Dans ce cas, il faut utiliser un validateur global qui permet de valider les données soumises par l'internaute dans leur contexte. Il est possible d'enregistrer un validateur global avant ou après la validation individuelle des champs en utilisant respectivement un pré-validateur ou un post-validateur. Il est généralement préférable d'utiliser un post-validateur car les données sont déjà validées et surtout nettoyées, donc avec un format prévisible. Le Listing 2-13 montre l'implémentation de la comparaison de deux mots de passe en utilisant le validateur sfValidatorSchemaCompare
.
Listing 2-13 - Utilisation du Validateur sfValidatorSchemaCompare
$this->validatorSchema->setPostValidator(new sfValidatorSchemaCompare('password', sfValidatorSchemaCompare::EQUAL, 'password_again'));
A partir de symfony 1.2, vous pouvez également utiliser les opérateurs PHP plutôt que les constantes de la classe sfValidatorSchemaCompare
. L'exemple précédent est équivalent au code suivant :
$this->validatorSchema->setPostValidator(new sfValidatorSchemaCompare('password', '==', 'password_again'));
tip
La classe sfValidatorSchemaCompare
hérite du validateur sfValidatorSchema
comme tous les validateurs globaux. sfValidatorSchema
est lui-même un validateur global puisqu'il permet de valider l'ensemble des données soumises par l'internaute en déléguant la validation de chaque champ à d'autres validateurs.
Le Listing 2-14 montre l'utilisation du même validateur pour valider qu'une date de départ est antérieure à une date de fin avec une personnalisation de message d'erreur.
Listing 2-14 - Utilisation du Validateur sfValidatorSchemaCompare
$this->validatorSchema->setPostValidator( new sfValidatorSchemaCompare('start_date', sfValidatorSchemaCompare::LESS_THAN_EQUAL, 'end_date', array(), array('invalid' => 'The start date ("%left_field%") must be before the end date ("%right_field%")') ) );
L'utilisation d'un post-validateur permet de garantir que la comparaison des deux dates sera possible. En effet, quel que soit le format de dates utilisé par l'internaute lors de sa saisie, la validation des champs start_date
et end_date
a permis leur conversion dans un format comparable (Y-m-d H:i
par défaut).
Par défaut, les pré-validateurs et les post-validateurs renvoient des erreurs globales au formulaire. Néanmoins, certains d'entre eux permettent d'attacher l'erreur à un champ en particulier. Par exemple, l'option throw_global_error
du validateur sfValidatorSchemaCompare
permet de choisir entre une erreur globale (Figure 2-10) et une erreur attachée au premier champ passé en argument du constructeur (Figure 2-11). Le Listing 2-15 montre l'utilisation de l'option throw_global_error
.
Listing 2-15 - Utilisation de l'option throw_global_error
$this->validatorSchema->setPostValidator( new sfValidatorSchemaCompare('start_date', sfValidatorSchemaCompare::LESS_THAN_EQUAL, 'end_date', array('throw_global_error' => true), array('invalid' => 'The start date ("%left_field%") must be before the end date ("%right_field%")') ) );
Figure 2-10 - Erreur globale pour un Validateur global
Figure 2-11 - Erreur locale pour un Validateur global
Enfin, l'utilisation d'un validateur logique permet de combiner plusieurs post-validators comme le montre le Listing 2-16.
Listing 2-16 - Combinaison de plusieurs Post-Validateurs grâce à un Validateur logique
$this->validatorSchema->setPostValidator(new sfValidatorAnd(array( new sfValidatorSchemaCompare('start_date', sfValidatorSchemaCompare::LESS_THAN_EQUAL, 'end_date'), new sfValidatorSchemaCompare('password', sfValidatorSchemaCompare::EQUAL, 'password_again'), )));
Le téléchargement de Fichiers
En PHP, comme dans tous les langages utilisés pour le Web, la gestion du téléchargement de fichiers nécessite une prise en charge particulière tant au niveau du code HTML que pour la récupération des fichiers côté serveur. Nous allons voir dans cette section les outils que le framework de formulaire met à la disposition du développeur pour lui simplifier la vie. Nous verrons également comment éviter les principaux pièges.
Modifions le formulaire de contact pour permettre aux internautes d'attacher une pièce jointe à
leur message. Pour cela, ajoutons un champ file
comme le montre le Listing 2-17.
Listing 2-17 - Ajout d'un Champ file
au Formulaire ContactForm
// lib/form/ContactForm.class.php class ContactForm extends BaseForm { protected static $subjects = array('Subject A', 'Subject B', 'Subject C'); public function configure() { $this->setWidgets(array( 'name' => new sfWidgetFormInputText(), 'email' => new sfWidgetFormInputText(), 'subject' => new sfWidgetFormSelect(array('choices' => self::$subjects)), 'message' => new sfWidgetFormTextarea(), 'file' => new sfWidgetFormInputFile(), )); $this->widgetSchema->setNameFormat('contact[%s]'); $this->setValidators(array( 'name' => new sfValidatorString(array('required' => false)), 'email' => new sfValidatorEmail(), 'subject' => new sfValidatorChoice(array('choices' => array_keys(self::$subjects))), 'message' => new sfValidatorString(array('min_length' => 4)), 'file' => new sfValidatorFile(), )); } }
Lorsqu'un formulaire contient un widget de type sfWidgetFormInputFile
permettant le téléchargement d'un fichier, il ne faut pas oublier d'ajouter l'attribut enctype
au tag form
comme le montre le Listing 2-18.
Listing 2-18 - Modification de la Template pour prendre en compte le Champ file
<form action="<?php echo url_for('contact/index') ?>" method="POST" enctype="multipart/form-data"> <table> <?php echo $form ?> <tr> <td colspan="2"> <input type="submit" /> </td> </tr> </table> </form>
note
Si vous générez dynamiquement le Template associée à un formulaire, la méthode isMultipart()
de l'objet formulaire permet de savoir si un formulaire nécessite l'attribut enctype
.
En PHP, les informations sur les fichiers téléchargés ne sont pas stockées avec les autres valeurs soumises. Il faut donc modifier l'appel à la méthode bind()
pour lui passer ces informations en deuxième argument comme le montre le Listing 2-19.
Listing 2-19 - Passage des Fichiers téléchargés à la Méthode bind()
class contactActions extends sfActions { public function executeIndex($request) { $this->form = new ContactForm(); if ($request->isMethod('post')) { $this->form->bind($request->getParameter('contact'), $request->getFiles('contact')); if ($this->form->isValid()) { $values = $this->form->getValues(); // do something with the values // ... } } } public function executeThankyou() { } }
Maintenant que le formulaire est opérationnel, il reste à modifier l'action pour stocker le fichier téléchargé. Comme nous l'avons vu au début de ce chapitre, le validateur sfValidatorFile
convertit les informations concernant le fichier téléchargé en un objet sfValidatedFile
. Le Listing 2-20 montre comment manipuler cet objet pour stocker le fichier dans le répertoire web/uploads
.
Listing 2-20 - Utilisation de l'Objet sfValidatedFile
if ($this->form->isValid()) { $file = $this->form->getValue('file'); $filename = 'uploaded_'.sha1($file->getOriginalName()); $extension = $file->getExtension($file->getOriginalExtension()); $file->save(sfConfig::get('sf_upload_dir').'/'.$filename.$extension); // ... }
Le tableau suivant détaille les méthodes de l'objet sfValidatedFile
:
Méthode | Description |
---|---|
save() | Sauvegarde le fichier téléchargé |
isSaved() | Retourne true si le fichier a été sauvegardé |
getSavedName() | Retourne le nom du fichier sauvegardé |
getExtension() | Retourne l'extension du fichier en fonction du type mime |
getOriginalName() | Retourne le nom du fichier téléchargé |
getOriginalExtension() | Retourne l'extension du nom du fichier téléchargé |
getTempName() | Retourne le chemin vers le fichier temporaire |
getType() | Retourne le type mime du fichier |
getSize() | Retourne la taille du fichier |
tip
Le type mime fourni par le navigateur lors du téléchargement d'un fichier n'est pas considéré comme une donnée fiable. Pour assurer une sécurité maximum, les fonctions finfo_open
, mime_content_type
et l'utilitaire file
sont utilisés à tour de rôle lors de la validation du fichier. Si aucune de ces fonctions n'arrive à déterminer le type mime ou si aucune n'est disponible sur le système, le type mime du navigateur est alors utilisé. Il est possible d'ajouter ou de modifier les fonctions déterminant le mime type en passant l'option mime_type_guessers
au validateur sfValidatorFile
.
This work is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license.