Problem/Motivation
Related, but independent to #3398974: Follow-up for #3382510: FormStateInterface::setErrorByName() needs not #name but a variation.
Configuration UIs should present things in a way that makes sense for the end user's mental model.
🆚
Configuration should store things in a way that makes things as simple as possible for the module developer AND optimizes for git diff
.
This means that config UIs CAN and arguably SHOULD not have a 1:1 relationship between UI/form elements and the underlying config. #3382510: Introduce a new #config_target Form API property to make it super simple to use validation constraints on simple config forms, and adopt it in several core config forms does assume that, which is a reasonable default, but it must not get in the way. Unfortunately it does.
A validation error for some specific config property path should be associated with the closest containing form element. And that does not happen today: if a validation error for a property path does not have a 1:1 relationship to a form element, a PHP error appears:
Attempt to read property "elementName" on null in Drupal\Core\Form\ConfigFormBase->validateForm() (line 204 of core/lib/Drupal/Core/Form/ConfigFormBase.php).
Steps to reproduce
- Install Drupal 10.2
- Install the CDN module, and apply #3394172-6: [PP-1] Adopt Drupal core 10.2 config validation infrastructure
- Choose "only files", do not specify any file extensions, click "Save configuration".
- You will see:
There are a few examples in core of forms whose logic is a bit too complicated for the 1:1 use case preferred by #config_target
:
- \Drupal\language\Form\NegotiationBrowserForm
- \Drupal\locale\Form\LocaleSettingsForm (fixed here)
- \Drupal\language\Form\NegotiationConfigureForm
Proposed resolution
Root cause:
$map["$config_name:$property_path"]
is assumed to exist. But … this will ONLY exist if there's a 1:1 relationship between property paths and form elements. It may very well NOT exist.
So instead, try to find a parent property path, if it exists.
- The 1:N case
- (1 form element, N property paths
- Allow passing multiple property paths to
ConfigTarget
- If multiple property paths are indeed passed, then
fromConfig
andtoConfig
callables become required, not optional.
- Allow passing multiple property paths to
- The N:1 case
- (N form elements, 1 property path — see #27 for an example)
-
Allow making the necessary conditional decisions:
- Allow the
toConfig
callable to specify aFormStateInterface
parameter, detect this using reflection, and if detected, pass it. - Allow the
toConfig
callable tothrow new \OutOfBoundsException();
, and if thrown, do not set any value for this config target on the Config object.
- Allow the
- Use case for this: This allows for the scenario of a
input[type=radios]
(withmode
as the target) to choose the simple or advanced way to configure something in the UI.
When the user chooses "simple", it would be mapped tomode: { style: automatic }
(i.e. full config subtree set)
When the user chooses "advanced", a conditionally displayedinput[type=text]
would appear in the UI (target:mode.something_very_advanced
).That conditionally displayed form element's
toConfig
callable would default to throwingOutOfBoundsException
, unless it can see in theFormStateInterface
object that the radio button is set to "advanced"👈 that's why those 2 pieces are needed!See #27 for a concrete example with accompanying code.
All of this complexity is encapsulated in ConfigTarget
— ConfigFormBase
becomes simpler😊
Remaining tasks
Reviews.
User interface changes
None.
API changes
ConfigTarget
(new in 10.2
!):
- Allow passing multiple property paths to
ConfigTarget
- If multiple property paths are indeed passed, then
fromConfig
andtoConfig
callables become required, not optional. - Allow the
toConfig
callable to specify aFormStateInterface
parameter, detect this using reflection, and if detected, pass it. - Allow the
toConfig
callable tothrow new \OutOfBoundsException();
, and if thrown, do not set any value for this config target on the Config object.
Data model changes
None.
Release notes snippet
None.