Formulaires dans Symfony2


Table des matières

Référence
Création d'un formulaire simple
Construire le formulaire
Rendre la form
Création de la route
Manipulation soumissions de formulaire
Formulaire de validation
HTML5 validation
Groupes de validation
Built-in types de champs
Les champs de texte
Les champs Choice
Les champs Date et Heure
Autres domaines
Groupes de champs
Les champs cachés
Champs de base
Options de type de champ
L'option requise
L'option label
Déduction d'un type de champ
Options de type de champ déduit ( Guessing )
Rendu d'un formulaire dans un modèle
Rendu de chaque champ manuellement
Twig de référence des fonctions Modèle
Création de classes de formulaire
Réglage de la data_class
Formulaires et Doctrine
Embedded formes
L'incorporation d'un seul objet
L'incorporation d'un ensemble de formulaires
Formulaire Thématisation
Nommé des fragments de formulaire
Héritage fragment de modèle
Thématisation forme globale
Twig
Personnalisation de sortie formulaire le tout dans un seul fichier avec Twig
CSRF protection
L'utilisation d'un formulaire sans classe
Ajout d'une validation
Réflexions finales

Référence

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.

Création d'un formulaire simple

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.

Construire le formulaire

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 ).

Rendre la form

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:

Version Twig

{# 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>

Version Php

<!-- 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>

Création de la route

Fichier : app/config/routing.yml

.
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()).

Manipulation soumissions de formulaire

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.

Formulaire de validation

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.

Version Yaml

# Acme/TaskBundle/Resources/config/validation.yml
Acme\TaskBundle\Entity\Task:
    properties:
        task:
            - NotBlank: ~
        dueDate:
            - NotBlank: ~
            - Type: \DateTime

Version Annotations

// 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;
}

Version Xml

<!-- 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>

Version Php

// 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.

HTML5 validation

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.

Groupes de validation

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.

Built-in types de champs

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:

Les champs de texte

Les champs Choice

Les champs Date et Heure

Autres domaines

Groupes de champs

Les champs cachés

Champs de base

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

Options de type de champ

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 requise

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'option label

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.

Déduction d'un type de champ

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é).

Options de type de champ déduit ( Guessing )

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))

Rendu d'un formulaire dans un modèle

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:

Version Twig

{# 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>

Version Php

<!-- // 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:

Version Twig

{{ form.vars.value.task }}

Version Php

<?php echo $view['form']->get('value')->getTask() ?>

Rendu de chaque champ manuellement

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:

Version Twig

{{ 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) }}

Version Php

<?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:

Version Twig

{{ form_label(form.task, 'Task Description') }}

Version Php

<?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:

Version Twig

{{ form_widget(form.task, { 'attr': {'class': 'task_field'} }) }}

Version Php

<?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:

Version Twig

{{ form.task.vars.id }}

Version Php

<?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:

Version Twig

{{ form.task.vars.full_name }}

Version Php

<?php echo $form['task']->get('full_name') ?>

Twig de référence des fonctions Modèle

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.

Création de classes de formulaire

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.

Réglage de la data_class

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.

Formulaires et Doctrine

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).

Embedded formes

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.

L'incorporation d'un seul objet

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:

Version Twig

{# ... #}

<h3>Category</h3>
<div class="category">
    {{ form_row(form.category.name) }}
</div>

{{ form_rest(form) }}
{# ... #}

Version Php

<!-- ... -->

<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.

L'incorporation d'un ensemble de formulaires

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.

Formulaire Thématisation

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:

Version Twig

{# 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 %}

Version Php

<!-- 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:

Version Twig

{# 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 ...>

Version Php

<!-- 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 .

Nommé des fragments 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).

Héritage fragment de modèle

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.

Thématisation forme globale

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.

Twig

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:

Version Yaml

# app/config/config.yml

twig:
    form:
        resources:
            - 'AcmeTaskBundle:Form:fields.html.twig'
    # ...

Version Xml

<!-- app/config/config.xml -->

<twig:config ...>
        <twig:form>
            <resource>AcmeTaskBundle:Form:fields.html.twig</resource>
        </twig:form>
        <!-- ... -->
</twig:config>

Version Php

// 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.

Personnalisation de sortie formulaire le tout dans un seul fichier avec Twig

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:

Version Yaml

# app/config/config.yml

framework:
    templating:
        form:
            resources:
                - 'AcmeTaskBundle:Form'
# ...

Version Xml

<!-- app/config/config.xml -->

<framework:config ...>
    <framework:templating>
        <framework:form>
            <resource>AcmeTaskBundle:Form</resource>
        </framework:form>
    </framework:templating>
    <!-- ... -->
</framework:config>

Version Php

// 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 protection

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.

L'utilisation d'un formulaire sans classe

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.

Ajout d'une validation

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.

Réflexions finales

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.