4

In Drupal 7 with custom forms and variables, one could easily store arrays of information in the 'variables' table. I would sometimes construct configuration forms that provided a setting per say user role. Then I could either store the setting for each role in a separate variable, or pack all the settings into one array and store it that way.

I am wondering what the standard best practices way of doing a similar thing in Drupal 8.

in Drupal 7 I would have something like this for a form code:

// build list of roles
  $user_roles = user_roles(TRUE);
  // remove authenticated user role from options
  if (isset($user_roles[2])) {
    unset($user_roles[2]);
  }
$role_settings = variable_get('MYMODULE_role_type_mapping', $role_settings);
foreach ($user_roles as $rid => $role) {
      $form['roles']['other_roles']['MYMODULE_role_' . $rid . '_type_mapping'] = [
        '#title'         => $role,
        '#type'          => 'select',
        '#options'       => $membership_types,
        '#default_value' => !empty($role_settings[$rid]) ? $role_settings[$rid] : '',
      ];
    }

and then something like this in the form submit callback:

    // build list of roles
      $user_roles = user_roles(TRUE);
      // remove authenticated user role from options
      if (isset($user_roles[2])) {
        unset($user_roles[2]);
      }
      foreach ($user_roles as $rid => $role) {
        $role_settings[$rid] = !empty($form_state['values']['MYMODULE_role_' . $rid . '_type_mapping']) ? $form_state['values']['MYMODULE_role_' . $rid . '_type_mapping'] : '';
      }
variable_set('MYMODULE_role_type_mapping', $role_settings);

This type of thing is necessary when you install the custom module on multiple sites, where there could be different number of roles on each site.

So now with Drupal 8 we have this new Configuration API. I can't assume I know the roles in advance.

I was hoping to achieve this with "Simple Configuration" but I realize it may be necessary to create a custom configuration entity, although if it can be done without that that would be nice and "simple".

According to this resource: https://www.drupal.org/node/1905070#types There are just a few schema types for configuration fields, none of which seem intuitively appropriate to this case.

Reading that page, it is not obvious even how one would store the value of a multi-valued select list in configuration, let alone variable that could be an array of all kinds of different information. In D7 a multi-valued select list would return an array. In D7 variables are stored as serialized variables. Even D7's "simple_settings_form" would handle this no problem.

Surely this type of problem has been solved many times, but it is not "simple" to find any information.

So the problem is two-fold 1) Just storing an array of values into configuration, with unknown number of array elements, number depending on custom site conditions (like user roles on a site) 2) Unpacking this array and setting up form elements for it, which on submit, packs it all up into one variable.

Similar question, simpler question is: If one is defining configuration schema for a multi-valued select list, what data type does one use? String?

Is the model I used here for D7 even portable to D8, there's probably some better way to go no doubt, but making a configuration entity for each role in the site and all of that doesn't seem simple and definitely not as simple as could be done in D7. I do want to give a thought to config export / import between sites. dev/staging/production versions of the site may all have the same user roles, but definitely not separate websites altogether.

Although I ask several questions in this post, the common theme is how to store arrays of information as configuration appropriately in Drupal 8? Simple case is the value of a multi-valued select list, more complex scenario of the setting per user role like I describe.

Thanks in advance!

  • 1
    For the simple case the principle hasn't changed much - the data is still stored as a serialised PHP string; you can extend ConfigFormBase in place of simple_settings_form, you're just reading from config in the buildForm method, and writing to config in submitForm instead. The (de-)structuring remains the same. It's also worth mentioning the State API in this, as that may be a more appropriate storage depending on your needs – Clive Aug 26 at 18:45
  • I found I had to use a type 'sequence' for a multi-valued select list. My case is actually more complex than I asked about, writing a openid_connect plugin and the plugin class has some logic for how the settings form is produced and saved to configuration that may be different from submitForm on ConfigFormBase. – jackrabbithanna Aug 26 at 22:44
7

You define a schema type sequence:

Example: /core/modules/options/config/schema/options.schema.yml

# Schema for the configuration files of the Options module.

field.storage_settings.list_integer:
  type: mapping
  label: 'List (integer) settings'
  mapping:
    allowed_values:
      type: sequence
      label: 'Allowed values list'
      sequence:
        type: mapping
        label: 'Allowed value with label'
        mapping:
          value:
            type: integer
            label: 'Value'
          label:
            type: label
            label: 'Label'

You'll find the form for this example in /core/modules/options/src/Plugin/Field/FieldType/ListItemBase.php

More info https://www.drupal.org/docs/8/api/configuration-api/configuration-schemametadata

  • If you put a mapping into a sequence, then the variables need to be named. If you put a sequence into a sequence, then they can be unnamed. – oknate Aug 26 at 19:53
  • Yes, you can build multi-dimensional arrays like this but on the lowest level you want most times mapped properties to make the schema predictable. – 4k4 Aug 26 at 20:04
  • ... unless you have only one string like in your answer. – 4k4 Aug 26 at 20:17
  • I can see how both ways are good for different cases...I like stackoverflow! Thanks is making sense for the schema. In my case I could name them, role_id and membership_type .. but I have done things that would be hard to specifically map so I'm sure I'll use that sometime. It's these little tidbits of knowledge that are golden and hard to find documented. Thanks both of you :) – jackrabbithanna Aug 26 at 20:21
2

I was recently struggling how to store data with a nested array of strings,

allowed_view_modes:
  image:
    embed: embed
    full: full

The only way I could figure out how to validate it was by running functional tests, where it validates the configs when you save a config entity.

I was able to save data like the above data by nesting 'sequences':

ckeditor.plugin.drupalmedia:
  type: mapping
  label: 'Media Embed'
  mapping:
    allowed_view_modes:
      type: sequence
      label: 'Allowed View Modes'
      nullable: true
      sequence:
        type: sequence
        nullable: true
        label: 'View Mode'
        sequence:
          type: string

This worked and passed the config linter.

The top level sequence is for the media type, the inner sequence is for each view mode. There are lots of complex examples in drupal core. Unfortunately, I don't know where to point you for the documentation. I've mostly been learning this by trial and error.

2

This was custom openid_connect plugin I was working on, so a bit different than a ConfigFormBase normal configuration form, but I hope this methodology applies and is helpful to someone in the future. I used a schema like so (snippet):

# Schema for the configuration files of the OpenID Connect module.
openid_connect.settings.nimble:
  type: config_object
  label: 'OpenID Connect Nimble settings'
  mapping:
    enabled:
      type: boolean
      label: 'Enable client'
    settings:
      type: mapping
      mapping:
        roles_membership_types:
        type: sequence
        label: 'Role Membership Types'
        sequence:
          type: mapping
          label: 'Roles mapped to membership types'
          mapping:
            role:
              type: string
              label: 'Role id'
            membership_type:
              type: string
              label: 'Membership Type'

FAPI setup like so (I had NO form index for the actual name of the config item, roles_membership_types:

    $roles = user_role_names(TRUE);
    if (isset($roles['authenticated'])) {
      unset($roles['authenticated']);
    }
    $membership_types = ['' => 'None'] + $this->getMembershipTypes($this->configuration['instance']);
    $roles_membership_types = $this->configuration['roles_membership_types'];
    if (!empty($roles_membership_types)) {
      foreach ($roles_membership_types as $type) {
        $roles_membership_types_default_values[$type['role']] = $type['membership_type'];
      }
    }
    foreach ($roles as $rid => $role) {
      $form['roles_mapped_membership_types'][$rid] = [
        '#title'         => $role,
        '#type'          => 'select',
        '#options'       => $membership_types,
        '#default_value' => !empty($roles_membership_types_default_values[$rid]) ? $roles_membership_types_default_values[$rid] : '',
      ];
    }

Little bit of code in the submit handler to get the values from the role form elements, and put it together in the format necessary to map the config.

$values = $form_state->getValues();
if (!empty($values['roles_mapped_membership_types'])) {
  foreach ($values['roles_mapped_membership_types'] as $rid => $membership_type) {
    $roles_to_membership_type_value[] = ['role' => $rid, 'membership_type' => $membership_type];
  }
$form_state->setValue('roles_membership_types', $roles_to_membership_type_value);
}

So eventually this code would work:

// Save plugin settings.
$this->configFactory()
  ->getEditable('openid_connect.settings.' . $plugin_id)
  ->set('settings', $subform_state->getValues())
  ->save();

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.