Table des matières
Traiter avec des formulaires HTML est l'un des plus commun - et difficile - des tâches pour un développeur web. Symfony2 intègre une composante formulaire qui fait face à des formes simples. Dans ce chapitre, vous allez construire une forme complexe à partir du sol en place, l'apprentissage des caractéristiques les plus importantes de la bibliothèque de formulaires en cours de route.
Le composant de formulaire de symfony est une bibliothèque autonome qui peut être utilisé en dehors de Symfony2 projets. Pour plus d'informations, voir le composant de formulaire Symfony2 sur Github.
Supposons que vous construisez une application simple liste todo qui devront afficher «Tâches». Parce que vos utilisateurs auront besoin d'éditer et de créer des tâches, vous allez avoir besoin de construire un formulaire. Mais avant de commencer, tout d'abord se concentrer sur la classe Task générique qui représente et stocke les données pour une seule tâche:
mkdir src/Acme/TaskBundle/Entity
// src/Acme/TaskBundle/Entity/Task.php namespace Acme\TaskBundle\Entity; class Task { protected $task; protected $dueDate; public function getTask() { return $this->task; } public function setTask($task) { $this->task = $task; } public function getDueDate() { return $this->dueDate; } public function setDueDate(\DateTime $dueDate = null) { $this->dueDate = $dueDate; } }
Si vous codez avec cet exemple, créez la première fois par AcmeTaskBundle en exécutant la commande suivante (et en acceptant toutes les options par défaut):
php app/console generate:bundle --namespace=Acme/TaskBundle
Cette classe est une "plain-old-PHP-object» parce que, jusqu'à présent, il n'a rien à voir avec Symfony ou toute autre bibliothèque. C'est tout simplement un objet PHP normal qui résout un problème directement à l'intérieur de votre application (à savoir la nécessité de représenter une tâche dans votre application). Bien sûr, d'ici la fin de ce chapitre, vous serez en mesure de soumettre des données à une instance de travail (via un formulaire HTML), de valider ses données, et il persiste à la base de données.
Maintenant que vous avez créé une classe spéciale, la prochaine étape est de créer et de rendre le formulaire HTML réel. En Symfony2, cela se fait par la construction d'un objet de formulaire, puis le rendant dans un modèle. Pour l'instant, tout cela peut être fait à l'intérieur d'un contrôleur:
// src/Acme/TaskBundle/Controller/DefaultController.php namespace Acme\TaskBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Acme\TaskBundle\Entity\Task; use Symfony\Component\HttpFoundation\Request; class DefaultController extends Controller { public function newAction(Request $request) { // create a task and give it some dummy data for this example $task = new Task(); $task->setTask('Write a blog post'); $task->setDueDate(new \DateTime('tomorrow')); $form = $this->createFormBuilder($task) ->add('task', 'text') ->add('dueDate', 'date') ->getForm(); return $this->render('AcmeTaskBundle:Default:new.html.twig', array( 'form' = > $form->createView(), )); } }
Cet exemple vous montre comment construire votre formulaire directement dans le contrôleur. Plus tard, dans la "Création de classes de formulaire", vous allez apprendre à construire votre formulaire dans une classe autonome, ce qui est recommandé pour que votre formulaire devienne réutilisable.
La création d'un formulaire requiert relativement peu de code parce Symfony2 objets de formulaire sont construits avec un "générateur de formulaire». Le but du constructeur de formulaire est de vous permettre d'écrire de simples formulaires "recettes", et de laissez faire faire tout le gros du travail de fait la construction de la form.
Dans cet exemple, vous avez ajouté deux champs à votre formulaire de tâches de front et DueDate - correspondant à la tâche et les propriétés DueDate de la classe Task. Vous avez également attribué à chaque. Un "type" (texte, par exemple, la date), qui, entre autres choses, qui détermine la balise form HTML (s) est rendu pour ce champ
Symfony2 est livré avec de nombreux types intégrés qui seront discutés prochainement (voir intégré dans les types de champs ).
Maintenant que le formulaire a été créé, l'étape suivante consiste à la rendre. Cela se fait en passant une forme spéciale "vue" objet à votre modèle (notez le $form->createView() dans le contrôleur ci-dessus) et en utilisant un ensemble de fonctions d'assistance du formulaire:
{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #} <form action="{{ path('task_new') }}" method="post" {{ form_enctype(form) }}> {{ form_widget(form) }} <input type="submit" /> </form>
<!-- src/Acme/TaskBundle/Resources/views/Default/new.html.php --> <form action="<?php echo $view['router']->generate('task_new') ?>" method="post" <?php echo $view['form']->enctype($form) ?> > <?php echo $view['form']->widget($form) ?> <input type="submit" /> </form>
. task_new: pattern: /task/new defaults: { _controller: AcmeTaskBundle:Default:new } .
url : http://guidtest/task/new
Ça y est! Par form_widget impression (formulaire), chaque champ du formulaire est rendu, accompagné d'un message d'erreur et de l'étiquette (s'il en existe un). Aussi facile que cela, c'est que ce n'est pas très souple (encore). Habituellement, vous aurez envie de rendre chaque champ de formulaire individuel de sorte que vous pouvez contrôler la façon dont la forme ressemble. Vous allez apprendre à le faire dans le " Rendu d'un formulaire dans un modèle
Avant de poursuivre, remarquez comment le champ de tâche rendue d'entrée a la valeur de la propriété de tâche de l'objet $ tâche (c.-à-«Écrire un article de blog"). C'est le premier emploi d'une forme: pour prendre des données à partir d'un objet et de le traduire en un format qui est apte à être rendu dans un formulaire HTML.
Le système de formulaire est assez intelligent pour accéder à la valeur de la propriété protégée par la tâche getTask() et setTask() sur la classe Task. Sauf si une propriété est publique, elle doit avoir un "getter" et "setter" méthode pour que le composant de formulaire peut obtenir et mettre les données sur la propriété. Pour une propriété booléenne, vous pouvez utiliser un "isser" méthode (p. ex isPublished ()) au lieu d'un getter (par exemple getPublished()).
Le deuxième emploi d'un formulaire est de traduire les données soumises par les utilisateurs de retour aux propriétés d'un objet. Pour ce faire, les données présentées à partir de l'utilisateur doit être lié à la forme. Ajouter les fonctionnalités suivantes à votre contrôleur:
// ... public function newAction(Request $request) { // just setup a fresh $task object (remove the dummy data) $task = new Task(); $form = $this->createFormBuilder($task) ->add('task', 'text') ->add('dueDate', 'date') ->getForm(); if ($request->getMethod() == 'POST') { $form->bindRequest($request); if ($form->isValid()) { // perform some action, such as saving the task to the database return $this->redirect($this->generateUrl('task_success')); } } // ... }
Maintenant, lors de la soumission du formulaire, le contrôleur se lie les données soumises à la form, ce qui traduit ces données vers l'objet Task et les propriétés de l'objet. Tout cela se passe via la méthode BindRequest().
Dès que BindRequest() est appelée, les données présentes sont transférées à l'objet sous-jacent immédiatement. Cela se produit indépendamment si oui ou non les données sous-jacente sont en fait valable.
Ce contrôleur suit un schéma commun de gestion des formulaires, et a trois chemins possibles:
La redirection d'un utilisateur après soumission d'un formulaire succès empêche l'utilisateur d'être en mesure de frapper "refresh" et re-publier les données.
Dans la section précédente, vous avez appris comment une form peut être soumis avec des données valides ou non valides. En Symfony2, la validation est appliquée à l'objet sous-jacent (de travail, par exemple). En d'autres termes, la question n'est pas de savoir si la «form» est valable, mais si oui ou non l'objet $task est valide après que le formulaire a appliqué les données qui lui sont soumis. L'appel $form->isValid() est un raccourci qui demande à l'objet $ tâche si elle a ou non des données valides.
La validation se fait par l'ajout d'un ensemble de règles (appelées contraintes) à une classe. Pour le voir en action, ajouter des contraintes de validation de sorte que le champ de tâche ne peut pas être vide et le champ dueDate ne peut pas être vide et doit être un objet DateTime valide.
# Acme/TaskBundle/Resources/config/validation.yml Acme\TaskBundle\Entity\Task: properties: task: - NotBlank: ~ dueDate: - NotBlank: ~ - Type: \DateTime
// Acme/TaskBundle/Entity/Task.php use Symfony\Component\Validator\Constraints as Assert; class Task { /** * @Assert\NotBlank() */ public $task; /** * @Assert\NotBlank() * @Assert\Type("\DateTime") */ protected $dueDate; }
<!-- Acme/TaskBundle/Resources/config/validation.xml --> <class name="Acme\TaskBundle\Entity\Task"> <property name="task"> <constraint name="NotBlank" /> </property> <property name="dueDate"> <constraint name="NotBlank" /> <constraint name="Type"> <value>\DateTime</value> </constraint> </property> </class>
// Acme/TaskBundle/Entity/Task.php use Symfony\Component\Validator\Mapping\ClassMetadata; use Symfony\Component\Validator\Constraints\NotBlank; use Symfony\Component\Validator\Constraints\Type; class Task { // ... public static function loadValidatorMetadata(ClassMetadata $metadata) { $metadata->addPropertyConstraint('task', new NotBlank()); $metadata->addPropertyConstraint('dueDate', new NotBlank()); $metadata->addPropertyConstraint('dueDate', new Type('\DateTime')); } }
Ça y est! Si vous re-soumettez le formulaire avec des données non valides, vous verrez les erreurs correspondantes imprimées avec le formulaire.
Comme de HTML5, de nombreux navigateurs peuvent imposer des contraintes de validation en mode natif certains sur le côté client. La validation la plus courante est activé par le rendu d'un attribut obligatoire sur les champs qui sont nécessaires. Pour les navigateurs qui soutiennent HTML5, cela se traduira par un message affiché navigateur natif si l'utilisateur tente d'envoyer le formulaire avec ce champ vide.
Les formulaires générés profitent pleinement de cette nouvelle fonctionnalité en ajoutant des attributs HTML sensibles qui déclenchent la validation. La validation côté client, cependant, peut être désactivée en ajoutant l'attribut novalidate à la balise form ou formnovalidate à la balise submit. Ceci est particulièrement utile lorsque vous voulez tester vos contraintes de validation côté serveur, mais en sont empêchés par votre navigateur à partir de, par exemple, en soumettant des champs vides.
La validation est une fonctionnalité très puissante de Symfony2 et dispose de son propre chapitre dédié Validation dans Symfony2.
Si vous n'utilisez pas les groupes de validation dans Symfony2, alors vous pouvez sauter cette section.
Si votre objet tire parti des groupes de validation dans Symfony2, vous aurez besoin de spécifier quel groupe de validation(s) de votre formulaire sera utiliser:
$form = $this->createFormBuilder($users, array( 'validation_groups' = > array('registration'), ))->add(...)
Si vous créez des classes de formulaires (une bonne pratique), alors vous aurez besoin d'ajouter ce qui suit pour les méthodes getDefaultOptions()
public function getDefaultOptions(array $options) { return array( 'validation_groups' = > array('registration') ); }
Dans ces deux cas, seul le groupe de validation d'inscription seront utilisées pour valider l'objet sous-jacent.
Symfony est livré en standard avec un grand nombre de types de champs qui couvrent tous les champs du formulaire et de types de données que vous rencontrerez:
Vous pouvez également créer vos propres types de champs personnalisés. Ce sujet est couvert dans Comment faire pour créer un champ personnalisé de type formulaire
Chaque type de champ a un certain nombre d'options qui peuvent être utilisés pour le configurer. Par exemple, le domaine dueDate est en cours de rendu que 3 boîtes de sélection. Toutefois, le champ de date peut être configuré pour être rendu comme une zone de texte simple (où l'utilisateur serait entrer la date comme une chaîne dans la boîte):
->add('dueDate', 'date', array('widget' = > 'single_text'))
Chaque type de champ a un certain nombre de différentes options qui peuvent être passés. Beaucoup d'entre eux sont spécifiques au type de champ et les détails peuvent être trouvés dans la documentation pour chaque type.
L'option la plus commune est l'option souhaitée, qui peut être appliquée à n'importe quel domaine. Par défaut, l'option requise est définie sur true, ce qui signifie que HTML5 prêts navigateurs s'appliquera la validation côté client si le champ est laissé vide. Si vous ne voulez pas ce comportement, soit définir l'option requise sur votre champs à false ou désactiver la validation HTML5 .
A noter également que la mise en l'option voulue pour vrai n'entraînera pas la validation côté serveur doit être appliquée. En d'autres termes, si un utilisateur soumet une valeur vide pour le champ (soit avec un ancien navigateur ou d'un service Web, par exemple), il sera accepté comme une valeur valide, sauf si vous utilisez NotBlank Symfony ou la contrainte de validation NotNull.
En d'autres termes, l'option requise est "nice", mais c'est vrai la validation côté serveur doit toujours être utilisé.
L'étiquette pour le champ de formulaire peut être réglé en utilisant l'option étiquette, qui peut être appliquée à n'importe quel domaine:
->add('dueDate', 'date', array( 'widget' = > 'single_text', 'label' = > 'Due Date', ))
L'étiquette d'un champ peut également être définie dans le modèle de rendu sous la forme, voir ci-dessous.
Maintenant que vous avez ajouté des métadonnées de validation pour la classe Task, Symfony sait déjà un peu de vos champs. Si vous le permettez, Symfony peut "deviner" le type de votre domaine et de le configurer pour vous. Dans cet exemple, Symfony peut le deviner à partir des règles de validation qui à la fois le champ de tâche est un champ de texte normal et le domaine dueDate est un champ de date:
public function newAction() { $task = new Task(); $form = $this->createFormBuilder($task) ->add('task') ->add('dueDate', null, array('widget' = > 'single_text')) ->getForm(); }
Le "guessing" est activé lorsque vous omettez le deuxième argument de la méthode add() (ou si vous transmettez la valeur null à elle). Si vous passez un tableau d'options que le troisième argument (fait pour dueDate ci-dessus), ces options sont appliquées sur le champs deviné.
Si votre formulaire utilise un groupe de validation spécifique, le guesser type de champ sera toujours considérer toutes les contraintes de validation lorsque deviner vos types de champs (y compris des contraintes qui ne font pas partie du groupe de validation (s) utilisé).
En plus de deviner le «type» pour un champ, Symfony peut aussi essayer de deviner les valeurs correctes d'un certain nombre d'options sur le champs.
Lorsque ces options sont définies, le champ sera rendu avec des attributs HTML spéciaux qui prévoient HTML5 validation côté client. Cependant, il ne génère pas les équivalents côté serveur contraintes (p. ex Assert MaxLength \). Et si vous devez ajouter manuellement votre validation côté serveur, ces options de type de champ peut alors être deviné à partir de cette information.
Ces options sont sur le champs seulement deviné si vous utilisez Symfony de deviner le type de champ (c.-à-omettez ou transmettez null comme deuxième argument à ajouter ()).
Si vous souhaitez modifier l'une des valeurs devinées, vous pouvez l'ignorer en passant l'option dans le tableau champ d'options:
->add('task', null, array('min_length' = > 4))
Jusqu'ici, vous avez vu comment toute une forme peut être rendu avec une seule ligne de code. Bien sûr, vous aurez généralement besoin de beaucoup plus de flexibilité lors du rendu:
{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #} <form action="{{ path('task_new') }}" method="post" {{ form_enctype(form) }}> {{ form_errors(form) }} {{ form_row(form.task) }} {{ form_row(form.dueDate) }} {{ form_rest(form) }} <input type="submit" /> </form>
<!-- // src/Acme/TaskBundle/Resources/views/Default/newAction.html.php --> <form action="<?php echo $view['router']->generate('task_new') ?>" method="post" <?php echo $view['form']->enctype($form) ?>> <?php echo $view['form']->errors($form) ?> <?php echo $view['form']->row($form['task']) ?> <?php echo $view['form']->row($form['dueDate']) ?> <?php echo $view['form']->rest($form) ?> <input type="submit" /> </form>
Jetons un coup d'oeil à chaque partie:
La majorité du travail est fait par l'aide form_row, ce qui rend l'étiquette, les erreurs et widget de formulaire HTML de chaque champ à l'intérieur d'une balise div par défaut. Dans le formulaire Thématisation section, vous apprendrez comment la sortie form_row peuvent être personnalisés sur de nombreux niveaux différents.
Vous pouvez accéder aux données actuelles de votre formulaire via form.vars.value:
{{ form.vars.value.task }}
<?php echo $view['form']->get('value')->getTask() ?>
L'aide form_row est génial car on peut très rapidement rendre chaque champ de votre formulaire (et le balisage utilisé pour la "ligne" peut être personnalisé ainsi). Mais puisque la vie n'est pas toujours si simple, vous pouvez également rendre chaque champ entièrement à la main. Le produit final de ce qui suit est le même que lorsque vous avez utilisé l'assistant form_row:
{{ form_errors(form) }} <div> {{ form_label(form.task) }} {{ form_errors(form.task) }} {{ form_widget(form.task) }} </div> <div> {{ form_label(form.dueDate) }} {{ form_errors(form.dueDate) }} {{ form_widget(form.dueDate) }} </div> {{ form_rest(form) }}
<?php echo $view['form']->errors($form) ?> <div> <?php echo $view['form']->label($form['task']) ?> <?php echo $view['form']->errors($form['task']) ?> <?php echo $view['form']->widget($form['task']) ?> </div> <div> <?php echo $view['form']->label($form['dueDate']) ?> <?php echo $view['form']->errors($form['dueDate']) ?> <?php echo $view['form']->widget($form['dueDate']) ?> </div> <?php echo $view['form']->rest($form) ?>
Si l'étiquette auto-généré pour un champ n'est pas tout à fait juste, vous pouvez spécifier explicitement:
{{ form_label(form.task, 'Task Description') }}
<?php echo $view['form']->label($form['task'], 'Task Description') ?>
Certains types de champs ont des options de rendu supplémentaires qui peuvent être transmis au widget. Ces options sont documentées à chaque type, mais une des options communes est attr, qui vous permet de modifier les attributs sur l'élément de formulaire. Ce qui suit ajoutera la classe task_field au champ de saisie de texte rendu:
{{ form_widget(form.task, { 'attr': {'class': 'task_field'} }) }}
<?php echo $view['form']->widget($form['task'], array( 'attr' = > array('class' = > 'task_field'), )) ?>
Si vous avez besoin pour rendre les champs de formulaire "à la main", alors vous pouvez accéder aux valeurs individuelles pour des domaines tels que l'identifiant, le nom et l'étiquette. Par exemple, pour obtenir l'id:
{{ form.task.vars.id }}
<?php echo $form['task']->get('id') ?>
Pour obtenir la valeur utilisée pour le nom du champ de formulaire que vous devez attribuer à utiliser la valeur full_name:
{{ form.task.vars.full_name }}
<?php echo $form['task']->get('full_name') ?>
Si vous utilisez Twig, une référence complète des fonctions de rendu de formulaire est disponible dans le manuel de référence . Lisez ceci pour tout savoir sur les aides disponibles et les options qui peuvent être utilisés les uns avec les autres.
Comme vous l'avez vu, un formulaire peut être créé et utilisé directement dans un contrôleur. Toutefois, une meilleure pratique consiste à créer le formulaire dans un document distinct, PHP classe autonome, qui peut ensuite être réutilisé n'importe où dans votre application. Créer une nouvelle classe qui abritera la logique de la construction du formulaire de tâche:
// src/Acme/TaskBundle/Form/Type/TaskType.php namespace Acme\TaskBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilder; class TaskType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder->add('task'); $builder->add('dueDate', null, array('widget' = > 'single_text')); } public function getName() { return 'task'; } }
Cette nouvelle classe contient toutes les directions nécessaires pour créer le formulaire de tâche (à noter que la méthode getName() doit retourner un identifiant unique pour cette forme "type"). Il peut être utilisé pour construire rapidement un objet de formulaire dans le contrôleur:
// src/Acme/TaskBundle/Controller/DefaultController.php // add this new use statement at the top of the class use Acme\TaskBundle\Form\Type\TaskType; public function newAction() { $task = // ... $form = $this->createForm(new TaskType(), $task); // ... }
Placer la logique sous forme dans sa propre classe signifie que la forme peut être facilement réutilisé ailleurs dans votre projet. C'est la meilleure façon de créer des formulaires, mais le choix est finalement à vous.
Chaque formulaire doit connaître le nom de la classe qui détient les données sous-jacentes (par exemple Acme\TaskBundle\Entity\Task). Habituellement, il s'agit juste deviné basé sur de l'objet passé au deuxième argument de CreateForm (c.-à-$ task). Plus tard, lorsque vous commencez intégrant les formes, ce ne sera plus suffisante. Ainsi, alors que pas toujours nécessaire, c'est généralement une bonne idée de spécifier explicitement l'option data_class en ajoutant la ligne suivante à votre classe de type formulaire:
public function getDefaultOptions(array $options) { return array( 'data_class' = > 'Acme\TaskBundle\Entity\Task', ); }
Lors du mappage des formes aux objets, tous les champs sont mappés. Tous les champs du formulaire qui n'existent pas sur l'objet mappé va provoquer une levée de l'exception.
Dans le cas où vous avez besoin des champs supplémentaires dans le formulaire (par exemple: «Etes vous d'accord avec ces termes« case à cocher) qui ne seront pas mis en correspondance avec l'objet sous-jacent, vous devez définir l'option property_path à false:
public function buildForm(FormBuilder $builder, array $options) { $builder->add('task'); $builder->add('dueDate', null, array('property_path' = > false)); }
En outre, s'il y a des champs du formulaire qui ne sont pas inclus dans les données soumises, ces champs seront explicitement la valeur null.
L'objectif d'un formulaire est à traduire les données d'un objet (par exemple de travail) à un formulaire HTML et ensuite traduire les données soumises par les utilisateurs de retour à l'objet original. En tant que tel, le sujet de la persistance de l'objet de travail à la base de données est totalement étranger à la question des formes. Mais, si vous avez configuré la classe Task être conservées par l'intermédiaire Doctrine (c.-à-vous avez ajouté des métadonnées de cartographie pour lui), il persiste après soumission d'un formulaire qui peut être fait lorsque le formulaire est valable:
Si, pour une raison quelconque, vous n'avez pas accès à votre objet $task d'origine, vous pouvez le chercher dans la forme:
$task = $form->getData();
Pour plus d'informations, voir le chapitre Bases de données et Doctrine ("Le modèle") .
L'essentiel à comprendre, c'est que lorsque le formulaire est lié, les données présentées est transférée à l'objet sous-jacent immédiatement. Si vous souhaitez conserver ces données, il vous suffit de conserver l'objet lui-même (qui contient déjà les données présentées).
Souvent, vous aurez envie de construire une forme qui comprendra les champs de nombreux objets différents. Par exemple, un formulaire d'inscription peut contenir des données appartenant à un objet utilisateur ainsi que des objets d'adresses de nombreux. Heureusement, c'est facile et naturelle avec le composant forme.
Supposons que chaque tâche appartient à un objet simple catégorie. Cliquez sur Démarrer, bien sûr, en créant l'objet Catégorie:
// src/Acme/TaskBundle/Entity/Category.php namespace Acme\TaskBundle\Entity; use Symfony\Component\Validator\Constraints as Assert; class Category { /** * @Assert\NotBlank() */ public $name; }
Ensuite, ajoutez une propriété nouvelle catégorie à la classe Task:
// ... class Task { // ... /** * @Assert\Type(type="Acme\TaskBundle\Entity\Category") */ protected $category; // ... public function getCategory() { return $this->category; } public function setCategory(Category $category = null) { $this->category = $category; } }
Maintenant que votre demande a été mise à jour pour refléter les nouvelles exigences, de créer une classe de formulaire de telle sorte que un objet Catégorie peuvent être modifiées par l'utilisateur:
// src/Acme/TaskBundle/Form/Type/CategoryType.php namespace Acme\TaskBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilder; class CategoryType extends AbstractType { public function buildForm(FormBuilder $builder, array $options) { $builder->add('name'); } public function getDefaultOptions(array $options) { return array( 'data_class' = > 'Acme\TaskBundle\Entity\Category', ); } public function getName() { return 'category'; } }
L'objectif final est de permettre à la catégorie d'une tâche à modifier le droit à l'intérieur du formulaire de tâche elle-même. Pour ce faire, ajoutez un champ de catégorie de l'objet TaskType dont le type est une instance de la classe CategoryType nouvelle:
public function buildForm(FormBuilder $builder, array $options) { // ... $builder->add('category', new CategoryType()); }
Les champs de CategoryType peut maintenant être rendu aux côtés de ceux de la classe TaskType. Rendre les champs Catégorie de la même manière que les champs de travail originaux:
{# ... #} <h3>Category</h3> <div class="category"> {{ form_row(form.category.name) }} </div> {{ form_rest(form) }} {# ... #}
<!-- ... --> <h3>Category</h3> <div class="category"> <?php echo $view['form']->row($form['category']['name']) ?> </div> <?php echo $view['form']->rest($form) ?> <!-- ... -->
Lorsque l'utilisateur soumet le formulaire, les données présentées pour les champs de catégorie sont utilisés pour construire une instance de la catégorie, qui est ensuite mis sur le champ de catégorie de l'instance de travail.
L'instance est accessible Catégorie naturellement via $ task-> getCategory () et peut être conservé à la base de données ou utiliser de la façon dont vous avez besoin.
Vous pouvez également intégrer une collection de formes en une seule forme (imaginer une forme Catégorie avec le produit de nombreux sous-formes). Ceci est fait en utilisant le type de champ de captage.
Pour plus d'informations consultez la section Comment faire pour intégrer une collection de formes et l'entrée de la collection de référence type de champ.
Chaque partie de la façon dont une forme est rendue peuvent être personnalisés. Vous êtes libre de changer la façon dont chaque forme «rangée» rend, modifie le balisage utilisé pour rendre les erreurs, ou même de personnaliser la façon dont une balise textarea devrait être rendu. Rien n'est hors-limites, et les personnalisations différentes peuvent être utilisées dans des endroits différents.
Symfony utilise des modèles pour rendre chaque et toute partie d'un formulaire, telles que les balises d'étiquettes, étiquettes d'entrée, les messages d'erreur et tout le reste.
En Twig, chaque forme «fragment» est représenté par un bloc de Rameau. Pour personnaliser une partie de la façon dont une forme rend, il vous suffit de remplacer le bloc approprié.
En PHP, chaque forme «fragment» est rendu par l'intermédiaire d'un fichier de modèle particulier. Pour personnaliser une partie de la façon dont une forme rend, il vous suffit de remplacer le modèle existant en créant un nouveau.
Pour comprendre comment cela fonctionne, nous allons personnaliser le fragment form_row et ajouter un attribut de classe à l'élément div qui entoure chaque rangée. Pour ce faire, créez un nouveau fichier de modèle qui va stocker le nouveau balisage:
{# src/Acme/TaskBundle/Resources/views/Form/fields.html.twig #} {% block field_row %} {% spaceless %} <div class="form_row"> {{ form_label(form) }} {{ form_errors(form) }} {{ form_widget(form) }} </div> {% endspaceless %} {% endblock field_row %}
<!-- src/Acme/TaskBundle/Resources/views/Form/field_row.html.php --> <div class="form_row"> <?php echo $view['form']->label($form, $label) ?> <?php echo $view['form']->errors($form) ?> <?php echo $view['form']->widget($form, $parameters) ?> </div>
Le fragment forme field_row est utilisé lors du rendu de la plupart des champs via la fonction form_row. Pour dire la composante de formulaire à utiliser votre fragment field_row nouvelle défini ci-dessus, ajoutez la ligne suivante vers le haut de la matrice qui rend la forme:
{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
{% form_theme form 'AcmeTaskBundle:Form:fields.html.twig' %}
{% form_theme form 'AcmeTaskBundle:Form:fields.html.twig' 'AcmeTaskBundle:Form:fields2.html.twig' %}
<form ...>
<!-- src/Acme/TaskBundle/Resources/views/Default/new.html.php --> <?php $view['form']->setTheme($form, array('AcmeTaskBundle:Form')) ?> <?php $view['form']->setTheme($form, array('AcmeTaskBundle:Form', 'AcmeTaskBundle:Form')) ?> <form ...>
La balise form_theme (en Twig) "importations" des fragments définis dans le modèle de donnée et les utilise lors du rendu du formulaire. En d'autres termes, lorsque la fonction est appelée form_row plus tard dans ce modèle, il va utiliser le bloc field_row de votre thème personnalisé (au lieu du bloc field_row par défaut livré avec Symfony).
Votre thème personnalisé n'a pas besoin de remplacer tous les blocs. Lors du rendu d'un bloc qui n'est pas substituée dans votre thème personnalisé, le moteur de thèmes retombera sur le thème global (défini au niveau paquet).
Si plusieurs thèmes personnalisés sont fournis, ils seront recherchés dans l'ordre indiqué, avant de retomber sur le thème global.
Pour personnaliser une partie d'un formulaire, il vous suffit de remplacer le fragment approprié. Savoir exactement qui bloquent ou un fichier à remplacer est le sujet de la prochaine section.
Pour une discussion plus approfondie, voir Comment personnaliser le rendu de formulaire .
Dans Symfony, chaque partie d'un formulaire qui est rendue - des éléments de formulaire HTML, des erreurs, des étiquettes, etc - est définie dans un thème de base, qui est une collection de blocs dans Twig et une collection de fichiers de modèles en PHP.
En Twig, chaque bloc nécessaire est défini dans un fichier de modèle unique ( form_div_layout.html.twig ) qui vit à l'intérieur du pont Twig . Dans ce fichier, vous pouvez voir chaque bloc nécessaire pour rendre un formulaire et tous les types de champ par défaut.
En PHP, les fragments sont des fichiers de modèles individuels. Par défaut, ils sont situés dans le répertoire Resources/views/Form du framework bundle ( vue sur GitHub ).
Chaque nom de fragment suit le même schéma de base et est divisé en deux morceaux, séparés par un seul caractère de soulignement (_). Quelques exemples sont:
Chaque fragment suit le même schéma de base: type_part. La partie de type correspond au type de champ est rendu (par exemple textarea, case à cocher, date, etc) tandis que la partie de la pièce correspond à ce qui est rendu (par exemple, l'étiquette, widget, erreurs, etc.) Par défaut, il y a 4 pièces possibles d'une forme qui peut être rendue:
label | (Par exemple field_label) | rend l'étiquette du champ |
widget | (Par exemple field_widget) | rend la représentation HTML du champ |
errors | (Par exemple field_errors) | rend les erreurs du champ |
row | (Par exemple field_row) | rend rangée entière du champ (l'étiquette, le widget et des erreurs) |
Il y a en fait 3 autres parties - lignes, rest, et enctype - mais vous devriez rarement, voire jamais besoin de s'inquiéter à leur sujet impérieuses.
En connaissant le type de champ (par exemple textarea) et la partie que vous souhaitez personnaliser (widget, par exemple), vous pouvez construire le nom du fragment qui doit être remplacé (textarea_widget par exemple).
Dans certains cas, le fragment que vous souhaitez personnaliser apparaît comme manquant. Par exemple, il n'y a aucun fragment textarea_errors dans les thèmes par défaut fournis avec Symfony. Alors, comment sont les erreurs d'un champ textarea rendus?
La réponse est: par l'intermédiaire du fragment field_errors. Lorsque Symfony rend les erreurs d'un type textarea, il regarde d'abord pour un fragment textarea_errors avant de retomber au fragment field_errors. Chaque type de champ a un type de parent (le type de parent textarea est sur le champs), et Symfony utilise le fragment pour le type de parent, si le fragment de base n'existe pas.
Donc, pour remplacer les erreurs pour les champs textarea seulement, copier le fragment field_errors, renommez-le en textarea_errors et le personnaliser. Pour remplacer le rendu d'erreur par défaut pour tous les domaines, de copier et de personnaliser le fragment field_errors directement.
Le «parent» le type de chaque type de champ est disponible dans la référence type de formulaire pour chaque type de champ.
Dans l'exemple ci-dessus, vous avez utilisé l'assistant form_theme (en Twig) à «importer» des fragments de formulaire personnalisés dans tout cette forme. Vous pouvez également dire à symfony d'importer des personnalisations de forme au sein de votre projet dans son ensemble.
Pour inclure automatiquement les blocs personnalisés à partir du modèle de fields.html.twig créé précédemment dans tous les modèles, modifier votre fichier de configuration d'application:
# app/config/config.yml twig: form: resources: - 'AcmeTaskBundle:Form:fields.html.twig' # ...
<!-- app/config/config.xml --> <twig:config ...> <twig:form> <resource>AcmeTaskBundle:Form:fields.html.twig</resource> </twig:form> <!-- ... --> </twig:config>
// app/config/config.php $container->loadFromExtension('twig', array( 'form' = > array('resources' = > array( 'AcmeTaskBundle:Form:fields.html.twig', )) // ... ));
Tous les blocs à l'intérieur du modèle de fields.html.twig sont maintenant utilisés dans le monde pour définir la sortie formulaire.
En Twig, vous pouvez également personnaliser un bloc bonne forme à l'intérieur du modèle où que la personnalisation est nécessaire:
{% extends '::base.html.twig' %} {# import "_self" as the form theme #} {% form_theme form _self %} {# make the form fragment customization #} {% block field_row %} {# custom field row output #} {% endblock field_row %} {% block content %} {# ... #} {{ form_row(form.task) }} {% endblock %}
Le tag {% form_theme form _self %} permet de former des blocs pour qu'ils soient personnalisés directement à l'intérieur du modèle qui va utiliser ces personnalisations. Utilisez cette méthode pour effectuer rapidement des personnalisations de sortie de forme qui ne fera que jamais nécessaires dans un modèle unique.
Pour inclure automatiquement les modèles personnalisés à partir du répertoire Acme/TaskBundle/Resources/views/Form créé précédemment dans tous les modèles, modifier votre fichier de configuration d'application:
# app/config/config.yml framework: templating: form: resources: - 'AcmeTaskBundle:Form' # ...
<!-- app/config/config.xml --> <framework:config ...> <framework:templating> <framework:form> <resource>AcmeTaskBundle:Form</resource> </framework:form> </framework:templating> <!-- ... --> </framework:config>
// app/config/config.php $container->loadFromExtension('framework', array( 'templating' = > array('form' = > array('resources' = > array( 'AcmeTaskBundle:Form', ))) // ... ));
Tous les fragments à l'intérieur du répertoire Acme/TaskBundle/Resources/views/Form sont maintenant utilisés globalement le monde pour définir la sortie formulaire.
CSRF - ou la falsification de requêtes cross-site - est une méthode par laquelle un utilisateur malveillant tente de faire vos utilisateurs légitimes, sans le savoir soumettre des données qu'ils n'ont pas l'intention de se soumettre. Heureusement, les attaques CSRF peut être évité en utilisant un jeton CSRF l'intérieur de vos formes.
Les bonnes nouvelles sont que, par défaut, Symfony intègre et valide CSRF jetons automatiquement pour vous. Cela signifie que vous pouvez profiter de la protection CSRF sans rien faire. En fait, toutes les formes dans ce chapitre ont pris avantage de la protection CSRF!
Protection CSRF fonctionne par l'ajout d'un champ caché à votre formulaire - appelé _token par défaut - qui contient une valeur que vous seul et votre nom d'utilisateur sait. Cela garantit que l'utilisateur - et non une autre entité - est la présentation des données fournies. Symfony valide automatiquement la présence et l'exactitude de ce jeton.
Le champ _token est un champ caché et sera automatiquement rendue si vous incluez le form_rest() dans votre modèle, ce qui assure que tous les champs non-rendus sont de sortie.
Les jetons CSRF peuvent être personnalisés sur la base formulaire par formulaire. Par exemple:
class TaskType extends AbstractType { // ... public function getDefaultOptions(array $options) { return array( 'data_class' = > 'Acme\TaskBundle\Entity\Task', 'csrf_protection' = > true, 'csrf_field_name' = > '_token', // a unique key to help generate the secret token 'intention' = > 'task_item', ); } // ... }
Pour désactiver la protection CSRF, définissez l'option csrf_protection à false. Les personnalisations peuvent également être réalisés au niveau global dans votre projet. Pour plus d'informations, consultez le formulaire de configuration de référence de l'article.
L'option est facultative, mais l'intention améliore considérablement la sécurité du jeton généré par ce qui en fait différent pour chaque forme.
Dans la plupart des cas, un formulaire est lié à un objet, et les champs du formulaire obtiennent et stocke leurs données sur les propriétés de cet objet. C'est exactement ce que vous avez vu jusqu'à présent dans ce chapitre avec la classe Task.
Mais parfois, vous pouvez juste vouloir utiliser un formulaire sans classe, et de revenir un tableau des données soumises. C'est effectivement très facile:
// make sure you've imported the Request namespace above the class use Symfony\Component\HttpFoundation\Request // ... public function contactAction(Request $request) { $defaultData = array('message' = > 'Type your message here'); $form = $this->createFormBuilder($defaultData) ->add('name', 'text') ->add('email', 'email') ->add('message', 'textarea') ->getForm(); if ($request->getMethod() == 'POST') { $form->bindRequest($request); // data is an array with "name", "email", and "message" keys $data = $form->getData(); } // ... render the form }
Par défaut, une forme suppose effectivement que vous voulez travailler avec des tableaux de données, au lieu d'un objet. Il y a exactement deux façons que vous pouvez changer ce comportement et attacher la forme d'un objet au lieu:
Si vous n'utilisez l'une de ces options, alors la form renverra les données comme un tableau. Dans cet exemple, car $DefaultData n'est pas un objet (et pas d'autre choix data_class est réglé), $ form->getData () retourne un tableau en fin de compte.
Vous pouvez également accéder à des valeurs POST (dans ce cas "nom") directement à travers l'objet de requête, comme ceci:
$this->get('request')->request->get('name');
Soyez toutefois avisé cependant, que dans la plupart des cas en utilisant le méthode getData() est un meilleur choix, car il renvoie les données (généralement un objet) après qu'il a été transformé par le framework de formulaire.
La seule pièce manquante est la validation. Habituellement, lorsque vous appelez $form->isValid(), l'objet est validé par la lecture des contraintes que vous avez appliqué à cette classe. Mais sans une classe, comment pouvez-vous ajouter des contraintes aux données de votre formulaire?
La réponse consiste à configurer les contraintes vous-même, et de les transmettre dans votre formulaire. L'approche globale est couvert un peu plus dans le chapitre de Validation dans Symfony2 , mais voici un petit exemple:
// import the namespaces above your controller class use Symfony\Component\Validator\Constraints\Email; use Symfony\Component\Validator\Constraints\MinLength; use Symfony\Component\Validator\Constraints\Collection; $collectionConstraint = new Collection(array( 'name' = > new MinLength(5), 'email' = > new Email(array('message' = > 'Invalid email address')), )); // create a form, no default values, pass in the constraint option $form = $this->createFormBuilder(null, array( 'validation_constraint' = > $collectionConstraint, ))->add('email', 'email') // ...
Maintenant, lorsque vous appelez $form->BindRequest($ request), la configuration des contraintes ici sont exécutées sur les données de votre formulaire. Si vous utilisez une classe de formulaire, substituez la méthode getDefaultOptions pour préciser l'option:
namespace Acme\TaskBundle\Form\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilder; use Symfony\Component\Validator\Constraints\Email; use Symfony\Component\Validator\Constraints\MinLength; use Symfony\Component\Validator\Constraints\Collection; class ContactType extends AbstractType { // ... public function getDefaultOptions(array $options) { $collectionConstraint = new Collection(array( 'name' = > new MinLength(5), 'email' = > new Email(array('message' = > 'Invalid email address')), )); return array('validation_constraint' = > $collectionConstraint); } }
Maintenant, vous avez la possibilité de créer des forme - de la validation - qui retourne un tableau de données, au lieu d'un objet. Dans la plupart des cas, il vaut mieux - et certainement plus robuste - pour lier votre formulaire à un objet. Mais pour des formes simples, il s'agit d'une excellente approche.
Vous connaissez maintenant tous les blocs de construction nécessaires pour construire des formes complexes et fonctionnel pour votre application. Lors de la construction des formes, garder à l'esprit que le but premier d'un formulaire est à traduire les données d'un objet (de travail) vers un formulaire HTML afin que l'utilisateur peut modifier ces données. Le deuxième objectif d'un formulaire est à prendre les données soumises par l'utilisateur et de le ré-appliquer à l'objet.
Il y a encore beaucoup à apprendre sur le monde puissant des forms, telles que la façon de gérer le téléchargement de fichiers avec Doctrine ou comment créer un formulaire où un nombre dynamique de sous-formulaires peuvent être ajoutés (par exemple une liste de tâches où vous pouvez continuer à ajouter plus de domaines via Javascript avant de soumettre). Aussi, soyez sûr de vous pencher sur la documentation de référence du champ de type , qui comprend des exemples de la façon d'utiliser chaque type de champ et de ses options.