Home >Backend Development >PHP Tutorial >Detailed explanation of form usage in Symfony2 framework study notes, symfony2 study notes_PHP tutorial
The examples in this article describe the usage of Symfony2 framework forms. Share it with everyone for your reference, the details are as follows:
For a web developer, processing HTML forms is one of the most common and challenging tasks. Symfony2 integrates a Form component to make processing forms easy. In this section, we will
Create a complex form from the basics and learn the most important content in the form library.
The Form component of Symfony2 is an independent class library that you can use outside the Symfony2 project.
Create a simple form:
Suppose you want to create a todo list for an application and need to display some tasks. Because your users need to edit and create tasks, you need to create a form. Before you start, first look at the general Task class, used to represent and store data for a single task:
// 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; } }
If you code according to the examples we provide, you need to create an AcmeTaskBundle first:
$ php app/console generate:bundle --namespace=Acme/TaskBundle
This class is an ordinary PHP object class, because they do not have any Symfony or other class library references. A very simple PHP object class, it directly solves the data representing tasks in your program. Of course, by the end of this section, you will be able to submit a Task instance data through an HTML form, verify its value, and persist it to the database.
Create a Form
Now that a Task class has been created, the next step is to create and render a real HTML form. In symfony2, it is done by creating a form object and rendering it to the template. Now, the form can be handled from inside the controller.
//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(), )); }
The above example shows how to create a form directly in the Controller. In order to reuse the form, you can create the form in a separate class file.
Because Symfony2 creates form objects through a form builder, you can complete the form creation task with very little code. The purpose of the form builder is to allow you to write simple form creation methods and let it take care of the heavy lifting.
In this example, you have added two fields to your form, one is task and the other is dueDate. They are associated with the task and dueDate properties of the Task class. You have specified types for them (for example, text, date, etc.), and these types determine what kind of HTML form tags are generated for these fields.
Symfony2 has many built-in types, which we will briefly introduce next.
Render a form
After the form is created, the next step is to render it. This is accomplished by passing a specific form "view" object (the view object returned by $form->createView() in the example above) to your template and through a series of form helper functions.
Twig format:
{# 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>
PHP code format:
<!-- 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>
Here it is assumed that you have created a route named task_new pointing to AcmeTaskBundle:Default:new Controller.
That’s it, by printing form_widget(form), every field in the form will be rendered. There is also a text label and error message. Isn't it very simple, but now it is not flexible enough. Typically, we want to render each field in a form individually so that we can have better control over the style of the form. We’ll cover this in the section Rendering Forms in Templates.
Before continuing, we noticed why the task input box we rendered has an attribute value "Write a blog post" from the $task object. This is the first job of the form: getting data from an object and converting it into the appropriate format to render into an HTML form.
Note that the form system is smart enough that they can access the protected attribute task in the Task class through methods like getTask() and setTask(). Unless a property is public, there must be a getter and setter method defined for the form component to obtain and persist data from these properties. For boolean properties, you can use an "isser" method (such as isPublished()) instead of a getter method (getPublished()).
Handle form submission
The second task of the form system is to pass the data submitted by the user back to the properties of an object. To do this, the user-submitted data must be bound to the form. Add the following code to your Controller class:
//... public function newAction(Request $request) { //只是创建一个新的$task对象(不需要假数据) $task = new Task(); $form= $this->createFormBuilder($task) ->add('task','text') ->add('dueDate','date') ->getForm(); if($request->getMethod() == "POST"){ $form->bindRequest($request); if($form->isValid()){ //执行一些行为,比如保持task到数据库 return $this->redirect($this->generateUrl('task_success')); } } //... }
Now, when the form is submitted, the Controller can bind the submitted data to the form, and the form will pass the data back to the task and dueDate properties of the $task object. These are all done in the bindRequest() method. As long as the bindRequest() method is called, the submitted data will be immediately transferred to the underlying object. No matter whether the data is actually verified or not.
Controllers generally follow a general pattern to process forms, and there are three possible ways:
1. When a page is initially loaded in the browser, the request method is GET, and the form processing is only creation and rendering.
2. When the user submits a form with illegal data (method is POST), the form will be bound and then rendered. At this time, all verification errors will be displayed.
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
在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; }
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>
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')); } }
就是这样了,如果你现在再提交包含非法数据的表单,你将会看到相应的错误被打印在表单上。
HTML5 校验
作为HTML5,许多浏览器都加强了客户端某些校验约束。最常用的校验活动是在一个必须的字段上渲染一个required属性。对于支持HTML5的浏览器来说,如果用户此时提交一个空字段到表单时,浏览器会显示提示信息。生成的表单广泛吸收了这些新内容的优点,通过添加一些HTML属性来监控校验。客户端校验可以通过添加novalidate属性到form标签或者formnovalidate 到提交标签而关闭。这对你想检查服务端校验规则时非常有用。
校验分组
如果你的对象想从校验组中受益,你需要指定你的表单使用哪个校验组。
$form = $this->createFormBuilder($users, array( 'validation_groups' => array('registration'), ))->add(...) ;
如果你创建表单类,你需要添加羡慕的getDefaultOptions()方法:
public function getDefaultOptions(array $options) { return array( 'validation_groups' => array('registration') ); }
在这两种情况下,只有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('dueData','data', array('widget' = 'single_text'))
required选项:
最常用到的选项是required选项,它可以应用于任何字段。默认情况下它被设置为true。这就意味着支持HTML5的浏览器会使用客户端校验来判断字段是否为空。如果你不想让它发生,或者把在你的字段上把required选项设置为false,或者关闭HTML5校验。设置required为true并不意味着服务端校验被应用。换句话说,如果用户提交一个空数值到该字段,它将接受这个控制除非你使用Symfony的NotBlank或者NotNull校验约束。也就是说,required选项是很好,但是服务端校验还是要继续用。
label选项:
表单字段可以使用label选项设置显示字符标签,可以应用于任何字段:
->add('dueDate', 'date',array( 'widget' =>'single_text', 'label' => 'Due Date', ))
字段类型猜测:
现在你已经添加了校验元数据到Task类,Symfony早已经了解一点关于你的字段了。如果你允许,Symfony可以猜到你的字段数据类型并为你设置它。在下面的例子中,Symfony可以根据校验规则猜测到task字段是一个标准的text字段,dueDate是date字段。
public function newAction() { $task = new Task(); $form = $this->createFormBuilder($task) ->add('task') ->add('dueDate', null, array('widget' => 'single_text')) ->getForm(); }
当你省略了add方法的第二个参数(或者你输入null)时,Symfony的猜测能力就起作用了。如果你输入一个选项数组作为第三个参数(比如上面的dueDate),那么这些选项会成为Symfony猜测的依据。如果你的表单使用了指定的校验数组,字段类型猜测器将还是要考虑所有的校验规则来综合猜测你的字段类型。
字段类型可选项猜测
除了猜测字段类型,Symfony还能是这猜测一些可选项字段值。当这些可选项被设置时,字段将会被渲染到特定HTML属性中,让HTML5客户端来提供校验。
然而,它们不会在服务端生成相应的校验规则。尽管你需要手动的在服务端添加这些规则,但是这些字段类型选项还是能根据这些信息猜测到。
required: required规则可以在校验规则或者Doctrine元数据的基础上猜测到。这当你的客户端校验将自动匹配你的校验规则时很有用。
max_length: 如果字段是一些列文本字段,那么max_length选项可以从校验规则或者Doctrine元数据中猜到。
如果你喜欢改变一个猜到的数值,你可以通过在可选项数组中传递该选项来重写它。
->add('task',null, array('max_length'=>4))
在模板中渲染表单
到目前为止,我们已经看了一个完整的表单是如何通过一行代码被渲染的。当然,你通常需要更加灵活的渲染方式:
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>
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>
让我们看看这组代码的详细:
form_enctype(form) 只要有一个字段是文件上传,那么它就会义务的设置为 enctype="multipart/form-data";
form_errors(form) 渲染任何整个form的任何错误信息(特定字段的错误,会显示在每个字段的下面一行)。
form_row(form.dueDate) 默认情况下,为给定的字段在一个div中渲染一个文本标签,任何错误,和HTML表单部件。
form_rest(form) 渲染没有指出的其余任何字段,通常在表单的末尾调用它防止遗忘或者渲染一些你不愿意手动设置的隐藏字段。它同时还能为我们提供CSRF保护。
大部分工作是由form_row帮助方法类完成的,它默认在一个div中为每个字段渲染显示标签,错误信息和HTML表单部件。
注意,你可以通过form.vars.value 来访问你当前是表当数据:
Twig格式:
{{ form.vars.value.task }}
PHP代码格式:
<?php echo $view['form']->get('value')->getTask() ?>
手工渲染每一个表单字段
form_row帮助器能让你很快的渲染你表单中的每一个字段,并且每一行可以被自定义化。但是生活不总是那么简单的,你也可能要手动的渲染每一个字段。
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) }}
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) ?>
如果自动生成显示标签不准确,那么你可以显式的指定它:
Twig格式:
{{ form_label(form.task, 'Task Description') }}
PHP代码格式:
<?php echo $view['form']->label($form['task'], 'Task Description') ?>
一些字段类型有一些额外的渲染选项可以传入widget,一个常用的选项为attr,它允许你修改表单元素的属性。下面的示例将添加task_field class到渲染的文本输入字段:
Twig格式:
{{ form_widget(form.task, {'attr': {'class':'task_field'} }) }}
PHP代码格式:
<?php echo $view['form']->widget($form['task'], array( 'attr' => array('class' => 'task_field'), )) ?>
如果你想手工渲染表单字段,你可以单独访问每个字段的值,比如id,name和label,这里我们获取id
Twig格式:
{{ form.task.vars.id }}
PHP代码格式:
<?php echo $form['task']->get('id') ?>
需要获取表单字段名称属性你需要使用full_name值:
Twig格式:
{{ form.task.vars.full_name }}
PHP代码格式:
<?php echo $form['task']->get('full_name') ?>
创建表单类
正如你看到的,一个表单可以直接在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('task'); $builder->add('dueDate', null, array('widget' => 'single_text')); } public function getName() { return 'task'; } }
这个新类包含了所有创建一个task表单所需要的内容,注意getName()方法将返回一个该表单类型的唯一标识,用于快速创建该表单。
// src/Acme/TaskBundle/Controller/DefaultController.php // 在类上添加这个新的引用语句 use Acme\TaskBundle\Form\Type\TaskType; public function newAction() { $task = // ... $form = $this->createForm(new TaskType(), $task); // ... }
设置data_class
每个表单都需要知道它底层保存数据的类名称,(比如Acme\TaskBundle\Entity\Task)。通常情况下,是根据createForm方法的第二个参数来猜测的。以后,当你开始嵌入表单时,这个可能就不怎么充分了,所以,通常一个好的方法是通过添加下面代码到你的表单类型类来显式的指定data_class 选项。
public function getDefaultOptions(array $options) { return array( 'data_class' => 'Acme\TaskBundle\Entity\Task', ); }
当然,这种做法也不总是必须的。
当你映射表单到一个对象是,所有的字段都被映射。 表单的任何字段如果在映射的对象上不存在那么就会造成抛出异常。在这种情况下,你需要在表单中获取字段(比如,一个“你同意这些说法吗?”复选框)将不能映射到底层对象,那么你需要设置property_path为false以避免抛出异常。
public function buildForm(FormBuilder $builder, array $options) { $builder->add('task'); $builder->add('dueDate', null, array('property_path' => false)); }
另外,如果有任何的表单字段没有被包含着提交的数据中,那么这些字段需要显式的设置为null。
在controller类中我们可以访问字段数据:
$form->get('dueDate')->getData();
Forms和Doctrine
表单的目的是把数据从一个底层对象传递给一个HTML表单然后把用户提交的数据传回到原先的底层对象。因此,底层对象把数据持久化到数据库就跟表单没有任何的关系了。但是,如果你已经配置了底层类是通过Doctrine来持久化,(你已经定义了映射元数据在底层类),接下来当表单提交数据后,当表单合法后就可以持久化它了。
if ($form->isValid()) { $em = $this->getDoctrine()->getEntityManager(); $em->persist($task); $em->flush(); return $this->redirect($this->generateUrl('task_success')); }
如果处于某种原因,你不想访问原有的$task对象,你可以从表单中直接获取数据:
$task = $form->getData();
在这里,关键要理解当表单跟底层对象绑定后,用户提交的数据会立刻传递给底层对象。如果你想持久化这些数据,你只需要持久化对象本身即可。
嵌入式表单:(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; }
接下来,添加一个新的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; } }
现在我们来相应我们应用程序的一个新需求,需要创建一个 表单可以让用户修改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('name'); } public function getDefaultOptions(array $options) { return array( 'data_class' => 'Acme\TaskBundle\Entity\Category', ); } public function getName() { return 'category'; } }
我们的最终目的是能够让用户在Task表单中修改Category对象,所以,我们需要添加一个类型为CategoryType表单类的category字段到TaskType 表单类。
public function buildForm(FormBuilder $builder, array $options) { // ... $builder->add('category', new CategoryType()); }
这时我们可以在TaskType类字段渲染的旁边渲染CategoryType类的字段了:
Twig格式:
{# ... #} <h3>Category</h3> <div class="category"> {{ form_row(form.category.name) }} </div> {{ form_rest(form) }} {# ... #}
PHP代码格式:
<!-- ... --> <h3>Category</h3> <div class="category"> <?php echo $view['form']->row($form['category']['name']) ?> </div> <?php echo $view['form']->rest($form) ?> <!-- ... -->
当用户提交表单时,提交的Category字段数据被用于创建一个Category实例,然后被设置到Task实例的category字段。该Category实例可以通过Task实例来访问,同时也能被持久化到数据或者用作它用。
$task->getCategory()
嵌入一个表单集合
你也可以将一个表单集合嵌入到一个表单(想象一个Category 表单和许多Product子表单)。它是通过一个字段类型集合类实现的。
表单主题化
表单的每一部分渲染都是可以被自定义个性化的。你可以自由的改变每一个表单行的渲染,改变渲染错误的标志,更或者是textarea标签应该怎样显示等。没有任何限制,不同的个性化设置能用到不同的区域。
Symfony使用模板渲染每一个或者部分表单,比如label标签,input标签,错误信息以及任何其它内容。在Twig中,每个表单片段会被一个Twig block来渲染。要个性化渲染表单,你只需要重写相应的block即可。在PHP模板中,它是通过单独的模板文件来渲染表单片段的,所以你需要通过编写新的模板来替代旧的模板即可。在理解了它们是怎么工作的之后,让我们来个性化form_row片段并添加一个class属性到包裹每一表单行的div元素。首先创建一个新模板文件用于存放新的标志:
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 %}
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>
field_row表单片段会在通过form_row函数渲染大部分的表单字段时使用。 要告诉你的表单组件使用你的新的field_row片段,需要添加下面的内容到你渲染该表单的模板顶部:
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 ...>
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 ...>
其中的form_theme 标签导入前面定义的片段。换句话说,当form_row函数在模板中被调用后,它将从你的自定义主题中使用field_row 块(替代Symfony已有的field_row block)。你的个性化主题不必重写所有的块。当渲染一个你没有重写过的块时,主题引起会找全局的主题(定义在bundle级的主题)使用。
在拥有多个个性化主题的情况下,它会在使用全局主题之前查找定制列表。要个性化你表单的任何部分,你只需要重写相关的片段即可。
表单片段命名
在symfony中,表单的每一部分都会被渲染,HTML表单元素,错误消息,显示标签等这些都是被定义在基础主题里的。它组成了一个Twig的块集合和一个PHP模板集合。
在Twig中,每个需要的块都被定义到一个单独的模板文件中(form_dive_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: - 'AcmeTaskBundle:Form:fields.html.twig' # ...
XML格式:
<!-- app/config/config.xml --> <twig:config ...> <twig:form> <resource>AcmeTaskBundle:Form:fields.html.twig</resource> </twig:form> <!-- ... --> </twig:config>
PHP代码格式:
// app/config/config.php $container->loadFromExtension('twig', array( 'form' => array('resources' => array( 'AcmeTaskBundle:Form:fields.html.twig', )) // ... ));
现在在fields.html.twig模板中的任何块都可以被广泛的使用来定义表单输出了。
自定义表单输出到一个单一的Twig文件中
在Twig中,你也可以个性化一个表单块在模板中
{% extends '::base.html.twig'%} {# 导入"_self" 作为一个表单主题 #} {% form_theme form _self %} {# 个性化表单片段 #} {% block field_row %} {# 自定义字段行输出 #} {% endblock field_row %} {% block content %} {# ... #} {{ form_row(form.task) }} {% endblock %}
这里{% 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: - 'AcmeTaskBundle:Form' # ...
XML格式:
<!-- app/config/config.xml --> <framework:config ...> <framework:templating> <framework:form> <resource>AcmeTaskBundle:Form</resource> </framework:form> </framework:templating> <!-- ... --> </framework:config>
PHP代码格式:
// app/config/config.php $container->loadFromExtension('framework', array( 'templating' => array('form' => array('resources' => array( 'AcmeTaskBundle:Form', ))) // ... ));
此时在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( 'data_class' => 'Acme\TaskBundle\Entity\Task', 'csrf_protection' => true, 'csrf_field_name' => '_token', // 一个唯一的键值来保证生成令牌 'intention' => 'task_item', ); } // ... }
要关闭CSRF保护,设置csrf_protection 选项为false。intentsion选项是可选的,但为不同的表单生成不同的令牌极大的加强了安全性。
使用一个无底层类表单
大多数情况下,一个表单要绑定一个对象的,并且表单中所有的字段获取或者保存它们的数据到该对象属性。但有时候,你可能只想使用一个没有类的表单,返回一个提交数据的数组,这个非常容易实现:
// 确认你在类上方导入了Request对象 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); // 数据是一个数组并包含 "name", "email", 和"message" 键 $data = $form->getData(); } // ... 渲染表单 }
默认情况下,一个表单真的假设你想要一个数据数组而不是数据对象。
这里有两种方式你可以改变它的行为并绑定一个对象;
1.当创建表单时传入一个对象(作为createFormBuilder的第一个参数或者createForm的第二个参数)。
2.在你的表单中声明data_class 选项
如果以上两种方式都没有,那么表单会返回一个数组数据。在这个示例中因为$defaultData不是一个对象,又没有设置data_class选项,则$form->getData()最终返回一个数组。
你也可以通过Request对象直接访问POST的值,
$this->get('request')->request->get('name');
注意,大多数的情况下我们使用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( 'name' => new MinLength(5), 'email' => new Email(array('message' => 'Invalid email address')), )); // 创建一个表单没有默认值,传入约束选项。 $form = $this->createFormBuilder(null, array( 'validation_constraint' => $collectionConstraint, ))->add('email', 'email') // ... ;
现在,当你调用$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( 'name' => new MinLength(5), 'email' => new Email(array('message' => 'Invalid email address')), )); return array('validation_constraint' => $collectionConstraint); } }
这样你有了足够的灵活性来创建表单类和约束了,它返回一个数据数组而不是一个对象。大多数情况下,这个是不错的,而绑定一个表单到一个对象,从某种程度上说更加健壮。对于简单表单来说是个不错的选择。
总结思考
你现在已经了解了所有建造复杂功能性的表单所需要的所有建造块。当生成表单时,记住一个表单的首要目标是从一个对象把数据传递给一个HTML表单以方便用户修改它们。第二个目标就是把用户提交的数据重写提交回对象。
希望本文所述对大家基于Symfony框架的PHP程序设计有所帮助。