class Mapping

Same name in this branch
  1. 11.x core/modules/views/src/Plugin/views/style/Mapping.php \Drupal\views\Plugin\views\style\Mapping
Same name and namespace in other branches
  1. 9 core/modules/views/src/Plugin/views/style/Mapping.php \Drupal\views\Plugin\views\style\Mapping
  2. 9 core/lib/Drupal/Core/Config/Schema/Mapping.php \Drupal\Core\Config\Schema\Mapping
  3. 8.9.x core/modules/views/src/Plugin/views/style/Mapping.php \Drupal\views\Plugin\views\style\Mapping
  4. 8.9.x core/lib/Drupal/Core/Config/Schema/Mapping.php \Drupal\Core\Config\Schema\Mapping
  5. 10 core/modules/views/src/Plugin/views/style/Mapping.php \Drupal\views\Plugin\views\style\Mapping
  6. 10 core/lib/Drupal/Core/Config/Schema/Mapping.php \Drupal\Core\Config\Schema\Mapping

Defines a mapping configuration element.

This object may contain any number and type of nested properties and each property key may have its own definition in the 'mapping' property of the configuration schema.

Properties in the configuration value that are not defined in the mapping will get the 'undefined' data type.

Read https://www.drupal.org/node/1905070 for more details about configuration schema, types and type resolution.

Hierarchy

Expanded class hierarchy of Mapping

11 files declare their use of Mapping
ConfigActionManager.php in core/lib/Drupal/Core/Config/Action/ConfigActionManager.php
ConfigEntityAdapterTest.php in core/tests/Drupal/KernelTests/Core/Entity/ConfigEntityAdapterTest.php
ConfigEntityValidationTestBase.php in core/tests/Drupal/KernelTests/Core/Config/ConfigEntityValidationTestBase.php
ConfigMapperManagerTest.php in core/modules/config_translation/tests/src/Unit/ConfigMapperManagerTest.php
ConfigSchemaTest.php in core/tests/Drupal/KernelTests/Core/Config/ConfigSchemaTest.php

... See full list

92 string references to 'Mapping'
action_bulk_test.schema.yml in core/modules/views/tests/modules/action_bulk_test/config/schema/action_bulk_test.schema.yml
core/modules/views/tests/modules/action_bulk_test/config/schema/action_bulk_test.schema.yml
book.schema.yml in core/modules/book/config/schema/book.schema.yml
core/modules/book/config/schema/book.schema.yml
ckeditor5.pair.schema.yml in core/modules/ckeditor5/config/schema/ckeditor5.pair.schema.yml
core/modules/ckeditor5/config/schema/ckeditor5.pair.schema.yml
ckeditor5.schema.yml in core/modules/ckeditor5/config/schema/ckeditor5.schema.yml
core/modules/ckeditor5/config/schema/ckeditor5.schema.yml
ckeditor5_plugin_elements_subset.schema.yml in core/modules/ckeditor5/tests/modules/ckeditor5_plugin_elements_subset/config/schema/ckeditor5_plugin_elements_subset.schema.yml
core/modules/ckeditor5/tests/modules/ckeditor5_plugin_elements_subset/config/schema/ckeditor5_plugin_elements_subset.schema.yml

... See full list

File

core/lib/Drupal/Core/Config/Schema/Mapping.php, line 23

Namespace

Drupal\Core\Config\Schema
View source
class Mapping extends ArrayElement {
    
    /**
     * {@inheritdoc}
     */
    public function __construct(DataDefinitionInterface $definition, $name = NULL, ?TypedDataInterface $parent = NULL) {
        assert($definition instanceof MapDataDefinition);
        // Validate basic structure.
        foreach ($definition['mapping'] as $key => $key_definition) {
            // Guide developers when a config schema definition is wrong.
            if (!is_array($key_definition)) {
                if (!$parent) {
                    throw new \LogicException(sprintf("The mapping definition at `%s` is invalid: its `%s` key contains a %s. It must be an array.", $name, $key, gettype($key_definition)));
                }
                else {
                    throw new \LogicException(sprintf("The mapping definition at `%s:%s` is invalid: its `%s` key contains a %s. It must be an array.", $parent->getPropertyPath(), $name, $key, gettype($key_definition)));
                }
            }
        }
        $this->processRequiredKeyFlags($definition);
        parent::__construct($definition, $name, $parent);
    }
    
    /**
     * {@inheritdoc}
     */
    protected function getElementDefinition($key) {
        $value = $this->value[$key] ?? NULL;
        $definition = $this->definition['mapping'][$key] ?? [];
        return $this->buildDataDefinition($definition, $value, $key);
    }
    
    /**
     * Gets all keys allowed in this mapping.
     *
     * @return string[]
     *   A list of keys allowed in this mapping.
     */
    public function getValidKeys() : array {
        $all_keys = $this->getDefinedKeys();
        return array_keys($all_keys);
    }
    
    /**
     * Gets all required keys in this mapping.
     *
     * @return string[]
     *   A list of keys required in this mapping.
     */
    public function getRequiredKeys() : array {
        $all_keys = $this->getDefinedKeys();
        $required_keys = array_filter($all_keys, fn(array $schema_definition): bool => $schema_definition['requiredKey']);
        return array_keys($required_keys);
    }
    
    /**
     * Gets the keys defined for this mapping (locally defined + inherited).
     *
     * @return array
     *   Raw schema definitions: keys are mapping keys, values are their
     *   definitions.
     */
    protected function getDefinedKeys() : array {
        $definition = $this->getDataDefinition();
        return $definition->toArray()['mapping'];
    }
    
    /**
     * Gets all dynamically valid keys.
     *
     * When the `type` of the mapping is dynamic itself. For example: the settings
     * associated with a FieldConfig depend on what kind of field it is (i.e.,
     * which field plugin it uses).
     *
     * Other examples:
     * - CKEditor 5 uses 'ckeditor5.plugin.[%key]'; the mapping is stored in a
     *   sequence, and `[%key]` is replaced by the mapping's key in that sequence.
     * - third party settings use '[%parent.%parent.%type].third_party.[%key]';
     *   `[%parent.%parent.%type]` is replaced by the type of the mapping two
     *   levels up. For example, 'node.type.third_party.[%key]'.
     * - field instances' default values have a type of
     *   'field.value.[%parent.%parent.field_type]'. This uses the value of the
     *   `field_type` key from the mapping two levels up.
     * - Views filters have a type of 'views.filter.[plugin_id]'; `[plugin_id]` is
     *   replaced by the value of the mapping's `plugin_id` key.
     *
     * In each of these examples, the mapping may have keys that are dynamically
     * valid, meaning that which keys are considered valid may depend on other
     * values in the tree.
     *
     * @return string[][]
     *   A list of dynamically valid keys. An array with:
     *   - a key for every possible resolved type
     *   - the corresponding value an array of the additional mapping keys that
     *     are supported for this resolved type
     *
     * @see \Drupal\Core\Config\TypedConfigManager::resolveDynamicTypeName()
     * @see \Drupal\Core\Config\TypedConfigManager::resolveExpression()
     * @see https://www.drupal.org/files/ConfigSchemaCheatSheet2.0.pdf
     */
    public function getDynamicallyValidKeys() : array {
        $parent_data_def = $this->getParent()?->getDataDefinition();
        if ($parent_data_def === NULL) {
            return [];
        }
        // Use the parent data definition to determine the type of this mapping
        // (including the dynamic placeholders). For example:
        // - `editor.settings.[%parent.editor]`
        // - `editor.image_upload_settings.[status]`.
        $original_mapping_type = match (TRUE) {    $parent_data_def instanceof MapDataDefinition => $parent_data_def->toArray()['mapping'][$this->getName()]['type'],
            $parent_data_def instanceof SequenceDataDefinition => $parent_data_def->toArray()['sequence']['type'],
            default => throw new \LogicException('Invalid config schema detected.'),
        
        };
        // If this mapping's type isn't dynamic, there's nothing to do.
        if (!str_contains($original_mapping_type, ']')) {
            return [];
        }
        elseif (str_starts_with($original_mapping_type, '[')) {
            return [];
        }
        // Expand the dynamic placeholders to find all mapping types derived from
        // the original mapping type. To continue the previous example:
        // - `editor.settings.unicorn`
        // - `editor.image_upload_settings.*`
        // - `editor.image_upload_settings.1`
        $possible_types = $this->getPossibleTypes($original_mapping_type);
        // TRICKY: it is tempting to not consider this a dynamic type if only one
        // concrete type exists. But that would lead to different validation errors
        // when modules are installed or uninstalled.
        assert(!empty($possible_types));
        // Determine all valid keys, across all possible types.
        $typed_data_manager = $this->getTypedDataManager();
        $all_type_definitions = $typed_data_manager->getDefinitions();
        $possible_type_definitions = array_intersect_key($all_type_definitions, array_fill_keys($possible_types, TRUE));
        // TRICKY: \Drupal\Core\Config\TypedConfigManager::getDefinition() does the
        // necessary resolving, but TypedConfigManager::getDefinitions() does not! 🤷‍♂️
        // @see \Drupal\Core\Config\TypedConfigManager::getDefinitionWithReplacements()
        // @see ::getValidKeys()
        $valid_keys_per_type = [];
        foreach (array_keys($possible_type_definitions) as $possible_type_name) {
            $valid_keys_per_type[$possible_type_name] = array_keys($typed_data_manager->getDefinition($possible_type_name)['mapping'] ?? []);
        }
        // From all valid keys across all types, get the ones for the fallback type:
        // its keys are inherited by all type definitions and are therefore always
        // ("statically") valid. Not all types have a fallback type.
        // @see \Drupal\Core\Config\TypedConfigManager::getDefinitionWithReplacements()
        $fallback_type = $typed_data_manager->findFallback($original_mapping_type);
        $valid_keys_everywhere = array_intersect_key($valid_keys_per_type, [
            $fallback_type => NULL,
        ]);
        assert(count($valid_keys_everywhere) <= 1);
        $statically_required_keys = NestedArray::mergeDeepArray($valid_keys_everywhere);
        // Now that statically valid keys are known, determine which valid keys are
        // only valid in *some* cases: remove the statically valid keys from every
        // per-type array of valid keys.
        $valid_keys_some = array_diff_key($valid_keys_per_type, $valid_keys_everywhere);
        $valid_keys_some_processed = array_map(fn(array $keys) => array_values(array_filter($keys, fn(string $key) => !in_array($key, $statically_required_keys, TRUE))), $valid_keys_some);
        return $valid_keys_some_processed;
    }
    
    /**
     * Gets all optional keys in this mapping.
     *
     * @return string[]
     *   A list of optional keys given the values in this mapping.
     */
    public function getOptionalKeys() : array {
        return array_values(array_diff($this->getValidKeys(), $this->getRequiredKeys()));
    }
    
    /**
     * Validates optional `requiredKey` flags, guarantees one will be set.
     *
     * For each key-value pair:
     * - If the `requiredKey` flag is set, it must be `false`, to avoid pointless
     *   information in the schema.
     * - If the `requiredKey` flag is not set and the `deprecated` flag is set,
     *   this will set `requiredKey: false`: deprecated keys are always optional.
     * - If the `requiredKey` flag is not set, nor the `deprecated` flag,
     *   will set `requiredKey: true`.
     *
     * @param \Drupal\Core\TypedData\MapDataDefinition $definition
     *   The config schema definition for a `type: mapping`.
     *
     * @return void
     *
     * @throws \LogicException
     *   Thrown when `requiredKey: true` is specified.
     */
    protected function processRequiredKeyFlags(MapDataDefinition $definition) : void {
        foreach ($definition['mapping'] as $key => $key_definition) {
            // Validates `requiredKey` flag in mapping definitions.
            if (array_key_exists('requiredKey', $key_definition) && $key_definition['requiredKey'] !== FALSE) {
                throw new \LogicException('The `requiredKey` flag must either be omitted or have `false` as the value.');
            }
            // Generates the `requiredKey` flag if it is not set.
            if (!array_key_exists('requiredKey', $key_definition)) {
                // Required by default, unless this key is marked as deprecated.
                // @see https://www.drupal.org/node/3129881
                $definition['mapping'][$key]['requiredKey'] = !array_key_exists('deprecated', $key_definition);
            }
        }
    }
    
    /**
     * Returns all possible types for the type with the given name.
     *
     * @param string $name
     *   Configuration name or key.
     *
     * @return string[]
     *   All possible types for a given type. For example,
     *   `core_date_format_pattern.[%parent.locked]` will return:
     *   - `core_date_format_pattern.0`
     *   - `core_date_format_pattern.1`
     *   If a fallback name is available, that will be returned too. In this
     *   example, that would be `core_date_format_pattern.*`.
     */
    protected function getPossibleTypes(string $name) : array {
        // First, parse from e.g.
        // `module.something.foo_[%parent.locked]`
        // this:
        // `[%parent.locked]`
        // or from
        // `[%parent.%parent.%type].third_party.[%key]`
        // this:
        // `[%parent.%parent.%type]` and `[%key]`.
        // And collapse all these to just `[]`.
        // @see \Drupal\Core\Config\TypedConfigManager::replaceVariable()
        $matches = [];
        if (preg_match_all('/(\\[[^\\]]+\\])/', $name, $matches) >= 1) {
            $name = str_replace($matches[0], '[]', $name);
        }
        // Then, replace all `[]` occurrences with `.*` and escape all periods for
        // use in a regex. So:
        // `module\.something\.foo_.*`
        // or
        // `.*\.third_party\..*`
        $regex = str_replace([
            '.',
            '[]',
        ], [
            '\\.',
            '.*',
        ], $name);
        // Now find all possible types:
        // 1. `module.something.foo_foo`, `module.something.foo_bar`, etc.
        $possible_types = array_filter(array_keys($this->getTypedDataManager()
            ->getDefinitions()), fn(string $type) => preg_match("/^{$regex}\$/", $type) === 1);
        // 2. The fallback: `module.something.*` — if no concrete definition for it
        // exists.
        $fallback_type = $this->getTypedDataManager()
            ->findFallback($name);
        if ($fallback_type && !in_array($fallback_type, $possible_types, TRUE)) {
            $possible_types[] = $fallback_type;
        }
        return $possible_types;
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title Overrides
ArrayElement::$elements protected property Parsed elements.
ArrayElement::buildDataDefinition protected function Creates a new data definition object from an array and configuration.
ArrayElement::createElement protected function Creates a contained typed configuration object.
ArrayElement::get public function Gets a contained typed configuration element. Overrides TypedConfigInterface::get
ArrayElement::getAllKeys protected function Gets valid configuration data keys.
ArrayElement::getElements public function Gets an array of contained elements. Overrides TypedConfigInterface::getElements
ArrayElement::getIterator public function
ArrayElement::getProperties public function Gets an array of property objects. Overrides ComplexDataInterface::getProperties
ArrayElement::hasTranslatableElements public function Determines if there is a translatable value.
ArrayElement::isEmpty public function Determines whether the data structure is empty. Overrides TypedConfigInterface::isEmpty
ArrayElement::isNullable public function Determines if this element allows NULL as a value.
ArrayElement::onChange public function React to changes to a child property or item. Overrides TraversableTypedDataInterface::onChange
ArrayElement::parse protected function Builds an array of contained elements.
ArrayElement::set public function Sets a property value. Overrides ComplexDataInterface::set
ArrayElement::toArray public function Returns an array of all property values. Overrides TypedConfigInterface::toArray
DependencySerializationTrait::$_entityStorages protected property
DependencySerializationTrait::$_serviceIds protected property
DependencySerializationTrait::__sleep public function 1
DependencySerializationTrait::__wakeup public function 2
Element::$value protected property The configuration value.
Element::getTypedDataManager public function Gets the typed configuration manager. Overrides TypedDataTrait::getTypedDataManager
Element::setTypedDataManager public function Sets the typed config manager. Overrides TypedDataTrait::setTypedDataManager
Mapping::getDefinedKeys protected function Gets the keys defined for this mapping (locally defined + inherited).
Mapping::getDynamicallyValidKeys public function Gets all dynamically valid keys.
Mapping::getElementDefinition protected function Gets data definition object for contained element. Overrides ArrayElement::getElementDefinition
Mapping::getOptionalKeys public function Gets all optional keys in this mapping.
Mapping::getPossibleTypes protected function Returns all possible types for the type with the given name.
Mapping::getRequiredKeys public function Gets all required keys in this mapping.
Mapping::getValidKeys public function Gets all keys allowed in this mapping.
Mapping::processRequiredKeyFlags protected function Validates optional `requiredKey` flags, guarantees one will be set.
Mapping::__construct public function Constructs a TypedData object given its definition and context. Overrides TypedData::__construct
StringTranslationTrait::$stringTranslation protected property The string translation service. 3
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.
TypedData::$definition protected property The data definition. 1
TypedData::$name protected property The property name.
TypedData::$parent protected property The parent typed data object.
TypedData::applyDefaultValue public function Applies the default value. Overrides TypedDataInterface::applyDefaultValue 3
TypedData::createInstance public static function Constructs a TypedData object given its definition and context. Overrides TypedDataInterface::createInstance
TypedData::getConstraints public function Gets a list of validation constraints. Overrides TypedDataInterface::getConstraints 8
TypedData::getDataDefinition public function Gets the data definition. Overrides TypedDataInterface::getDataDefinition
TypedData::getName public function Returns the name of a property or item. Overrides TypedDataInterface::getName
TypedData::getParent public function Returns the parent data structure; i.e. either complex data or a list. Overrides TypedDataInterface::getParent
TypedData::getPluginDefinition public function Gets the definition of the plugin implementation. Overrides PluginInspectionInterface::getPluginDefinition
TypedData::getPluginId public function Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface::getPluginId
TypedData::getPropertyPath public function Returns the property path of the data. Overrides TypedDataInterface::getPropertyPath
TypedData::getRoot public function Returns the root of the typed data tree. Overrides TypedDataInterface::getRoot
TypedData::getString public function Returns a string representation of the data. Overrides TypedDataInterface::getString 6
TypedData::getValue public function Gets the data value. Overrides TypedDataInterface::getValue 10
TypedData::setContext public function Sets the context of a property or item via a context aware parent. Overrides TypedDataInterface::setContext
TypedData::setValue public function Sets the data value. Overrides TypedDataInterface::setValue 10
TypedData::validate public function Validates the currently set data value. Overrides TypedDataInterface::validate
TypedDataTrait::$typedDataManager protected property The typed data manager used for creating the data types.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.