Slide 1

Slide 1 text

Ingénierie inversée du composant Form Sarah Khalil Hugo Hamon https://www.flickr.com/photos/orlandolawyer/6120103619/sizes/o/in/photostream/

Slide 2

Slide 2 text

Vos humbles serviteurs Sarah Khalil Cultivatrice de projets @ Hugo Hamon Responsable des formations @ @hhamon @saro0h

Slide 3

Slide 3 text

De quoi allons-nous vous parler?

Slide 4

Slide 4 text

Au menu I. Les formulaires au quotidien II. Construction d’un formulaire III.Traitement du formulaire

Slide 5

Slide 5 text

Quelques chiffres

Slide 6

Slide 6 text

Quelques chiffres 45 568/311 349 LOC 52 Dossiers 292 Fichiers 53 Namespaces 30 Interfaces 272 Classes 38 Constantes 2 312 Méthodes

Slide 7

Slide 7 text

Sur Github • Récupérer le composant : • via Composer • https://github.com/symfony/form • +200 issues ouvertes

Slide 8

Slide 8 text

Les dépendances Event Dispatcher Options Resolver Property Access Intl HttpKernel HttpFoundation Validator Dependency Injection Twig Bridge Security (CSRF) Obligatoires Facultatives

Slide 9

Slide 9 text

Problématiques

Slide 10

Slide 10 text

On nous demande ceci

Slide 11

Slide 11 text

On le traduit comme ça

Slide 12

Slide 12 text

Objet métier (domain object pour les intimes) namespace Live\ArticleBundle\Entity; ! class Article { private $title; private $tags; private $publishedAt; ! public function __construct($title, array $tags, $publishedAt) { $this->title = $title; $this->tags = $tags; ! if (!$publishedAt instanceof \DateTime) { $publishedAt = new \DateTime($publishedAt); } ! $this->publishedAt = $publishedAt; } }

Slide 13

Slide 13 text

ArticleType (définition des éléments du formulaire) namespace Live\ArticleBundle\Form; ! use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; ! class ArticleType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('title') ->add('tags') ->add('publishedAt', 'datetime') ; } ! // ... }

Slide 14

Slide 14 text

Le contrôleur (utilisation du formulaire) namespace Live\ArticleBundle\Controller; ! // use ... class ArticleController extends Controller { /** @Template */ public function newAction(Request $request) { $article = new Article('The Symfony Book', [ 'php', 'http' ], '2014-04-08 12:00'); ! $form = $this->createForm(new ArticleType(), $article); $form->handleRequest($request); ! if ($form->isValid()) { // process the data } ! return [ 'form' => $form->createView() ]; } }

Slide 15

Slide 15 text

Du point de vue de l’humain

Slide 16

Slide 16 text

Du point de vue de Symfony article ArticleType title TextType tags TextType publishedAt DateTimeType date DateType time TimeType year ChoiceType month ChoiceType day ChoiceType hour ChoiceType minute ChoiceType

Slide 17

Slide 17 text

Ce qu’on pense communément du Composant Form

Slide 18

Slide 18 text

Construction d’un formulaire

Slide 19

Slide 19 text

La notion de type Un type est une structure unique qui définit et configure un élément du formulaire

Slide 20

Slide 20 text

$ php app/console container:debug | grep form.type Les types natifs sont enregistrés en tant que services avec le tag « form.type »

Slide 21

Slide 21 text

Héritage statique : Structure des types AbstractType TextType EmailType ChoiceType DateTimeType … FormTypeInterface

Slide 22

Slide 22 text

Héritage dynamique de type class DateType extends AbstractType { public function getParent() { return 'form'; } } ! class BirthdayType extends AbstractType { public function getParent() { return 'date'; } }

Slide 23

Slide 23 text

Comment ça fonctionne ? http://cdn-parismatch.ladmedia.fr

Slide 24

Slide 24 text

Revenons à notre contrôleur class ArticleController extends Controller { /** @Template */ public function newAction(Request $request) { $article = new Article('...'); ! $form = $this->createForm(new ArticleType(), $article); $form->handleRequest($request); ! // ... } }

Slide 25

Slide 25 text

Dans un premier temps $form = $this->createForm(new ArticleType(), $article); $form = $this->createForm(‘article’, $article); ou $this->container->get('form.factory')->create($type, $data, $options); ou Symfony\Component\Form\FormFactory

Slide 26

Slide 26 text

createResolvedType($type) new ResolvedFormType() Return the Form object createBuilder() buildForm($builder) return the FormBuilder object getForm() FormFactory ResolvedFormTypeFactory ResolvedFormType Form FormBuilder Vue d’ensemble

Slide 27

Slide 27 text

createResolvedType($type) new ResolvedFormType() Return the Form object createBuilder() buildForm($builder) return the FormBuilder object getForm() FormFactory ResolvedFormTypeFactory ResolvedFormType Form FormBuilder Vue d’ensemble

Slide 28

Slide 28 text

Résolution dynamique d’un type namespace Symfony\Component\Form; ! class ResolvedFormTypeFactory implements ResolvedFormTypeFactoryInterface { public function createResolvedType( FormTypeInterface $type, array $typeExtensions, ResolvedFormTypeInterface $parent = null ) { return new ResolvedFormType($type, $typeExtensions, $parent); } }

Slide 29

Slide 29 text

createResolvedType($type) new ResolvedFormType() Return the Form object createBuilder() buildForm($builder) return the FormBuilder object getForm() FormFactory ResolvedFormTypeFactory ResolvedFormType Form FormBuilder Vue d’ensemble

Slide 30

Slide 30 text

Composition d’un type résolu // birthday type $type = new ResolvedFormType( new BirthdayType(), [], new ResolvedFormType( new DateType(), [], new ResolvedFormType( new FormType(), [] ) ) );

Slide 31

Slide 31 text

Composition d’un type résolu ResolvedFormType BirthdayType DateType FormType ResolvedFormType ResolvedFormType

Slide 32

Slide 32 text

Collecter les infos de chaque type // text type $collector = new FormDataCollector(…); ! $type = new ResolvedTypeDataCollectorProxy( new ResolvedFormType( new TextType(), [], new ResolvedTypeDataCollectorProxy( new ResolvedFormType(new FormType(), []), $collector ), ), $collector );

Slide 33

Slide 33 text

Débogage des formulaires

Slide 34

Slide 34 text

createResolvedType($type) new ResolvedFormType() Return the Form object createBuilder() buildForm($builder) return the FormBuilder object getForm() FormFactory ResolvedFormTypeFactory ResolvedFormType Form FormBuilder Vue d’ensemble

Slide 35

Slide 35 text

Création du FormBuilder ! • Construit et configure l’instance de formulaire • Résout toutes les options avec l’OptionsResolver • Enregistre les écouteurs et les convertisseurs de données • 1 ResolvedFormType 1 FormBuilder 1 EventDispatcher

Slide 36

Slide 36 text

createResolvedType($type) new ResolvedFormType() Return the Form object createBuilder() buildForm($builder) return the FormBuilder object getForm() FormFactory ResolvedFormTypeFactory ResolvedFormType Form FormBuilder Vue d’ensemble

Slide 37

Slide 37 text

Préparation du FormBuilder namespace Live\ArticleBundle\Form; ! use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; ! class ArticleType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('title') ->add('tags') ->add('publishedAt', 'datetime') ; } ! // ... }

Slide 38

Slide 38 text

createResolvedType($type) new ResolvedFormType() Return the Form object createBuilder() buildForm($builder) return the FormBuilder object getForm() FormFactory ResolvedFormTypeFactory ResolvedFormType Form FormBuilder Vue d’ensemble

Slide 39

Slide 39 text

Dans la méthode FormBuilder::getForm() // Création du formulaire $form = new Form($formConfig); foreach ($this->getChildren() as $child) { $form->add($child); } ! // Initialisation des valeurs par défaut $form->setData($article);

Slide 40

Slide 40 text

Création du formulaire // Création du formulaire $form = new Form($formConfig); foreach ($this->getChildren() as $child) { $form->add($child); } ! // Initialisation des valeurs par défaut $form->setData($article);

Slide 41

Slide 41 text

Et voilà ! Le formulaire article Form title Form tags Form publishedAt Form date Form time Form year Form month Form day Form hour Form minute Form

Slide 42

Slide 42 text

Initialisation des valeurs par défaut $form->setData($article);

Slide 43

Slide 43 text

article new Article(…) title ‘The Symfony Book’ tags ‘php, http’ publishedAt DateTime(’2014-04-08 12:00’) date [ ‘year’ => 2014, … ] time [ ‘hour’ => 12, … ] year ‘2014’ month ’04' day ’08' hour ’12' minute ’00' setData() setData() setData() setData() setData() setData() setData() setData() setData() setData()

Slide 44

Slide 44 text

Composition d’un formulaire Form EventDispatcher FormConfigBuilder RequestHandler DataMapper FormFactory ModelTransformer[] ViewTransformer[] Options

Slide 45

Slide 45 text

Récapitulons FormFactory ResolvedFormType FormBuilder Form Type résolu contenant : ✓ les types de formulaire assemblés ✓ options résolues. Fabrique des instances de formulaires à partir de types de formulaires Configure un seul formulaire Instance du formulaire chargée de traiter les données qui lui sont fournies résout crée construit

Slide 46

Slide 46 text

Normalisation des données https://www.flickr.com/photos/infofestival

Slide 47

Slide 47 text

3 représentations des données Les données appartenant au modèle de données Les données normalisées permettent le passage d’une représentation à une autre. Les données utilisables pour l’affichage des informations dans le navigateur. Model Data NormData View Data

Slide 48

Slide 48 text

2 règles d’Or Ne pas changer le contenu de la donnée mais seulement sa représentation Les données normalisées doivent contenir le maximum d’informations.

Slide 49

Slide 49 text

Changer la représentation '2014-04-08' => '2015-09-10' '2014-04-08' => DateTime('2014-04-08')

Slide 50

Slide 50 text

Définition des valeurs par défaut du formulaire Model Data NormData View Data Normalisation / Transformation POST_SET_DATA PRE_SET_DATA FormEvent DataTransformers

Slide 51

Slide 51 text

Normaliser les tags de l’article ['php', 'http'] ['php', 'http'] 'php, http' Normalisation / Transformation DataTransformers

Slide 52

Slide 52 text

Les convertisseurs de données Les convertisseurs de données aka « DataTransformer » modifient la représentation des données.

Slide 53

Slide 53 text

Créer un convertisseur de données namespace Live\ArticleBundle\Form\DataTransformer; ! use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\Exception\TransformationFailedException; ! class ArrayToDelimitedStringTransformer implements DataTransformerInterface { public function transform($value) { if (!is_array($value)) { throw new TransformationFailedException(); } ! if (0 === count($value)) { return ''; } ! return implode(', ', $value); } }

Slide 54

Slide 54 text

Enregistrer un convertisseur de données $builder->addViewTransformer( new ArrayToDelimitedStringTransformer() ); ! $builder->addModelTransformer( new My\Custom\ModelDataTransformer() );

Slide 55

Slide 55 text

Quelques convertisseurs natifs DataTransformer !"" ArrayToPartsTransformer.php !"" BaseDateTimeTransformer.php !"" BooleanToStringTransformer.php !"" ChoiceToBooleanArrayTransformer.php !"" ChoiceToValueTransformer.php !"" ChoicesToBooleanArrayTransformer.php !"" ChoicesToValuesTransformer.php !"" DataTransformerChain.php !"" DateTimeToArrayTransformer.php !"" DateTimeToLocalizedStringTransformer.php !"" DateTimeToRfc3339Transformer.php !"" DateTimeToStringTransformer.php !"" DateTimeToTimestampTransformer.php !"" IntegerToLocalizedStringTransformer.php !"" MoneyToLocalizedStringTransformer.php !"" NumberToLocalizedStringTransformer.php !"" PercentToLocalizedStringTransformer.php #"" ValueToDuplicatesTransformer.php

Slide 56

Slide 56 text

Soumission des données https://www.flickr.com/photos/safaguezguez/4782575498/sizes/l/in/photostream/

Slide 57

Slide 57 text

Vous vous souvenez ? class ArticleController extends Controller { /** @Template */ public function newAction(Request $request) { $article = new Article('...'); ! $form = $this->createForm(new ArticleType(), $article); $form->handleRequest($request); ! // ... } }

Slide 58

Slide 58 text

C’est reparti ! class ArticleController extends Controller { /** @Template */ public function newAction(Request $request) { $article = new Article('...'); ! $form = $this->createForm(new ArticleType(), $article); $form->handleRequest($request); ! // ... } }

Slide 59

Slide 59 text

Soumission du formulaire $form->submit($request->request->get(‘article’)); ou $form->handleRequest($request);

Slide 60

Slide 60 text

HttpFoundationRequestHandler • Vérifie que le formulaire est soumis avec le bon type de requête • Récupère les données depuis la requête • $request->query | $request->request | $request->files • Appelle la méthode Form::submit()

Slide 61

Slide 61 text

Form::submit() est appelé sur tous les objets Form fils Réintégration des données au modèle MD ND VD Dénormalisation PRE_SUBMIT FormEvent DataTransformers SUBMIT ND POST_SUBMIT VD Dénormalisation

Slide 62

Slide 62 text

Dénormaliser une chaîne de tags class ArrayToDelimitedStringTransformer implements DataTransformerInterface { public function reverseTransform($value) { if (!is_string($value)) { throw new TransformationFailedException(); } ! if (empty($value)) { return array(); } ! return explode(', ', $value); } }

Slide 63

Slide 63 text

Les événements Les événements du formulaire permettent de modifier le contenu de la donnée ou bien de modifier la structure du formulaire.

Slide 64

Slide 64 text

Utilisation des événements Constante Nom de l’événement Objet de l’événement PRE_SET_DATA form.pre_set_data FormEvent($form, $modelData) POST_SET_DATA form.post_set_data FormEvent($form, $modelData) PRE_SUBMIT form.pre_bind FormEvent($form, $submittedData) SUBMIT form.bind FormEvent($form, $normData) POST_SUBMIT form.post_bind FormEvent($form, $viewData)

Slide 65

Slide 65 text

Quels usages ? ! • Ajouter / Supprimer des champs dans une collection • Ajouter des champs en fonction de l’utilisateur • Modifier / Filter le contenu donné ! • Exemple : changer la casse d’une chaîne de caractères • Exemple : alimenter des listes déroulantes liées

Slide 66

Slide 66 text

Mettre le titre de l’article en « title case » // ArticleType.php use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; ! // Dans la méthode ArticleType::buildForm() $titleBuilder = $builder->create('title'); $titleBuilder->addEventListener( FormEvents::PRE_SUBMIT, function (FormEvent $event) { $title = ucwords($event->getData()); $event->setData($title); } ); ! $builder->add($titleBuilder);

Slide 67

Slide 67 text

Enregistrer un « event subscriber » $builder->add(! $builder! ->create('title')! ->addEventSubscriber(! ! ! ! ! ! ! new StringUtilEventSubscriber()! ! ! ! ! ! )! );

Slide 68

Slide 68 text

A vous de jouer !

Slide 69

Slide 69 text

job@sensiolabs.com https://www.flickr.com/photos/bigbrotherbackpacking

Slide 70

Slide 70 text

Fin hugo.hamon@sensiolabs.com // sarah.khalil@sensiolabs.com // @poledev https://www.flickr.com/photos/nesto