class TypedConfigManager

Same name and namespace in other branches
  1. 9 core/lib/Drupal/Core/Config/TypedConfigManager.php \Drupal\Core\Config\TypedConfigManager
  2. 8.9.x core/lib/Drupal/Core/Config/TypedConfigManager.php \Drupal\Core\Config\TypedConfigManager
  3. 10 core/lib/Drupal/Core/Config/TypedConfigManager.php \Drupal\Core\Config\TypedConfigManager

Manages config schema type plugins.

Hierarchy

Expanded class hierarchy of TypedConfigManager

2 files declare their use of TypedConfigManager
TestViewsTest.php in core/modules/views/tests/src/Kernel/TestViewsTest.php
ValidKeysConstraintValidator.php in core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/ValidKeysConstraintValidator.php

File

core/lib/Drupal/Core/Config/TypedConfigManager.php, line 22

Namespace

Drupal\Core\Config
View source
class TypedConfigManager extends TypedDataManager implements TypedConfigManagerInterface {
    
    /**
     * A storage instance for reading configuration data.
     *
     * @var \Drupal\Core\Config\StorageInterface
     */
    protected $configStorage;
    
    /**
     * A storage instance for reading configuration schema data.
     *
     * @var \Drupal\Core\Config\StorageInterface
     */
    protected $schemaStorage;
    
    /**
     * The array of plugin definitions, keyed by plugin id.
     *
     * @var array
     */
    protected $definitions;
    
    /**
     * Creates a new typed configuration manager.
     *
     * @param \Drupal\Core\Config\StorageInterface $configStorage
     *   The storage object to use for reading schema data
     * @param \Drupal\Core\Config\StorageInterface $schemaStorage
     *   The storage object to use for reading schema data
     * @param \Drupal\Core\Cache\CacheBackendInterface $cache
     *   The cache backend to use for caching the definitions.
     * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
     *   The module handler.
     * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
     *   (optional) The class resolver.
     */
    public function __construct(StorageInterface $configStorage, StorageInterface $schemaStorage, CacheBackendInterface $cache, ModuleHandlerInterface $module_handler, ?ClassResolverInterface $class_resolver = NULL) {
        $this->configStorage = $configStorage;
        $this->schemaStorage = $schemaStorage;
        $this->setCacheBackend($cache, 'typed_config_definitions');
        $this->alterInfo('config_schema_info');
        $this->moduleHandler = $module_handler;
        $this->classResolver = $class_resolver ?: \Drupal::service('class_resolver');
    }
    
    /**
     * {@inheritdoc}
     */
    protected function getDiscovery() {
        if (!isset($this->discovery)) {
            $this->discovery = new ConfigSchemaDiscovery($this->schemaStorage);
        }
        return $this->discovery;
    }
    
    /**
     * {@inheritdoc}
     */
    public function get($name) {
        $data = $this->configStorage
            ->read($name);
        if ($data === FALSE) {
            // For a typed config the data MUST exist.
            throw new \InvalidArgumentException("Missing required data for typed configuration: {$name}");
        }
        return $this->createFromNameAndData($name, $data);
    }
    
    /**
     * {@inheritdoc}
     */
    public function buildDataDefinition(array $definition, $value, $name = NULL, $parent = NULL) {
        // Add default values for data type and replace variables.
        $definition += [
            'type' => 'undefined',
        ];
        $replace = [];
        $type = $definition['type'];
        if (strpos($type, ']')) {
            // Replace variable names in definition.
            $replace = is_array($value) ? $value : [];
            if (isset($parent)) {
                $replace['%parent'] = $parent;
            }
            if (isset($name)) {
                $replace['%key'] = $name;
            }
            $type = TypeResolver::resolveDynamicTypeName($type, $replace);
            // Remove the type from the definition so that it is replaced with the
            // concrete type from schema definitions.
            unset($definition['type']);
        }
        // Add default values from type definition.
        $definition += $this->getDefinitionWithReplacements($type, $replace);
        $data_definition = $this->createDataDefinition($definition['type']);
        // Pass remaining values from definition array to data definition.
        foreach ($definition as $key => $value) {
            if (!isset($data_definition[$key])) {
                $data_definition[$key] = $value;
            }
        }
        // All values are optional by default (meaning they can be NULL), except for
        // mappings and sequences. A sequence can only be NULL when `nullable: true`
        // is set on the config schema type definition. This is unintuitive and
        // contradicts Drupal core's documentation.
        // @see https://www.drupal.org/node/2264179
        // @see https://www.drupal.org/node/1978714
        // To gradually evolve configuration schemas in the Drupal ecosystem to be
        // validatable, this needs to be clarified in a non-disruptive way. Any
        // config schema type definition — that is, a top-level entry in a
        // *.schema.yml file — can opt into stricter behavior, whereby a property
        // cannot be NULL unless it specifies `nullable: true`, by adding
        // `FullyValidatable` as a top-level validation constraint.
        // @see https://www.drupal.org/node/3364108
        // @see https://www.drupal.org/node/3364109
        // @see \Drupal\Core\TypedData\TypedDataManager::getDefaultConstraints()
        if ($parent) {
            $root_type = $parent->getRoot()
                ->getDataDefinition()
                ->getDataType();
            $root_type_has_opted_in = FALSE;
            foreach ($parent->getRoot()
                ->getConstraints() as $constraint) {
                if ($constraint instanceof FullyValidatableConstraint) {
                    $root_type_has_opted_in = TRUE;
                    break;
                }
            }
            // If this is a dynamically typed property path, then not only must the
            // (absolute) root type be considered, but also the (relative) static root
            // type: the resolved type.
            // For example, `block.block.*:settings` has a dynamic type defined:
            // `block.settings.[%parent.plugin]`, but `block.block.*:plugin` does not.
            // Consequently, the value at the `plugin` property path depends only on
            // the `block.block.*` config schema type and hence only that config
            // schema type must have the `FullyValidatable` constraint, because it
            // defines which value are required.
            // In contrast, the `block.block.*:settings` property path depends on
            // whichever dynamic type `block.settings.[%parent.plugin]` resolved to,
            // to be able to know which values are required. Therefore that resolved
            // type determines which values are required and whether it is fully
            // validatable.
            // So for example the `block.settings.system_branding_block` config schema
            // type would also need to have the `FullyValidatable` constraint to
            // consider its schema-defined keys to require values:
            // - use_site_logo
            // - use_site_name
            // - use_site_slogan
            $static_type_root = TypedConfigManager::getStaticTypeRoot($parent);
            $static_type_root_type = $static_type_root->getDataDefinition()
                ->getDataType();
            if ($root_type !== $static_type_root_type) {
                $root_type_has_opted_in = FALSE;
                foreach ($static_type_root->getConstraints() as $c) {
                    if ($c instanceof FullyValidatableConstraint) {
                        $root_type_has_opted_in = TRUE;
                        break;
                    }
                }
            }
            if ($root_type_has_opted_in) {
                $data_definition->setRequired(!isset($data_definition['nullable']) || $data_definition['nullable'] === FALSE);
            }
        }
        return $data_definition;
    }
    
    /**
     * Gets the static type root for a config schema object.
     *
     * @param \Drupal\Core\TypedData\TraversableTypedDataInterface $object
     *   A config schema object to get the static type root for.
     *
     * @return \Drupal\Core\TypedData\TraversableTypedDataInterface
     *   The ancestral config schema object at which the static type root lies:
     *   either the first ancestor with a dynamic type (for example:
     *   `block.block.*:settings`, which has the `block.settings.[%parent.plugin]`
     *   type) or the (absolute) root of the config object (in this example:
     *   `block.block.*`).
     */
    public static function getStaticTypeRoot(TraversableTypedDataInterface $object) : TraversableTypedDataInterface {
        $root = $object->getRoot();
        $static_type_root = NULL;
        while ($static_type_root === NULL && $object !== $root) {
            // 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]`.
            $parent_data_def = $object->getParent()
                ->getDataDefinition();
            $original_mapping_type = match (TRUE) {    $parent_data_def instanceof MapDataDefinition => $parent_data_def->toArray()['mapping'][$object->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 was dynamically defined, then this is the static
            // type root inside which all types are statically defined.
            if (str_contains($original_mapping_type, ']')) {
                $static_type_root = $object;
                break;
            }
            $object = $object->getParent();
        }
        // Either the discovered static type root is not the actual root, or no
        // static type root was found and it is the root config object.
        assert($static_type_root !== NULL && $static_type_root !== $root || $static_type_root === NULL && $object->getParent() === NULL);
        return $static_type_root ?? $root;
    }
    
    /**
     * Determines the typed config type for a plugin ID.
     *
     * @param string $base_plugin_id
     *   The plugin ID.
     * @param array $definitions
     *   An array of typed config definitions.
     *
     * @return string
     *   The typed config type for the given plugin ID.
     */
    protected function determineType($base_plugin_id, array $definitions) {
        if (isset($definitions[$base_plugin_id])) {
            $type = $base_plugin_id;
        }
        elseif (strpos($base_plugin_id, '.') && ($name = $this->getFallbackName($base_plugin_id))) {
            // Found a generic name, replacing the last element by '*'.
            $type = $name;
        }
        else {
            // If we don't have definition, return the 'undefined' element.
            $type = 'undefined';
        }
        return $type;
    }
    
    /**
     * Gets a schema definition with replacements for dynamic type names.
     *
     * @param string $base_plugin_id
     *   A plugin ID.
     * @param array $replacements
     *   An array of replacements for dynamic type names.
     * @param bool $exception_on_invalid
     *   (optional) This parameter is passed along to self::getDefinition().
     *   However, self::getDefinition() does not respect this parameter, so it is
     *   effectively useless in this context.
     *
     * @return array
     *   A schema definition array.
     */
    protected function getDefinitionWithReplacements($base_plugin_id, array $replacements, $exception_on_invalid = TRUE) {
        $definitions = $this->getDefinitions();
        $type = $this->determineType($base_plugin_id, $definitions);
        $definition = $definitions[$type];
        // Check whether this type is an extension of another one and compile it.
        if (isset($definition['type'])) {
            $merge = $this->getDefinition($definition['type'], $exception_on_invalid);
            // Preserve integer keys on merge, so sequence item types can override
            // parent settings as opposed to adding unused second, third, etc. items.
            $definition = NestedArray::mergeDeepArray([
                $merge,
                $definition,
            ], TRUE);
            // Replace dynamic portions of the definition type.
            if (!empty($replacements) && strpos($definition['type'], ']')) {
                $sub_type = $this->determineType(TypeResolver::resolveDynamicTypeName($definition['type'], $replacements), $definitions);
                $sub_definition = $definitions[$sub_type];
                if (isset($definitions[$sub_type]['type'])) {
                    $sub_merge = $this->getDefinition($definitions[$sub_type]['type'], $exception_on_invalid);
                    $sub_definition = NestedArray::mergeDeepArray([
                        $sub_merge,
                        $definitions[$sub_type],
                    ], TRUE);
                }
                // Merge the newly determined subtype definition with the original
                // definition.
                $definition = NestedArray::mergeDeepArray([
                    $sub_definition,
                    $definition,
                ], TRUE);
                $type = "{$type}||{$sub_type}";
            }
            // Unset type so we try the merge only once per type.
            unset($definition['type']);
            $this->definitions[$type] = $definition;
        }
        // Add type and default definition class.
        $definition += [
            'definition_class' => '\\Drupal\\Core\\TypedData\\DataDefinition',
            'type' => $type,
            'unwrap_for_canonical_representation' => TRUE,
        ];
        return $definition;
    }
    
    /**
     * {@inheritdoc}
     */
    public function getDefinition($base_plugin_id, $exception_on_invalid = TRUE) {
        return $this->getDefinitionWithReplacements($base_plugin_id, [], $exception_on_invalid);
    }
    
    /**
     * {@inheritdoc}
     */
    public function clearCachedDefinitions() {
        $this->schemaStorage
            ->reset();
        parent::clearCachedDefinitions();
    }
    
    /**
     * Finds fallback configuration schema name.
     *
     * @param string $name
     *   Configuration name or key.
     *
     * @return null|string
     *   The resolved schema name for the given configuration name or key. Returns
     *   null if there is no schema name to fallback to. For example,
     *   breakpoint.breakpoint.module.toolbar.narrow will check for definitions in
     *   the following order:
     *     breakpoint.breakpoint.module.toolbar.*
     *     breakpoint.breakpoint.module.*.*
     *     breakpoint.breakpoint.module.*
     *     breakpoint.breakpoint.*.*.*
     *     breakpoint.breakpoint.*
     *     breakpoint.*.*.*.*
     *     breakpoint.*
     *   Colons are also used, for example,
     *   block.settings.system_menu_block:footer will check for definitions in the
     *   following order:
     *     block.settings.system_menu_block:*
     *     block.settings.*:*
     *     block.settings.*
     *     block.*.*:*
     *     block.*
     */
    public function findFallback(string $name) : ?string {
        $fallback = $this->getFallbackName($name);
        assert($fallback === NULL || str_ends_with($fallback, '.*'));
        return $fallback;
    }
    
    /**
     * Gets fallback configuration schema name.
     *
     * @param string $name
     *   Configuration name or key.
     *
     * @return null|string
     *   The resolved schema name for the given configuration name or key.
     */
    protected function getFallbackName($name) {
        // Check for definition of $name with filesystem marker.
        $replaced = preg_replace('/([^\\.:]+)([\\.:\\*]*)$/', '*\\2', $name);
        if ($replaced != $name) {
            if (isset($this->definitions[$replaced])) {
                return $replaced;
            }
            else {
                // No definition for this level. Collapse multiple wildcards to a single
                // wildcard to see if there is a greedy match. For example,
                // breakpoint.breakpoint.*.* becomes
                // breakpoint.breakpoint.*
                $one_star = preg_replace('/\\.([:\\.\\*]*)$/', '.*', $replaced);
                if ($one_star != $replaced && isset($this->definitions[$one_star])) {
                    return $one_star;
                }
                // Check for next level. For example, if breakpoint.breakpoint.* has
                // been checked and no match found then check breakpoint.*.*
                return $this->getFallbackName($replaced);
            }
        }
    }
    
    /**
     * {@inheritdoc}
     */
    public function hasConfigSchema($name) {
        // The schema system falls back on the Undefined class for unknown types.
        $definition = $this->getDefinition($name);
        return is_array($definition) && $definition['class'] != Undefined::class;
    }
    
    /**
     * {@inheritdoc}
     */
    protected function alterDefinitions(&$definitions) {
        $discovered_schema = array_keys($definitions);
        parent::alterDefinitions($definitions);
        $altered_schema = array_keys($definitions);
        if ($discovered_schema != $altered_schema) {
            $added_keys = implode(',', array_diff($altered_schema, $discovered_schema));
            $removed_keys = implode(',', array_diff($discovered_schema, $altered_schema));
            if (!empty($added_keys) && !empty($removed_keys)) {
                $message = "Invoking hook_config_schema_info_alter() has added ({$added_keys}) and removed ({$removed_keys}) schema definitions";
            }
            elseif (!empty($added_keys)) {
                $message = "Invoking hook_config_schema_info_alter() has added ({$added_keys}) schema definitions";
            }
            else {
                $message = "Invoking hook_config_schema_info_alter() has removed ({$removed_keys}) schema definitions";
            }
            throw new ConfigSchemaAlterException($message);
        }
    }
    
    /**
     * {@inheritdoc}
     */
    public function createFromNameAndData($config_name, array $config_data) {
        $definition = $this->getDefinition($config_name);
        $data_definition = $this->buildDataDefinition($definition, $config_data);
        return $this->create($data_definition, $config_data, $config_name);
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title Overrides
DefaultPluginManager::$additionalAnnotationNamespaces protected property Additional annotation namespaces.
DefaultPluginManager::$alterHook protected property Name of the alter hook if one should be invoked.
DefaultPluginManager::$cacheKey protected property The cache key.
DefaultPluginManager::$cacheTags protected property An array of cache tags to use for the cached definitions.
DefaultPluginManager::$defaults protected property A set of defaults to be referenced by $this->processDefinition(). 12
DefaultPluginManager::$moduleExtensionList protected property The module extension list.
DefaultPluginManager::$moduleHandler protected property The module handler to invoke the alter hook. 1
DefaultPluginManager::$namespaces protected property An object of root paths that are traversable.
DefaultPluginManager::$pluginDefinitionAnnotationName protected property The name of the annotation that contains the plugin definition.
DefaultPluginManager::$pluginDefinitionAttributeName protected property The name of the attribute that contains the plugin definition.
DefaultPluginManager::$pluginInterface protected property The interface each plugin should implement. 1
DefaultPluginManager::$subdir protected property The subdirectory within a namespace to look for plugins.
DefaultPluginManager::alterInfo protected function Sets the alter hook name.
DefaultPluginManager::extractProviderFromDefinition protected function Extracts the provider from a plugin definition.
DefaultPluginManager::findDefinitions protected function Finds plugin definitions. 7
DefaultPluginManager::getCacheContexts public function The cache contexts associated with this object. Overrides CacheableDependencyInterface::getCacheContexts
DefaultPluginManager::getCachedDefinitions protected function Returns the cached plugin definitions of the decorated discovery class.
DefaultPluginManager::getCacheMaxAge public function The maximum age for which this object may be cached. Overrides CacheableDependencyInterface::getCacheMaxAge
DefaultPluginManager::getCacheTags public function The cache tags associated with this object. Overrides CacheableDependencyInterface::getCacheTags
DefaultPluginManager::getDefinitions public function Gets the definition of all plugins for this type. Overrides DiscoveryTrait::getDefinitions 2
DefaultPluginManager::getFactory protected function Gets the plugin factory. Overrides PluginManagerBase::getFactory
DefaultPluginManager::processDefinition public function Performs extra processing on plugin definitions. 14
DefaultPluginManager::providerExists protected function Determines if the provider of a definition exists. 5
DefaultPluginManager::setCacheBackend public function Initialize the cache backend.
DefaultPluginManager::setCachedDefinitions protected function Sets a cache of plugin definitions for the decorated discovery class.
DefaultPluginManager::useCaches public function Disable the use of caches. Overrides CachedDiscoveryInterface::useCaches 1
DependencySerializationTrait::$_entityStorages protected property
DependencySerializationTrait::$_serviceIds protected property
DependencySerializationTrait::__sleep public function 1
DependencySerializationTrait::__wakeup public function 2
DiscoveryTrait::doGetDefinition protected function Gets a specific plugin definition.
DiscoveryTrait::hasDefinition public function
PluginManagerBase::$discovery protected property The object that discovers plugins managed by this manager.
PluginManagerBase::$factory protected property The object that instantiates plugins managed by this manager.
PluginManagerBase::$mapper protected property The object that returns the preconfigured plugin instance appropriate for a particular runtime condition.
PluginManagerBase::getFallbackPluginId protected function Gets a fallback id for a missing plugin. 6
PluginManagerBase::handlePluginNotFound protected function Allows plugin managers to specify custom behavior if a plugin is not found. 1
TypedConfigManager::$configStorage protected property A storage instance for reading configuration data.
TypedConfigManager::$definitions protected property The array of plugin definitions, keyed by plugin id. Overrides DiscoveryCachedTrait::$definitions
TypedConfigManager::$schemaStorage protected property A storage instance for reading configuration schema data.
TypedConfigManager::alterDefinitions protected function Invokes the hook to alter the definitions if the alter hook is set. Overrides DefaultPluginManager::alterDefinitions
TypedConfigManager::buildDataDefinition public function Creates a new data definition object. Overrides TypedConfigManagerInterface::buildDataDefinition
TypedConfigManager::clearCachedDefinitions public function Clears static and persistent plugin definition caches. Overrides TypedDataManager::clearCachedDefinitions
TypedConfigManager::createFromNameAndData public function Gets typed data for a given configuration name and its values. Overrides TypedConfigManagerInterface::createFromNameAndData
TypedConfigManager::determineType protected function Determines the typed config type for a plugin ID.
TypedConfigManager::findFallback public function Finds fallback configuration schema name.
TypedConfigManager::get public function Gets typed configuration data. Overrides TypedConfigManagerInterface::get
TypedConfigManager::getDefinition public function Gets a specific plugin definition. Overrides DiscoveryCachedTrait::getDefinition
TypedConfigManager::getDefinitionWithReplacements protected function Gets a schema definition with replacements for dynamic type names.
TypedConfigManager::getDiscovery protected function Gets the plugin discovery. Overrides DefaultPluginManager::getDiscovery
TypedConfigManager::getFallbackName protected function Gets fallback configuration schema name.
TypedConfigManager::getStaticTypeRoot public static function Gets the static type root for a config schema object.
TypedConfigManager::hasConfigSchema public function Checks if the configuration schema with the given config name exists. Overrides TypedConfigManagerInterface::hasConfigSchema
TypedConfigManager::__construct public function Creates a new typed configuration manager. Overrides TypedDataManager::__construct
TypedDataManager::$classResolver protected property The class resolver.
TypedDataManager::$constraintManager protected property The validation constraint manager to use for instantiating constraints.
TypedDataManager::$prototypes protected property An array of typed data property prototypes.
TypedDataManager::$validator protected property The validator used for validating typed data.
TypedDataManager::create public function Creates a new typed data object instance. Overrides TypedDataManagerInterface::create
TypedDataManager::createDataDefinition public function Creates a new data definition object. Overrides TypedDataManagerInterface::createDataDefinition
TypedDataManager::createInstance public function Instantiates a typed data object. Overrides PluginManagerBase::createInstance
TypedDataManager::createListDataDefinition public function Creates a new list data definition for items of the given data type. Overrides TypedDataManagerInterface::createListDataDefinition
TypedDataManager::getCanonicalRepresentation public function Gets the canonical representation of a TypedData object. Overrides TypedDataManagerInterface::getCanonicalRepresentation
TypedDataManager::getDefaultConstraints public function Gets default constraints for the given data definition. Overrides TypedDataManagerInterface::getDefaultConstraints
TypedDataManager::getInstance public function Overrides PluginManagerBase::getInstance
TypedDataManager::getPropertyInstance public function Get a typed data instance for a property of a given typed data object. Overrides TypedDataManagerInterface::getPropertyInstance
TypedDataManager::getValidationConstraintManager public function Gets the validation constraint manager. Overrides TypedDataManagerInterface::getValidationConstraintManager
TypedDataManager::getValidator public function Gets the validator for validating typed data. Overrides TypedDataManagerInterface::getValidator
TypedDataManager::setValidationConstraintManager public function Sets the validation constraint manager. Overrides TypedDataManagerInterface::setValidationConstraintManager
TypedDataManager::setValidator public function Sets the validator for validating typed data. Overrides TypedDataManagerInterface::setValidator
UseCacheBackendTrait::$cacheBackend protected property Cache backend instance.
UseCacheBackendTrait::$useCaches protected property Flag whether caches should be used or skipped.
UseCacheBackendTrait::cacheGet protected function Fetches from the cache backend, respecting the use caches flag.
UseCacheBackendTrait::cacheSet protected function Stores data in the persistent cache, respecting the use caches flag.

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