-
-
Notifications
You must be signed in to change notification settings - Fork 9.7k
[Form] Support event listeners in buttons #33639
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,7 +11,10 @@ | |
|
||
namespace Symfony\Component\Form; | ||
|
||
use Symfony\Component\EventDispatcher\EventDispatcher; | ||
use Symfony\Component\EventDispatcher\EventDispatcherInterface; | ||
use Symfony\Component\EventDispatcher\EventSubscriberInterface; | ||
use Symfony\Component\EventDispatcher\ImmutableEventDispatcher; | ||
use Symfony\Component\Form\Exception\BadMethodCallException; | ||
use Symfony\Component\Form\Exception\InvalidArgumentException; | ||
|
||
|
@@ -22,6 +25,11 @@ | |
*/ | ||
class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface | ||
{ | ||
private const UNSUPPORTED_FORM_EVENTS = [ | ||
FormEvents::PRE_SET_DATA => [FormEvents::POST_SET_DATA], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just don't know if we can prevent userland from using our events? I chose to not dispatch useless events. However, if userland dispatch them manually, it can still be useful to be able to listen to them. But I guess this would be a weird practice 😕 Personally, I just prefer to forbid them. |
||
FormEvents::SUBMIT => [FormEvents::PRE_SUBMIT, FormEvents::POST_SUBMIT], | ||
]; | ||
|
||
protected $locked = false; | ||
|
||
/** | ||
|
@@ -49,16 +57,36 @@ class ButtonBuilder implements \IteratorAggregate, FormBuilderInterface | |
*/ | ||
private $options; | ||
|
||
private $dispatcher; | ||
|
||
/** | ||
* @throws InvalidArgumentException if the name is empty | ||
*/ | ||
public function __construct(?string $name, array $options = []) | ||
public function __construct(?string $name, /*EventDispatcherInterface*/ $dispatcher = null, array $options = []) | ||
{ | ||
if (\func_num_args() > 1) { | ||
if (!$dispatcher instanceof EventDispatcherInterface) { | ||
@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); | ||
|
||
if (!\is_array($dispatcher)) { | ||
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))); | ||
} | ||
|
||
$options = $dispatcher; | ||
$dispatcher = new EventDispatcher(); | ||
} | ||
} else { | ||
@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); | ||
|
||
$dispatcher = new EventDispatcher(); | ||
} | ||
|
||
if ('' === $name || null === $name) { | ||
throw new InvalidArgumentException('Buttons cannot have empty names.'); | ||
} | ||
|
||
$this->name = $name; | ||
$this->dispatcher = $dispatcher; | ||
$this->options = $options; | ||
|
||
if (preg_match('/^([^a-z0-9_].*)?(.*[^a-zA-Z0-9_\-:].*)?$/D', $name, $matches)) { | ||
|
@@ -159,31 +187,45 @@ public function getForm() | |
} | ||
|
||
/** | ||
* Unsupported method. | ||
* | ||
* This method should not be invoked. | ||
* | ||
* @param string $eventName | ||
* @param callable $listener | ||
* @param int $priority | ||
* {@inheritdoc} | ||
* | ||
* @throws BadMethodCallException | ||
*/ | ||
public function addEventListener($eventName, $listener, $priority = 0) | ||
{ | ||
throw new BadMethodCallException('Buttons do not support event listeners.'); | ||
if ($this->locked) { | ||
throw new BadMethodCallException('ButtonBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); | ||
} | ||
|
||
if (isset(self::UNSUPPORTED_FORM_EVENTS[$eventName])) { | ||
throw new BadMethodCallException(sprintf('Buttons do not support the "%s" form event. Use "%s" instead.', $eventName, implode('" or "', self::UNSUPPORTED_FORM_EVENTS[$eventName]))); | ||
fancyweb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
$this->dispatcher->addListener($eventName, $listener, $priority); | ||
|
||
return $this; | ||
} | ||
|
||
/** | ||
* Unsupported method. | ||
* | ||
* This method should not be invoked. | ||
* {@inheritdoc} | ||
* | ||
* @throws BadMethodCallException | ||
*/ | ||
public function addEventSubscriber(EventSubscriberInterface $subscriber) | ||
{ | ||
throw new BadMethodCallException('Buttons do not support event subscribers.'); | ||
if ($this->locked) { | ||
throw new BadMethodCallException('ButtonBuilder methods cannot be accessed anymore once the builder is turned into a FormConfigInterface instance.'); | ||
} | ||
|
||
foreach ($subscriber::getSubscribedEvents() as $eventName => $_) { | ||
if (isset(self::UNSUPPORTED_FORM_EVENTS[$eventName])) { | ||
throw new BadMethodCallException(sprintf('Buttons do not support the "%s" form event. Use "%s" instead.', $eventName, implode('" or "', self::UNSUPPORTED_FORM_EVENTS[$eventName]))); | ||
} | ||
} | ||
|
||
$this->dispatcher->addSubscriber($subscriber); | ||
|
||
return $this; | ||
} | ||
|
||
/** | ||
|
@@ -513,11 +555,15 @@ public function getFormConfig() | |
} | ||
|
||
/** | ||
* Unsupported method. | ||
* {@inheritdoc} | ||
*/ | ||
public function getEventDispatcher() | ||
{ | ||
return null; | ||
if ($this->locked && !$this->dispatcher instanceof ImmutableEventDispatcher) { | ||
$this->dispatcher = new ImmutableEventDispatcher($this->dispatcher); | ||
} | ||
|
||
return $this->dispatcher; | ||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -206,11 +206,11 @@ public function getOptionsResolver() | |
protected function newBuilder($name, $dataClass, FormFactoryInterface $factory, array $options) | ||
{ | ||
if ($this->innerType instanceof ButtonTypeInterface) { | ||
return new ButtonBuilder($name, $options); | ||
return new ButtonBuilder($name, new EventDispatcher(), $options); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is not point in this support, we never check server side if a button is clicked, only one submit button per request. |
||
} | ||
|
||
if ($this->innerType instanceof SubmitButtonTypeInterface) { | ||
return new SubmitButtonBuilder($name, $options); | ||
return new SubmitButtonBuilder($name, new EventDispatcher(), $options); | ||
} | ||
|
||
return new FormBuilder($name, $dataClass, new EventDispatcher(), $factory, $options); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This also define the supported form events, what about
UNSUPPORTED_SUPPORTED_FORM_EVENTS
instead?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For me:
FORM_EVENTS
= The 5 form events from theFormEvents
class.UNSUPPORTED_FORM_EVENTS
= Events from theFormEvents
class that are not supported by buttons.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but
self::UNSUPPORTED_FORM_EVENTS[$eventName]
= Events from theFormEvents
class that are supported by buttons :)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, I see what you mean 😄 However, I still think the name applies to the whole const (what it represents) and not to its "values" only.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My suggestion is to find a better name that makes it clearer. I could also name it
SUPPORTED_FORM_EVENTS
due to its values, so we've the chicken-and-egg situation here :)Other names could be:
FORM_EVENTS_SUPPORT
,BUTTON_EVENTS
, ... though not sure they're better :)