Exemple et contexte
Imaginons que l'on travaille sur un site communautaire. Les membres peuvent uploader une image pour leur profil.
La validation de base sur les fichiers fonctionne bien :
- taille maximale en Ko
- type mime du fichier (image)
Comment valider les dimensions de l'image ?
Ca serait pratique d'avoir des images de taille comprise entre 100 et 250 pixels de coté.
Pour valider les dimensions, on fabrique un validateur personnalisé.
Position dans l'arborescence et héritage
Un validateur est une classe, donc on peut le placer dans le répertoire /lib.
Comme on veut faciliter la maintenance du code et retrouver les fichiers facilement, on va le placer dans /lib/validator.
Le nom du fichier doit suivre les standards de symfony, donc pour la classe myCustomValidator, le fichier s'appelle myCustomValidator.class.php .
Note : Il faut faire attention à la casse (MAJUSCULES vs minuscules)
Chaque validateur étend sfValidatorBase ou un autre validateur (étendant déjà sfValidatorBase)
Pour valider une image, on valide d'abord un type de fichier puis les dimensions de l'image. La déclaration de la classe est donc :
// validateur image étendant le validateur de fichier
class sfValidatorFileImage extends sfValidatorFile
{
}
// ou si on étend un validateur de base
class myCustomValidator extends sfValidatorBase
{
}
Création des options et des messages d'erreurLe constructeur fonctionne avec 2 tableaux en paramètre :
- le premier concerne les régles, appelées "options"
- le seconde concerne les messages d'erreur, appelés "messages"
Ces tableaux sont définis dans la méthode configure().
Pour ajouter une option ou un message, on utilise simplement addOption() et addMessage().
Si l'on étend un validateur spécifiqueil faut appeler la méthode parente : parent::configure().
protected function configure($options = array(), $messages = array())
{
/* options sur les dimensions maximales*/
$this->addOption('min_size_x');
$this->addOption('max_size_x');
$this->addOption('min_size_y');
$this->addOption('max_size_y');
/* valeur par défaut pour le type de fichier : images uniquement */
$this->addOption('mime_types' , 'web_images');
/* messages d'erreur pour les dimensions*/
$this->addMessage('min_size_x', 'L\'image n\'est pas assez large (minimum %min_size_x% pixels).');
$this->addMessage('max_size_x', 'L\'image est trop large (maximum %max_size_x% pixels).');
$this->addMessage('min_size_y', 'L\'image n\'est pas assez haute (minimum %min_size_y% pixels).');
$this->addMessage('max_size_y', 'L\'image est trop haute(maximum %max_size_y% pixels).');
/* messages d'erreur sur le type de l'image */
$this->addMessage('mime_types', 'mime type invalide (%mime_type%). Le fichier n\'est pas une image.');
/* on appelle le configure() parent pour l'utiliser sur la validation de fichier */
parent::configure($options, $messages);
}
Syntaxe :Pour définir une option :
$this->addOption('option_name')
Pour définir une option, avec valeur par défaut :
$this->addOption('option_name', 'default_value)
Pour définir un message d'erreur :
$this->addMessage('option_name', 'Texte du message');
Pour afficher une valeur dans un message d'erreur, on écrit simplement
%size_x%. La valeur sera remplacée lorsque le message d'erreur sera appelé.
Bon. Maintenant on peut personnaliser les options et message d'erreur, mais on ne peut pas encore valider (tester) les données. Allons-y !
Le coeur de la validation : doClean()
On a fait la partie la plus facile de la personnalisation. Maintenant on attaque la partie facile.
Pour chaque option (régle de validation), on vérifie 2 choses :
- si l'option a été définie (par le script utilisant le validateur)
- si la régle de validation est respectée
On doit utiliser la valeur de retour de la méthode parente doClean().
Pour valider la taille maximale en x, on écrira ce genre de code :
protected function doClean($value)
{
$validatedFile = parent::doClean($value);
// on recupère les dimensions de l'image
$size = getimagesize($validatedFile->getTempName());
// on compare la taille en x de l'image à la taille maximale "max_size_x"
if ($this->hasOption('max_size_x') && $this->getOption('max_size_x') < (int) $size[0])
{
throw new sfValidatorError($this, 'max_size_x', array('max_size_x' => $this->getOption('max_size_x'), 'size_x' => (int) $size[0]));
}
// autres vérifications
// ...
// on renvoie le fichier validé
return $validatedFile;
}
Syntaxe :$this->hasOption('option_name') vérifie que l'option est définie par le script utilisant le validateur. Si l'option n'est pas définie, la régle est ignorée.
Si la régle n'est pas respectée, une exception est levée :
throw new sfValidatorError($this, 'max_size_x', array('max_size_x' => $this->getOption('max_size_x'), 'size_x' => (int) $size[0]));
Syntaxe :
throw new sfValidatorError($this, 'option_name', array('name' => 'la valeur qui remplacera %name% dans le message d erreur' );
Et voilà, on a notre validateur personnalisé !
Le code complet de l'exemple est :
// validateur image étendant le validateur de fichier
class sfValidatorFileImage extends sfValidatorFile
{
protected function configure($options = array(), $messages = array())
{
/* options sur les dimensions maximales*/
$this->addOption('min_size_x');
$this->addOption('max_size_x');
$this->addOption('min_size_y');
$this->addOption('max_size_y');
/* valeur par défaut pour le type de fichier : images uniquement */
$this->addOption('mime_types' , 'web_images');
/* messages d'erreur pour les dimensions*/
$this->addMessage('min_size_x', 'L\'image n\'est pas assez large (minimum %min_size_x% pixels).');
$this->addMessage('max_size_x', 'L\'image est trop large (maximum %max_size_x% pixels).');
$this->addMessage('min_size_y', 'L\'image n\'est pas assez haute (minimum %min_size_y% pixels).');
$this->addMessage('max_size_y', 'L\'image est trop haute(maximum %max_size_y% pixels).');
/* messages d'erreur sur le type de l'image */
$this->addMessage('mime_types', 'mime type invalide (%mime_type%). Le fichier n\'est pas une image.');
/* on appelle le configure() parent pour l'utiliser sur la validation de fichier */
parent::configure($options, $messages);
} // END configure()
protected function doClean($value)
{
$validatedFile = parent::doClean($value);
// on recupère les dimensions de l'image
$size = getimagesize($validatedFile->getTempName());
// on compare la taille en x de l'image à la taille maximale "max_size_x"
if ($this->hasOption('max_size_x') && $this->getOption('max_size_x') < (int) $size[0])
{
throw new sfValidatorError($this, 'max_size_x', array('max_size_x' => $this->getOption('max_size_x'), 'size_x' => (int) $size[0]));
}
// on compare la taille en x de l'image à la taille minimale "min_size_x"
if ($this->hasOption('min_size_x') && $this->getOption('min_size_x') > (int) $size[0])
{
throw new sfValidatorError($this, 'min_size_x', array('min_size_x' => $this->getOption('min_size_x'), 'size_x' => (int) $size[0]));
}
// on compare la taille en y de l'image à la taille maximale "max_size_y"
if ($this->hasOption('max_size_y') && $this->getOption('max_size_y') < (int) $size[1])
{
throw new sfValidatorError($this, 'max_size_y', array('max_size_y' => $this->getOption('max_size_y'), 'size_y' => (int) $size[1]));
}
// on compare la taille en y de l'image à la taille minimale "min_size_y"
if ($this->hasOption('min_size_y') && $this->getOption('min_size_y') > (int) $size[1])
{
throw new sfValidatorError($this, 'min_size_y', array('min_size_y' => $this->getOption('min_size_y'), 'size_y' => (int) $size[1]));
}
// on renvoie le fichier validé
return $validatedFile;
} // END doClean()
}
Note :
On n'a pas vérifié si min_size_x <= max_size_x dans les options, mais on pourrait le faire.
En résumé :
La création de validateur dans symfony n'est pas compliquée.
Voici la check list de la création de validateur :
- Vérifier qu'un validateur de base remplit les besoins, ou une partie des besoins
- Vérifier qu'un validateur sous forme de plugin existe pour ces besoins
- Créer une classe étendant sfValidatorBase ou un validateur proche des besoins dans le répertoire /lib/validator
- Ecrire la méthode configure() pour définir les options (régles de validation nommées) et les messages (messages d'erreur)
- Ecrire la validation dans la méthode doClean() : Si une option est définie, tester la régle correspondante et lever une exception en cas de non respect de cette régle
- Tester, tester, tester le validateur
Références
- La validation dans les formulaires : http://www.symfony-project.org/forms/1_ ... Validation
- L'api des validateurs : http://www.symfony-project.org/api/1_2/validator
- Tutoriel original (en anglais) : http://forum.symfony-project.org/index.php/t/16172/
Pascal