跳到内容

表单组件

编辑此页

表单组件允许你创建、处理和复用表单。

表单组件是一个工具,可以帮助你解决最终用户与应用程序中的数据交互并修改数据的问题。虽然传统上这是通过 HTML 表单实现的,但该组件侧重于处理来自和发送到客户端及应用程序的数据,无论该数据是来自普通的表单 POST 还是来自 API。

安装

1
$ composer require symfony/form

注意

如果在 Symfony 应用程序之外安装此组件,则必须在代码中 require vendor/autoload.php 文件,以启用 Composer 提供的类自动加载机制。阅读 这篇文章 以获取更多详细信息。

配置

另请参阅

本文介绍了如何在任何 PHP 应用程序中将 Form 功能用作独立组件。阅读 表单 文章,了解如何在 Symfony 应用程序中使用它。

在 Symfony 中,表单由对象表示,这些对象通过使用表单工厂构建。构建表单工厂通过工厂方法 Forms::createFormFactory 完成

1
2
3
use Symfony\Component\Form\Forms;

$formFactory = Forms::createFormFactory();

此工厂已可用于创建基本表单,但它缺少对非常重要功能的支持

  • 请求处理: 支持请求处理和文件上传;
  • CSRF 保护: 支持防止跨站请求伪造 (CSRF) 攻击;
  • 模板: 与模板层的集成,允许你在渲染表单时重用 HTML 片段;
  • 翻译: 支持翻译错误消息、字段标签和其他字符串;
  • 验证: 与验证库集成,为提交的数据生成错误消息。

Symfony 表单组件依赖于其他库来解决这些问题。大多数时候,你将使用 Twig 和 Symfony 的 HttpFoundationTranslationValidator 组件,但你可以将其中任何一个替换为你选择的不同库。

以下部分解释了如何将这些库插入到表单工厂中。

提示

有关工作示例,请参阅 https://github.com/webmozart/standalone-forms

请求处理

要处理表单数据,你需要调用 handleRequest() 方法

1
$form->handleRequest();

在幕后,这使用 NativeRequestHandler 对象,根据表单上配置的 HTTP 方法(默认为 POST)从正确的 PHP 超全局变量(即 $_POST$_GET)中读取数据。

另请参阅

如果你需要更精确地控制表单何时提交或传递给表单的数据,请使用 submit() 方法来处理表单提交

如果你使用 HttpFoundation 组件,那么你应该将 HttpFoundationExtension 添加到你的表单工厂

1
2
3
4
5
6
use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationExtension;
use Symfony\Component\Form\Forms;

$formFactory = Forms::createFormFactoryBuilder()
    ->addExtension(new HttpFoundationExtension())
    ->getFormFactory();

现在,当你处理表单时,你可以将 Request 对象传递给 handleRequest()

1
$form->handleRequest($request);

注意

有关 HttpFoundation 组件或如何安装它的更多信息,请参阅 HttpFoundation 组件

CSRF 保护

防止 CSRF 攻击的保护已内置于 Form 组件中,但你需要显式启用它或用自定义解决方案替换它。如果你想使用内置支持,请首先安装 Security CSRF 组件

1
$ composer require symfony/security-csrf

以下代码片段将 CSRF 保护添加到表单工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use Symfony\Component\Form\Extension\Csrf\CsrfExtension;
use Symfony\Component\Form\Forms;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Csrf\CsrfTokenManager;
use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator;
use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage;

// creates a RequestStack object using the current request
$requestStack = new RequestStack([$request]);

$csrfGenerator = new UriSafeTokenGenerator();
$csrfStorage = new SessionTokenStorage($requestStack);
$csrfManager = new CsrfTokenManager($csrfGenerator, $csrfStorage);

$formFactory = Forms::createFormFactoryBuilder()
    // ...
    ->addExtension(new CsrfExtension($csrfManager))
    ->getFormFactory();

7.2

Symfony 7.2 中引入了支持将请求传递给 RequestStack 类的构造函数。

在内部,此扩展将自动向每个表单添加一个隐藏字段(默认情况下称为 _token),其值由 CSRF 生成器自动生成,并在绑定表单时进行验证。

提示

如果你不使用 HttpFoundation 组件,你可以使用 NativeSessionTokenStorage 代替,它依赖于 PHP 的原生会话处理

1
2
3
4
use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage;

$csrfStorage = new NativeSessionTokenStorage();
// ...

你可以使用 csrf_protection 选项禁用每个表单的 CSRF 保护

1
2
3
4
use Symfony\Component\Form\Extension\Core\Type\FormType;

$form = $formFactory->createBuilder(FormType::class, null, ['csrf_protection' => false])
    ->getForm();

Twig 模板

如果你正在使用 Form 组件来处理 HTML 表单,你需要一种将表单渲染为 HTML 表单字段的方法(包括字段值、错误和标签)。如果你使用 Twig 作为你的模板引擎,Form 组件提供了丰富的集成。

要使用集成,你需要 twig bridge,它提供了 Twig 和多个 Symfony 组件之间的集成

1
$ composer require symfony/twig-bridge

TwigBridge 集成为你提供了多个 Twig 函数,这些函数可以帮助你渲染每个字段的 HTML 小部件、标签、帮助和错误(以及其他一些东西)。要配置集成,你需要引导或访问 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\Bridge\Twig\Extension\FormExtension;
use Symfony\Bridge\Twig\Form\TwigRendererEngine;
use Symfony\Component\Form\FormRenderer;
use Symfony\Component\Form\Forms;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
use Twig\RuntimeLoader\FactoryRuntimeLoader;

// the Twig file that holds all the default markup for rendering forms
// this file comes with TwigBridge
$defaultFormTheme = 'form_div_layout.html.twig';

$vendorDirectory = realpath(__DIR__.'/../vendor');
// the path to TwigBridge library so Twig can locate the
// form_div_layout.html.twig file
$appVariableReflection = new \ReflectionClass('\Symfony\Bridge\Twig\AppVariable');
$vendorTwigBridgeDirectory = dirname($appVariableReflection->getFileName());
// the path to your other templates
$viewsDirectory = realpath(__DIR__.'/../views');

$twig = new Environment(new FilesystemLoader([
    $viewsDirectory,
    $vendorTwigBridgeDirectory.'/Resources/views/Form',
]));
$formEngine = new TwigRendererEngine([$defaultFormTheme], $twig);
$twig->addRuntimeLoader(new FactoryRuntimeLoader([
    FormRenderer::class => function () use ($formEngine, $csrfManager): FormRenderer {
        return new FormRenderer($formEngine, $csrfManager);
    },
]));

// ... (see the previous CSRF Protection section for more information)

// adds the FormExtension to Twig
$twig->addExtension(new FormExtension());

// creates a form factory
$formFactory = Forms::createFormFactoryBuilder()
    // ...
    ->getFormFactory();

你的 Twig 配置 的确切细节会有所不同,但目标始终是将 FormExtension 添加到 Twig,这使你可以访问用于渲染表单的 Twig 函数。为此,你首先需要创建一个 TwigRendererEngine,你可以在其中定义你的 表单主题(即定义表单 HTML 标记的资源/文件)。

有关渲染表单的常规详细信息,请参阅 如何自定义表单渲染

注意

如果你使用 Twig 集成,请阅读下面的“表单组件”,了解所需翻译过滤器的详细信息。

翻译

如果你将 Twig 集成与默认表单主题文件之一(例如 form_div_layout.html.twig)一起使用,则有一个 Twig 过滤器(trans)用于翻译表单标签、错误、选项文本和其他字符串。

要添加 trans Twig 过滤器,你可以使用内置的 TranslationExtension,它与 Symfony 的 Translation 组件集成,或者通过你自己的 Twig 扩展自己添加 Twig 过滤器。

要使用内置集成,请确保你的项目已安装 Symfony 的 Translation 和 Config 组件

1
$ composer require symfony/translation symfony/config

接下来,将 TranslationExtension 添加到你的 Twig\Environment 实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use Symfony\Bridge\Twig\Extension\TranslationExtension;
use Symfony\Component\Form\Forms;
use Symfony\Component\Translation\Loader\XliffFileLoader;
use Symfony\Component\Translation\Translator;

// creates the Translator
$translator = new Translator('en');
// somehow load some translations into it
$translator->addLoader('xlf', new XliffFileLoader());
$translator->addResource(
    'xlf',
    __DIR__.'/path/to/translations/messages.en.xlf',
    'en'
);

// adds the TranslationExtension (it gives us trans filter)
$twig->addExtension(new TranslationExtension($translator));

$formFactory = Forms::createFormFactoryBuilder()
    // ...
    ->getFormFactory();

根据你的翻译加载方式,你现在可以将字符串键(例如字段标签)及其翻译添加到你的翻译文件中。

有关翻译的更多详细信息,请参阅 翻译

验证

Form 组件与 Symfony 的 Validator 组件紧密集成(但可选)。如果你使用不同的验证解决方案,没问题!获取表单的提交/绑定数据(这是一个数组或对象),并将其传递到你自己的验证系统中。

要使用与 Symfony 的 Validator 组件的集成,请首先确保它已安装在你的应用程序中

1
$ composer require symfony/validator

如果你不熟悉 Symfony 的 Validator 组件,请阅读更多相关信息:验证。Form 组件带有一个 ValidatorExtension 类,它会在绑定时自动将验证应用于你的数据。这些错误随后将映射到正确的字段并呈现。

你与 Validation 组件的集成将如下所示

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
use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
use Symfony\Component\Form\Forms;
use Symfony\Component\Validator\Validation;

$vendorDirectory = realpath(__DIR__.'/../vendor');
$vendorFormDirectory = $vendorDirectory.'/symfony/form';
$vendorValidatorDirectory = $vendorDirectory.'/symfony/validator';

// creates the validator - details will vary
$validator = Validation::createValidator();

// there are built-in translations for the core error messages
$translator->addResource(
    'xlf',
    $vendorFormDirectory.'/Resources/translations/validators.en.xlf',
    'en',
    'validators'
);
$translator->addResource(
    'xlf',
    $vendorValidatorDirectory.'/Resources/translations/validators.en.xlf',
    'en',
    'validators'
);

$formFactory = Forms::createFormFactoryBuilder()
    // ...
    ->addExtension(new ValidatorExtension($validator))
    ->getFormFactory();

要了解更多信息,请跳至 表单组件 部分。

访问表单工厂

你的应用程序只需要一个表单工厂,并且应该使用这一个工厂对象来创建应用程序中的任何和所有表单对象。这意味着你应在应用程序的某些中央引导部分中创建它,然后在需要构建表单时访问它。

注意

在本文档中,表单工厂始终是一个名为 $formFactory 的局部变量。这里的重点是,你可能需要在更“全局”的方式中创建此对象,以便你可以从任何地方访问它。

确切如何访问你的一个表单工厂取决于你。如果你使用服务容器(例如 DependencyInjection 组件 提供的服务容器),那么你应该将表单工厂添加到你的容器中,并在需要时取出它。如果你的应用程序使用全局或静态变量(通常不是一个好主意),那么你可以将该对象存储在某些静态类上或执行类似的操作。

创建一个简单的表单

提示

如果你使用 Symfony 框架,则表单工厂会自动作为名为 form.factory 的服务提供,你可以将其注入为 Symfony\Component\Form\FormFactoryInterface。此外,默认的基础控制器类具有一个 createFormBuilder() 方法,它是获取表单工厂并在其上调用 createBuilder() 的快捷方式。

创建表单是通过 FormBuilder 对象完成的,你可以在其中构建和配置不同的字段。表单构建器是从表单工厂创建的。

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
// src/Controller/TaskController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class TaskController extends AbstractController
{
    public function new(Request $request): Response
    {
        // 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('task/new.html.twig', [
            'form' => $form->createView(),
        ]);
    }
}

如你所见,创建表单就像编写食谱:对于要创建的每个新字段,你都调用 add()add() 的第一个参数是你的字段名称,第二个参数是完全限定的类名。Form 组件附带许多 内置类型

现在你已经构建了表单,请学习如何 渲染 它和 处理表单提交

设置默认值

如果你的表单需要加载一些默认值(或者你正在构建“编辑”表单),请在创建表单构建器时传入默认数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/Controller/DefaultController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\Response;

class DefaultController extends AbstractController
{
    public function new(Request $request): Response
    {
        $defaults = [
            'dueDate' => new \DateTime('tomorrow'),
        ];

        $form = $this->createFormBuilder($defaults)
            ->add('task', TextType::class)
            ->add('dueDate', DateType::class)
            ->getForm();

        // ...
    }
}

提示

在此示例中,默认数据是一个数组。稍后,当你使用 data_class 选项将数据直接绑定到对象时,你的默认数据将是该对象的实例。

渲染表单

现在已经创建了表单,下一步是渲染它。这是通过将特殊的表单“视图”对象传递给你的模板(请注意上面控制器中的 $form->createView()),并使用一组 表单辅助函数 来完成的

1
2
3
4
5
{{ form_start(form) }}
    {{ form_widget(form) }}

    <input type="submit">
{{ form_end(form) }}
An HTML form showing a text box labelled "Task", three select boxes for a year, month and day labelled "Due date" and a button labelled "Create Task".

就是这样!通过打印 form_widget(form),表单中的每个字段都将被渲染,以及标签和错误消息(如果有)。虽然这很方便,但它还不是很灵活(目前)。通常,你希望单独渲染每个表单字段,以便你可以控制表单的外观。你将在 表单自定义 文章中学习如何做到这一点。

更改表单的方法和动作

默认情况下,表单以 HTTP POST 请求提交到渲染表单的同一 URI。可以使用 FormType 字段FormType 字段 选项更改此行为(method 选项也由 handleRequest() 使用,以确定是否已提交表单)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/Controller/DefaultController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\HttpFoundation\Response;

class DefaultController extends AbstractController
{
    public function search(): Response
    {
        $formBuilder = $this->createFormBuilder(null, [
            '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
29
30
// src/Controller/TaskController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\Response;

class TaskController extends AbstractController
{
    public function new(Request $request): Response
    {
        $form = $this->createFormBuilder()
            ->add('task', TextType::class)
            ->add('dueDate', DateType::class)
            ->getForm();

        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $data = $form->getData();

            // ... perform some action, such as saving the data to the database

            return $this->redirectToRoute('task_success');
        }

        // ...
    }
}

警告

表单的 createView() 方法应在调用 handleRequest() 之后 调用。否则,当使用 表单事件 时,在 *_SUBMIT 事件中所做的更改将不会应用于视图(例如验证错误)。

这定义了一个常见的表单“工作流程”,其中包含 3 种不同的可能性

  1. 在初始 GET 请求时(即当用户“浏览”到你的页面时),构建你的表单并渲染它;

    如果请求是 POST,则处理提交的数据(通过 handleRequest())。

    然后

  2. 如果表单无效,则重新渲染表单(现在将包含错误);
  3. 如果表单有效,则执行某些操作并重定向。

幸运的是,你不需要决定表单是否已被提交。只需将当前请求传递给 handleRequest() 方法。然后,Form 组件将为你完成所有必要的工作。

表单验证

向表单添加验证的最简单方法是在构建每个字段时使用 constraints 选项

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
// src/Controller/DefaultController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Type;

class DefaultController extends AbstractController
{
    public function new(Request $request): Response
    {
        $form = $this->createFormBuilder()
            ->add('task', TextType::class, [
                'constraints' => new NotBlank(),
            ])
            ->add('dueDate', DateType::class, [
                'constraints' => [
                    new NotBlank(),
                    new Type(\DateTime::class),
                ],
            ])
            ->getForm();
        // ...
    }
}

当表单被绑定时,这些验证约束将自动应用,错误将显示在出错字段旁边。

注意

有关所有内置验证约束的列表,请参阅验证约束参考

访问表单错误

你可以使用 getErrors() 方法来访问错误列表。它返回一个 FormErrorIterator 实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$form = ...;

// ...

// a FormErrorIterator instance, but only errors attached to this
// form level (e.g. global errors)
$errors = $form->getErrors();

// a FormErrorIterator instance, but only errors attached to the
// "firstName" field
$errors = $form['firstName']->getErrors();

// a FormErrorIterator instance including child forms in a flattened structure
// use getOrigin() to determine the form causing the error
$errors = $form->getErrors(true);

// a FormErrorIterator instance including child forms without flattening the output structure
$errors = $form->getErrors(true, false);

清除表单错误

可以使用 clearErrors() 方法手动清除任何错误。 当你想在不向用户显示验证错误的情况下验证表单时(例如在部分 AJAX 提交或 动态表单修改 期间),这非常有用。

由于清除错误会使表单有效,因此只有在测试表单是否有效之后才能调用 clearErrors()

这项工作,包括代码示例,根据 Creative Commons BY-SA 3.0 许可获得许可。
目录
    版本