8000 [Form] Support event listeners in buttons · symfony/symfony@142fa17 · GitHub
[go: up one dir, main page]

Skip to content

Commit 142fa17

Browse files
committed
[Form] Support event listeners in buttons
1 parent 2b71c6f commit 142fa17

File tree

10 files changed

+232
-27
lines changed

10 files changed

+232
-27
lines changed

UPGRADE-4.4.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ Form
8888
reference date is deprecated.
8989
* Using `int` or `float` as data for the `NumberType` when the `input` option is set to `string` is deprecated.
9090
* Overriding the methods `FormIntegrationTestCase::setUp()`, `TypeTestCase::setUp()` and `TypeTestCase::tearDown()` without the `void` return-type is deprecated.
91+
* The `ButtonBuilder::__construct()` method second argument should be an `EventDispatcherInterface` instance. The existing array `$options` argument has been moved to the third position.
9192

9293
FrameworkBundle
9394
---------------

src/Symfony/Component/Form/Button.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111

1212
namespace Symfony\Component\Form;
1313

14+
use Symfony\Component\EventDispatcher\LegacyEventDispatcherProxy;
15+
use Symfony\Component\Form\Event\PostSetDataEvent;
16+
use Symfony\Component\Form\Event\PostSubmitEvent;
17+
use Symfony\Component\Form\Event\PreSubmitEvent;
1418
use Symfony\Component\Form\Exception\AlreadySubmittedException;
1519
use Symfony\Component\Form\Exception\BadMethodCallException;
1620

@@ -200,6 +204,11 @@ public function getErrors($deep = false, $flatten = true)
200204
*/
201205
public function setData($modelData)
202206
{
207+
$dispatcher = LegacyEventDispatcherProxy::decorate($this->config->getEventDispatcher());
208+
if ($dispatcher->hasListeners(FormEvents::POST_SET_DATA)) {
209+
$dispatcher->dispatch(new PostSetDataEvent($this, $modelData), FormEvents::POST_SET_DATA);
210+
}
211+
203212
// no-op, called during initialization of the form tree
204213
return $this;
205214
}
@@ -384,8 +393,17 @@ public function submit($submittedData, $clearMissing = true)
384393
throw new AlreadySubmittedException('A form can only be submitted once');
385394
}
386395

396+
$dispatcher = LegacyEventDispatcherProxy::decorate($this->config->getEventDispatcher());
397+
if ($dispatcher->hasListeners(FormEvents::PRE_SUBMIT)) {
398+
$dispatcher->dispatch(new PreSubmitEvent($this, $submittedData), FormEvents::PRE_SUBMIT);
399+
}
400+
387401
$this->submitted = true;
388402

403+
if ($dispatcher->hasListeners(FormEvents::POST_SUBMIT)) {
404+
$dispatcher->dispatch(new PostSubmitEvent($this, $submittedData), FormEvents::POST_SUBMIT);
405+
}
406+
389407
return $this;
390408
}
391409

src/Symfony/Component/Form/ButtonBuilder.php

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@
1111

1212
namespace Symfony\Component\Form;
1313

14+
use Symfony\Component\EventDispatcher\EventDispatcher;
15+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
1416
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
17+
use Symfony\Component\EventDispatcher\ImmutableEventDispatcher;
1518
use Symfony\Component\Form\Exception\BadMethodCallException;
1619
use Symfony\Component\Form\Exception\InvalidArgumentException;
1720

@@ -22,6 +25,11 @@
2225
*/
2326
class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface
2427
{
28+
private const UNSUPPORTED_FORM_EVENTS = [
29+
FormEvents::PRE_SET_DATA => [FormEvents::POST_SET_DATA],
30+
FormEvents::SUBMIT => [FormEvents::PRE_SUBMIT, FormEvents::POST_SUBMIT],
31+
];
32+
2533
protected $locked = false;
2634

2735
/**
@@ -49,16 +57,36 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface
4957
*/
5058
private $options;
5159

60+
private $dispatcher;
61+
5262
/**
5363
* @throws InvalidArgumentException if the name is empty
5464
*/
55-
public function __construct(?string $name, array $options = [])
65+
public function __construct(?string $name, /*EventDispatcherInterface*/ $dispatcher = [], array $options = [])
5666
{
67+
if (\func_num_args() > 1) {
68+
if (!$dispatcher instanceof EventDispatcherInterface) {
69+
@trigger_error(sprintf('The "%s" method second argument must be an instance of "%s" since Symfony 4.4. The existing array "$options" argument has been moved to the third position.', __METHOD__, EventDispatcherInterface::class), E_USER_DEPRECATED);
70+
71+
if (!\is_array($dispatcher)) {
72+
throw new \TypeError(sprintf('The second argument passed to the "%s" method must be an instance of "%s", "%s" given.', __METHOD__, EventDispatcherInterface::class, \is_object($dispatcher) ? \get_class($dispatcher) : \gettype($dispatcher)));
73+
}
74+
75+
$options = $dispatcher;
76+
$dispatcher = new EventDispatcher();
77+
}
78+
} else {
79+
@trigger_error(sprintf('The "%s" method requires an instance of "%s" as its second argument since Symfony 4.4.', __METHOD__, EventDispatcherInterface::class), E_USER_DEPRECATED);
80+
81+
$dispatcher = new EventDispatcher();
82+
}
83+
5784
if ('' === $name || null === $name) {
5885
throw new InvalidArgumentException('Buttons cannot have empty names.');
5986
}
6087

6188
$this->name = $name;
89+
$this->dispatcher = $dispatcher;
6290
$this->options = $options;
6391

6492
if (preg_match('/^([^a-z0-9_].*)?(.*[^a-zA-Z0-9_\-:].*)?$/D', $name, $matches)) {
@@ -159,31 +187,45 @@ public function getForm()
159187
}
160188

161189
/**
162-
* Unsupported method.
163-
*
164-
* This method should not be invoked.
165-
*
166-
* @param string $eventName
167-
* @param callable $listener
168-
* @param int $priority
190+
* {@inheritdoc}
169191
*
170192
* @throws BadMethodCallException
171193
*/
172194
public function addEventListener($eventName, $listener, $priority = 0)
173195
{
174-
throw new BadMethodCallException('Buttons do not support event listeners.');
196+
if ($this->locked) {
197+
throw new BadMethodCallException('ButtonBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
198+
}
199+
200+
if (isset(self::UNSUPPORTED_FORM_EVENTS[$eventName])) {
201+
throw new BadMethodCallException(sprintf('Buttons do not support the "%s" form event. Use "%s" instead.', $eventName, implode('" or "', self::UNSUPPORTED_FORM_EVENTS[$eventName])));
202+
}
203+
204+
$this->dispatcher->addListener($eventName, $listener, $priority);
205+
206+
return $this;
175207
}
176208

177209
/**
178-
* Unsupported method.
179-
*
180-
* This method should not be invoked.
210+
* {@inheritdoc}
181211
*
182212
* @throws BadMethodCallException
183213
*/
184214
public function addEventSubscriber(EventSubscriberInterface $subscriber)
185215
{
186-
throw new BadMethodCallException('Buttons do not support event subscribers.');
216+
if ($this->locked) {
217+
throw new BadMethodCallException('ButtonBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.');
218+
}
219+
220+
foreach ($subscriber::getSubscribedEvents() as $eventName => $_) {
221+
if (isset(self::UNSUPPORTED_FORM_EVENTS[$eventName])) {
222+
throw new BadMethodCallException(sprintf('Buttons do not support the "%s" form event. Use "%s" instead.', $eventName, implode('" or "', self::UNSUPPORTED_FORM_EVENTS[$eventName])));
223+
}
224+
}
225+
226+
$this->dispatcher->addSubscriber($subscriber);
227+
228+
return $this;
187229
}
188230

189231
/**
@@ -513,11 +555,15 @@ public function getFormConfig()
513555
}
514556

515557
/**
516-
* Unsupported method.
558+
* {@inheritdoc}
517559
*/
518560
public function getEventDispatcher()
519561
{
520-
return null;
562+
if ($this->locked && !$this->dispatcher instanceof ImmutableEventDispatcher) {
563+
$this->dispatcher = new ImmutableEventDispatcher($this->dispatcher);
564+
}
565+
566+
return $this->dispatcher;
521567
}
522568

523569
/**

src/Symfony/Component/Form/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ CHANGELOG
1212
* Overriding the methods `FormIntegrationTestCase::setUp()`, `TypeTestCase::setUp()` and `TypeTestCase::tearDown()` without the `void` return-type is deprecated.
1313
* marked all dispatched event classes as `@final`
1414
* Added the `validate` option to `SubmitType` to toggle the browser built-in form validation.
15+
* The `ButtonBuilder::__construct()` method second argument should be an `EventDispatcherInterface` instance. The existing array `$options` argument has been moved to the third position.
16+
* Added support for event listeners on buttons.
1517

1618
4.3.0
1719
-----

src/Symfony/Component/Form/ResolvedFormType.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,11 +206,11 @@ public function getOptionsResolver()
206206
protected function newBuilder($name, $dataClass, FormFactoryInterface $factory, array $options)
207207
{
208208
if ($this->innerType instanceof ButtonTypeInterface) {
209-
return new ButtonBuilder($name, $options);
209+
return new ButtonBuilder($name, new EventDispatcher(), $options);
210210
}
211211

212212
if ($this->innerType instanceof SubmitButtonTypeInterface) {
213-
return new SubmitButtonBuilder($name, $options);
213+
return new SubmitButtonBuilder($name, new EventDispatcher(), $options);
214214
}
215215

216216
return new FormBuilder($name, $dataClass, new EventDispatcher(), $factory, $options);

src/Symfony/Component/Form/Tests/ButtonBuilderTest.php

Lines changed: 108 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,13 @@
1212
namespace Symfony\Component\Form\Tests;
1313

1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\EventDispatcher\EventDispatcher;
16+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
17+
use Symfony\Component\EventDispatcher\ImmutableEventDispatcher;
1518
use Symfony\Component\Form\ButtonBuilder;
19+
use Symfony\Component\Form\Exception\BadMethodCallException;
1620
use Symfony\Component\Form\Exception\InvalidArgumentException;
21+
use Symfony\Component\Form\FormEvents;
1722

1823
/**
1924
* @author Alexander Cheprasov <cheprasov.84@ya.ru>
@@ -36,15 +41,15 @@ public function getValidNames()
3641
*/
3742
public function testValidNames($name)
3843
{
39-
$this->assertInstanceOf('\Symfony\Component\Form\ButtonBuilder', new ButtonBuilder($name));
44+
$this->assertInstanceOf('\Symfony\Component\Form\ButtonBuilder', new ButtonBuilder($name, new EventDispatcher()));
4045
}
4146

4247
/**
4348
* @group legacy
4449
*/
4550
public function testNameContainingIllegalCharacters()
4651
{
47-
$this->assertInstanceOf('\Symfony\Component\Form\ButtonBuilder', new ButtonBuilder('button[]'));
52+
$this->assertInstanceOf('\Symfony\Component\Form\ButtonBuilder', new ButtonBuilder('button[]', new EventDispatcher()));
4853
}
4954

5055
/**
@@ -71,6 +76,106 @@ public function testInvalidNames($name)
7176
{
7277
$this->expectException(InvalidArgumentException::class);
7378
$this->expectExceptionMessage('Buttons cannot have empty names.');
74-
new ButtonBuilder($name);
79+
new ButtonBuilder($name, new EventDispatcher());
80+
}
81+
82+
/**
83+
* @dataProvider supportedEventsProvider
84+
*/
85+
public function testSupportedEvents(string $event)
86+
{
87+
$buttonBuilder = new ButtonBuilder('foo', new EventDispatcher());
88+
89+
$buttonBuilder->addEventListener($event, ['class', 'method']);
90+
91+
$subscriber = new class implements EventSubscriberInterface {
92+
public static $event = 'foo';
93+
94+
/**
95+
* {@inheritdoc}
96+
*/
97+
public static function getSubscribedEvents(): array
98+
{
99+
return [
100+
self::$event => 'method',
101+
];
102+
}
103+
};
104+
$subscriber::$event = $event;
105+
$buttonBuilder->addEventSubscriber($subscriber);
106+
107+
$this->assertSame([
108+
['class', 'method'],
109+
[$subscriber, 'method'],
110+
], $buttonBuilder->getEventDispatcher()->getListeners($event));
111+
}
112+
113+
public function supportedEventsProvider()
114+
{
115+
return [
116+
[FormEvents::POST_SET_DATA],
117+
[FormEvents::PRE_SUBMIT],
118+
[FormEvents::POST_SUBMIT],
119+
];
120+
}
121+
122+
/**
123+
* @dataProvider unsupportedEventsProvider
124+
*/
125+
public function testUnsupportedEventsWithAListener(string $expectedMessage, string $event)
126+
{
127+
$this->expectException(BadMethodCallException::class);
128+
$this->expectExceptionMessage($expectedMessage);
129+
130+
(new ButtonBuilder('foo', new EventDispatcher()))->addEventListener($event, []);
131+
}
132+
133+
/**
134+
* @dataProvider unsupportedEventsProvider
135+
*/
136+
public function testUnsupportedEventsWithASubscriber(string $expectedMessage, string $event)
137+
{
138+
$this->expectException(BadMethodCallException::class);
139+
$this->expectExceptionMessage($expectedMessage);
140+
141+
$subscriber = new class implements EventSubscriberInterface {
142+
public static $event = 'foo';
143+
144+
/**
145+
* {@inheritdoc}
146+
*/
147+
public static function getSubscribedEvents(): array
148+
{
149+
return [
150+
self::$event => 'method',
151+
];
152+
}
153+
};
154+
$subscriber::$event = $event;
155+
156+
(new ButtonBuilder('foo', new EventDispatcher()))->addEventSubscriber($subscriber);
157+
}
158+
159+
public function unsupportedEventsProvider()
160+
{
161+
return [
162+
['Buttons do not support the "form.pre_set_data" form event. Use "form.post_set_data" instead.', FormEvents::PRE_SET_DATA],
163+
['Buttons do not support the "form.submit" form event. Use "form.pre_submit" or "form.post_submit" instead.', FormEvents::SUBMIT],
164+
];
165+
}
166+
167+
public function testGetEventDispatcher()
168+
{
169+
$eventDispatcher = new EventDispatcher();
170+
$buttonBuilder = new ButtonBuilder('foo', $eventDispatcher);
171+
172+
$this->assertSame($eventDispatcher, $buttonBuilder->getEventDispatcher());
173+
174+
$eventDispatcher->addListener($event = 'foo.bar', $callable = function() {});
175+
176+
$immutableEventDispatcher = $buttonBuilder->getFormConfig()->getEventDispatcher();
177+
178+
$this->assertInstanceOf(ImmutableEventDispatcher::class, $immutableEventDispatcher);
179+
$this->assertSame([$callable], $immutableEventDispatcher->getListeners($event));
75180
}
76181
}

0 commit comments

Comments
 (0)
0