对于一个Web开发者来讲,处理HTML表单是一个最为普通又具挑战的任务。Symfony2集成了一个Form组件,让处理表单变的容易起来。在这一节里,咱们将
从基础开始建立一个复杂的表单,学习表单类库中最重要的内容。php
Symfony2 的Form组件是一个独立的类库,你能够在Symfony2项目以外使用它。html
建立一个简单的表单:
假设你要建立一个应用程序的todo列表,须要显示一些任务。由于你的用户须要编辑和建立任务,因此你须要建立一个表单。在你开始以前,首先来看通用的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; } }
若是你是按照咱们提供的示例编码,那么你须要先建立一个AcmeTaskBundle:数组
$ php app/console generate:bundle --namespace=Acme/TaskBundle
该类是一个普通的PHP对象类,由于他们没有任何Symfony或者其它类库引用。很是简单的一个PHP对象类,它直接解决的是你程序中表现 task的数据。固然,在本节的最后,你将可以经过HTML表单提交一个Task实例数据,校验它的数值,并把它持久化到数据库。浏览器
建立一个Form
如今已经建立了一个Task类,下一步就是建立和渲染一个真正的HTML表单了。在symfony2中,它是经过建立一个表单对象并渲染到模板的。如今,能够从controller内部处理form。安全
//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(), )); }
上面的示例显示了如何直接在Controller中建立一个表单,为了可让表单重用你彻底能够在一个单独的类文件中建立表单。app
由于Symfony2经过一个表单生成器“form builder"来建立表单对象,因此你可使用不多的代码就能完成建立表单任务。表单生成器的目的是让你能编写简单的表单建立方法,让它来负责繁重的建立任务。框架
在这个示例中,你已经添加了两个字段到你的表单,一个是task一个是dueDate。它们关联到Task类的task和dueDate属性。你已经为它们分别指定了类型(好比,text,date等),由这些类型来决定为这些字段生成什么样的HTML表单标签。less
Symfony2 拥有许多内建的类型,接下来咱们将简单的介绍。函数
渲染一个表单
表单建立之后,下一步就是渲染它。这是经过传递一个特定的表单”view"对象(就是上例中的 $form->createView()返回的view对象)到你的模板并经过一些列的表单帮助函数来实现的。
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>
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>
在这里假设你已经建立了一个名叫task_new的路由指向AcmeTaskBundle:Default:new Controller。
就是这些了,经过打印form_widget(form),表单中的每一个字段都会被渲染出来。同时还有一个文本标签和错误信息。是否是很简单, 不过如今它还不够灵活。一般状况下,咱们渴望单独渲染表单中的每个字段,这样咱们能够更好的控制表单的样式。咱们会在在模板中渲染表单一节介绍。
在继续下去以前,咱们注意到,为何咱们渲染出来的task输入框中有一个来自$task对象的属性值“Write a blog post"。这是表单的第一个工做:从一个对象中获取数据并把它转换为合适的格式渲染到一个HTML表单中。
注意,表单系统已经足够聪明,它们可以经过像getTask()和setTask()方法来访问Task类中受保护的属性task。除非一个是公共属 性,不然必须有一个getter和setter方法被定义来用于表单组件从这些属性中获取和保持数据。对于布尔型的属性,你可使用一个”isser"方 法(好比 isPublished())替代getter方法(getPublished())。
处理表单提交
表单系统的第二个任务就是传递用户提交的数据回到一个对象的属性中。要作到这一点,用户提交的数据必须绑定到表单才行。添加以下代码到你的Controller类:
//... 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')); } } //... }
如今,当表单被提交时,Controller能够绑定被提交的数据到表单,表单会把数据传回$task对象的task和dueDate属性。这 些都在bindRequest()方法中完成。只要bindRequest()方法被调用,提交的数据就会马上被传输到底层对象。无论数据是否被真正的校 验经过。
controller通常会遵循一个通用的模式来处理表单,它有三个可能的途径:
1.当在浏览器初始加载一个页面时,请求方法是GET,表单处理仅仅是建立和渲染。
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
在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表单以方便用户修改它们。第二个目标就是把用户提交的数据重写提交回对象。