Explication détaillée de l'utilisation du formulaire du framework Symfony2

*文
Libérer: 2023-03-19 10:44:02
original
2559 Les gens l'ont consulté

Cet article présente principalement l'utilisation des formulaires dans les notes d'apprentissage du framework Symfony2, et analyse en détail les techniques courantes de Symfony2 pour la création, la vérification, la soumission, etc. Les amis dans le besoin peuvent s'y référer. J'espère que cela sera utile à tout le monde.

Pour un développeur Web, le traitement des formulaires HTML est l'une des tâches les plus courantes et les plus difficiles. Symfony2 intègre un composant Form pour faciliter le traitement des formulaires. Dans cette section, nous partirons des bases pour créer un formulaire complexe et apprendre le contenu le plus important de la bibliothèque de formulaires.

Le composant Form de Symfony2 est une bibliothèque de classes indépendante que vous pouvez utiliser en dehors du projet Symfony2.

Créez un formulaire simple :

Supposons que vous souhaitiez créer une liste de tâches pour une application et que vous deviez afficher certaines tâches. Étant donné que vos utilisateurs doivent modifier et créer des tâches, vous devez créer un formulaire. Avant de commencer, regardez d'abord la classe générale Task, utilisée pour représenter et stocker les données d'une seule tâche :

// 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;
  }
}
Copier après la connexion

Si vous codez selon l'exemple que nous avons fourni, alors vous devez créer un AcmeTaskBundle first :

$ php app/console generate:bundle --namespace=Acme/TaskBundle
Copier après la connexion

Cette classe est une classe d'objet PHP ordinaire, car elle n'a aucune référence à Symfony ou à d'autres bibliothèques de classes. Classe d'objet PHP très simple, elle résout directement les données représentant les tâches de votre programme. Bien entendu, à la fin de cette section, vous pourrez soumettre les données d'une instance de tâche via un formulaire HTML, vérifier sa valeur et la conserver dans la base de données.

Créer un formulaire

Maintenant qu'une classe Task a été créée, l'étape suivante consiste à créer et à afficher un véritable formulaire HTML. Dans symfony2, cela se fait en créant un objet formulaire et en le rendant dans le modèle. Désormais, le formulaire peut être géré depuis l’intérieur du contrôleur.

//src/Acme/TaskBundle/Controller/DefaultController.php
namespace Acme\TaskBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Acme\TaskBundle\Entity\Task;
class DefaultController extends Controller
{
    //创建一个任务并给它一些假数据作为示例
    $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(),
    ));
}
Copier après la connexion

L'exemple ci-dessus montre comment créer un formulaire directement dans le Controller. Afin de réutiliser le formulaire, vous pouvez créer le formulaire dans un fichier de classe séparé.

Étant donné que Symfony2 crée des objets de formulaire via un "générateur de formulaire", vous pouvez effectuer la tâche de création d'un formulaire avec très peu de code. Le but du générateur de formulaires est de vous permettre d'écrire des méthodes simples de création de formulaires et de le laisser se charger du gros du travail.

Dans cet exemple, vous avez ajouté deux champs à votre formulaire, l'un est la tâche et l'autre est la date d'échéance. Ils sont associés aux propriétés task et dueDate de la classe Task. Vous leur avez spécifié des types (par exemple, texte, date, etc.), et ces types déterminent le type de balises de formulaire HTML générées pour ces champs.

Symfony2 possède de nombreux types intégrés, que nous présenterons brièvement ensuite.

Rendre un formulaire

Une fois le formulaire créé, l'étape suivante consiste à le restituer. Ceci est accompli en passant un objet "view" de formulaire spécifique (l'objet view renvoyé par $form->createView() dans l'exemple ci-dessus) à votre modèle et via une série de fonctions d'assistance de formulaire.

Format de brindille :

{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
<form action="{{ path(&#39;task_new&#39;) }}" method ="post" {{ form_enctype(form) }}>
  {{ form_widget(form) }}
   <input type="submit" />
</form>
Copier après la connexion

Format de code PHP :

<!-- src/Acme/TaskBundle/Resources/views/Default/new.html.php -->
<form action="<?php echo $view[&#39;router&#39;]->generate(&#39;task_new&#39;) ?>" method="post" <?php echo $view[&#39;form&#39;]->enctype($form) ?> >
   <?php echo $view[&#39;form&#39;]->widget($form) ?>
   <input type="submit" />
</form>
Copier après la connexion

Ici, on suppose que vous avez créé une route nommée task_new pointant vers AcmeTaskBundle:Default:new Contrôleur.

Ça y est, en imprimant form_widget(form), chaque champ du formulaire sera rendu. Il y a également une étiquette de texte et un message d'erreur. N'est-ce pas très simple, mais maintenant ce n'est pas assez flexible. En règle générale, nous souhaitons afficher chaque champ d'un formulaire individuellement afin de pouvoir mieux contrôler le style du formulaire. Nous aborderons cela dans la section Rendu des formulaires dans les modèles.

Avant de continuer, nous avons remarqué pourquoi la zone de saisie de tâche que nous avons rendue a une valeur d'attribut "Écrire un article de blog" de l'objet $task. Il s'agit de la première tâche du formulaire : extraire les données d'un objet et les convertir au format approprié pour les afficher dans un formulaire HTML.

Notez que le système de formulaires est suffisamment intelligent pour pouvoir accéder à la tâche d'attribut protégé dans la classe Task via des méthodes telles que getTask() et setTask(). Sauf si une propriété est publique, une méthode getter et setter doit être définie pour le composant de formulaire afin d'obtenir et de conserver les données de ces propriétés. Pour les propriétés booléennes, vous pouvez utiliser une méthode « isser » (telle que isPublished()) au lieu d'une méthode getter (getPublished()).

Traitement de la soumission du formulaire

La deuxième tâche du système de formulaire est de transmettre les données soumises par l'utilisateur aux propriétés d'un objet. Pour ce faire, les données soumises par l'utilisateur doivent être liées au formulaire. Ajoutez le code suivant à votre classe Controller :

//...
public function newAction(Request $request)
{
     //只是创建一个新的$task对象(不需要假数据)
    $task = new Task();
    $form= $this->createFormBuilder($task)
       ->add(&#39;task&#39;,&#39;text&#39;)
       ->add(&#39;dueDate&#39;,&#39;date&#39;)
       ->getForm();
    if($request->getMethod() == "POST"){
       $form->bindRequest($request);
       if($form->isValid()){
           //执行一些行为,比如保持task到数据库
           return $this->redirect($this->generateUrl(&#39;task_success&#39;));
       }
    }
    //...
}
Copier après la connexion

Maintenant, lorsque le formulaire est soumis, le contrôleur peut lier les données soumises au formulaire, et le formulaire transmettra les données à la tâche et à la date d'échéance. de la propriété $task. Tout cela est effectué dans la méthode bindRequest(). Tant que la méthode bindRequest() est appelée, les données soumises seront immédiatement transférées vers l'objet sous-jacent. Peu importe que les données soient réellement vérifiées ou non.

Le contrôleur suit généralement un modèle général pour traiter les formulaires, qui a trois manières possibles :

1 Lorsqu'une page est initialement chargée dans le navigateur, la méthode de requête est GET et le formulaire est. traité Il suffit de créer et de restituer.

2.当用户提交带有不合法数据的表单(方法为POST)时,表单会并绑定然后渲染,这时候显示所有校验错误。

3.当用户提交的表单带有的数据均合法时,表单绑定并且在页面跳转之前你有机会去使用数据去执行一些业务逻辑活动,比如持久化它到数据库)。

表单校验

在前面我们提到了,如何提交一个带有合法数据和非法数据的表单。在Symfony2中,校验是在底层对象上进行的。换句话说,form表单合法与否不重要,主要看在表单提交数据以后,底层对象比如$task对象是否合法。调用$form->isvalid() 是一个询问底层对象是否获得合法数据的快捷方式。

校验是通过添加一些列规则(约束)到一个类来完成的。我们给Task类添加规则和约束,使它的task属性不能为空,duDate字段不能空并且是一个合法的DateTime对象。

YAML格式:

# Acme/TaskBundle/Resources/config/validation.yml
Acme\TaskBundle\Entity\Task:
  properties:
    task:
      - NotBlank: ~
    dueDate:
      - NotBlank: ~
      - Type: \DateTime
Copier après la connexion

在Task类中声明格式:

// 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;
}
Copier après la connexion

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>
Copier après la connexion

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(&#39;task&#39;, new NotBlank());
    $metadata->addPropertyConstraint(&#39;dueDate&#39;, new NotBlank());
    $metadata->addPropertyConstraint(&#39;dueDate&#39;, new Type(&#39;\DateTime&#39;));
  }
}
Copier après la connexion

就是这样了,如果你现在再提交包含非法数据的表单,你将会看到相应的错误被打印在表单上。

HTML5 校验

作为HTML5,许多浏览器都加强了客户端某些校验约束。最常用的校验活动是在一个必须的字段上渲染一个required属性。对于支持HTML5的浏览器来说,如果用户此时提交一个空字段到表单时,浏览器会显示提示信息。生成的表单广泛吸收了这些新内容的优点,通过添加一些HTML属性来监控校验。客户端校验可以通过添加novalidate属性到form标签或者formnovalidate 到提交标签而关闭。这对你想检查服务端校验规则时非常有用。

校验分组

如果你的对象想从校验组中受益,你需要指定你的表单使用哪个校验组。

$form = $this->createFormBuilder($users, array(
  &#39;validation_groups&#39; => array(&#39;registration&#39;),
))->add(...)
;
Copier après la connexion

如果你创建表单类,你需要添加羡慕的getDefaultOptions()方法:

public function getDefaultOptions(array $options)
{
  return array(
    &#39;validation_groups&#39; => array(&#39;registration&#39;)
  );
}
Copier après la connexion

在这两种情况下,只有registration 校验组将被用于校验底层对象。

内建字段类型

Symfony标准版含有大量的字段类型,它们几乎涵盖了所有通用表单的字段和数据类型。

文本字段:
text
textarea
email
integer
money
number
password
percent
search
url

选择字段:
choice
entity
country
language
locale
timezone

日期和时间字段:
date
datetime
time
birthday

其它字段:
checkbox
file
radio

字段组:
collection
repeated

隐藏字段:
hidden
csrf

基础字段:
field
form

当然,你也可以定义自己的字段类型。

字段类型选项

每一个字段类型都有一定数量的选项用于配置。比如,dueDate字段当前被渲染成3个选择框。而日期字段可以被配置渲染成一个单一的文本框,用户可以输入字符串作为日期。

->add(&#39;dueData&#39;,&#39;data&#39;, array(&#39;widget&#39; = &#39;single_text&#39;))
Copier après la connexion

required选项:

最常用到的选项是required选项,它可以应用于任何字段。默认情况下它被设置为true。这就意味着支持HTML5的浏览器会使用客户端校验来判断字段是否为空。如果你不想让它发生,或者把在你的字段上把required选项设置为false,或者关闭HTML5校验。设置required为true并不意味着服务端校验被应用。换句话说,如果用户提交一个空数值到该字段,它将接受这个控制除非你使用Symfony的NotBlank或者NotNull校验约束。也就是说,required选项是很好,但是服务端校验还是要继续用。

label选项:

表单字段可以使用label选项设置显示字符标签,可以应用于任何字段:

->add(&#39;dueDate&#39;, &#39;date&#39;,array(
  &#39;widget&#39; =>&#39;single_text&#39;,
  &#39;label&#39; => &#39;Due Date&#39;,
))
Copier après la connexion

字段类型猜测:

现在你已经添加了校验元数据到Task类,Symfony早已经了解一点关于你的字段了。如果你允许,Symfony可以猜到你的字段数据类型并为你设置它。在下面的例子中,Symfony可以根据校验规则猜测到task字段是一个标准的text字段,dueDate是date字段。

public function newAction()
{
  $task = new Task();
  $form = $this->createFormBuilder($task)
    ->add(&#39;task&#39;)
    ->add(&#39;dueDate&#39;, null, array(&#39;widget&#39; => &#39;single_text&#39;))
    ->getForm();
}
Copier après la connexion

当你省略了add方法的第二个参数(或者你输入null)时,Symfony的猜测能力就起作用了。如果你输入一个选项数组作为第三个参数(比如上面的dueDate),那么这些选项会成为Symfony猜测的依据。如果你的表单使用了指定的校验数组,字段类型猜测器将还是要考虑所有的校验规则来综合猜测你的字段类型。

字段类型可选项猜测

除了猜测字段类型,Symfony还能是这猜测一些可选项字段值。当这些可选项被设置时,字段将会被渲染到特定HTML属性中,让HTML5客户端来提供校验。

然而,它们不会在服务端生成相应的校验规则。尽管你需要手动的在服务端添加这些规则,但是这些字段类型选项还是能根据这些信息猜测到。

required: required规则可以在校验规则或者Doctrine元数据的基础上猜测到。这当你的客户端校验将自动匹配你的校验规则时很有用。
max_length: 如果字段是一些列文本字段,那么max_length选项可以从校验规则或者Doctrine元数据中猜到。
如果你喜欢改变一个猜到的数值,你可以通过在可选项数组中传递该选项来重写它。

->add(&#39;task&#39;,null, array(&#39;max_length&#39;=>4))
Copier après la connexion

在模板中渲染表单

到目前为止,我们已经看了一个完整的表单是如何通过一行代码被渲染的。当然,你通常需要更加灵活的渲染方式:

Twig格式:

{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
<form action="{{ path(&#39;task_new&#39;) }}" method="post" {{ form_enctype(form) }}>
  {{ form_errors(form) }}
  {{ form_row(form.task) }}
  {{ form_row(form.dueDate) }}
  {{ form_rest(form) }}
  <input type="submit" />
</form>
Copier après la connexion

PHP代码格式:

<!-- // src/Acme/TaskBundle/Resources/views/Default/newAction.html.php -->
<form action="<?php echo $view[&#39;router&#39;]->generate(&#39;task_new&#39;) ?>" method="post" <?php echo $view[&#39;form&#39;]->enctype($form) ?>>
  <?php echo $view[&#39;form&#39;]->errors($form) ?>
  <?php echo $view[&#39;form&#39;]->row($form[&#39;task&#39;]) ?>
  <?php echo $view[&#39;form&#39;]->row($form[&#39;dueDate&#39;]) ?>
  <?php echo $view[&#39;form&#39;]->rest($form) ?>
  <input type="submit" />
</form>
Copier après la connexion

让我们看看这组代码的详细:

form_enctype(form) 只要有一个字段是文件上传,那么它就会义务的设置为 enctype="multipart/form-data";
form_errors(form) 渲染任何整个form的任何错误信息(特定字段的错误,会显示在每个字段的下面一行)。
form_row(form.dueDate) 默认情况下,为给定的字段在一个p中渲染一个文本标签,任何错误,和HTML表单部件。
form_rest(form) 渲染没有指出的其余任何字段,通常在表单的末尾调用它防止遗忘或者渲染一些你不愿意手动设置的隐藏字段。它同时还能为我们提供CSRF保护。

大部分工作是由form_row帮助方法类完成的,它默认在一个p中为每个字段渲染显示标签,错误信息和HTML表单部件。

注意,你可以通过form.vars.value 来访问你当前是表当数据:

Twig格式:

{{ form.vars.value.task }}
Copier après la connexion

PHP代码格式:

<?php echo $view[&#39;form&#39;]->get(&#39;value&#39;)->getTask() ?>
Copier après la connexion

手工渲染每一个表单字段

form_row帮助器能让你很快的渲染你表单中的每一个字段,并且每一行可以被自定义化。但是生活不总是那么简单的,你也可能要手动的渲染每一个字段。

Twig格式:

{{ form_errors(form) }}
<p>
  {{ form_label(form.task) }}
  {{ form_errors(form.task) }}
  {{ form_widget(form.task) }}
</p>
<p>
  {{ form_label(form.dueDate) }}
  {{ form_errors(form.dueDate) }}
  {{ form_widget(form.dueDate) }}
</p>
{{ form_rest(form) }}
Copier après la connexion

PHP代码格式:

<?php echo $view[&#39;form&#39;]->errors($form) ?>
<p>
  <?php echo $view[&#39;form&#39;]->label($form[&#39;task&#39;]) ?>
  <?php echo $view[&#39;form&#39;]->errors($form[&#39;task&#39;]) ?>
  <?php echo $view[&#39;form&#39;]->widget($form[&#39;task&#39;]) ?>
</p>
<p>
  <?php echo $view[&#39;form&#39;]->label($form[&#39;dueDate&#39;]) ?>
  <?php echo $view[&#39;form&#39;]->errors($form[&#39;dueDate&#39;]) ?>
  <?php echo $view[&#39;form&#39;]->widget($form[&#39;dueDate&#39;]) ?>
</p>
<?php echo $view[&#39;form&#39;]->rest($form) ?>
Copier après la connexion

如果自动生成显示标签不准确,那么你可以显式的指定它:

Twig格式:

{{ form_label(form.task, &#39;Task Description&#39;) }}
Copier après la connexion

PHP代码格式:

<?php echo $view[&#39;form&#39;]->label($form[&#39;task&#39;], &#39;Task Description&#39;) ?>
Copier après la connexion

一些字段类型有一些额外的渲染选项可以传入widget,一个常用的选项为attr,它允许你修改表单元素的属性。下面的示例将添加task_field class到渲染的文本输入字段:

Twig格式:

{{ form_widget(form.task, {&#39;attr&#39;: {&#39;class&#39;:&#39;task_field&#39;} }) }}
Copier après la connexion

PHP代码格式:

<?php echo $view[&#39;form&#39;]->widget($form[&#39;task&#39;], array(
&#39;attr&#39; => array(&#39;class&#39; => &#39;task_field&#39;),
)) ?>
Copier après la connexion

如果你想手工渲染表单字段,你可以单独访问每个字段的值,比如id,name和label,这里我们获取id

Twig格式:

{{ form.task.vars.id }}
Copier après la connexion

PHP代码格式:

<?php echo $form[&#39;task&#39;]->get(&#39;id&#39;) ?>
Copier après la connexion

需要获取表单字段名称属性你需要使用full_name值:

Twig格式:

{{ form.task.vars.full_name }}
Copier après la connexion

PHP代码格式:

<?php echo $form[&#39;task&#39;]->get(&#39;full_name&#39;) ?>
Copier après la connexion

创建表单类

正如你看到的,一个表单可以直接在controller类中被创建和使用。然而,一个更好的做法是在一个单独的PHP类中创建表单。它可以被重用到你应用程序的任何地方。创建一个新类来保存生成task表单的逻辑:

// 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(&#39;task&#39;);
    $builder->add(&#39;dueDate&#39;, null, array(&#39;widget&#39; => &#39;single_text&#39;));
  }
  public function getName()
  {
    return &#39;task&#39;;
  }
}
Copier après la connexion

这个新类包含了所有创建一个task表单所需要的内容,注意getName()方法将返回一个该表单类型的唯一标识,用于快速创建该表单。

// src/Acme/TaskBundle/Controller/DefaultController.php
// 在类上添加这个新的引用语句
use Acme\TaskBundle\Form\Type\TaskType;
public function newAction()
{
    $task = // ...
    $form = $this->createForm(new TaskType(), $task);
    // ...
}
Copier après la connexion

设置data_class

每个表单都需要知道它底层保存数据的类名称,(比如Acme\TaskBundle\Entity\Task)。通常情况下,是根据createForm方法的第二个参数来猜测的。以后,当你开始嵌入表单时,这个可能就不怎么充分了,所以,通常一个好的方法是通过添加下面代码到你的表单类型类来显式的指定data_class 选项。

public function getDefaultOptions(array $options)
{
   return array(
        &#39;data_class&#39; => &#39;Acme\TaskBundle\Entity\Task&#39;,
   );
}
Copier après la connexion

当然,这种做法也不总是必须的。

当你映射表单到一个对象是,所有的字段都被映射。 表单的任何字段如果在映射的对象上不存在那么就会造成抛出异常。在这种情况下,你需要在表单中获取字段(比如,一个“你同意这些说法吗?”复选框)将不能映射到底层对象,那么你需要设置property_path为false以避免抛出异常。

public function buildForm(FormBuilder $builder, array $options)
{
     $builder->add(&#39;task&#39;);
     $builder->add(&#39;dueDate&#39;, null, array(&#39;property_path&#39; => false));
}
Copier après la connexion

另外,如果有任何的表单字段没有被包含着提交的数据中,那么这些字段需要显式的设置为null。

在controller类中我们可以访问字段数据:


$form->get(&#39;dueDate&#39;)->getData();
Copier après la connexion

Forms和Doctrine

表单的目的是把数据从一个底层对象传递给一个HTML表单然后把用户提交的数据传回到原先的底层对象。因此,底层对象把数据持久化到数据库就跟表单没有任何的关系了。但是,如果你已经配置了底层类是通过Doctrine来持久化,(你已经定义了映射元数据在底层类),接下来当表单提交数据后,当表单合法后就可以持久化它了。

if ($form->isValid()) {
  $em = $this->getDoctrine()->getEntityManager();
  $em->persist($task);
  $em->flush();
  return $this->redirect($this->generateUrl(&#39;task_success&#39;));
}
Copier après la connexion

如果处于某种原因,你不想访问原有的$task对象,你可以从表单中直接获取数据:

$task = $form->getData();
Copier après la connexion

在这里,关键要理解当表单跟底层对象绑定后,用户提交的数据会立刻传递给底层对象。如果你想持久化这些数据,你只需要持久化对象本身即可。

嵌入式表单:(Embedded Forms)

通常,你可能想生成一个表单,它包含来自不同对象的字段。比如,一个注册表单可能包含属于User对象和Address对象的字段。幸运的是,这些对于form组件来说都是很容易很自然的事。嵌入一个单独对象:假设每个Task属于一个Category对象,首先创建这个Category对象:

// src/Acme/TaskBundle/Entity/Category.php
namespace Acme\TaskBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Category
{
  /**
   * @Assert\NotBlank()
   */
  public $name;
}
Copier après la connexion

接下来,添加一个新的category属性到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;
  }
}
Copier après la connexion

现在我们来相应我们应用程序的一个新需求,需要创建一个 表单可以让用户修改Category对象。

// 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(&#39;name&#39;);
  }
  public function getDefaultOptions(array $options)
  {
    return array(
      &#39;data_class&#39; => &#39;Acme\TaskBundle\Entity\Category&#39;,
    );
  }
  public function getName()
  {
    return &#39;category&#39;;
  }
}
Copier après la connexion

我们的最终目的是能够让用户在Task表单中修改Category对象,所以,我们需要添加一个类型为CategoryType表单类的category字段到TaskType 表单类。

public function buildForm(FormBuilder $builder, array $options)
{
  // ...
  $builder->add(&#39;category&#39;, new CategoryType());
}
Copier après la connexion

这时我们可以在TaskType类字段渲染的旁边渲染CategoryType类的字段了:
Twig格式:

{# ... #}
<h3>Category</h3>
<p class="category">
  {{ form_row(form.category.name) }}
</p>
{{ form_rest(form) }}
{# ... #}
Copier après la connexion

PHP代码格式:

<!-- ... -->
<h3>Category</h3>
<p class="category">
  <?php echo $view[&#39;form&#39;]->row($form[&#39;category&#39;][&#39;name&#39;]) ?>
</p>
<?php echo $view[&#39;form&#39;]->rest($form) ?>
<!-- ... -->
Copier après la connexion

当用户提交表单时,提交的Category字段数据被用于创建一个Category实例,然后被设置到Task实例的category字段。该Category实例可以通过Task实例来访问,同时也能被持久化到数据或者用作它用。

$task->getCategory()
Copier après la connexion

嵌入一个表单集合

你也可以将一个表单集合嵌入到一个表单(想象一个Category 表单和许多Product子表单)。它是通过一个字段类型集合类实现的。

表单主题化

表单的每一部分渲染都是可以被自定义个性化的。你可以自由的改变每一个表单行的渲染,改变渲染错误的标志,更或者是textarea标签应该怎样显示等。没有任何限制,不同的个性化设置能用到不同的区域。

Symfony使用模板渲染每一个或者部分表单,比如label标签,input标签,错误信息以及任何其它内容。在Twig中,每个表单片段会被一个Twig block来渲染。要个性化渲染表单,你只需要重写相应的block即可。在PHP模板中,它是通过单独的模板文件来渲染表单片段的,所以你需要通过编写新的模板来替代旧的模板即可。在理解了它们是怎么工作的之后,让我们来个性化form_row片段并添加一个class属性到包裹每一表单行的p元素。首先创建一个新模板文件用于存放新的标志:

Twig格式:

{# src/Acme/TaskBundle/Resources/views/Form/fields.html.twig #}
{% block field_row %}
{% spaceless %}
  <p class="form_row">
    {{ form_label(form) }}
    {{ form_errors(form) }}
    {{ form_widget(form) }}
  </p>
{% endspaceless %}
{% endblock field_row %}
Copier après la connexion

PHP代码格式:

<!-- src/Acme/TaskBundle/Resources/views/Form/field_row.html.php -->
<p class="form_row">
  <?php echo $view[&#39;form&#39;]->label($form, $label) ?>
  <?php echo $view[&#39;form&#39;]->errors($form) ?>
  <?php echo $view[&#39;form&#39;]->widget($form, $parameters) ?>
</p>
Copier après la connexion

field_row表单片段会在通过form_row函数渲染大部分的表单字段时使用。 要告诉你的表单组件使用你的新的field_row片段,需要添加下面的内容到你渲染该表单的模板顶部:

Twig格式:

{# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #}
{% form_theme form &#39;AcmeTaskBundle:Form:fields.html.twig&#39; %}
{% form_theme form &#39;AcmeTaskBundle:Form:fields.html.twig&#39; &#39;AcmeTaskBundle:Form:fields2.html.twig&#39; %}
<form ...>
Copier après la connexion

PHP代码格式:

<!-- src/Acme/TaskBundle/Resources/views/Default/new.html.php -->
<?php $view[&#39;form&#39;]->setTheme($form, array(&#39;AcmeTaskBundle:Form&#39;)) ?>
<?php $view[&#39;form&#39;]->setTheme($form, array(&#39;AcmeTaskBundle:Form&#39;, &#39;AcmeTaskBundle:Form&#39;)) ?>
<form ...>
Copier après la connexion

其中的form_theme 标签导入前面定义的片段。换句话说,当form_row函数在模板中被调用后,它将从你的自定义主题中使用field_row 块(替代Symfony已有的field_row block)。你的个性化主题不必重写所有的块。当渲染一个你没有重写过的块时,主题引起会找全局的主题(定义在bundle级的主题)使用。

在拥有多个个性化主题的情况下,它会在使用全局主题之前查找定制列表。要个性化你表单的任何部分,你只需要重写相关的片段即可。

表单片段命名

在symfony中,表单的每一部分都会被渲染,HTML表单元素,错误消息,显示标签等这些都是被定义在基础主题里的。它组成了一个Twig的块集合和一个PHP模板集合。

在Twig中,每个需要的块都被定义到一个单独的模板文件中(form_pe_layout.html.twig),它们被保存在Twig Bridge里。在这个文件中,你可以看到渲染一个表单多需要的每一个block和默认的字段类型。

在PHP模板中,片段是单独的模板文件。 默认情况下它们位于框架bundle的Resources/views/Form 目录下。每个偏度名称都遵循相同的基本模式,用一个下划线(_)分为两部分,比如:

field_row 用于form_row渲染大部分的字段
textarea_widget 用于form_widget渲染一个textarea字段类型
field_errors 用于form_errors渲染一个字段的错误信息

每个片段都命名都遵循:type_part 模式。type部分对应被渲染的字段类型(比如textarea,checkbox,date等),而part部分对应着是什么被渲染(比如label,widget,errors等)

默认情况下,有4种可能的表单part被用来渲染:

label 渲染字段的标签 如field_label
widget 渲染字段的HTML表示 如field_widget
errors 渲染字段的错误信息 如field_errors
row 渲染字段的整个行(包括label,widget和errors) 如 filed_row

还有其它3个part类型,分别是rows,rest和enctype,不过这三个一般不会用到。

通过知道字段类型(比如:textarea)和你想渲染那一部分(比如:widget),你可以创建一个你需要重写的片段名称(比如:textarea_widget).

模板片段继承

在某些情况下,你个性化的片段可能会丢失。比如,在Symfony提供的默认主题中没有提供textarea_errors片段。那么如何来渲染一个textarea字段的错误信息呢?

答案是通过field_errors片段。当Symfony渲染一个textarea类型的错误时,它首先查找一个textarea_errors片段,如果没有找到则会回到field_errors片段。

每个field类型有一个parenttype(textarea的父类型为field),Symfony如果没有发现本身的片段,就会转而使用父类片段。

所以,要重写textarea字段的errors,拷贝field_errors片段,重命名为textarea_errors并个性化它们。为所有字段重写默认的error渲染,则需要直接拷贝和个性化field_errors片段。

全局表单主题

在上面的示例中,我们使用了form_theme helper来导入自定义个的表单片段到表单。你也可以告诉Symfony在全项目中导入自定义的form。

Twig

为了从所有之前创建的fileds.html.twig模板中自动包含个性化的block,修改你的应用程序配置文件:

YAML格式:

# app/config/config.yml
twig:
  form:
    resources:
      - &#39;AcmeTaskBundle:Form:fields.html.twig&#39;
  # ...
Copier après la connexion

XML格式:

<!-- app/config/config.xml -->
<twig:config ...>
    <twig:form>
      <resource>AcmeTaskBundle:Form:fields.html.twig</resource>
    </twig:form>
    <!-- ... -->
</twig:config>
Copier après la connexion

PHP代码格式:

// app/config/config.php
$container->loadFromExtension(&#39;twig&#39;, array(
  &#39;form&#39; => array(&#39;resources&#39; => array(
    &#39;AcmeTaskBundle:Form:fields.html.twig&#39;,
   ))
  // ...
));
Copier après la connexion

现在在fields.html.twig模板中的任何块都可以被广泛的使用来定义表单输出了。

自定义表单输出到一个单一的Twig文件中

在Twig中,你也可以个性化一个表单块在模板中

{% extends &#39;::base.html.twig&#39;%}
{# 导入"_self" 作为一个表单主题 #}
{% form_theme form _self %}
{# 个性化表单片段 #}
{% block field_row %}
    {# 自定义字段行输出 #}
{% endblock field_row %}
{% block content %}
    {# ... #}
    {{ form_row(form.task) }}
{% endblock %}
Copier après la connexion

这里{% form_theme form _self %}标签允许表单块在使用那些自动化内容的模板中被直接自定义化。使用这个方法来快速的生成个性化输出。

注意,{% form_theme form _self %}的功能只有在继承自其它模板时才能起作用,如果不是继承自其它模板,则需要指出form_theme 到单独模板中。

PHP

从以前在所有模板中创建的Acme/TaskBundle/Resources/views/Form 目录自动导入个性化模板。修改你的配置文件:

YAML格式:

# app/config/config.yml
framework:
  templating:
    form:
      resources:
        - &#39;AcmeTaskBundle:Form&#39;
# ...
Copier après la connexion

XML格式:

<!-- app/config/config.xml -->
<framework:config ...>
  <framework:templating>
    <framework:form>
      <resource>AcmeTaskBundle:Form</resource>
    </framework:form>
  </framework:templating>
  <!-- ... -->
</framework:config>
Copier après la connexion

PHP代码格式:

// app/config/config.php
$container->loadFromExtension(&#39;framework&#39;, array(
  &#39;templating&#39; => array(&#39;form&#39; =>
    array(&#39;resources&#39; => array(
      &#39;AcmeTaskBundle:Form&#39;,
   )))
  // ...
));
Copier après la connexion

此时在Acme/TaskBundle/Resources/views/Form目录中的任何片段都可以全局范围内定义表单输出了。

CSRF 保护

CSRF--Cross-site request forgery,跨站伪造请求 是恶意攻击者试图让你的合法用户在不知不觉中提交他们本不想提交的数据的一种方法。

幸运的是,CSRF攻击可以通过在你的表单中使用CSRF 记号来阻止。

默认情况下,Symfony自动为你嵌入一个合法的CSRF令牌。这就意味着你不需要做任何事情就可以得到CSRF保护。CSRF保护是通过在你的表单中添加一个隐藏字段,默认的名叫_token。它包含一个值,这个值只有你和你的用户知道。这确保了是用户而不是其它实体在提交数据。Symfony自动校验该token是否存在以及其准确性。

_token 字段是一个隐藏字段并且会自动的渲染,只要你在你的模板中包含了form_rest()函数。它确保了没有被渲染过的字段全部渲染出来。CSRF令牌可以按照表单来个性化,比如:

class TaskType extends AbstractType
{
   // ...
   public function getDefaultOptions(array $options)
   {
      return array(
          &#39;data_class&#39; => &#39;Acme\TaskBundle\Entity\Task&#39;,
          &#39;csrf_protection&#39; => true,
          &#39;csrf_field_name&#39; => &#39;_token&#39;,
          // 一个唯一的键值来保证生成令牌
          &#39;intention&#39; => &#39;task_item&#39;,
      );
   }
   // ...
}
Copier après la connexion

要关闭CSRF保护,设置csrf_protection 选项为false。intentsion选项是可选的,但为不同的表单生成不同的令牌极大的加强了安全性。

使用一个无底层类表单

大多数情况下,一个表单要绑定一个对象的,并且表单中所有的字段获取或者保存它们的数据到该对象属性。但有时候,你可能只想使用一个没有类的表单,返回一个提交数据的数组,这个非常容易实现:

// 确认你在类上方导入了Request对象
use Symfony\Component\HttpFoundation\Request
// ...
public function contactAction(Request $request)
{
    $defaultData = array(&#39;message&#39; => &#39;Type your message here&#39;);
    $form = $this->createFormBuilder($defaultData)
       ->add(&#39;name&#39;, &#39;text&#39;)
       ->add(&#39;email&#39;, &#39;email&#39;)
       ->add(&#39;message&#39;, &#39;textarea&#39;)
       ->getForm();
     if ($request->getMethod() == &#39;POST&#39;) {
        $form->bindRequest($request);
        // 数据是一个数组并包含 "name", "email", 和"message" 键
        $data = $form->getData();
     }
    // ... 渲染表单
}
Copier après la connexion

默认情况下,一个表单真的假设你想要一个数据数组而不是数据对象。

这里有两种方式你可以改变它的行为并绑定一个对象;

1.当创建表单时传入一个对象(作为createFormBuilder的第一个参数或者createForm的第二个参数)。

2.在你的表单中声明data_class 选项

如果以上两种方式都没有,那么表单会返回一个数组数据。在这个示例中因为$defaultData不是一个对象,又没有设置data_class选项,则$form->getData()最终返回一个数组。

你也可以通过Request对象直接访问POST的值,

$this->get(&#39;request&#39;)->request->get(&#39;name&#39;);
Copier après la connexion

注意,大多数的情况下我们使用getData()方法是更好一点的选择。因为它返回的是经过表单框架转换过的数据。

添加校验规则

唯一遗漏的地方就是校验规则了,通常当你调用$form->isvalid()时,对象会调用你在类东提供的校验规则进行校验。但如果没有类,你怎么来添加对你表单数据的约束规则呢?答案是自己创建约束,然后传入到表单。

// 在controller类前导入命名空间
use Symfony\Component\Validator\Constraints\Email;
use Symfony\Component\Validator\Constraints\MinLength;
use Symfony\Component\Validator\Constraints\Collection;
$collectionConstraint = new Collection(array(
      &#39;name&#39; => new MinLength(5),
      &#39;email&#39; => new Email(array(&#39;message&#39; => &#39;Invalid email address&#39;)),
));
// 创建一个表单没有默认值,传入约束选项。
$form = $this->createFormBuilder(null, array(
           &#39;validation_constraint&#39; => $collectionConstraint,
    ))->add(&#39;email&#39;, &#39;email&#39;)
// ...
;
Copier après la connexion

现在,当你调用$form->bindRequest($request)时,约束就会被创建并作用于你的表单数据。如果你使用表单类,重写getDefaultOptions 方法来指定可选项:

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(
      &#39;name&#39; => new MinLength(5),
      &#39;email&#39; => new Email(array(&#39;message&#39; => &#39;Invalid email address&#39;)),
    ));
    return array(&#39;validation_constraint&#39; => $collectionConstraint);
  }
}
Copier après la connexion

这样你有了足够的灵活性来创建表单类和约束了,它返回一个数据数组而不是一个对象。大多数情况下,这个是不错的,而绑定一个表单到一个对象,从某种程度上说更加健壮。对于简单表单来说是个不错的选择。

总结思考

你现在已经了解了所有建造复杂功能性的表单所需要的所有建造块。当生成表单时,记住一个表单的首要目标是从一个对象把数据传递给一个HTML表单以方便用户修改它们。第二个目标就是把用户提交的数据重写提交回对象。

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal