Problem/Motivation

Config schema provides several different means for dynamic schemas. However, they do not work for the type of a top-level item.

For example, Page Manager reuses block plugins.
It needs to designate part of it's config schema as reusing the block plugin config schema.

page_manager.block_plugin.*:
  type: block.settings.[id]

Proposed resolution

Reuse the "expand type to account for dynamic portions" code to also run for the top-level type.
This is accomplished by splitting \Drupal\Core\Config\TypedConfigManager::getDefinition() into two standalone methods, and using them at all levels to process the type.

Remaining tasks

N/A

User interface changes

N/A

API changes

N/A, thanks @vasi for figuring out a BC-safe way to do this!

CommentFileSizeAuthor
#45 interdiff.txt2.84 KBtim.plunkett
#45 2392057-config-schema-45.patch7.84 KBtim.plunkett
#42 interdiff.txt1.98 KBtim.plunkett
#42 2392057-config-schema-41.patch7.84 KBtim.plunkett
#39 interdiff.txt593 bytesvasi
#39 2392057-config-schema-39.patch7.59 KBvasi
#36 interdiff.txt2.12 KBtim.plunkett
#36 2392057-config-schema-36.patch7.59 KBtim.plunkett
#33 interdiff.txt4.48 KBtim.plunkett
#33 2392057-config-schema-33-PASS.patch7.58 KBtim.plunkett
#33 2392057-config-schema-33-FAIL.patch3.64 KBtim.plunkett
#32 2392057-config-schema-32.patch7.57 KBtim.plunkett
#32 interdiff.txt1.54 KBtim.plunkett
#28 interdiff.txt2.34 KBtim.plunkett
#28 2392057-config-28-PASS.patch7.52 KBtim.plunkett
#28 2392057-config-28-FAIL.patch3.74 KBtim.plunkett
#27 2392057-config-schema-27.patch6.05 KBtim.plunkett
#27 interdiff.txt2.97 KBtim.plunkett
#25 interdiff.txt2.73 KBvasi
#25 2392057-config-schema-24.patch6.19 KBvasi
#14 2392057-config-schema-14.patch5.31 KBtim.plunkett
#14 interdiff.txt3.53 KBtim.plunkett
#9 interdiff.txt2.12 KBbenjy
#9 2392057-9.patch4.39 KBbenjy
#6 2392057-6.patch2.26 KBbenjy
#1 2392057-config_schema-1.patch2.17 KBtim.plunkett
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

tim.plunkett’s picture

Status: Active » Needs review
FileSize
2.17 KB

This should fail.

tim.plunkett’s picture

+++ b/core/modules/config/tests/config_schema_test/config/schema/config_schema_test.schema.yml
@@ -207,3 +207,22 @@ test_with_parents.plugin_types.*:
+  type: test.plugin_types.[plugin_id]

If you change this line to refer to the plugin directly, test.plugin_types.wrapper, then the test passes.

Status: Needs review » Needs work

The last submitted patch, 1: 2392057-config_schema-1.patch, failed testing.

mahtoranjeet’s picture

Assigned: Unassigned » mahtoranjeet
mahtoranjeet’s picture

Assigned: mahtoranjeet » Unassigned
benjy’s picture

Status: Needs work » Needs review
FileSize
2.26 KB

I've re-rolled this with the failing patch, attached.

I started debugging this in TypedConfigManager::buildDataDefinition() and i'm not sure if it's correct but what i'm seeing is that:

  $type = $this->replaceName($type, $replace); // $type = 'wrapping.test.plugin_types.wrapper';
  $definition += $this->getDefinition($type);

Inside getDefinition, $type gets turned back into wrapping.test.plugin_types.* which then does a getDefinition('test.plugin_types.[plugin_id]') which does not pick up test.plugin_types.wrapper definition and therefore internal_value does not get it's schema.

Status: Needs review » Needs work

The last submitted patch, 6: 2392057-6.patch, failed testing.

The last submitted patch, 6: 2392057-6.patch, failed testing.

benjy’s picture

Status: Needs work » Needs review
FileSize
4.39 KB
2.12 KB

OK, this patch fixes the issue. I'm not sure it's exactly right but I wanted to see what else might fail.

Gábor Hojtsy’s picture

Status: Needs review » Needs work
+++ b/core/modules/config/tests/config_schema_test/config/schema/config_schema_test.schema.yml
@@ -213,3 +213,23 @@ config_test.dynamic.*.third_party.config_schema_test:
+wrapping.config_schema_test.plugin_types:
+  type: config_object
+  mapping:
+    tests:
+      type: sequence
+      sequence:
+        - type: wrapping.test.plugin_types.[plugin_id]
+
+wrapping.test.plugin_types.*:
+  type: test.plugin_types.[plugin_id]
+  mapping:
+    wrapper_value:
+      type: string
+
+test.plugin_types.wrapper:
+  type: test.plugin_types
+  mapping:
+    internal_value:
+      type: string

Where is the plugin_id key defined (with its type)?

benjy’s picture

I think it's just missing from this test example? test.plugin_types.* would provide the definition for that?

Gábor Hojtsy’s picture

Status: Needs work » Needs review
Issue tags: +Configuration schema

All right, its just in the dynamic type but it is defined in the base type. Makes sense.

jibran queued 9: 2392057-9.patch for re-testing.

tim.plunkett’s picture

This blocks CTools.

The sub_type processing didn't calculate fallbacks properly.

Gábor Hojtsy’s picture

Status: Needs review » Needs work
Issue tags: +Needs issue summary update

I think the patch looks generally good, it would be nice to outline the solution in the summary, so committers have an understanding. Two notes though:

  1. +++ b/core/lib/Drupal/Core/Config/TypedConfigManager.php
    @@ -100,9 +100,13 @@ public function buildDataDefinition(array $definition, $value, $name = NULL, $pa
    +      $definition += $this->getDefinition($type, TRUE, $replace);
    +    }
    +    else {
    +      // Add default values from type definition.
    +      $definition += $this->getDefinition($type);
    

    Would be good to add a short summary comment on why the prior call is different.

  2. +++ b/core/lib/Drupal/Core/Config/TypedConfigManager.php
    @@ -116,10 +120,17 @@ public function buildDataDefinition(array $definition, $value, $name = NULL, $pa
    -  public function getDefinition($base_plugin_id, $exception_on_invalid = TRUE) {
    -    $definitions = $this->getDefinitions();
    
    @@ -131,6 +142,15 @@ public function getDefinition($base_plugin_id, $exception_on_invalid = TRUE) {
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function getDefinition($base_plugin_id, $exception_on_invalid = TRUE, $value = NULL) {
    +    $definitions = $this->getDefinitions();
    

    So you just added this new value argument or is that already on the interface?

dsnopek’s picture

Just a note: this issue is now blocking Panels and Panelizer as well, and the patch from #14 allows everything to work for me!

benjy’s picture

Would be good to add a short summary comment on why the prior call is different.

So you just added this new value argument or is that already on the interface?

I added the new parameter, when we're handing sub-types we need to pass the parent definition along to getDefinition() which is why they're different from the calling code. This does raise a good point, anyone using TypedConfigManager needs to be aware that without passing $value along to getDefinition(), sub-types won't be expanded correctly, in addition, we should update the interface docs.

Gábor Hojtsy’s picture

All right, if the public interface needs to change, that may not be allowed before Drupal 9. How does PHP react if an optional argument is added on an interface but not yet added in an implementation? Does that count as a fatal for not implementing the interface properly or not? http://php.net/manual/en/language.oop5.interfaces.php does not seem to document that, maybe this comment is relevant: http://php.net/manual/en/language.oop5.interfaces.php#77950 -- that would suggest we cannot add a new optional argument on an interface and assume that will not break BC?

benjy’s picture

Yeah that would be a fatal, we could probably write this in a different way to avoid backward compatibility. We'd need an internal method that did something like what getDefinition() does now which can be used correctly by buildDataDefinition() and then we'd keep getDefinition() as is, and add a new method like, getComplexDefinition()?

Although, that's all just a workaround, the right thing is to add the new parameter as it is required for getDefinition() to correctly calculate the definition, unless we can solve that another way? I don't think it's possible to derive it.

alexpott’s picture

We need to update the documentation on TypedConfigManagerInterface::getDefinition()... I'm not wild about adding a parameter to that method considering it comes from \Drupal\Component\Plugin\Discovery\DiscoveryInterface

If we proceed with the patch as is we're going to have to make a tough choice about was this means for BC. I'm not sure this is acceptable in a patch release.

alexpott’s picture

+++ b/core/lib/Drupal/Core/Config/TypedConfigManager.php
@@ -138,6 +158,14 @@ public function getDefinition($base_plugin_id, $exception_on_invalid = TRUE) {
+      // If the sub type has a bracket then lets look that up as well.
+      if (isset($value) && strpos($definition['type'], ']')) {
+        $sub_type = $this->determineType($this->replaceName($definition['type'], $value), $definitions);
+        $merge = $definitions[$sub_type];
+        $definition = NestedArray::mergeDeepArray(array($merge, $definition), TRUE);
+      }

What happens if we get another bracket?

tim.plunkett’s picture

I need to revisit this. Running protected $strictConfigSchema = FALSE; in all my tests is unfortunate.

tim.plunkett’s picture

@alexpott, do you mean type: test.[foo].[bar]? Yowch.

tim.plunkett’s picture

Version: 8.0.x-dev » 8.1.x-dev

Agreed about the patch release, unless we come up with something clever/safer

vasi’s picture

Version: 8.1.x-dev » 8.0.x-dev
FileSize
6.19 KB
2.73 KB

I don't think we actually have to change the interface, we're only using the extra parameter internally. We can just pull out the logic of getDefinition() into an internal helper method, and then keep the interface the same.

benjy’s picture

I need to revisit this. Running protected $strictConfigSchema = FALSE; in all my tests is unfortunate.

You may be interested in #2580389: Allow test classes to specify the config object exceptions - that lets you exclude individual objects from the validation.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
2.97 KB
6.05 KB

#26 Yeah I know that part, it's just that the parts that aren't validating are dynamic and unpredictable. Hence this issue :)

#25 is simple yet brilliant. Thanks so much @vasi!

Still unsure about #21.

This streamlines the new method a bit.

tim.plunkett’s picture

Okay, added test coverage for double brackets. It Just Works™, thankfully.

The last submitted patch, 28: 2392057-config-28-FAIL.patch, failed testing.

tim.plunkett’s picture

Issue summary: View changes
Issue tags: -Needs issue summary update
YesCT’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/config/src/Tests/ConfigSchemaTest.php
    @@ -506,4 +506,72 @@ public function testConfigSchemaInfoAlter() {
    +    $untyped_to_typed = $untyped_values;
    ...
    +    $untyped_to_typed = $untyped_values;
    

    what is this for?

  2. +++ b/core/modules/config/tests/config_schema_test/config/schema/config_schema_test.schema.yml
    @@ -213,3 +213,50 @@ config_test.dynamic.*.third_party.config_schema_test:
    +        - type: wrapping.test.double_brackets.[foo]
    ...
    +  type: test.double_brackets.[foo].[bar]
    

    this might be a bit more clear if we dont use "foo" to mean two different possible things.

  3. +++ b/core/lib/Drupal/Core/Config/TypedConfigManager.php
    @@ -116,10 +120,17 @@ public function buildDataDefinition(array $definition, $value, $name = NULL, $pa
    +   * @param $base_plugin_id
    

    missing type

  4. +++ b/core/lib/Drupal/Core/Config/TypedConfigManager.php
    @@ -131,13 +142,38 @@ public function getDefinition($base_plugin_id, $exception_on_invalid = TRUE) {
    +      // If the sub type has a bracket then lets look that up as well.
    

    could be better english grammar.

  5. +++ b/core/lib/Drupal/Core/Config/TypedConfigManager.php
    @@ -131,13 +142,38 @@ public function getDefinition($base_plugin_id, $exception_on_invalid = TRUE) {
    +        $merge = $definitions[$sub_type];
    

    merge? maybe a better var name, or inline.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
1.54 KB
7.57 KB

Thanks @YesCT!

tim.plunkett’s picture

Whoops, missed the feedback from #31.1
Changing #31.2 to be less confusing by not duplicating 'foo'

Also I streamlined the initial call to getDefinitionWithReplacements by skipping the else, and using an array typehint.

Since I changed the logic AND the test slightly, redoing the PASS/FAIL thing.

YesCT’s picture

Status: Needs review » Reviewed & tested by the community

thanks for the issue summary update, and the conversation while we discussed this, latest patch addresses the feedback I had and alex's, thanks!

The last submitted patch, 33: 2392057-config-schema-33-FAIL.patch, failed testing.

tim.plunkett’s picture

Okay that variable name and its optionalness was bothering me. Just a nit, so not changing status back.

Also, please check the credit box for @YesCT, she was instrumental in helping me push this forward, in addition to her thorough reviews

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

This approach looks great - nice work.

  1. +++ b/core/modules/config/src/Tests/ConfigSchemaTest.php
    @@ -506,4 +506,70 @@ public function testConfigSchemaInfoAlter() {
    +   * Tests saving config when the type is wrapped by a dynamic type.
    ...
    +   * Tests saving config when the type is wrapped by a dynamic type.
    

    Same description for two different tests - which is a bit confusing. Perhaps we can improve the test documentation to show what schemas are being used where - so reviewers have to think less :)

  2. Less importantly and out-of-scope here - can we file a followup to examine what to do with $exception_on_invalid - it could be just document that it does nothing - and it would be good to have a test for this behaviour. Or maybe we could implement it.
alexpott’s picture

Added credit ticking for reviewers.

vasi’s picture

Status: Needs work » Needs review
FileSize
7.59 KB
593 bytes

Changed the description for the second test, I hope that's descriptive enough.

tim.plunkett’s picture

Status: Needs review » Reviewed & tested by the community

I think @vasi's change is sufficient.

As far as the follow-up, we do still "use" the $exception_on_invalid, it defaults to TRUE, and before we didn't specify it:

+++ b/core/lib/Drupal/Core/Config/TypedConfigManager.php
@@ -102,7 +103,7 @@ public function buildDataDefinition(array $definition, $value, $name = NULL, $pa
-    $definition += $this->getDefinition($type);
alexpott’s picture

So how will this code throw or not an exception based on this boolean?

tim.plunkett’s picture

As @alexpott pointed out in IRC, since getDefinition() never calls parent::, the $exception_on_invalid handling in \Drupal\Component\Plugin\Discovery\DiscoveryTrait::doGetDefinition() never runs.

So let's pass around this pointless parameter to maintain the previous behavior, but document its pointlessness.

alexpott’s picture

Status: Reviewed & tested by the community » Patch (to be ported)

Committed 9ccbfda and pushed to 8.1.x. Thanks!

+++ b/core/lib/Drupal/Core/Config/TypedConfigManager.php
@@ -116,10 +117,17 @@ public function buildDataDefinition(array $definition, $value, $name = NULL, $pa
+  protected function determineType($base_plugin_id, array $definitions) {

@@ -131,6 +139,27 @@ public function getDefinition($base_plugin_id, $exception_on_invalid = TRUE) {
+  protected function getDefinitionWithReplacements($base_plugin_id, array $replacements, $exception_on_invalid = TRUE) {

To commit this bug fix to 8.0.x we need to prefix these methods with an underscore.

  • alexpott committed 9ccbfda on 8.1.x
    Issue #2392057 by tim.plunkett, vasi, benjy, alexpott, Gábor Hojtsy,...
tim.plunkett’s picture

Status: Patch (to be ported) » Needs review
FileSize
7.84 KB
2.84 KB

Thanks! Added the underscores.

tim.plunkett’s picture

Status: Needs review » Reviewed & tested by the community

Re-RTBC-ing now that it passed with underscores.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Thanks Tim.

Committed 4335d12 and pushed to 8.0.x. Thanks!

  • alexpott committed 4335d12 on 8.0.x
    Issue #2392057 by tim.plunkett, vasi, benjy, alexpott, Gábor Hojtsy,...
Gábor Hojtsy’s picture

Yay, thanks all! Great seeing important bugs fixed with config schema :) All of contrib will be happy :)

benjy’s picture

alexpott’s picture

Berdir’s picture

(Edit: crossposted with alex)

Found a nasty bug with this: #2663410: TypedConfigManager::_getDefinitionWithReplacements() incorrectly replaces generic definition.

Page Manager only works because we don't have any tests that are adding more than one block of different types to the same display variant.

See also #2663376: The core block_settings schema defines schema for block_content blocks for another confusing thing that is partially hiding this the problem here.

Status: Fixed » Closed (fixed)

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