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

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. Pass the toConfig callable a FormStateInterface parameter.
  2. Allow the toConfig callable to return one of two special values:
    • ToConfig::NoOp, to indicate that the given form value does not need to be mapped onto the Config object
    • ToConfig::DeleteKey to indicate that the targeted property path should be deleted from config.
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 returning ToConfig::NoOp, 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. As well as the explicit test coverage for the conditional use case plus the infrastructure and how to use it.

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 return ToConfig::NoOp (no-op) or ToConfig::DeleteKey (delete the targeted property path's key).

Data model changes

None.

Release notes snippet

None.


Viewing all articles
Browse latest Browse all 293910

Trending Articles



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