Drupal's form API has been brilliant for many years. Still, recently I found myself wondering why I needed to build a configuration form if I already had a schema for my config. Defining a schema facilitates API-first validation (including some pretty smart constraints), specific typing (e.g. actual booleans or integers instead of '0' or '1' strings), and even translation in Drupal.
That last part got me thinking; if Drupal automatically provides translation forms for typed configuration, why must I build a form? I started diving into the code and found config_translation_config_schema_info_alter()
which maps certain config data types to element classes. The ConfigTranslationFormBase::buildForm()
class fetches the schema for each config property from the 'config.typed' service (\Drupal\Core\Config\TypedConfigManager
) before building the appropriate elements. So Drupal core automatically provides this translation form - notice the long textarea for the 'body' property:
I had built a block plugin that needed some regex-based validation on a couple of its configuration properties. Validation constraints seemed like a natural fit for these, as an inherent part of the property definitions, rather than just on the form level. Drupal has had good support for validation constraints on configuration since version 10.2. This allows forms to be simpler, and config to be fully validatable, even outside the context of a form (e.g. for setting via APIs or config synchronisation). So I defined my config schema like this:
block.settings.mymodule_myblock:
type: block_settings
label: 'MyBlock block settings'
mapping:
svcid:
type: string
label: 'Service ID'
constraints:
Regex:
pattern: '/^[a-zA-Z0-9_\-]+$/'
message: "The %value can only contain simple letters, numbers, underscores or hyphens."
default: 'abcde'
locked: true
envid:
type: string
label: 'Environment ID'
constraints:
Regex:
pattern: '/^[a-zA-Z0-9_\-]+$/'
message: "The %value can only contain simple letters, numbers, underscores or hyphens."
default: 'x-j9WsahRe_1An51DhErab-C'
Then I set myself the challenge of building a configuration form 'automatically' from this schema - without using core's config_translation module at all, as this was for a monolingual site.
I only had two string properties, which meant two textfields, but I wrote the code to use form elements that could be appropriate for other types of property that might get added in the future. The #title
of each element could come directly from each label
in the schema. (Why do we usually set these in both the schema and form element?!) I added custom default
and locked
properties to the schema to help encapsulate everything I considered 'inherent' to each part of the config in one place. This meant the configuration form for my block could be fairly simple:
public function blockForm($form, FormStateInterface $form_state) {
// Each config property will be returned with its schema from $this->getConfigurables().
foreach ($this->getConfigurables() as $key => $schema_info) {
$form[$key] = [
'#type' => match ($schema_info['type']) {
'string', 'label' => 'textfield',
'text' => 'textarea',
'boolean' => 'checkbox',
'integer', 'float' => 'number',
'email' => 'email',
},
'#title' => $schema_info['label'],
'#default_value' => $this->configuration[$key],
'#required' => empty($schema_info['nullable']),
'#disabled' => $schema_info['locked'] ?? FALSE,
];
}
return $form;
}
Hopefully that gives an idea of how simple a config form could be - and this could really be reduced further by refactoring it into a generic trait. The code in core's config_translation module for mapping the type of each property to an element type could be much more useful than the fairly naïve match
statement above, if it was refactored out to be available even to monolingual sites.
You can explore my full code at https://gist.github.com/anotherjames/bcb7ba55ec56359240b26d322fe2f5a5. That includes the getConfigurables()
method which pulls the schema from the TypedConfigManager.
You'll see that I went a little further and picked up the regex constraints for each config property, for use in #pattern
form API properties. This provides quick feedback to admins about what characters are allowed using the HTML5 pattern attribute:
Not all configuration constraints could be built into the form level. It's arguable that since the Regex constraint and HTML pattern attribute support slightly different regular expression features, this particular one shouldn't be included in a generic trait. Then again, the Choice constraint could be especially useful to include, as it could be used to populate #options
for select, radios, or checkboxes elements. We've started using backed Enums with labels for fixed sets of options. Can we wire those up to choice constraints together, I wonder?
Whereas my example was for a configurable plugin's form (which I don't believe can use #config_target
), Joachim Noreiko (joachim) has submitted a feature request to Drupal core for forms extending ConfigFormBase
to get automatically built from schema. This idea of generating form elements from config schema is still in its infancy, so its limits and benefits need to be explored further. Please let us know in a comment here, or in Joachim's feature request, if you have done anything similar, or have ideas or concerns to point out!