支付宝扫一扫付款
微信扫一扫付款
(微信为保护隐私,不显示你的昵称)
表单组件让你轻松地创建、处理和复用表单。
表单组件是一个工具,用于帮你解决问题,让最终用户能够与你程序中的数据进行互动或是修改它们。传统上这是通过HTML表单完成的,本组件专注于处理“往复于你的程序和客户端之间”的数据,无论这些数据是来自于常规表单,还是来自于一个API。
你可以通过下述两种方式安装:
通过Composer安装(Packagist 上的 symfony/form
)
通过官方Git宝库(https://github.com/symfony/form)
然后,包容 vendor/autoload.php
文件,以开启Composer提供的自动加载机制。否则,你的程序将无法找到这个Symfony组件的类。
如果你使用的是完整版Symfony,框架已经帮你把表单组件配好了。若是如此,可直接跳到 创建一个简易表单。
在Symfony中,表单被打造成对象,这些对象的构建则是通过一个form factory 来完成。创建一个表单是很简单的
1 2 3 | use Symfony\Component\Form\Forms;
$formFactory = Forms::createFormFactory(); |
工厂(factory)可用于创建基本表单,但对一些极为重要的功能却缺少支持:
处理请求: 支持处理请求和文件上传;
CSRF保护: 支持对跨站攻击(Cross-Site-Request-Forgery)的防护;
模板: 整合模板层,让你在渲染模板时能够复用HTML片段;
翻译: 支持对报错信息、字段标签以及其他字符串的翻译;
验证: 整合了验证库,用于提交的信息生成验证失败的信息;
Symfony表单组件依赖于其他类库以解决上述问题。多数时候,你会利用Twig和Symfony的 HttpFoundation组件、Translation组件、Validator组件,但如果愿意,你也可以选择其他类库来替换它们。
下面诸小节解释了如何把这些类库整合到form factory之中。
如果需要一个可运行的表单示例,请参考 https://github.com/webmozart/standalone-forms。
为了处理表单数据,你需要调用 handleRequest()
方法:
1 | $form->handleRequest(); |
在这个方法的背后,是基于表单中所配置的HTTP方法(默认是POST)来借助正确的PHP超全局变量($_POST
或$_GET
),进而使用 NativeRequestHandler
对象来读取数据。
如果你需要精准控制表单的提交连同其数据,可以使用 submit()
方法。更多内容请参考 如何使用submit()函数来处理表单提交。
防护CSRF攻击,已被内建于表单组件,但你需要显式开启之,或者用定制方法来替代它。如果你要使用内建的防护,执行 composer require symfony/security-csrf
命令,即可包容Security CSRF Component。(译注:此为Security四大子组件之一。本文多半篇幅讲的是组件之间的整合,这是以单独使用诸多组件为前提的,若是使用完整版框架,就什么都不用管了……但Symfony组件的最大优势就是可以单独使用在任何程序中)
下面的例程向form factory中添加了CSRF保护:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | use Symfony\Component\Form\Forms;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Form\Extension\Csrf\CsrfExtension;
use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage;
use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator;
use Symfony\Component\Security\Csrf\CsrfTokenManager;
// create a Session object from the HttpFoundation component
// 从HttpFoundation组件中创建一个Session对象
$session = new Session();
$csrfGenerator = new UriSafeTokenGenerator();
$csrfStorage = new SessionTokenStorage($session);
$csrfManager = new CsrfTokenManager($csrfGenerator, $csrfStorage);
$formFactory = Forms::createFormFactoryBuilder()
// ...
->addExtension(new CsrfExtension($csrfStorage))
->getFormFactory(); |
从内部看,该扩展将自动添加一个隐藏字段到每一个表单(字段名称默认是_token
),而字段值是通过CSRF generator自动生成的,表单在binding时,这个字段要被验证通过才行。
如果你并未使用HttpFoundation组件,你可以用 NativeSessionTokenStorage
来替代(上述代码中的SessionTokenStorage),它利用的是PHP原生session处理机制:
1 2 3 4 | use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage;
$csrfStorage = new NativeSessionTokenStorage();
// ... |
如果你正在使用Form组件来处理HTML表单,你需要某种方式,来轻松把你的表单对象渲染成HTML表单字段(包括字段value、字段error、字段label)。如果你愿意使用 Twig 来作为模板引擎的话,表单组件给出的整合方案(在操作表单时)是多种多样的。
使用 TwigBridge
来进行整合,而桥的作用就是在Twig和多个Symfony组件之间进行整合。若你在用Composerd,可以安装最新的3.x版,把下面的 require
命令添加到你的 composer.json
文件中:
1 2 3 4 5 | {
"require": {
"symfony/twig-bridge": "~3.0"
}
} |
TwigBridge的整合,为你带来了一些 Twig函数,用于帮你渲染每个字段所需的HTML widget(表单小部件)、label和error信息(还有其他一些东东也包括在内)。要配置整合,你需要启动(bootstrap)或访问(access)Twig,然后添加 FormExtension
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | use Symfony\Component\Form\Forms;
use Symfony\Bridge\Twig\Extension\FormExtension;
use Symfony\Bridge\Twig\Form\TwigRenderer;
use Symfony\Bridge\Twig\Form\TwigRendererEngine;
// the Twig file that holds all the default markup for rendering forms
// this file comes with TwigBridge
// TwigBridge自带的、拥有表单渲染所需的全部默认的HTML标记
$defaultFormTheme = 'form_div_layout.html.twig';
$vendorDir = realpath(__DIR__.'/../vendor');
// the path to TwigBridge library so Twig can locate the
// form_div_layout.html.twig file
// TwigBride类库的路径,用于让Twig锁定form_div_layout.html.twig文件
$appVariableReflection = new \ReflectionClass('\Symfony\Bridge\Twig\AppVariable');
$vendorTwigBridgeDir = dirname($appVariableReflection->getFileName());
// the path to your other templates
// 你自己的模板之路径
$viewsDir = realpath(__DIR__.'/../views');
$twig = new Twig_Environment(new Twig_Loader_Filesystem(array(
$viewsDir,
$vendorTwigBridgeDir.'/Resources/views/Form',
)));
$formEngine = new TwigRendererEngine(array($defaultFormTheme));
$formEngine->setEnvironment($twig);
// ... (see the previous CSRF Protection section for more information)
// ...(参考前述CSRF防护小节以了解更多)
// add the FormExtension to Twig
// 添加FormExtension到Twig
$twig->addExtension(
new FormExtension(new TwigRenderer($formEngine, $csrfManager))
);
// create your form factory as normal
// 按照常规创建form factory
$formFactory = Forms::createFormFactoryBuilder()
// ...
->getFormFactory(); |
Twig配置 的确切细节是多种多样的,但最终目标都是添加 FormExtension
给Twig,这个扩展能够让你访问Twig函数以便渲染表单(译注:render被译为渲染,是指把表单对象在模板中进行精确输出)。要实现目标,你首先要创建一个 TwigRendererEngine
,这里是你定义 form themes 表单主题的地方(即是一些定义了表单的HTML标记块的资源或文件)。
渲染表单时的完整细节请参考 如何自定义表单渲染
如果你在表单组件中整合了Twig,下面的翻译小节中,有“表单翻译”所需的translation filters相关内容。(译注:Twig所说的filter,就是传统smarty模板中的变量调节)
如果你整合了Twig到表单组件,并且使用了默认的“表单主题”文件之一(如form_div_layout.html.twig
),可以使用两个Twig调节器(trans
和 transChoice
),它们可以用于翻译表单的labels、errors、选项文本(译注:choices类型的)和其他字符串。
要添加这俩Twig filters,你既可以使用内置的整合了Symfony的Translation组件的 TranslationExtension
,也可以通过创建Twig Extention(Twig扩展)来自行添加之。
若使用内建的翻译扩展,确保你在项目中安装了Symfony的Translation组件和 Config 组件。使用Composer时,你可以安装最新的3.x版组件,只需在composer.json
文件中添加下列代码:
1 2 3 4 5 6 | {
"require": {
"symfony/translation": "~3.0",
"symfony/config": "~3.0"
}
} |
下一步,添加 TranslationExtension
到Twig_Enviroment
实例中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | use Symfony\Component\Form\Forms;
use Symfony\Component\Translation\Translator;
use Symfony\Component\Translation\Loader\XliffFileLoader;
use Symfony\Bridge\Twig\Extension\TranslationExtension;
// create the Translator 创建Translator
$translator = new Translator('en');
// somehow load some translations into it 加载翻译信息源到translator
$translator->addLoader('xlf', new XliffFileLoader());
$translator->addResource(
'xlf',
__DIR__.'/path/to/translations/messages.en.xlf',
'en'
);
// add the TranslationExtension (gives us trans and transChoice filters)
// 添加Twig模板所需之TranslationExtension(以便能够使用|trans和|transChoice调节器)
$twig->addExtension(new TranslationExtension($translator));
$formFactory = Forms::createFormFactoryBuilder()
// ...
->getFormFactory(); |
根据所加载的翻译源信息之种类不同,现在你已经能够添加字符串的keys,比如表单labels等,连同其翻译信息,到你的翻译源文件中。
更多翻译内容请参考:翻译。
表单组件中紧密整合(但却是可选的)Symfony的Validator组件。你要使用其他验证方案,也没问题!只需把表单(可以是form type对象,或数组)中“已提交的/已绑定的”数据,传入你自己的验证体系。
如果选择Symfony的Validator组件,先安装它。你可以使用Composer来安装最新的3.x版,把它添加到 composer.json
中:
1 2 3 4 5 | {
"require": {
"symfony/validator": "~3.0"
}
} |
如果你对Symfony的Validator组件还不很熟悉的话,请参考 Validation 章节。表单组件内置了一个 ValidatorExtension
类,自动地将验证应用到你的已绑定数据上。然后,(验证未通过的)错误信息将被映射到相应字段,整合好的表单验证可能是下面代码这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | use Symfony\Component\Form\Forms;
use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
use Symfony\Component\Validator\Validation;
$vendorDir = realpath(__DIR__.'/../vendor');
$vendorFormDir = $vendorDir.'/symfony/form';
$vendorValidatorDir = $vendorDir.'/symfony/validator';
// create the validator - details will vary
// 创建一个validator - 具体可以不同
$validator = Validation::createValidator();
// there are built-in translations for the core error messages
// 对于核心错误信息已经有内置的翻译
$translator->addResource(
'xlf',
$vendorFormDir.'/Resources/translations/validators.en.xlf',
'en',
'validators'
);
$translator->addResource(
'xlf',
$vendorValidatorDir.'/Resources/translations/validators.en.xlf',
'en',
'validators'
);
$formFactory = Forms::createFormFactoryBuilder()
// ...
->addExtension(new ValidatorExtension($validator))
->getFormFactory(); |
可以跳到 表单验证 小节以了解更多。
你的程序仅仅需要一个form factory,然后一个工厂对象被用于创建程序所需的“任意、全部”的表单对象。这着你应该在程序中的一些中心点和起始点来创建工厂,然后可以在任何需要构建表单的时候来访问它。
在这个文档中,表单工厂始终是一个本地变量,被称为 $formFactory
。这表明,你也许需要以一种更加“全局化(global)”的方式来创建该对象,以便能够在任何地方来访问它。
具体如何来访问form factory取决于你自己。如果你使用了服务容器(比如 DependencyInjection组件),你应该把表单工厂添加到容器中,然后在你需要时取得。如果你的程序使用的是全局或静态变量(这不是什么好方案),那么你可以存储工厂对象到静态类或是类似的东西中。
若你使用了Symfony框架,表单工厂已经被自动注册成 form.factory
服务。同时,默认的控制器基类(base controller class)提供了一个 createFormBuilder()
快捷方法,用于取出form factory并调用其 createBuiler
。
FormBuilder
对象负责创建表单,它允许你构建和配置不同的字段。而form builder本身是被form factory创建的。
1 2 3 4 5 6 7 8 9 10 11 12 13 | use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
// ...
$form = $formFactory->createBuilder()
->add('task', TextType::class)
->add('dueDate', DateType::class)
->getForm();
var_dump($twig->render('new.html.twig', array(
'form' => $form->createView(),
))); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // src/Acme/TaskBundle/Controller/DefaultController.php
namespace Acme\TaskBundle\Controller;
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;
class DefaultController extends Controller
{
public function newAction(Request $request)
{
// createFormBuilder is a shortcut to get the "form factory"
// and then call "createBuilder()" on it
$form = $this->createFormBuilder()
->add('task', TextType::class)
->add('dueDate', DateType::class)
->getForm();
return $this->render('AcmeTaskBundle:Default:new.html.twig', array(
'form' => $form->createView(),
));
}
} |
如同你看到的,创建表单就像写一个食谱:对每一个你想建立的字段调用 add()
方法。add()
的第一个参数是字段名,第二个则是(译注:该字段对象的)FQCN类名。表单组件拥有大量的 内置表单类型。
现在,你构造出自己的表单,下面要学习如何把它 渲染出来 以及 处理表单提交。
如果你想让表单带出一些默认值(或者你在构建一个“编辑”表单时),直接把这些默认值传入表单的builder即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
// ...
$defaults = array(
'dueDate' => new \DateTime('tomorrow'),
);
$form = $formFactory->createBuilder(FormType::class, $defaults)
->add('task', TextType::class)
->add('dueDate', DateType::class)
->getForm(); |
1 2 3 4 5 6 7 8 9 10 11 12 13 | use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
// ...
$defaults = array(
'dueDate' => new \DateTime('tomorrow'),
);
$form = $this->createFormBuilder($defaults)
->add('task', TextType::class)
->add('dueDate', DateType::class)
->getForm(); |
本例中,表单的默认数据是一个数组。后面,你可以使用 data_class 选项来直接绑定数据到form type对象中,默认数据化身为那个表单对象的实例。
现在,表单已经创建完成,下一步是渲染它。这是通过传递一个特殊的“view”对象到你的模板中来完成的(注意上面代码中,控制器的 $form->createView()
方法),然后使用一组(Twig提供的)表单helper函数:
1 2 3 4 5 | {{ form_start(form) }}
{{ form_widget(form) }}
<input type="submit" />
{{ form_end(form) }} |
就是这样!使用 form_widget(form)
时,表单中的每个字段都会被渲染,连同其label和错误信息(如果有的话)等。这虽然简单,尚且缺乏灵活性(暂时)。通常,你想要独立渲染每一个表单字段,以便控制它们的呈现样式。你可以在学习如何在模板中输出表单。
默认情况是,表单以HTTP POST方式提交到“渲染它的URI”中去。这个行为可以用 action 和 method 选项来改变(mehtod
选项也适用于 handleRequest()
方法,以决定表单是否被提交):
1 2 3 4 5 6 7 8 9 10 | use Symfony\Component\Form\Extension\Core\Type\FormType;
// ...
$formBuilder = $formFactory->createBuilder(FormType::class, null, array(
'action' => '/search',
'method' => 'GET',
));
// ... |
1 2 3 4 5 6 7 8 9 10 11 12 13 | use Symfony\Component\Form\Extension\Core\Type\FormType;
// ...
public function searchAction()
{
$formBuilder = $this->createFormBuilder(null, array(
'action' => '/search',
'method' => 'GET',
));
// ...
} |
处理表单的提交,用的是 handleRequest()
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
// ...
$form = $formFactory->createBuilder()
->add('task', TextType::class)
->add('dueDate', DateType::class)
->getForm();
$request = Request::createFromGlobals();
$form->handleRequest($request);
if ($form->isValid()) {
$data = $form->getData();
// ... perform some action, such as saving the data to the database
// ... 进行一些操作,比如把数据存到数据库中
$response = new RedirectResponse('/task/success');
$response->prepare($request);
return $response->send();
}
// ... |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
// ...
public function newAction(Request $request)
{
$form = $this->createFormBuilder()
->add('task', TextType::class)
->add('dueDate', DateType::class)
->getForm();
$form->handleRequest($request);
if ($form->isValid()) {
$data = $form->getData();
// ... perform some action, such as saving the data to the database
// ... 进行一些操作,比如把数据存到数据库中
return $this->redirectToRoute('task_success');
}
// ...
} |
这里定义了一个普通表单的“工作流”,包括以下3种可能性:
如果请求是POST,对提交过来的数据进行处理(通过 handleRequest()
方法),然后:
如果表单是无效的,重新渲染表单(此时的表单会包含错误信息)
如果表单有效,进行某些操作,然后重定向。
幸运的是,你毋需判断表单是否被提交,只要把当前的request传到 handleRequest()
方法即可。表单组件会帮你打点剩下的必要工作。
为你的表单添加验证的最简单办法,就是在绑定每一个字段(到表单对象)时,使用 constraints
选项:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Type;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
$form = $formFactory->createBuilder()
->add('task', TextType::class, array(
'constraints' => new NotBlank(),
))
->add('dueDate', DateType::class, array(
'constraints' => array(
new NotBlank(),
new Type('\DateTime'),
)
))
->getForm(); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Type;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
$form = $this->createFormBuilder()
->add('task', TextType::class, array(
'constraints' => new NotBlank(),
))
->add('dueDate', DateType::class, array(
'constraints' => array(
new NotBlank(),
new Type('\DateTime'),
)
))
->getForm(); |
要查看全部的内置validation constraints之列表,请移步 Validation Constraints Reference 验证约束参考。
你可以使用 getErrors()
方法来获取错误列表。该方法返回的是 FormErrorIterator
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | $form = ...;
// ...
// a FormErrorIterator instance, but only errors attached to this
// form level (e.g. "global errors)
// FormErrorIterator实例,包含了整个表单(form level)的错误
$errors = $form->getErrors();
// a FormErrorIterator instance, but only errors attached to the
// "firstName" field
// FormErrorIterator实例,仅含有指定字段的错误
$errors = $form['firstName']->getErrors();
// a FormErrorIterator instance in a flattened structure
// use getOrigin() to determine the form causing the error
// FormErrorIterator实例,是扁平数组结构
// 使用getOrigin()方法来找出导致错误的表单
$errors = $form->getErrors(true);
// a FormErrorIterator instance representing the form tree structure
// FormErrorIterator实例,以表单树状结构来呈现
$errors = $form->getErrors(true, false); |
本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。