diff --git a/core/core.services.yml b/core/core.services.yml index 21a3e59..b5b34f5 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -362,7 +362,7 @@ services: arguments: ['@stream_wrapper_manager', '@settings', '@logger.channel.file'] form_builder: class: Drupal\Core\Form\FormBuilder - arguments: ['@form_validator', '@form_submitter', '@form_cache', '@module_handler', '@event_dispatcher', '@request_stack', '@class_resolver', '@element_info', '@theme.manager', '@?csrf_token'] + arguments: ['@form_validator', '@form_submitter', '@form_cache', '@event_dispatcher', '@request_stack', '@class_resolver', '@element_info', '@?csrf_token'] form_validator: class: Drupal\Core\Form\FormValidator arguments: ['@request_stack', '@string_translation', '@csrf_token', '@logger.channel.form', '@form_error_handler'] @@ -375,6 +375,12 @@ services: class: Drupal\Core\Form\FormCache arguments: ['@app.root', '@keyvalue.expirable', '@module_handler', '@current_user', '@csrf_token', '@logger.channel.form', '@request_stack', '@page_cache_request_policy'] public: false # Private to form_builder + form_builder.alter: + class: Drupal\Core\Form\FormAlter + arguments: ['@module_handler', '@theme.manager'] + public: false # Private to form_builder + tags: + - { name: event_subscriber } keyvalue: class: Drupal\Core\KeyValueStore\KeyValueFactory arguments: ['@service_container', '%factory.keyvalue%'] diff --git a/core/lib/Drupal/Core/Form/FormAlter.php b/core/lib/Drupal/Core/Form/FormAlter.php new file mode 100644 index 0000000..7010c6b --- /dev/null +++ b/core/lib/Drupal/Core/Form/FormAlter.php @@ -0,0 +1,95 @@ +moduleHandler = $module_handler; + $this->themeManager = $theme_manager; + } + + /** + * Returns the event name for altering a specific form ID. + * + * @param string $form_id + * The form ID. + * + * @return string + * The event name. + */ + public static function id($form_id) { + return static::ANY . ':' . $form_id; + } + + /** + * {@inheritdoc} + */ + public function alterForm(FormAlterEvent $event) { + $form_id = $event->getFormId(); + $form = &$event->getForm(); + $form_state = $event->getFormState(); + + // Invoke hook_form_alter(), hook_form_BASE_FORM_ID_alter(), and + // hook_form_FORM_ID_alter() implementations. + $hooks = ['form']; + $build_info = $form_state->getBuildInfo(); + if (isset($build_info['base_form_id'])) { + $hooks[] = 'form_' . $build_info['base_form_id']; + } + $hooks[] = 'form_' . $form_id; + $this->moduleHandler->alter($hooks, $form, $form_state, $form_id); + $this->themeManager->alter($hooks, $form, $form_state, $form_id); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + return [ + static::ANY => 'alterForm', + ]; + } + +} diff --git a/core/lib/Drupal/Core/Form/FormAlterEvent.php b/core/lib/Drupal/Core/Form/FormAlterEvent.php new file mode 100644 index 0000000..bae20ba --- /dev/null +++ b/core/lib/Drupal/Core/Form/FormAlterEvent.php @@ -0,0 +1,76 @@ +formId = $form_id; + $this->form = $form; + $this->formState = $form_state; + } + + /** + * Gets the ID of the form being built. + * + * @return string + */ + public function getFormId() { + return $this->formId; + } + + /** + * Gets the form structure. + * + * @return array + */ + public function &getForm() { + return $this->form; + } + + /** + * Gets the current form state. + * + * @return \Drupal\Core\Form\FormStateInterface + */ + public function getFormState() { + return $this->formState; + } + +} diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php index be000dc..17e044a 100644 --- a/core/lib/Drupal/Core/Form/FormBuilder.php +++ b/core/lib/Drupal/Core/Form/FormBuilder.php @@ -10,11 +10,9 @@ use Drupal\Core\Access\CsrfTokenGenerator; use Drupal\Core\DependencyInjection\ClassResolverInterface; use Drupal\Core\EventSubscriber\MainContentViewSubscriber; -use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\Exception\BrokenPostRequestException; use Drupal\Core\Render\Element; use Drupal\Core\Render\ElementInfoManagerInterface; -use Drupal\Core\Theme\ThemeManagerInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\FileBag; use Symfony\Component\HttpFoundation\RequestStack; @@ -28,13 +26,6 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormSubmitterInterface, FormCacheInterface { /** - * The module handler. - * - * @var \Drupal\Core\Extension\ModuleHandlerInterface - */ - protected $moduleHandler; - - /** * The event dispatcher. * * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface @@ -77,13 +68,6 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormS protected $currentUser; /** - * The theme manager. - * - * @var \Drupal\Core\Theme\ThemeManagerInterface - */ - protected $themeManager; - - /** * @var \Drupal\Core\Form\FormValidatorInterface */ protected $formValidator; @@ -148,8 +132,6 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormS * The form submission processor. * @param \Drupal\Core\Form\FormCacheInterface $form_cache * The form cache. - * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler - * The module handler. * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher * The event dispatcher. * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack @@ -158,22 +140,18 @@ class FormBuilder implements FormBuilderInterface, FormValidatorInterface, FormS * The class resolver. * @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info * The element info manager. - * @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager - * The theme manager. * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token * The CSRF token generator. */ - public function __construct(FormValidatorInterface $form_validator, FormSubmitterInterface $form_submitter, FormCacheInterface $form_cache, ModuleHandlerInterface $module_handler, EventDispatcherInterface $event_dispatcher, RequestStack $request_stack, ClassResolverInterface $class_resolver, ElementInfoManagerInterface $element_info, ThemeManagerInterface $theme_manager, CsrfTokenGenerator $csrf_token = NULL) { + public function __construct(FormValidatorInterface $form_validator, FormSubmitterInterface $form_submitter, FormCacheInterface $form_cache, EventDispatcherInterface $event_dispatcher, RequestStack $request_stack, ClassResolverInterface $class_resolver, ElementInfoManagerInterface $element_info, CsrfTokenGenerator $csrf_token = NULL) { $this->formValidator = $form_validator; $this->formSubmitter = $form_submitter; $this->formCache = $form_cache; - $this->moduleHandler = $module_handler; $this->eventDispatcher = $event_dispatcher; $this->requestStack = $request_stack; $this->classResolver = $class_resolver; $this->elementInfo = $element_info; $this->csrfToken = $csrf_token; - $this->themeManager = $theme_manager; } /** @@ -819,16 +797,13 @@ public function prepareForm($form_id, &$form, FormStateInterface &$form_state) { $form['#theme'][] = $build_info['base_form_id']; } } + $event = new FormAlterEvent($form_id, $form, $form_state); + $this->eventDispatcher->dispatch(FormAlter::ANY, $event); - // Invoke hook_form_alter(), hook_form_BASE_FORM_ID_alter(), and - // hook_form_FORM_ID_alter() implementations. - $hooks = ['form']; if (isset($build_info['base_form_id'])) { - $hooks[] = 'form_' . $build_info['base_form_id']; + $this->eventDispatcher->dispatch(FormAlter::id($build_info['base_form_id']), $event); } - $hooks[] = 'form_' . $form_id; - $this->moduleHandler->alter($hooks, $form, $form_state, $form_id); - $this->themeManager->alter($hooks, $form, $form_state, $form_id); + $this->eventDispatcher->dispatch(FormAlter::id($form_id), $event); } /** diff --git a/core/tests/Drupal/Tests/Core/Form/FormAlterTest.php b/core/tests/Drupal/Tests/Core/Form/FormAlterTest.php new file mode 100644 index 0000000..9f19404 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Form/FormAlterTest.php @@ -0,0 +1,101 @@ +moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'); + $this->themeManager = $this->getMock('Drupal\Core\Theme\ThemeManagerInterface'); + $this->formAlter = new FormAlter($this->moduleHandler, $this->themeManager); + } + + /** + * @covers ::alterForm + * + * @dataProvider providerTestAlterForm + */ + public function testAlterForm($form_id, $expected_hooks, $form_state_additions = []) { + $form = []; + $form_state = (new FormState())->setFormState($form_state_additions); + + $this->moduleHandler->expects($this->once()) + ->method('alter') + ->with($expected_hooks, $form, $form_state, $form_id); + $this->themeManager->expects($this->once()) + ->method('alter') + ->with($expected_hooks, $form, $form_state, $form_id); + + $event = new FormAlterEvent($form_id, $form, $form_state); + $this->formAlter->alterForm($event); + } + + /** + * Provides test data for testAlterForm(). + */ + public function providerTestAlterForm() { + $data = []; + $data['no_build_info'] = [ + 'red', + [ + 'form', + 'form_red', + ], + ]; + $data['with_build_info'] = [ + 'red', + [ + 'form', + 'form_blue', + 'form_red', + ], + [ + 'build_info' => [ + 'base_form_id' => 'blue', + ], + ], + ]; + return $data; + } + +} diff --git a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php index 76b7584..6246cf6 100644 --- a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php +++ b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php @@ -535,7 +535,7 @@ public function testExceededFileSize() { $request_stack = new RequestStack(); $request_stack->push($request); $this->formBuilder = $this->getMockBuilder('\Drupal\Core\Form\FormBuilder') - ->setConstructorArgs([$this->formValidator, $this->formSubmitter, $this->formCache, $this->moduleHandler, $this->eventDispatcher, $request_stack, $this->classResolver, $this->elementInfo, $this->themeManager, $this->csrfToken]) + ->setConstructorArgs([$this->formValidator, $this->formSubmitter, $this->formCache, $this->eventDispatcher, $request_stack, $this->classResolver, $this->elementInfo, $this->csrfToken]) ->setMethods(['getFileUploadMaxSize']) ->getMock(); $this->formBuilder->expects($this->once()) diff --git a/core/tests/Drupal/Tests/Core/Form/FormTestBase.php b/core/tests/Drupal/Tests/Core/Form/FormTestBase.php index ef08ab7..5ad6348 100644 --- a/core/tests/Drupal/Tests/Core/Form/FormTestBase.php +++ b/core/tests/Drupal/Tests/Core/Form/FormTestBase.php @@ -3,6 +3,7 @@ namespace Drupal\Tests\Core\Form; use Drupal\Component\Utility\Html; +use Drupal\Core\Form\FormAlter; use Drupal\Core\Form\FormBuilder; use Drupal\Core\Form\FormInterface; use Drupal\Core\Form\FormState; @@ -43,13 +44,6 @@ protected $urlGenerator; /** - * The mocked module handler. - * - * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $moduleHandler; - - /** * The form cache. * * @var \Drupal\Core\Form\FormCacheInterface|\PHPUnit_Framework_MockObject_MockObject @@ -57,11 +51,11 @@ protected $formCache; /** - * The cache backend to use. + * The form alter service. * - * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Form\FormAlter|\PHPUnit_Framework_MockObject_MockObject */ - protected $cache; + protected $formAlter; /** * The current user. @@ -134,12 +128,6 @@ */ protected $logger; - /** - * The mocked theme manager. - * - * @var \Drupal\Core\Theme\ThemeManagerInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $themeManager; /** * {@inheritdoc} @@ -150,12 +138,9 @@ protected function setUp() { // Add functions to the global namespace for testing. require_once __DIR__ . '/fixtures/form_base_test.inc'; - $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'); - $this->formCache = $this->getMock('Drupal\Core\Form\FormCacheInterface'); - $this->cache = $this->getMock('Drupal\Core\Cache\CacheBackendInterface'); $this->urlGenerator = $this->getMock('Drupal\Core\Routing\UrlGeneratorInterface'); - + $this->formAlter = $this->prophesize(FormAlter::class)->reveal(); $this->classResolver = $this->getClassResolverStub(); $this->elementInfo = $this->getMockBuilder('\Drupal\Core\Render\ElementInfoManagerInterface') @@ -172,7 +157,6 @@ protected function setUp() { ->disableOriginalConstructor() ->getMock(); $this->account = $this->getMock('Drupal\Core\Session\AccountInterface'); - $this->themeManager = $this->getMock('Drupal\Core\Theme\ThemeManagerInterface'); $this->request = new Request(); $this->eventDispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); $this->requestStack = new RequestStack(); @@ -189,7 +173,7 @@ protected function setUp() { ->getMock(); $this->root = dirname(dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__)))); - $this->formBuilder = new FormBuilder($this->formValidator, $this->formSubmitter, $this->formCache, $this->moduleHandler, $this->eventDispatcher, $this->requestStack, $this->classResolver, $this->elementInfo, $this->themeManager, $this->csrfToken); + $this->formBuilder = new FormBuilder($this->formValidator, $this->formSubmitter, $this->formCache, $this->eventDispatcher, $this->requestStack, $this->classResolver, $this->elementInfo, $this->csrfToken); } /**