Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Intégrer les formulaires et la validation Symfony dans vos applications PHP.

E2ed7c278c8c49bb3e7fe0b7de039997?s=47 Hugo Hamon
April 05, 2013

Intégrer les formulaires et la validation Symfony dans vos applications PHP.

Les formulaires sont partout et sont aussi des composants importants d'une application. Ils permettent en effet aux utilisateurs d'interagir avec votre système d'informations. Cependant, la conception et la validation des formulaires ne sont pas des tâches si aisées qu'elles n'y paraissent. Le but de cette conférence est de vous aider à tirer profit des composants de formulaire et de validation du framework Symfony. Nous aborderons les aspects d'architecture des formulaires, les concepts fondamentaux ainsi que quelques usages avancés comme les formulaires imbriqués et les collections. Nous passerons aussi en revue les différentes manières de valider des données avec le composant de validation. Vous serez ainsi en mesure d'intégrer ces deux composants majeurs du framework Symfony dans vos propres applications PHP.

E2ed7c278c8c49bb3e7fe0b7de039997?s=128

Hugo Hamon

April 05, 2013
Tweet

Transcript

  1. Intégrer les formulaires et la validation Symfony dans vos applications

    PHP . Hugo HAMON – SymfonyLive - Paris 2013
  2. Biographie… Hugo HAMON Responsable des formations SensioLabs Auteur chez Eyrolles

    Conférencier Contributeur à Symfony @hhamon
  3. SensioLabs A la recherche d’un job ?

  4. Introduction Formulaires & Validation

  5. Pourquoi la gestion des formulaires est-elle si compliquée ?

  6. Formulaires Architecture

  7. None
  8. Cœur du système de formulaire Core CSRF DI Doctrine Propel

    Twig PHP Smarty … Zend … Rendu Extensions Fondation
  9. Démarrage { "require": { "doctrine/common": "2.*", "symfony/form": "2.2.*", "symfony/yaml": "2.2.*",

    "symfony/http-foundation": "2.2.*", "symfony/validator": "2.2.*", "symfony/config": "2.2.*", "symfony/translation": "2.2.*", "symfony/twig-bridge": "2.2.*" } }
  10. use Symfony\Bridge\Twig\Form\TwigRendererEngine; use Symfony\Component\Form\Forms; use Symfony\Bridge\Twig\Extension\FormExtension; use Symfony\Bridge\Twig\Form\TwigRenderer; // Define

    some constants to the main resources define('VENDOR_DIR', realpath(__DIR__ . '/../vendor')); define('DEFAULT_FORM_THEME', 'form_div_layout.html.twig'); define('VENDOR_TWIG_BRIDGE_DIR', VENDOR_DIR . '/symfony/twig-bridge/Symfony/Bridge/Twig'); define('VIEWS_DIR', realpath(__DIR__ . '/../views')); // Initialize a Twig compatible rendering engine $twig = new Twig_Environment(new Twig_Loader_Filesystem(array( VIEWS_DIR, VENDOR_TWIG_BRIDGE_DIR . '/Resources/views/Form', ))); $formEngine = new TwigRendererEngine(array(DEFAULT_FORM_THEME)); $formEngine->setEnvironment($twig); // Register the Twig Form extension $twig->addExtension(new FormExtension(new TwigRenderer($formEngine))); // Set up the Form component $formFactoryBuilder = Forms::createFormFactoryBuilder(); $formFactory = $formFactoryBuilder->getFormFactory();
  11. Formulaires Les fondamentaux

  12. Créer un formulaire simple $form = $formFactory ->createBuilder() ->add('name') ->add('bio',

    'textarea') ->add('gender', 'choice', array( 'choices' => array( 'm' => 'Male', 'f' => 'Female' ), )) ->getForm();
  13. Créer un formulaire simple

  14. Vue arborescente du formulaire form   form bio   textarea

    name   text gender   choice Nom du champ   Type de champ  
  15. Traitement du formulaire $name = $form->getName(); if (!empty($_POST[$name])) { $form->bind($_POST[$name]);

    $data = $form->getData(); print_r($data); }
  16. Association avec un tableau $data['name'] $data['bio'] $data['gender']

  17. Préremplissage du formulaire $form->setData(array( 'name' => 'Jane Smith', 'bio' =>

    'I do great things!', 'gender' => 'f', ));
  18. Préremplissage du formulaire Jane Smith I do great things! Female

  19. Formulaires Types de champ natifs

  20. Types de champ natifs §  Birthday §  Checkbox §  Choice

    §  Collection* §  Country §  Date §  DateTime §  File §  Hidden §  Integer §  Language §  Locale §  Money §  Number §  Password §  Percent §  Radio §  Repeated* §  Search §  Textarea §  Text §  Time §  Timezone §  Url
  21. Types de champ natifs Text Choice Password File Form Date

    Country Language Timezone Birthday DateTime …
  22. Le champ répété $builder ->add('name') ->add('password', 'repeated', array( 'type' =>

    'password', 'invalid_message' => 'Passwords do not match.', 'first_options' => array('label' => 'Password'), 'second_options' => array('label' => 'Confirmation'), )) ->add('bio', 'textarea') // [...] ->getForm() ;  
  23. Le type « répété » est rendu sous la forme

    de deux champs de saisie de mot de passe.
  24. Formulaires Rendu des formulaires

  25. Afficher un formulaire // PHP rendering echo $engine->render('profile.php', array( 'form'

    => $form->createView(), )); // Twig rendering echo $twig->render('profile.twig', array( 'form' => $form->createView(), ));
  26. Prototypage du rendu (PHP) <form action="#" method="post"> <fieldset> <legend>Your profile</legend>

    <?php echo $view['form']->widget($form) ?> </fieldset> <div class="form-actions"> <button type="submit">Save changes</button> <button type="button">Cancel</button> </div> </form>
  27. Prototypage du rendu (Twig) <form action="#" method="post"> <fieldset> <legend>Your profile</legend>

    {{ form_widget(form) }} </fieldset> <div class="form-actions"> <button type="submit">Save changes</button> <button type="button">Cancel</button> </div> </form>
  28. <div> <label for="form_name" class="required">Name</label> <input type="text" id="form_name" name="form[name]" required="required" value="Jane

    Smith" /> </div> Prototypage du rendu
  29. // Form rendering <?php echo $view['form']->enctype($form) ?> <?php echo $view['form']->widget($form)

    ?> <?php echo $view['form']->errors($form) ?> <?php echo $view['form']->rest($form) ?> // Field rendering <?php echo $view['form']->row($form['bio']) ?> <?php echo $view['form']->errors($form['bio']) ?> <?php echo $view['form']->label($form['bio']) ?> <?php echo $view['form']->widget($form['bio'], array( 'attr' => array('class' => 'editor'), )) ?> Rendu personnalisé (PHP)
  30. {# General rendering #} {{ form_enctype(form) }} {{ form_widget(form) }}

    {{ form_errors(form) }} {{ form_rest(form) }} {# Field rendering #} {{ form_row(form.bio) }} {{ form_errors(form.bio) }} {{ form_label(form.bio, 'Biography') }} {{ form_widget(form.bio, { 'attr': { 'class': 'editor' }}) }} Rendu personnalisé (Twig)
  31. Formulaires Association avec des objets

  32. Association des propriétés publiques class Person { public $name; public

    $password; public $bio; public $gender; }
  33. Association des propriétés publiques $person = new Person(); $person->name =

    'John Doe'; $person->password = 'S3cR3T$1337'; $person->bio = 'Born in 1970...'; $person->gender = 'm'; $person->active = true; // The form reads & writes // the person object $form->setData($person);
  34. Association des propriétés privées class Person { private $name; //

    ... function setName($name) { $this->name = $name; } function getName() { return $this->name; } }
  35. Association des propriétés privées class Person { private $active; //

    ... public function isActive() { return $this->active; } }
  36. Association des propriétés privées $person = new Person(); $person->setName('John Doe');

    $person->setPassword('S3cR3T$1337'); $person->setBio('Born in 1970...'); $person->setGender('m'); $person->setActive(true); // The form reads & writes // the person object $form->setData($person);
  37. Association du type de données $form = $formFactory ->createBuilder('form', $person,

    array( 'data_class' => 'Person', )) // ... ->getForm() ;
  38. Formulaires Protection CSRF

  39. Activer la protection CSRF # bootstrap.php // ... use Symfony\Component\Form\Extension\Csrf\CsrfExtension;

    use Symfony\Component\Form\Extension\Csrf\CsrfProvider\DefaultCsrfProvider; use Symfony\Bridge\Twig\Form\TwigRenderer; define('CSRF_SECRET', 'c2ioeEU1n48QF2WsHGWd2HmiuUUT6dxr'); // Set up the CSRF provider $csrfProvider = new DefaultCsrfProvider(CSRF_SECRET); $renderer = new TwigRenderer($formEngine, $csrfProvider); // ... $formFactory = $formFactoryBuilder ->addExtension(new CsrfExtension($csrfProvider)) ->getFormFactory();
  40. Activer la protection CSRF <form method="post" action="#"> <fieldset> <legend>Your profile</legend>

    <div id="form"> <!-- ... fields ... --> <input type="hidden" value="0219d[…]61d69" name="form[_token]"> </div> </fieldset> <div class="form-actions"> <button type="submit">Save changes</button> <button type="button">Cancel</button> </div> </form>
  41. Formulaires Type de formulaire personnalisé

  42. Créer un type personnalisé use Symfony\Component\Form\AbstractType; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class PersonType

    extends AbstractType { public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array('data_class' => 'Person')); } public function getName() { return 'person'; } }
  43. // ... use Symfony\Component\Form\FormBuilderInterface; class PersonType extends AbstractType { public

    function buildForm( FormBuilderInterface $builder, array $options ) { $builder ->add('name') ->add('password', 'repeated', array(...)) ->add('bio', 'textarea') ->add('gender', 'choice', array(...)) ->add('active', 'checkbox') ; } }
  44. Créer un type personnalisé $person = new Person(); $person->setName('John Doe');

    // ... $options = array('trim' => true); $form = $formFactory ->create(new PersonType(), $person, $options) ;
  45. Enregistrer un nouveau type # in your code... $form =

    $formFactory->create('person', $person); # bootstrap.php $formFactory = Forms::createFormFactoryBuilder() // [...] ->addType(new PersonType(), 'person') ->getFormFactory() ;
  46. Formulaires Gestion des fichiers

  47. Gérer l’envoi de fichiers # bootstrap.php // ... use Symfony\…\HttpFoundation\HttpFoundationExtension;

    $formFactoryBuilder = Forms::createFormFactoryBuilder() $formFactory = $formFactoryBuilder ->addExtension(new HttpFoundationExtension()) // [...] ->getFormFactory() ;
  48. Gérer l’envoi de fichiers class PersonType extends AbstractType { function

    buildForm(FormBuilderInterface $builder, …) { $builder // […] ->add('picture', 'file', array( 'required' => false, )) ->add('active', 'checkbox') ; } }
  49. Générer l’attribut « enctype » <form {{ form_enctype(form) }} …>

    <!-- render fields --> </form>
  50. Traiter le formulaire use Symfony\Component\HttpFoundation\Request; $request = Request::createFromGlobals() $request->overrideGlobals(); if

    ($request->isMethod('POST')) { $form->bind($request); var_dump($form->getData()); }
  51. Traiter le formulaire object(Person) private 'name' => 'John Doe' private

    'picture' => object(Symfony\Component\HttpFoundation\File\UploadedFile) private 'test' => false private 'originalName' => '445.jpg' private 'mimeType' => 'image/jpeg' private 'size' => 21645 private 'error' => 0 private 'password' => 'secret' private 'bio' => 'Famous actor!' private 'gender' => 'm' private 'active' => true
  52. Traiter le formulaire $file = $form->get('picture')->getData(); $target = __DIR__. '/uploads';

    if ($file->isValid()) { $new = $file->move($target, 'jdoe.jpg'); }
  53. Formulaires Champs non associés

  54. Définir un champ non associé $builder ->add('rules', 'checkbox', array( 'mapped'

    => false, )) ;
  55. array( 'name' => 'John Doe' 'password' => 'secret' 'bio' =>

    'Actor!' 'gender' => 'm' 'picture' => null 'active' => true ) Champ non associé Pas de donnée « rules »
  56. Formulaires Collections de champs

  57. Définir une collection de champs $builder // ... ->add('hobbies', 'collection',

    array( 'allow_add' => true, 'allow_delete' => true, )) // ... ;
  58. Définir une collection de champs $person = new Person(); $person->setName('John

    Doe'); $person->addHobby('music'); $person->addHobby('movies'); $person->addHobby('travels');
  59. Définir une collection de champs

  60. Conventions de nommage class Person { public function addHobby($hobby) {

    $this->hobbies[] = $hobby; } public function removeHobby($hobby) { $key = array_search($hobby, $this->hobbies); if (false !== $key) { unset($this->hobbies[$key]); } } }
  61. Prototype HTML d’ajout de champ

  62. Formulaires Formulaires imbriqués

  63. Imbriquer les formulaires class Address { private $street; private $zipCode;

    private $city; private $state; private $country; // ... }
  64. class AddressType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array

    $options) { $builder ->add('street', 'textarea') ->add('zipCode') ->add('city') ->add('state') ->add('country', 'country') ; } public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array('data_class' => 'Address')); } public function getName() { return 'address'; } }
  65. Imbriquer les formulaires class PersonType extends AbstractType { function buildForm(FormBuilderInterface

    $builder, …) { $builder // ... ->add('address', new AddressType()) ; } }
  66. Imbriquer les formulaires class PersonType extends AbstractType { function buildForm(FormBuilderInterface

    $builder, …) { $builder // ... ->add('address', 'address') ; } }
  67. Imbriquer les formulaires $formBuilder = Forms::createFormFactoryBuilder(); $formFactory = $formBuilder //

    ... ->addType(new AddressType(), 'address') ->addType(new PersonType(), 'person') ->getFormFactory() ;
  68. Vue arborescente du formulaire form   form bio   textarea

    name   text …   ... Champ   Type   address   Address zipCode   text address   textarea city   text country   country state   text
  69. Person Object ( [name] => Hugo Hamon [picture] => [username]

    => hhamon [password] => secret [address] => Address Object ( [street] => 42 Sunshine Street [zipCode] => 12345 [city] => Miami [state] => Florida [country] => US ) [bio] => Speaker at conference [gender] => m [active] => 1 [hobbies] => Array ( [1] => movies [2] => travels [3] => conferences ) )
  70. Formulaires I18N & L10N

  71. $builder ->add('birthdate', 'birthday', array('format' => 'd/M/y')) ->add('salary', 'money', array('currency' =>

    'EUR')) ->add('language', 'language', array( 'preferred_choices' => array('fr'), ) ->add('country', 'country', array( 'preferred_choices' => array('FR'), ) ->add('timezone', 'timezone', array( 'preferred_choices' => array('Europe/Paris') ) ; Types de champs régionalisés
  72. Locale: fr_FR Locale: en_US

  73. Formulaires Gestion des thèmes

  74. {% form_theme form _self %} {% block password_widget %} <div

    class="input-append"> {{ block('field_widget') }} <span class="add-on"> <i class="icon-lock"></i> </span> </div> {% endblock password_widget %} Changer le rendu de tous les champs de saisie de mot de passe
  75. {% form_theme form _self %} {% block _person_username_widget %} <div

    class="input-append"> {{ block('field_widget') }} <span class="add-on"> <i class="icon-user"></i> </span> </div> {% endblock _person_username_widget %} Changer le rendu d’un champ précis
  76. Rendu personnalisé de champs

  77. Validation Le validateur

  78. // ... use Symfony\Component\Validator\Validation; use Symfony\Component\Form\Extension\Validator\ValidatorExtension; $validator = Validation::createValidatorBuilder() ->getValidator()

    ; $formFactoryBuilder = Forms::createFormFactoryBuilder() $formFactory = $formFactoryBuilder // ... ->addExtension(new ValidatorExtension($validator)) ->getFormFactory() ; Configurer le validateur
  79. Contraintes & Validateurs Pour chaque règle de validation, le composant

    embarque une classe de type Constraint et sa classe Validator associée. L’objet Constraint décrit la règle à valider et l’objet Validator est l’implementation de la logique métier associée à cette règle.
  80. // ... use Symfony\Component\Validator\Constraints\NotBlank as Assert; class PersonType extends AbstractType

    { public function buildForm(...) { $builder->add('name', 'text', array( 'constraints' => array( new Assert\NotBlank(), new Assert\Length(array('min' => 5, 'max' => 40)), ), )); // ... } } Association des contraintes aux champs
  81. $genders = array('m' => 'Male', 'f' => 'Female'); $builder->add('gender', 'choice',

    array( 'choices' => $genders, 'constraints' => array( new Assert\NotBlank(), new Assert\Choice(array( 'choices' => array_keys($genders), 'message' => 'Alien genders are not supported.', )), ), )); Association des contraintes aux champs
  82. Validation du formulaire $request = Request::createFromGlobals(); $request->overrideGlobals(); if ($request->isMethod('POST')) {

    $form->bind($request); if ($form->isValid()) { $data = $form->getData(); // ... handle sanitized data } }
  83. Validation du formulaire

  84. Classes de contraintes natives §  All §  Blank §  Callback

    §  Choice §  Collection §  Count §  Country §  Date §  DateTime §  Email §  False §  File §  Image §  Ip §  Language §  Length §  Locale §  NotBlank §  NotNull §  Null §  Range §  Regex §  Time §  True §  Type §  Url §  Valid…
  85. Validation Formats de configuration

  86. # bootstrap.php // ... $validator = Validation::createValidatorBuilder() ->addMethodMapping('loadValidatorMetadata') ->addXmlMapping(__DIR__.'/config/validation.xml') ->addYamlMapping(__DIR__.'/config/validation.yml')

    ->enableAnnotationMapping() ->getValidator() ; Configurer le validateur
  87. use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\Length; class Person { private

    $name; // ... public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('name', new NotBlank()); $metadata->addPropertyConstraint('name', new Length(array( 'min' => 5, 'max' => 40, ))); // ... } } Configuration en PHP
  88. # src/config/validation.yml Person: properties: name: - NotBlank: ~ - Length:

    { 'min': 5, 'max': 40 } # ... getters: # ... constraints: # ... Configuration en YAML
  89. <!-- src/config/validation.xml --> <?xml version="1.0" ?> <constraint-mapping ...> <class name="Person">

    <property name="name"> <constraint name="NotBlank"/> <constraint name="Length"> <option name="min">5</option> <option name="max">40</option> </constraint> </property> </class> </constraint-mapping> Configuration en XML
  90. use Symfony\Component\Validator\Constraints as Assert; class Person { /** * @Assert\NotBlank()

    * @Assert\Length(min = 5, max = 40) */ private $name; // ... } Configuration en annotations
  91. Validation Ajouter des contraintes

  92. Contraintes de propriétés use Symfony\Component\Validator\Constraints\Image; class Person { private $picture;

    // ... static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('picture', new Image(array( 'minWidth' => 100, 'maxWidth' => 150, 'minHeight' => 100, 'maxHeight' => 150, ))); // ... } }
  93. class Person { //... static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addConstraint(new

    Unique(array( 'field' => 'username', 'message' => 'This username already exist.', ))); //... } } Contraintes de classe
  94. use Symfony\Component\Validator\Constraints\True; class Person { private $username; private $password; public

    static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addGetterConstraint('passwordValid', new True(array( 'message' => 'Password and username cannot be same.', ))); // ... } public function isPasswordValid() { return strtolower($this->username) !== strtolower($this->password); } } Contraintes de méthodes
  95. Contraintes de méthodes Le message d’erreur remonte comme une erreur

    globale du formulaire.
  96. class PersonType extends AbstractType { public function setDefaultOptions(OptionsResolverInterface $resolver) {

    $resolver->setDefaults(array( 'data_class' => 'Person', 'error_mapping' => array('passwordValid' => 'password'), )); } } Associer une erreur globale à un champ
  97. Associer une erreur globale à un champ Le message d’erreur

    est désormais réaffecté au champ « password ».
  98. Validation Traduction des messages

  99. use Symfony\Component\Translation\Translator; use Symfony\Component\Translation\Loader\XliffFileLoader; use Symfony\Bridge\Twig\Extension\TranslationExtension; $t = new Translator('fr');

    $t->addLoader('xlf', new XliffFileLoader()); // Built in translations $t->addResource('xlf', FORM_DIR . '/Resources/translations/validators.fr.xlf', 'fr', 'validators'); $t->addResource('xlf', VALIDATOR_DIR . '/Resources/translations/validators.fr.xlf', 'fr', 'validators'); // Your translations $t->addResource('xlf', __DIR__. '/translations/validators.fr.xlf', 'fr', 'validators'); // ... $twig->addExtension(new TranslationExtension($t)); // ... Activer le support des traductions
  100. <?xml version="1.0"?> <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2"> <file source-language="en" datatype="plaintext" original="file.ext"> <body>

    <trans-unit id="1"> <source>Password and username cannot be same.</source> <target>Le mot de passe et le nom d'utilisateur doivent être différents.</target> </trans-unit> </body> </file> </xliff> Traduire les messages d’erreur
  101. Validation Configuration avancée

  102. Groupes de validation class Person { public static function loadValidatorMetadata(ClassMetadata

    $metadata) { $metadata->addPropertyConstraint('password', new NotBlank(array( 'groups' => array('Signup'), ))); $metadata->addGetterConstraint('passwordValid', new True(array( 'message' => 'Password and username cannot be same.', 'groups' => array('Signup', 'Profile'), ))); // ... } }
  103. class RegistrationType extends AbstractType { public function setDefaultOptions(OptionsResolverInterface $resolver) {

    $resolver->setDefaults(array( // ... 'validation_groups' => array('Signup'), )); } } class EditAccountType extends AbstractType { public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( // ... 'validation_groups' => array('Profile'), )); } }
  104. Conclusion

  105. Ressources officielles §  http://symfony.com/doc/current/book/forms.html §  http://symfony.com/doc/current/reference/forms/types.html §  http://symfony.com/doc/current/book/validation.html §  http://symfony.com/doc/current/reference/constraints.html

    §  http://symfony.com/doc/current/reference/forms/twig_reference.html §  https://github.com/bschussek/standalone-forms Ressources utiles
  106. Merci ! Hugo Hamon hugo.hamon@sensiolabs.com @hhamon