> Symfony中文手册 > 表单事件

表单事件

Form组件提供了一个结构化的处理过程,让你利用EventDispatcher组件,来定制你的表单。使用表单事件,你可以在工作流的不同阶段,修改某些信息或字段:从表单的加载,到利用请求提交数据。

使用表单组件的话,注册一个事件监听(event listener)是非常容易的。

例如,你想要注册一个方法给FormEvents::PRE_SUBMIT事件,则下面的代码可以让你根据request值的不同,而添加一个字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ...
 
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
 
$listener = function (FormEvent $event) {
    // ...
};
 
$form = $formFactory->createBuilder()
    // add form fields 添加字段
    ->addEventListener(FormEvents::PRE_SUBMIT, $listener);
 
// ...

表单的工作流 ¶

表单提交工作流 ¶

1)预加载表单(FormEvents::PRE_SET_DATA和FormEvents::POST_SET_DATA) ¶

Form::setData()被调用时,有两个事件在表单的pre-population预加载过程中被派遣:FormEvents::PRE_SET_DATAFormEvents::POST_SET_DATA

A)FormEvents::PRE_SET_DATA事件 ¶

FormEvents::PRE_SET_DATA事件,在Form::setData()方法的最初阶段被派遣。它可以用于:

  • 对预加载过程中给定的数据进行调整;

  • 根据预加载的数据来调整表单(动态添加/删除字段)。

Data Type(数据类型) Value(值)
Model data null
Normalized data null
View data null

可在表单事件信息表中找到完整信息。

FormEvents::PRE_SET_DATA期间,Form::setData()方法被锁定,如果使用该方法则会抛出异常 。如果你需要修改数据,应该使用FormEvent::setData()来替代。

表单组件中的FormEvents::PRE_SET_DATA

表单类型中的collection,依赖的是Symfony\Component\Form\Extension\Core\EventListener\ResizeFormListener订阅器,用于监听FormEvents::PRE_SET_DATA事件——这是为了能够“通过移除和添加全部的表单行,并且根据从预加载对象中的数据,来对表单字段重新排序”。

B)FormEvents::POST_SET_DATA事件 ¶

FormEvents::POST_SET_DATA事件在Form::setData()方法的最末阶段被派遣。这个事件往往被用于“在得到预加载表单之后”来读取数据。

Data Type(数据类型) Value(取值)
Model data 注入到setData()的Model data
Normalized data 通过model transformer转换而成的odel data
View data 通过view transformer转换而成的ormalized data

可在表单事件信息表中找到完整信息。

表单组件中的FormEvents::POST_SET_DATA

Symfony\Component\Form\Extension\DataCollector\EventListener\DataCollectorListener类是个subscriber,用于监听FormEvents::POST_SET_DATA事件,为的是收集关于表单“从denormalized model到view data”过程中的数据。 ``

2)提交表单(FormEvents::PRE_SUBMIT, FormEvents::SUBMIT和FormEvents::POST_SUBMIT) ¶

Form::handleRequest()Form::submit()方法被调用时,三个事件被派遣:FormEvents::PRE_SUBMITFormEvents::SUBMITFormEvents::POST_SUBMIT

A)FormEvents::PRE_SUBMIT事件 ¶

FormEvents::PRE_SUBMIT事件在Form::submit()方法的最初阶段被派遣。

它可以被用于:

  • 改变request中的数据,在它们被提交到表单之前;

  • 添加或删除字段,在数据被提交到表单之前。

Data Type(数据类型) Value(取值)
Model data 等同于FormEvents::POST_SET_DATA中的
Normalized data 等同于FormEvents::POST_SET_DATA中的
View data 等同于FormEvents::POST_SET_DATA中的

可在表单事件信息表中找到完整信息。

表单组件中的FormEvents::PRE_SUBMIT

Symfony\Component\Form\Extension\Core\EventListener\TrimListener订阅器订阅的是 FormEvents::PRE_SUBMIT事件,为的是去除request中的数据的空格(trim,仅对字符串值)。Symfony\Component\Form\Extension\Csrf\EventListener\CsrfValidationListener订阅器为了验证CSRF token,订阅的也是FormEvents::PRE_SUBMIT事件。

B)FormEvents::SUBMIT事件 ¶

FormEvents::SUBMIT事件被派遣的时间,刚好是在Form::submit()方法把normalized data给转换回model data和view data之前。

它可以被用于改变“从normalized data表现层”得到的数据。

Data Type(数据类型) Value(取值)
Model data 等同于FormEvents::POST_SET_DATA中的
Normalized data 通过view transformer转换而来的reqeust,再被反向转换回reqeust时所包含的数据
View data 等同于FormEvents::POST_SET_DATA中的

可在表单事件信息表中找到完整信息。

此时,你无法添加或移除表单字段。

表单组件中的FormEvents::SUBMIT

Symfony\Component\Form\Extension\Core\EventListener\FixUrlProtocolListener订阅器,监听的是FormEvents::SUBMIT事件,用于给那些“没有protocol”的URL字段,预置一个默认的protocal。

C)FormEvents::POST_SUBMIT事件 ¶

FormEvents::POST_SUBMIT事件的派遣时机,是在Form::submit()方法一旦将model data和view data的denormalize化完成之后。

它可以用于在denormalization进程之后取出数据。

Data Type(数据类型) Value(取值)
Model data 通过model transformer反向转换而成的Normalized数据
Normalized data 等同于FormEvents::SUBMIT中的
View data 通过view transformer转换而成的normalized data

可在表单事件信息表中找到完整信息。

此时,你无法添加或移除表单字段。

表单组件中的FormEvents::POST_SUBMIT

Symfony\Component\Form\Extension\DataCollector\EventListener\DataCollectorListener,订阅的是FormEvents::PSOT_SUBMIT事件,为的是收集表单信息。Symfony\Component\Form\Extension\Validator\EventListener\ValidationListener订阅FormEvents::POST_SUBMIT则是为了自动验证denormalized对象,同时更新denormalized表现层和view层。

注册事件监听和订阅 ¶

为了能使用表单事件,你需要创建一个监听或订阅,并将它们注册到一个事件。

每一个“表单”事件的名称,都被定义成FormEvents类中的一个常量。而且,每一个事件的callback回调(监听方法/订阅方法)都被当成一个单独的参数传入FormEvent的实例。事件对象包括一个对表单当前状态的引用,以及被处理过的当前数据。

Name(事件名) FormEvents ConstAnt(常量) Event's Data(事件数据)
form.pre_set_data FormEvents::PRE_SET_DATA Model data
form.post_set_data FormEvents::POST_SET_DATA Model data
form.pre_bind FormEvents::PRE_SUBMIT Request data
form.bind FormEvents::SUBMIT Normalized data
form.post_bind FormEvents::POST_SUBMIT View data

Event Listeners(表单监听器) ¶

在表单事件中,一个event listener可以是任何类型的有效回调(callable)。

创建并绑定一个监听给表单,是很容易的:

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
// ...
 
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
 
$form = $formFactory->createBuilder()
    ->add('username', TextType::class)
    ->add('show_email', CheckboxType::class)
    ->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
        $user = $event->getData();
        $form = $event->getForm();
 
        if (!$user) {
            return;
        }
 
        // Check whether the user has chosen to display his email or not.
        // If the data was submitted previously, the additional value that is
        // included in the request variables needs to be removed.
        // 检查用户是否决定显示其email字段
        // 若数据之前被提交过,request对象中所包含的附加值将被移除
        if (true === $user['show_email']) {
            $form->add('email', EmailType::class);
        } else {
            unset($user['email']);
            $event->setData($user);
        }
    })
    ->getForm();
 
// ...

当你使用一个form type类时,使用其自有方法来完成用于监听的回调,可以增加程序的可读性:

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\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
 
// ...
 
class SubscriptionType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('username', TextType::class);
        $builder->add('show_email', CheckboxType::class);
        $builder->addEventListener(
            FormEvents::PRE_SET_DATA,
            array($this, 'onPreSetData')
        );
    }
 
    public function onPreSetData(FormEvent $event)
    {
        // ...
    }
}

Event Subscribers(表单订阅器) ¶

在表单事件中,事件订阅器有不同的作用:

  • 改善代码的可读性;

  • 可以监听多个事件;

  • 在一个类中,重组多个监听(方法)

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
41
42
43
44
45
46
47
48
49
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
 
class AddEmailFieldListener implements EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        return array(
            FormEvents::PRE_SET_DATA => 'onPreSetData',
            FormEvents::PRE_SUBMIT   => 'onPreSubmit',
        );
    }
 
    public function onPreSetData(FormEvent $event)
    {
        $user = $event->getData();
        $form = $event->getForm();
 
        // Check whether the user from the initial data has chosen to
        // display his email or not.
        if (true === $user->isshowEmail()) {
            $form->add('email', EmailType::class);
        }
    }
 
    public function onPreSubmit(FormEvent $event)
    {
        $user = $event->getData();
        $form = $event->getForm();
 
        if (!$user) {
            return;
        }
 
        // Check whether the user has chosen to display his email or not.
        // If the data was submitted previously, the additional value that
        // is included in the request variables needs to be removed.
        // 检查用户是否决定显示其email字段
        // 若数据之前被提交过,request对象中所包含的附加值将被移除
        if (true === $user['show_email']) {
            $form->add('email', EmailType::class);
        } else {
            unset($user['email']);
            $event->setData($user);
        }
    }
}

注册表单订阅,使用form builder中的addEventSubscriber()方法:

1
2
3
4
5
6
7
8
9
10
11
12
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
 
// ...
 
$form = $formFactory->createBuilder()
    ->add('username', TextType::class)
    ->add('show_email', CheckboxType::class)
    ->addEventSubscriber(new AddEmailFieldListener())
    ->getForm();
 
// ...