Skip to content

Commit 64603b1

Browse files
committed
[Form] Introduce validation events
1 parent 0dc7507 commit 64603b1

File tree

6 files changed

+149
-1
lines changed

6 files changed

+149
-1
lines changed

src/Symfony/Component/Form/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CHANGELOG
66

77
* Allow passing `TranslatableInterface` objects to the `ChoiceView` label
88
* Allow passing `TranslatableInterface` objects to the `help` option
9+
* Add `form.pre_validate` and `form.post_validate` events
910

1011
6.1
1112
---
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Extension\Validator\Event;
13+
14+
use Symfony\Component\Form\FormEvent;
15+
16+
/**
17+
* This event is dispatched after the root form validation.
18+
*/
19+
final class PostValidateEvent extends FormEvent
20+
{
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Extension\Validator\Event;
13+
14+
use Symfony\Component\Form\FormEvent;
15+
16+
/**
17+
* This event is dispatched before the root form validation.
18+
*/
19+
final class PreValidateEvent extends FormEvent
20+
{
21+
}

src/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@
1313

1414
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
1515
use Symfony\Component\Form\Extension\Validator\Constraints\Form;
16+
use Symfony\Component\Form\Extension\Validator\Event\PostValidateEvent;
17+
use Symfony\Component\Form\Extension\Validator\ValidatorFormEvents;
1618
use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationMapperInterface;
1719
use Symfony\Component\Form\FormEvent;
1820
use Symfony\Component\Form\FormEvents;
21+
use Symfony\Component\Form\FormInterface;
1922
use Symfony\Component\Validator\Validator\ValidatorInterface;
2023

2124
/**
@@ -26,6 +29,9 @@ class ValidationListener implements EventSubscriberInterface
2629
private ValidatorInterface $validator;
2730
private ViolationMapperInterface $violationMapper;
2831

32+
/** @var FormInterface[][] */
33+
private static array $dispatchEvents = [];
34+
2935
/**
3036
* {@inheritdoc}
3137
*/
@@ -44,7 +50,16 @@ public function validateForm(FormEvent $event)
4450
{
4551
$form = $event->getForm();
4652

53+
// Register events to dispatch during (root form) validation
54+
foreach (ValidatorFormEvents::ALIASES as $eventName) {
55+
if ($form->getConfig()->getEventDispatcher()->hasListeners($eventName)) {
56+
self::$dispatchEvents[$eventName][] = $form;
57+
}
58+
}
59+
4760
if ($form->isRoot()) {
61+
$this->dispatchEvents(ValidatorFormEvents::PRE_VALIDATE);
62+
4863
// Form groups are validated internally (FormValidator). Here we don't set groups as they are retrieved into the validator.
4964
foreach ($this->validator->validate($form) as $violation) {
5065
// Allow the "invalid" constraint to be put onto
@@ -53,6 +68,24 @@ public function validateForm(FormEvent $event)
5368

5469
$this->violationMapper->mapViolation($violation, $form, $allowNonSynchronized);
5570
}
71+
72+
$this->dispatchEvents(ValidatorFormEvents::POST_VALIDATE);
73+
}
74+
}
75+
76+
private function dispatchEvents(string $eventName)
77+
{
78+
if (!isset(self::$dispatchEvents[$eventName])) {
79+
return;
5680
}
81+
82+
$event = array_flip(ValidatorFormEvents::ALIASES)[$eventName];
83+
84+
foreach (self::$dispatchEvents[$eventName] as $form) {
85+
$event = new $event($form, $form->getData());
86+
$form->getConfig()->getEventDispatcher()->dispatch($event, $eventName);
87+
}
88+
89+
unset(self::$dispatchEvents[$eventName]);
5790
}
5891
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Extension\Validator;
13+
14+
use Symfony\Component\Form\Extension\Validator\Event\PostValidateEvent;
15+
use Symfony\Component\Form\Extension\Validator\Event\PreValidateEvent;
16+
17+
final class ValidatorFormEvents
18+
{
19+
/**
20+
* This event is dispatched after validation completes.
21+
*
22+
* @Event("Symfony\Component\Form\Extension\Validator\Event\PreValidateEvent")
23+
*/
24+
const PRE_VALIDATE = 'form.pre_validate';
25+
26+
/**
27+
* This event is dispatched after validation completes.
28+
*
29+
* @Event("Symfony\Component\Form\Extension\Validator\Event\PostValidateEvent")
30+
*/
31+
const POST_VALIDATE = 'form.post_validate';
32+
33+
/**
34+
* Event aliases.
35+
*
36+
* These aliases can be consumed by RegisterListenersPass.
37+
*/
38+
public const ALIASES = [
39+
PreValidateEvent::class => self::PRE_VALIDATE,
40+
PostValidateEvent::class => self::POST_VALIDATE,
41+
];
42+
43+
private function __construct()
44+
{
45+
}
46+
}

src/Symfony/Component/Form/Tests/Extension/Validator/EventListener/ValidationListenerTest.php

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\Form\Extension\Core\DataMapper\DataMapper;
1818
use Symfony\Component\Form\Extension\Validator\Constraints\Form as FormConstraint;
1919
use Symfony\Component\Form\Extension\Validator\EventListener\ValidationListener;
20+
use Symfony\Component\Form\Extension\Validator\ValidatorFormEvents;
2021
use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationMapper;
2122
use Symfony\Component\Form\Form;
2223
use Symfony\Component\Form\FormBuilder;
@@ -73,7 +74,7 @@ protected function setUp(): void
7374
$this->params = ['foo' => 'bar'];
7475
}
7576

76-
private function createForm($name = '', $compound = false)
77+
private function createForm($name = '', $compound = false, $listener = null)
7778
{
7879
$config = new FormBuilder($name, null, new EventDispatcher(), (new FormFactoryBuilder())->getFormFactory());
7980
$config->setCompound($compound);
@@ -82,6 +83,11 @@ private function createForm($name = '', $compound = false)
8283
$config->setDataMapper(new DataMapper());
8384
}
8485

86+
if ($listener) {
87+
$config->addEventListener(ValidatorFormEvents::PRE_VALIDATE, [$listener, 'preValidate']);
88+
$config->addEventListener(ValidatorFormEvents::POST_VALIDATE, [$listener, 'postValidate']);
89+
}
90+
8591
return new Form($config);
8692
}
8793

@@ -136,6 +142,26 @@ public function testValidateWithEmptyViolationList()
136142

137143
$this->assertTrue($form->isValid());
138144
}
145+
146+
public function testEventsAreDispatched()
147+
{
148+
$listenerMock = $this->getMockBuilder(\stdClass::class)->setMethods(['preValidate', 'postValidate'])->getMock();
149+
150+
$childForm = $this->createForm('child', false, $listenerMock);
151+
$form = $this->createForm('', true, $listenerMock);
152+
$form->add($childForm);
153+
154+
$form->submit(['child' => null]);
155+
156+
$this->listener->validateForm(new FormEvent($childForm, null));
157+
158+
// Events are triggered only when the root form is validated
159+
$listenerMock->expects($this->exactly(2))->method('preValidate');
160+
$listenerMock->expects($this->exactly(2))->method('postValidate');
161+
$this->listener->validateForm(new FormEvent($form, null));
162+
163+
$this->assertTrue($form->isValid());
164+
}
139165
}
140166

141167
class SubmittedNotSynchronizedForm extends Form

0 commit comments

Comments
 (0)