Problem/Motivation

Thanks to the awesome work by @vijaycs85 in #1953404: Add config schema to field and instance config entities, we now have a schema for the configuration files for Field API's $field and $instance config entities.

This schema leverages the "include" mechanism provided by the config schema system, to reflect the fact that certain subtrees in the YML structures (e.g. the content of the 'settings' or 'default_value' entries in field.instance.[entity_type].[bundle].[field_id].yml) depend on the value of another key (in this case, the field type).

This "include" mechanism, however, is purely static : a static schema description shipped by field.module contains the schema for field and instance config structures, and can "include" another static schema snippet, shipped by another module, based on the value of the 'field_type' key in the specific config file whose schema is being generated:

in core/modules/field/config/schema/field.schema.yml:
field.instance.*.*.*:
(...)
  settings:
     type: field.[%parent.field_type].instance_settings
in core/modules/text/config/schema/text.schema.yml:
field.text_with_summary.instance_settings:
  type: mapping
  label: 'Text area with a summary'
  mapping:
    text_processing:
      type: boolean
      label: 'Text processing'
    display_summary:
      type: boolean
      label: 'Summary input'
    user_register_form:
      type: boolean
      label: 'Display on user registration form.'

When computing the schema for field.instance.node.article.body.yml, (field_type: 'text_with_summary'), this results in the generated schema being expanded into:

(...)
  settings:
    type: mapping
      label: 'Text area with a summary'
      mapping:
        text_processing:
          type: boolean
          label: 'Text processing'
        display_summary:
          type: boolean
          label: 'Summary input'
        user_register_form:
          type: boolean
          label: 'Display on user registration form.'

and this would be different for field.instance.node.article.field_tags.yml.

The problem is : the list of available settings on a given field type goes through an alter hook.
The 'user_register_form' setting above is altered in by user.module, text.module doesn't know it exists, and cannot be expected to provide schema metadata about it. All the more for settings altered in by contrib modules.

Same applies to widget settings or formatter settings, and most probably to other plugins / "pluggable-not-yet-but-soon-to-be-plugins" subsystems. We have at least two dozen contrib modules relying on alterable settings, and a very vocal @Dave Reid about alterability being a critical feature :).

Proposed resolution

Not too clear :-/.

Obvious approach would be some syntax that allows runtime code execution to generate config schema snippets :

field.instance.*.*.*:
(...)
  settings:
     type: "call field_instance_settings_schema([%parent.field_type])"  # or more probably some method on some class

#1764380: Merge PluginSettingsInterface into ConfigurablePluginInterface could help providing the base code that would make this easily leveraged by all plugin types.

But I think I recall @Gabor in the epic #1648930: Introduce configuration schema and use for translation saying that requiring runtime code execution to generate schemas would make the life of localize.drupal.org impossible.

So, a bit at loss here...
However, with #1953404: Add config schema to field and instance config entities around the corner, the elephant has officially (re-)entered the room :-)

Comments

Yeah, any code execution would require a live Drupal instance of the given version, with the given modules, with the given configuration. How can we not express the schema variance through values in the data that we can attach the type variance on? Surrely, if the schema needs to be different, the data will be different too.

So, core/modules/text/config/schema/text.schema.yml would have:

field.text_with_summary.instance_settings:
  type: mapping
  label: 'Text area with a summary'
  mapping:
    text_processing:
      type: boolean
      label: 'Text processing'
    display_summary:
      type: boolean
      label: 'Summary input'

And user.module and others need to alter this. We have an event subscriber mechanism for modules to alter config. Could #1764380: Merge PluginSettingsInterface into ConfigurablePluginInterface leverage that instead of requiring some kind of function referencing to be in the schema files themselves?

requiring runtime code execution to generate schemas would make the life of localize.drupal.org impossible

Well, localize.drupal.org is for translating default configuration, not site-specific config. And default values for plugin settings altered in by other modules are in those modules' PHP files, not in YML files, so they're localized already through t(), aren't they?

@Gábor Hojtsy:

How can we not express the schema variance through values in the data that we can attach the type variance on? . Surely, if the schema needs to be different, the data will be different too.

Nope. Enable module foo, resave your field instance without touching anything, the 'settings' entry has a new item for 'some_setting_altered_in_by_foo_module", the rest of the file hasn't changed a bit :-). And only foo.module knows what the schema for that setting might be, but we're only asking text.module.

@effulgentsia

We have an event subscriber mechanism for modules to alter config. Could #1764380: Merge PluginSettingsInterface into ConfigurablePluginInterface leverage that instead of requiring some kind of function referencing to be in the schema files themselves?

I really haven't looked into any of this, dunno, maybe. Note that this is not about altering config, though, but about altering the schema being assembled for a given config file.

It's like, when we encounter field.[%parent.field_type].instance_settings (and if %parent.field_type is, say,'text_with_summary'), we should :
- fetch the field.text_with_summary.instance_settings snippet wherever it is (will be in text.module) - like we do now,
- then "merge" whatever field.text_with_summary__alter.instance_settings snippets we can find anywhere

Well, localize.drupal.org is for translating default configuration, not site-specific config. And default values for plugin settings altered in by other modules are in those modules' PHP files, not in YML files, so they're localized already through t(), aren't they?

Yes, but doesn't help here. We're not dealing with the default values, but with the existence of the setting, and its schema (data type & label).

Note that this is not about altering config, though, but about altering the schema being assembled for a given config file.

Right, but aren't the schema files read via the config() API?

We're not dealing with the default values, but with the existence of the setting, and its schema (data type & label).

What I'm saying though (but I might be wrong) is that it's ok for localize.drupal.org to not know about the existence of the extra 'user_register_form' setting that user.module adds to text.module field types. Because it doesn't need to translate the default values for that setting, since user.module supplies those default values via t(), not via a user.*.yml config file. Only a Config translation UI for a particular site would need to know about that setting, and that site would have a running Drupal instance with all the used modules enabled, so needing to execute runtime code to get that schema info would be ok.

Oh sorry, no I'm wrong. The problem is that the Standard profile *does* ship with a yml config file that references 'user_register_form'. That particular example isn't translatable, but I'm sure we'll run into ones that are. And localize.drupal.org will need to translate those. Darn.

Exactly. Standard profile, or the profile of an arbitrary contrib distro that includes random contrib modules that might very well fall into this pattern of "I'm adding extra settings to this field type / widget / formatter, and some of them are human readable labels, and some of them are included in the profile's default config".

then "merge" whatever field.text_with_summary__alter.instance_settings snippets we can find anywhere

Yes. Seems like we should be able to do that. And it's already the case that localize.drupal.org needs to know that if it's trying to understand what's in a config file shipped with Standard profile that it needs to scan all schema files shipped with modules that Standard profile depends on. That's how it finds the field.text_with_summary.instance_settings snippet in text module to begin with. So I don't think adding a scan for the field.text_with_summary__alter.instance_settings snippets adds any additional infrastructure requirement, does it?

A big problem is that drupal_alter() is, well, fundamentally runtime.
What happens in hook_field_info_alter() is entirely up to the implementation, and can depend on runtime conditions like the phase of the moon. So transposing / mirroring its effects in static files without being able to run code is going to be, er, difficult.

Or more simply, the hook_field_info_alter() can just say "I'll add this setting to every field type that exists on the site, for as long as I'm enabled". Yet it can't ship the corresponding field.[some_field_type].setting static schema snippets for all those field types.

Sure it can. user.alter.field.*.instance_settings.

That doesn't solve cases like "add this setting to only the fields that the administrator selected on some other configuration form", but it also doesn't really harm anything for the schema to imply settings that end up not really existing.

Once again, I dont get this. If the config data is not diffeerent, then the schema should not be different. If the config data is different, it can be packaged inside a wrapper storage level that tells us about its type/plugin like a view is an arbitrary collection of random data from different plugins from the point of view of the schema system. If we just dont have any data about the plugin providing the random data in the config data, that is an issue, but the we have an api that is not translation compatible by design... Problem with mixing t() with config translation is that t() data will save in the localized language when stored in cmi while config data as shipped will be English. So the sulting data would be very inconsistent. We need the kind of data wrapping in a tiny container that tells us about the plugin, like views.

On the other hand, the real elephant in the room with schemas is versioning. Eg. if your module depends on three other modules (via .info file), that provide plugins for your config, and one of those modules is updated. As it is now, the data has no information about plugin/schema versions, so the new format of the data will not work with the old schema but the old data will not match the new schema. Obviously if you have a runtime system, presumably your versions will be properly set up so your data is up to date to your schema (practically you ran update.php on your site). But for localize.drupal.org the real problem will be this scenario. It can store/cache schemas, but when the .info file provides no guidance for the version of the required modules it might pick the wrong schema. Unfortunately the localize.drupal.org work did not even start yet (#1933988: Support for Drupal 8 shipped configuration translatables).

Now that some of the other major config schema issues landed, I've opened #1977498: Configuration data vs. schema lacks version tracking for the versioning problem (which I think is the real elephant), this issue just needs local data for informing typing which is a best practice :) If there is no info about the data typing at pluggable locations, it is just a huge black box. The schema is especially designed to break out the black box, so yes, it needs more data vs. just assuming it should stay a black box.

Status:Active» Closed (duplicate)

#2005434: Let 3rd party modules store extra configuration in EntityDisplay would be the correct fix for "alterable but non translatable" settings

Yay!

Issue summary:View changes

minor