Quantcast
Channel: Issues for Drupal core
Viewing all articles
Browse latest Browse all 298620

ConfigFormBase + validation constraints: support non-1:1 form element-to-config property mapping again

$
0
0

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

  1. Install Drupal 10.2
  2. Install the CDN module, and apply #3394172-6: [PP-1] Adopt Drupal core 10.2 config validation infrastructure
  3. Choose "only files", do not specify any file extensions, click "Save configuration".
  4. 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.

Per #9.← Abandoned since #31.

Per #31 + #32:

The 1:N case
(1 form element, N property paths
  1. Allow passing multiple property paths to ConfigTarget
  2. If multiple property paths are indeed passed, then fromConfig and toConfig callables become required, not optional.
The N:1 case
(N form elements, 1 property path — see #27 for an example)
Allow making the necessary conditional decisions:
  1. Allow the toConfig callable to specify a FormStateInterface parameter, detect this using reflection, and if detected, pass it.
  2. Allow the toConfig callable to throw new \OutOfBoundsException();, and if thrown, do not set any value for this config target on the Config object.
Use case for this: This allows for the scenario of a input[type=radios] (with mode 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 to mode: { style: automatic } (i.e. full config subtree set)
When the user chooses "advanced", a conditionally displayed input[type=text] would appear in the UI (target: mode.something_very_advanced).

That conditionally displayed form element's toConfig callable would default to throwing OutOfBoundsException, unless it can see in the FormStateInterface 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 ConfigTargetConfigFormBase becomes simpler😊

Remaining tasks

Reviews.

User interface changes

None.

API changes

ConfigTarget (new in 10.2!):

  1. Allow passing multiple property paths to ConfigTarget
  2. If multiple property paths are indeed passed, then fromConfig and toConfig callables become required, not optional.
  3. Allow the toConfig callable to specify a FormStateInterface parameter, detect this using reflection, and if detected, pass it.
  4. Allow the toConfig callable to throw 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.


Viewing all articles
Browse latest Browse all 298620

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>