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: Adopt Drupal core 10.3 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 ConfigTarget — ConfigFormBase 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.

Issue fork drupal-3398982

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

Wim Leers created an issue. See original summary.

wim leers’s picture

Issue summary: View changes
Status: Active » Needs review
StatusFileSize
new167 KB
new169.88 KB

As of https://git.drupalcode.org/project/drupal/-/merge_requests/5240/diffs?co..., this is what you'll see instead of an error:

… but this is obviously still not associated with the right form element.

Apply #3398974: Follow-up for #3382510: FormStateInterface::setErrorByName() needs not #name but a variation as well and you'll see:

alexpott’s picture

Issue tags: +Needs tests

I think we definitely need some tests here.

smustgrave’s picture

Status: Needs review » Needs work

Did not test, moving to NW for tests requested in #4

phenaproxima made their first commit to this issue’s fork.

alexpott’s picture

Status: Needs work » Needs review

This now contains #3398974: Follow-up for #3382510: FormStateInterface::setErrorByName() needs not #name but a variation and more test coverage of \Drupal\Core\Form\ConfigFormBase::validateForm() handling violations.

wim leers’s picture

👍 I'm hoping @phenaproxima can finish this — I'm working on #3364108: Configuration schema & required keys.

wim leers’s picture

StatusFileSize
new2.84 KB

I propose an alternative solution, which applies cleanly to Drupal core at commit 13883cc3808f9442268c904f4d96379372ecf0bf. This works for everything, not just composite form elements.

In a nutshell, this restores the ability that #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 removed: the ability to implement non-1:1 mappings by allowing modules to override ::copyFormValuesToConfig() (but still call the parent method to do as much as possible using #config_target) and optionally implement mapConfigKeyToFormElementName().

This patch allows modules with human-optimized UIs to use config validation again in their config forms, by:

  1. making ::copyFormValuesToConfig protected instead of private
  2. allowing ::mapConfigKeyToFormElementName() to be implemented again, and automatically calling it for any property path not found using #config_target

… which is what #3364506: Add optional validation constraint support to ConfigFormBase originally introduced, until #3382510 removed it.

Concrete example

The CDN module's CdnSettingsForm in #3394172-13: Adopt Drupal core 10.3 config validation infrastructure uses:

  1. #config_target 6 times, for all the 1:1 mappings 👍
  2. For the non-1:1 mapping of a few form elements in mapping][simple onto cdn.settings:mapping.conditions in config:
      /**
       * {@inheritdoc}
       */
      protected static function copyFormValuesToConfig(Config $config, FormStateInterface $form_state): void {
        // The 1:1 things can be handled by the base class.
        parent::copyFormValuesToConfig($config, $form_state);
    
        // The custom UI for configuring `mapping.conditions`, optimized for the
        // user's mental model, that does not map 1:1 to the underlying config.
        if ($form_state->getValue(['mapping', 'type']) === 'simple') {
          $toggle = $form_state->getValue(['mapping', 'simple', 'extensions_condition_toggle']);
          // Only the 'extensions' condition is supported in this UI, to KISS.
          if ($toggle === 'limited') {
            // Set the 'extensions' condition unconditionally.
            $config->set('mapping.conditions', [
              'extensions' => explode(' ', trim($form_state->getValue(['mapping', 'simple', 'extensions_condition_value']))),
            ]);
          }
          // Plus one particular common preset: 'nocssjs', which means all files
          // except CSS and JS.
          elseif ($toggle === 'nocssjs') {
            $config->set('mapping.conditions', static::CONDITIONS_PRESET_NOCSSJS);
          }
          else {
            $config->set('mapping.conditions', []);
          }
        }
      }
    
      /**
       * {@inheritdoc}
       */
      protected static function mapConfigKeyToFormElementName(string $config_name, string $key) : string {
        // TRICKY: cannot use #config_target because it is conditionally present.
        if ($key === 'mapping.conditions.extensions') {
          return 'mapping][simple][extensions_condition_value';
        }
    
        return str_starts_with($key, 'mapping.conditions') ? 'mapping][simple' : '';
      }
    
phenaproxima’s picture

I like Wim's idea in #9.

Restoring some of the removed infrastructure gives modules like CDN an "out" for the more complex cases.

#config_target really is only meant for the simpler cases. I spent quite a bit of time trying to figure out how to make #config_target smart enough to handle these complex cases, but all I really ended up doing was tying my brain into knots. That leads me to think that we shouldn't try to make #config_target "smarter", which will probably introduce more problems than it would solve, but instead make sure developers have the tools to do things the right way for their specific needs.

Indeed, while working on #3384790: Update all remaining ConfigFormBase subclasses in Drupal core to use #config_target, I already found a few spots in core where we're going to need the ability to do complex logic that is out of #config_target's reach.

Therefore, Wim's idea makes sense to me.

wim leers’s picture

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

Indeed, while working on #3384790: Update all remaining ConfigFormBase subclasses in Drupal core to use #config_target, I already found a few spots in core where we're going to need the ability to do complex logic that is out of #config_target's reach.

Where? Examples in core would be most valuable!

wim leers’s picture

wim leers’s picture

Assigned: Unassigned » wim leers
Status: Needs review » Needs work
wim leers’s picture

The sole Nightwatch test failure for fe9b0f11 is unrelated. All other tests passed 👍

Just pushed a commit that fixes the test expectations, which will cause it to fail: the solution so far is known to be incomplete/inadequate!

phenaproxima’s picture

Issue summary: View changes

Re #11: I've updated the issue summary with a few core forms that have complicated mapping logic.

wim leers’s picture

Assigned: wim leers » Unassigned
Status: Needs work » Needs review
Issue tags: -Needs tests

🥳 That passed tests, with the exception of AjaxTest::testAjaxFocus(), which is unrelated. Next: prove that #config_target and a copyFormValuesToConfig() override can co-exist.

Then I'm done here 👍

wim leers’s picture

wim leers’s picture

Assigned: Unassigned » phenaproxima

@phenaproxima Can you please implement 1 of the 3 examples in this issue? Because per #10, this is actually a hard blocker for #3384790: Update all remaining ConfigFormBase subclasses in Drupal core to use #config_target.

phenaproxima’s picture

Issue summary: View changes

Re #18: Sure; done!

phenaproxima’s picture

Issue summary: View changes
Issue tags: -Needs change record

Took a shot at a change record: https://www.drupal.org/node/3399660

phenaproxima’s picture

Issue summary: View changes
phenaproxima’s picture

Issue summary: View changes
phenaproxima’s picture

Issue summary: View changes
borisson_’s picture

Found one minor nitpick, otherwise this looks good to go imho.

wim leers’s picture

Assigned: phenaproxima » wim leers
Status: Needs review » Needs work

#20: I think it'd be clearer if we updated https://www.drupal.org/node/3373502 once more? 😅

Working on trying to implement @alexpott's feedback.

wim leers’s picture

Addressed @alexpott's feedback!

I think do like this better.

Regardless of the DX, it has one major ecosystem/UX benefit: #config_target is present even for conditionally config property paths, which means #2408549: Display status message on configuration forms when there are overridden values will be more effective. 👍

Applied to the CDN contrib module: see interdiff-13-16.txt in #3394172-17: Adopt Drupal core 10.3 config validation infrastructure.

Next up: apply this to LocaleSettingsForm in core.

wim leers’s picture

Turns out that LocaleSettingsForm is quite different from CdnSettingsForm (see previous comment):

  • CdnSettingsForm has conditionality and maps 1 form element (a dropdown) to 1 subtree, unless that dropdown has a particular value, then a second form element corresponds to a value inside that same subtree
  • LocaleSettingsForm has no conditionality but maps 1 form element to 2 config property paths (to be more abstract: 2 independent subtrees), and vice versa

If you zoom out/abstract this, the above means that

N form elements → 1 config target
CdnSettingsForm maps N form elements to 1 "node" in the config tree (which would never make sense of course, unless there's conditionality, which is the case here!)
  • N form elements: $form['mapping']['simple']['extensions_condition_toggle'] = … and $form['mapping']['simple']['extensions_condition_value'] = …
  • 1 config tree node: cdn.settings:mapping.conditions.extensions (the first form element targets cdn.settings:mapping.conditions, the second targets cdn.settings:mapping.conditions.extensions, which means the second targets a subset of the former)
1 form element → N config targets
LocaleSettingsForm maps 1 form element to N "nodes" in the config tree
  • 1 form element: $form['overwrite'] = …
  • N config tree nodes: locale.settings:translation.overwrite_customized + locale.settings:translation.overwrite_not_customized

👍 So AFAICT we got 2 perfectly complementary examples here! 😄

wim leers’s picture

Assigned: wim leers » Unassigned
Status: Needs work » Needs review
wim leers’s picture

Needed to introduce ConfigMultiTarget to support the LocaleSettingsForm use case.

Looking forward to feedback :)

phenaproxima’s picture

#3399295: Allow all callables in ConfigTarget landed, so I've merged those changes into the MR.

phenaproxima’s picture

Before I hit the hay for the night, here's where I'm at: I'm trying to remove ConfigMultiTarget and make ConfigTarget handle multiple property paths. It's a bit tricky to do that while aiming to keep the DX as sensible as possible. Some tests are failing but they're very clearly related to flawed logic I haven't fully finished implementing yet.

I also think that we should always pass $form_state as the first argument to the toConfig callback, just for consistency's sake.

Long story short -- if a ConfigTarget instance is handling only one property path, then it should behave the way it does in HEAD. If it's handling multiple property paths, then the toConfig callback should return an associative array of values, keyed by property path.

To keep this logic relatively cleanly encapsulated, I'm thinking we might want to put this directly in ConfigTarget.

wim leers’s picture

Started doing the remaining things that @phenaproxima did not get to.

$values = array_map($this->fromConfig, $values); does not work correctly for the multiple values case, because it calls fromConfig for each value separately … which means that it no longer receives all the values at once. IOW: in an attempt to simplify the logic, the very purpose (being able to target multiple target property paths in config at once) was made impossible 😅

Fixing that got us from 11 to 10 test failures.

Just pushed 67628221b0 which will make the multi-target case actually work again, which should get us down to 4 failures.

wim leers’s picture

Issue summary: View changes
Issue tags: -Needs issue summary update

I also think that we should always pass $form_state as the first argument to the toConfig callback, just for consistency's sake.

I discussed this with @alexpott in Drupal Slack.

He and I agree that this was a wrong direction. It keeps Drupal core simple but deteriorates the DX for actual users of this API. That's the wrong trade-off, it must be the other way around.

By reverting that change, the remaining failing functional tests will also start passing again 👍

(Most of @phenaproxima's changes are still in the MR though: removal of ConfigMultiTarget, stricter validation, logic moved out of ConfigFormBase and into ConfigTarget.)

Issue summary updated.

wim leers’s picture

Assigned: Unassigned » wim leers
Status: Needs review » Needs work
wim leers’s picture

Green! Time to clean up and then rebase. 👍

wim leers’s picture

Assigned: wim leers » Unassigned
Issue summary: View changes
Status: Needs work » Needs review
wim leers’s picture

The rebased MR is still green 👍

Note that #3394172-17: Adopt Drupal core 10.3 config validation infrastructure still works fine, but I've also updated that patch to leverage the use of closures & arrow functions, and that really shows just how much cleaner/simpler all of this together makes config forms: #3394172-19: Adopt Drupal core 10.3 config validation infrastructure.

phenaproxima’s picture

Just updating where I'm leaving this tonight: I added a failing test case to prove a point to Wim. Other than that, as far as I know, all feedback is addressed and all other tests are passing.

wim leers’s picture

The failing test case was incorrect AFAICT. See https://git.drupalcode.org/project/drupal/-/merge_requests/5240?commit_i... for the detailed explanation.

This is now green again 👍

This is ready IMO.

phenaproxima’s picture

Status: Needs review » Reviewed & tested by the community

I'm satisfied here, although I've worked on this too. Hopefully someone who hasn't worked on it will come by and +1 RTBC...

borisson_’s picture

This is lovely, I agree with the rtbc in #40. The changes here make it so much more capable.

wim leers’s picture

YAY! 😊

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

Added some comments to the MR.

wim leers’s picture

Status: Needs work » Needs review

All of @alexpott's feedback has been addressed 👍

borisson_’s picture

Status: Needs review » Needs work

This is great: https://git.drupalcode.org/project/drupal/-/merge_requests/5240/diffs?co...
Good error messages are so helpful.

elseif ($extraneous_keys = array_diff(array_keys($value), $this->propertyPaths)) {
throw new \LogicException(sprintf('The toConfig callable returned an array that has key-value pairs that are extraneous because they do not match targeted property paths: %s.', implode(', ', $extraneous_keys)));

I though we retired the extraneous keys naming in the other issue, should we use something else here as well?

wim leers’s picture

Status: Needs work » Needs review

Good call! Fixed.

borisson_’s picture

Status: Needs review » Reviewed & tested by the community

All remarks by @alexpott and me have been fixed. This now looks like it has very good test coverage as well, back to rtbc.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

Added some comments to the MR. They need to be addressed.

Nice work on having an MR. I think the MR should be focussed on how module developers can use this for complex config forms rather than the internals of ConfigTarget.

wim leers’s picture

Assigned: Unassigned » wim leers
wim leers’s picture

Addressed all feedback.

That included opening #3401186: Follow-up for #3382510: Throw \LogicException when >1 #config_target in the same form targets the same property path — and having a test + fix ready there too.

The change record is wildly outdated. IMHO we should delete it and update https://www.drupal.org/node/3373502 instead, like we've done for all the other #config_target issues. Since it's all new, a single comprehensive change record is much more usable.

wim leers’s picture

Assigned: Unassigned » wim leers
Status: Needs review » Needs work

😬 Just as I was about to update the issue summary for #50 after having implemented @alexpott's proposal, I realized that this actually does not work 😇

By allowing toConfig: FALSE, you remove the ability to conditionally throw new \OutOfBoundsException(). See #3394172-19: Adopt Drupal core 10.3 config validation infrastructure, which uses:

+      '#config_target' => new ConfigTarget('cdn.settings', 'mapping.conditions.extensions',
+        fromConfig: fn (?array $extensions): string => implode(' ', $extensions ?: []),
+        toConfig: function (string $extensions_condition_value, FormStateInterface $form_state): array {
+          if ($form_state->getValue(['mapping', 'simple', 'extensions_condition_toggle']) !== 'limited') {
+            throw new \OutOfBoundsException();
+          }
+          return explode(' ', trim($extensions_condition_value));
+        },
+      ),

I'll work on adding explicit functional test coverage for this use case.

phenaproxima’s picture

If we still feel icky about communicating by exception (a feeling I generally share), another option here is to leverage an enum. We could add something like this:

enum ConfigTargetValueEnum {

  case NoMapping;

}

toConfig callbacks could return ConfigTargetValueEnum::NoMapping as a way of explicitly signaling they don't want to set a value for the property. Since it's a non-backed enum, there's no ambiguity, and no chance of a wrong value being accidentally stored in config.

The downside here is that it calls for a single-case enum, which is kind of lame.

alexpott’s picture

RE #51, #52 - I think though that toConfig: FALSE would work for #3394172-19: Adopt Drupal core 10.3 config validation infrastructure. We need to change where the extensions are being set... to something like what is below.

      '#config_target' => new ConfigTarget('cdn.settings', 'mapping.conditions',
         fromConfig: fn (array $mapping_conditions): string => match(TRUE) {
           $mapping_conditions === static::CONDITIONS_PRESET_NOCSSJS => 'nocssjs',
           !array_key_exists('extensions', $mapping_conditions) || empty($mapping_conditions['extensions']) => 'all',
           default => 'limited',
         },
         toConfig: function (string $extensions_condition_toggle, FormStateInterface $form_state): array => match ($extensions_condition_toggle) {
           // Conditions: none.
           'all' => [],
           // Conditions: no CSS/JS extensions (i.e. all files except *.css/*.js).
           'nocssjs' => static::CONDITIONS_PRESET_NOCSSJS,
           // Conditions: user-configured extensions.
           // @see ::setExtensionsConditionIfDropdownIsOnlyFiles()
           'limited' => ['extensions' => explode(' ', trim($$form_state->getValue(['mapping', 'simple', 'extensions_condition_extensions'])))],
         },
       ),
      '#config_target' => new ConfigTarget('cdn.settings', 'mapping.conditions.extensions',
         fromConfig: fn (?array $extensions): string => implode(' ', $extensions ?: []),
         // @see something about the toConfig above.
         toConfig: FALSE,
       ),
wim leers’s picture

Assigned: wim leers » Unassigned
Status: Needs work » Needs review

(I just walked @phenaproxima through #53 and explained why I agreed, but while implementing @alexpott's proposal, I realized that there's still a problem. But, I do think this line of questioning has gotten us to a point where we've actually gotten to a comprehensive solution 😊)

#53: I agree that pattern works for #3394172. But it doesn't work in all cases where there's conditional logic to determine which key-value pairs should be set in config.

That proposal works because one config target targets some config subtree (mapping.conditions), and another config target conditionally targets a node in that subtree (mapping.conditions.extensions).

I tried to find a more general case for the test coverage I aimed to add here (and apparently succeeded): one config targets one config subtree (favorite_fruits), and another config target conditionally targets a different subtree (could_not_live_without).

Possible cases

I think there is a need for multiple different cases:

  1. toConfig can return a value that must be set at the property path for this ConfigTarget ⇒ return that value ← this already works in HEAD ✅
  2. toConfig can SOMEHOW indicate that it's "no-op", because it knows some other ConfigTarget's toConfig callable will set this property path ← this is the case in #53/CdnSettingsForm, and in that particular example an unconditional "no-op" is possible, but that's not true in all cases
  3. toConfig can SOMEHOW indicate that the property path this ConfigTarget targets must be REMOVED ← this will become a hard requirement starting in #3364108: Configuration schema & required keys, and there may already be cases where this would be necessary (even if it there isn't explicit validation logic for it yet)

Possible approaches for case #2 (a form value NOT mapped to a value in config aka "no-op")

We've only been talking about point 2 here. The 3 choices so far:

  1. Callable can throw \OutOfBoundsException (@Wim Leers)
  2. "FALSE instead of callable" (@alexpott)
  3. Callable can return ConfigTargetValueEnum::NoMapping (@phenaproxima)

The limitation of choice 2.B here is that it only supports the unconditional scenario.

Not yet considered so far: case #3 (delete a key)

In principle, we don't need to support case 3 just yet, because few modules are likely to be explicitly relying on this. But AFAICT it is going to happen (we've got +1s from @bircher and @alexpott), plus it's possible some modules already are doing something like this, so we should take that into account here already. Hence added explicit test coverage for case 3.We could choose two different exception types, but both @alexpott and @phenaproxima raised concerns about communicating using exceptions.

Conclusion

So then … @phenaproxima's proposal actually seems the most appealing, because then there would be at least two cases:

enum ConfigTargetValueEnum {
  case NoMapping;
  case DeleteKey;
}

Combined with match(), this can then be implemented very elegantly:

  match ($value) {
      // No-op.
      ConfigTargetValue::NoMapping => function (): void {},
      // Delete.
      ConfigTargetValue::DeleteKey => $config->clear($property),
      // Set.
      default => $config->set($property, $value),
    }

Just pushed explicit test coverage, which IMO makes it obvious that this is not yet supported and should be.

wim leers’s picture

Issue summary: View changes

Issue summary updated for #54 and its conclusion + implementation.

phenaproxima’s picture

If there's not yet a need to explicitly unset keys...but there will be (you seem pretty certain about that, Wim, and I believe you), then yes, the enum seems like the best solution to me as well.

phenaproxima’s picture

Status: Needs review » Reviewed & tested by the community

I think Wim's points make sense, and I don't really have anything to complain about at the code level.

wim leers’s picture

@alexpott in Drupal Slack:

Lets call ConfigTargetValue::NoMapping something else. Maybe ConfigTargetValue::NoOp or ConfigTargetValue::NoOperation

Also ConfigTargetValue as a little awkward

it is not the value of a config target

ConfigTargetToConfigReturnValue is what it is. But that is awkward. To say the least.

  • +1 for NoOp.
  • What about ToConfig::NoOp and ToConfig::DeleteKey?

Implemented those renames: see commits I just pushed. Since it's "just" naming, keeping at RTBC.

wim leers’s picture

Issue summary: View changes

Added docs to make the connection between \Drupal\Core\Form\ConfigTarget and \Drupal\Core\Form\ToConfig explicit.

Update IS for #58.

wim leers’s picture

Issue summary: View changes

Created draft update to the change record — preview it here.

Also updated contrib module patch at #3394172-22: Adopt Drupal core 10.3 config validation infrastructure.

wim leers’s picture

Really hoping this will land prior to 10.2.0-beta1, because this is the very last piece to make #config_target/validatable simple config forms be usable everywhere and have a nicer DX 🤞

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed and pushed 421942795e4 to 11.x and 728f592f538 to 10.2.x. Thanks!

I backported this to 10.2.x because #config_target is new and this completes the functionality so that it can be used for more complex UIs like the cdn module.

  • alexpott committed 42194279 on 11.x
    Issue #3398982 by Wim Leers, phenaproxima, alexpott, borisson_:...

  • alexpott committed 728f592f on 10.2.x
    Issue #3398982 by Wim Leers, phenaproxima, alexpott, borisson_:...
wim leers’s picture

🥳

Deleted the obsolete draft CR at https://www.drupal.org/node/3399660.

Very glad (and relieved) to have this in 10.2!

alexpott’s picture

Version: 11.x-dev » 10.2.x-dev

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

idebr’s picture

Another interesting follow-up where a config value has to be mapping to multiple element properties: #3463868: Two #config_targets error when used on a text_format form element