表单
对一个Web开发者来说,处理HTML表单是一个最为普通又极具挑战的任务。Symfony整合了一个Form组件,让处理表单变得容易起来。在本章,你将从零开始创建一个复杂的表单,学习表单类库中的重要功能。
Symfony的Form组件是一个独立的类库,你可以在Symfony项目之外使用它。参考 Form组件文档 以了解更多。
创建一个简单的表单 ¶
假设你正在构建一个简单的待办事项列表,来显示一些“任务”。你需要创建一个表单来让你的用户编辑和创建任务。在这之前,先来看看 Task
类,它可呈现和存储一个单一任务的数据。Task
类,它可呈现和存储一个单一任务的数据。
// src/AppBundle/Entity/Task.phpnamespace AppBundle\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; }}
这是一个原生的PHP对象类,因为它没有和Symfony互动也没有引用其它类库。它是非常简单的一个PHP对象类,直接解决了 你 程序中的 task
(任务)之数据问题。当然,在本章的最后,你将能够通过HTML表单把数据提交到一个 Task
实例,验证它的值,并把它持久化到数据库。
构建表单 ¶
现在你已经创建了一个 Task
// src/AppBundle/Controller/DefaultController.phpnamespace AppBundle\Controller; use AppBundle\Entity\Task;use Symfony\Bundle\FrameworkBundle\Controller\Controller;use Symfony\Component\HttpFoundation\Request;use Symfony\Component\Form\Extension\Core\Type\TextType;use Symfony\Component\Form\Extension\Core\Type\DateType;use Symfony\Component\Form\Extension\Core\Type\SubmitType; class DefaultController extends Controller{ public function newAction(Request $request) { // create a task and give it some dummy data for this example // 创建一个task对象,赋一些例程中的假数据给它 $task = new Task(); $task->setTask('Write a blog post'); $task->setDueDate(new \DateTime('tomorrow')); $form = $this->createFormBuilder($task) ->add('task', TextType::class) ->add('dueDate', DateType::class) ->add('save', SubmitType::class, array('label' => 'Create Task')) ->getForm(); return $this->render('default/new.html.twig', array( 'form' => $form->createView(), )); }}这是一个原生的PHP对象类,因为它没有和Symfony互动也没有引用其它类库。它是非常简单的一个PHP对象类,直接解决了 你 程序中的
task
(任务)之数据问题。当然,在本章的最后,你将能够通过HTML表单把数据提交到一个 Task
实例,验证它的值,并把它持久化到数据库。构建表单 ¶
Task
类,下一步就是创建和渲染一个真正的html表单了。在Symfony中,这是通过构建一个表单对象并将其渲染到模版来完成的。现在,在控制器里即可完成所有这些:TWIG:{# app/Resources/views/default/new.html.twig #} {{ form_start(form) }} {{ form_widget(form) }} {{ form_end(form) }}
创建表单不需要很多代码,因为Symfony的表单对象是通过一个“form builder(表单生成器)”来创建的。form builder的目的是让你编写简单的表单创建“指令”,而真实创建表单时的全部“重载”任务则交由builder完成。
本例中,你已经添加了两个字段到表单,即 task
和 dueDate
。对应的是 Task
类中的 task
和 dueDate
属性。你已为它们分别指定了FQCN(Full Quilified Class Name/完整路径类名)的“类型”(如 TextType
, DateType
),由类型决定为字段生成哪一种HTML表单标签(标签组)。task
和 dueDate
。对应的是 Task
类中的 task
和 dueDate
属性。你已为它们分别指定了FQCN(Full Quilified Class Name/完整路径类名)的“类型”(如 TextType
, DateType
),由类型决定为字段生成哪一种HTML表单标签(标签组)。
最后,你添加了一个带有自定义label的提交按钮以向服务器提交表单。
Symfony附带了许多内置类型,它们将被简短地介绍(见下面的内置表单类型)。
渲染表单 ¶
表单创建之后,下一步就是渲染它。这是通过传递一个特定的表单“view”对象(注意上例控制器中的 $form->createView()
方法)到你的模板,并通过一系列的表单helper function(帮助函数)来实现的。
PHP:<!-- app/Resources/views/default/new.html.php --> <?php echo $view['form']->start($form) ?> <?php echo $view['form']->widget($form) ?> <?php echo $view['form']->end($form) ?>
// ...use Symfony\Component\HttpFoundation\Request; public function newAction(Request $request){ // just setup a fresh $task object (remove the dummy data) // 直接设置一个全新$task对象(删除了假数据) $task = new Task(); $form = $this->createFormBuilder($task) ->add('task', TextType::class) ->add('dueDate', DateType::class) ->add('save', SubmitType::class, array('label' => 'Create Task')) ->getForm(); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { // $form->getData() holds the submitted values // but, the original `$task` variable has also been updated // $form->getData() 持有提交过来的值 // 但是,原始的 `$task` 变量也已被更新了 $task = $form->getData(); // ... perform some action, such as saving the task to the database // for example, if Task is a Doctrine entity, save it! // 一些操作,比如把任务存到数据库中 // 例如,如果Tast对象是一个Doctrine entity,存下它! // $em = $this->getDoctrine()->getManager(); // $em->persist($task); // $em->flush(); return $this->redirectToRoute('task_success'); } return $this->render('default/new.html.twig', array( 'form' => $form->createView(), ));}
本例假设你以"POST"请求提交表单,并且提交到和“表单显示(页面)”相同的URL。后面你将学习如何改变请求方法(request method)和表单提交后的目标URL。
就是这样!只需要三行就可以渲染出完整的form表单:
form_start(form)
- 渲染表单的开始标签,包括在使用文件上传时的正确enctype属性。
form_widget(form)
- 渲染出全部字段,包含字段元素本身,字段label以及字段验证的任何错误信息。
form_end(form)
最后,你添加了一个带有自定义label的提交按钮以向服务器提交表单。- Symfony附带了许多内置类型,它们将被简短地介绍(见下面的内置表单类型)。
渲染表单 ¶
表单创建之后,下一步就是渲染它。这是通过传递一个特定的表单“view”对象(注意上例控制器中的
$form->createView()
方法)到你的模板,并通过一系列的表单helper function(帮助函数)来实现的。Annotations:// src/AppBundle/Entity/Task.phpnamespace AppBundle\Entity; use Symfony\Component\Validator\Constraints as Assert; class Task{ /** * @Assert\NotBlank() */ public $task; /** * @Assert\NotBlank() * @Assert\Type("\DateTime") */ protected $dueDate;}
YAML:# src/AppBundle/Resources/config/validation.ymlAppBundle\Entity\Task: properties: task: - NotBlank: ~ dueDate: - NotBlank: ~ - Type: \DateTime
- 🎜
form_start(form)
🎜🎜渲染表单的开始标签,包括在使用文件上传时的正确enctype属性。🎜🎜form_widget(form)
🎜🎜渲染出全部字段,包含字段元素本身,字段label以及字段验证的任何错误信息。🎜🎜form_end(form)
🎜🎜当你手动生成每个字段时,它可以渲染表单结束标签以及表单中所有尚未渲染的字段。这在渲染隐藏字段以及利用自动的
🎜🎜CSRF Protection🎜🎜 保护机制时非常有用。🎜🎜🎜🎜🎜就是这么简单,但不太灵活(暂时)。通常情况下,你希望单独渲染出表单中的每一个字段,以便控制表单的样式。你将在后面 🎜如何去控制表单渲染🎜 文章中掌握这种方法。🎜🎜🎜在继续下去之前,请注意,为什么渲染出来的 task
输入框中有一个来自 $task
对象的属性值(即“Write a blog post”)。这是表单的第一个任务:从一个对象中获取数据并把它转换成一种适当的格式,以便在HTML表单中被渲染。task
输入框中有一个来自 $task
对象的属性值(即“Write a blog post”)。这是表单的第一个任务:从一个对象中获取数据并把它转换成一种适当的格式,以便在HTML表单中被渲染。
表单系统足够智能,它们通过 getTask()
和 setTask()
方法来访问 Task
类中受保护的 task
属性。除非是public属性,否则 必须 有一个 "getter" 和 "setter" 方法被定义,以便表单组件能从这些属性中获取和写入数据。对于布尔型的属性,你可以使用一个 "isser" 和 "hasser" 方法(如 isPublished()
和 hasReminder()
)来替代getter方法(getPublished()
和 getReminder()
)。
处理表单提交 ¶
默认时,表单会把POST请求,向“渲染它的同一个控制器”提交回去。
此处,表单的第二个任务就是把用户提交的数据传回到一个对象的属性之中。要做到这一点,用户提交的数据必须写入表单对象才行。向控制器(Controller)中添加以下功能:
XML:<!-- src/AppBundle/Resources/config/validation.xml --><?xml version="1.0" encoding="UTF-8"?><constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd"> <class name="AppBundle\Entity\Task"> <property name="task"> <constraint name="NotBlank" /> </property> <property name="dueDate"> <constraint name="NotBlank" /> <constraint name="Type">\DateTime</constraint> </property> </class></constraint-mapping>
注意 createView()
方法应该在 handleRequest
被调用 之后 再调用。否则,针对 *_SUBMIT
getTask()
和 setTask()
方法来访问 Task
类中受保护的 task
属性。除非是public属性,否则 必须 有一个 "getter" 和 "setter" 方法被定义,以便表单组件能从这些属性中获取和写入数据。对于布尔型的属性,你可以使用一个 "isser" 和 "hasser" 方法(如 isPublished()
和 hasReminder()
)来替代getter方法(getPublished()
和 getReminder()
)。处理表单提交 ¶
🎜默认时,表单会把POST请求,向“渲染它的同一个控制器”提交回去。🎜🎜此处,表单的第二个任务就是把用户提交的数据传回到一个对象的属性之中。要做到这一点,用户提交的数据必须写入表单对象才行。向控制器(Controller)中添加以下功能:🎜PHP:// src/AppBundle/Entity/Task.phpuse 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') ); }}🎜🎜
createView()
方法应该在 handleRequest
被调用 之后 再调用。否则,针对 *_SUBMIT
表单事件的修改,将不会应用到视图层(比如验证时的错误信息)。🎜🎜控制器(controller)在处理表单时遵循的是一个通用模式(common pattern),它有三个可能的途径:
当浏览器初始加载一个页面时,表单被创建和渲染。
handleRequest()
意识到表单没有被提交进而什么都不做。如果表单未被提交,isSubmitted()
返回false;handleRequest()
意识到表单没有被提交进而什么都不做。如果表单未被提交,isSubmitted()
返回false;当用户提交表单时,
handleRequest()
会识别这个动作并立即将提交的数据写入到$task
对象的task
anddueDate
属性。然后该对象被验证。如果它是无效的(验证在下一章),isValid()
会返回false
,进而表单被再次渲染,只是这次有验证错误;当用户以合法数据提交表单的时,提交的数据会被再次写入到表单,但这一次
isValid()
返回true
。在把用户重定向到其他一些页面之前(如一个“谢谢”或“成功”的页面),你有机会用$task
handleRequest()
会识别这个动作并立即将提交的数据写入到 $task
对象的 task
and dueDate
属性。然后该对象被验证。如果它是无效的(验证在下一章),isValid()
会返回 false
,进而表单被再次渲染,只是这次有验证错误;isValid()
返回 true
。在把用户重定向到其他一些页面之前(如一个“谢谢”或“成功”的页面),你有机会用 $task
对象来进行某些操作(比如把它持久化到数据库)。如果你需要精确地控制何时表单被提交,或哪些数据被传给表单,你可以使用 submit()。更多信息请参考 手动调用Form::submit()。
表单验证 ¶
在上一节中,你了解了附带了有效或无效数据的表单是如何被提交的。在Symfony中,验证环节是在底层对象中进行的(例如 Task
)。换句话说,问题不在于“表单”是否有效,而是 $task
对象在“提交的数据应用到表单”之后是否合法。调用 $form->isvalid()
是一个快捷方式,询问底层 $task
对象是否获得了合法数据。Task
)。换句话说,问题不在于“表单”是否有效,而是 $task
对象在“提交的数据应用到表单”之后是否合法。调用 $form->isvalid()
是一个快捷方式,询问底层 $task
对象是否获得了合法数据。
验证(validation)是通过把一组规则(称之为“constraints/约束”)添加到一个类中来完成的。我们给 Task
类添加规则和约束,使task属性不能为空, duDate
字段不空且必须是一个有效的DateTime对象。
PHP:<!-- app/Resources/views/default/new.html.php --><?php echo $view['form']->form($form, array( 'attr' => array('novalidate' => 'novalidate'),)) ?>
Twig:{# app/Resources/views/default/new.html.twig #} {{ form(form, {'attr': {'novalidate': 'novalidate'}}) }}
1
->add('dueDate', DateType::class, array('widget' => 'single_text'))
就是这样!如果你现在重新以非法数据提交表单,你将会看到相应的错误被输出到表单。
验证是Symfony一个非常强大的功能,它拥有自己的专属章节。