ConfigFactory.php

Same filename in other branches
  1. 9 core/lib/Drupal/Core/Config/ConfigFactory.php
  2. 8.9.x core/lib/Drupal/Core/Config/ConfigFactory.php
  3. 10 core/lib/Drupal/Core/Config/ConfigFactory.php

Namespace

Drupal\Core\Config

File

core/lib/Drupal/Core/Config/ConfigFactory.php

View source
<?php

namespace Drupal\Core\Config;

use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Cache\Cache;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * Defines the configuration object factory.
 *
 * The configuration object factory instantiates a Config object for each
 * configuration object name that is accessed and returns it to callers.
 *
 * @see \Drupal\Core\Config\Config
 *
 * Each configuration object gets a storage object injected, which
 * is used for reading and writing the configuration data.
 *
 * @see \Drupal\Core\Config\StorageInterface
 *
 * @ingroup config_api
 */
class ConfigFactory implements ConfigFactoryInterface, EventSubscriberInterface {
    
    /**
     * A storage instance for reading and writing configuration data.
     *
     * @var \Drupal\Core\Config\StorageInterface
     */
    protected $storage;
    
    /**
     * An event dispatcher instance to use for configuration events.
     *
     * @var \Symfony\Contracts\EventDispatcher\EventDispatcherInterface
     */
    protected $eventDispatcher;
    
    /**
     * Cached configuration objects.
     *
     * @var \Drupal\Core\Config\Config[]
     */
    protected $cache = [];
    
    /**
     * The typed config manager.
     *
     * @var \Drupal\Core\Config\TypedConfigManagerInterface
     */
    protected $typedConfigManager;
    
    /**
     * An array of config factory override objects ordered by priority.
     *
     * @var \Drupal\Core\Config\ConfigFactoryOverrideInterface[]
     */
    protected $configFactoryOverrides = [];
    
    /**
     * Constructs the Config factory.
     *
     * @param \Drupal\Core\Config\StorageInterface $storage
     *   The configuration storage engine.
     * @param \Symfony\Contracts\EventDispatcher\EventDispatcherInterface $event_dispatcher
     *   An event dispatcher instance to use for configuration events.
     * @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config
     *   The typed configuration manager.
     */
    public function __construct(StorageInterface $storage, EventDispatcherInterface $event_dispatcher, TypedConfigManagerInterface $typed_config) {
        $this->storage = $storage;
        $this->eventDispatcher = $event_dispatcher;
        $this->typedConfigManager = $typed_config;
    }
    
    /**
     * {@inheritdoc}
     */
    public function getEditable($name) {
        return $this->doGet($name, FALSE);
    }
    
    /**
     * {@inheritdoc}
     */
    public function get($name) {
        return $this->doGet($name);
    }
    
    /**
     * Returns a configuration object for a given name.
     *
     * @param string $name
     *   The name of the configuration object to construct.
     * @param bool $immutable
     *   (optional) Create an immutable configuration object. Defaults to TRUE.
     *
     * @return \Drupal\Core\Config\Config|\Drupal\Core\Config\ImmutableConfig
     *   A configuration object.
     */
    protected function doGet($name, $immutable = TRUE) {
        if ($config = $this->doLoadMultiple([
            $name,
        ], $immutable)) {
            return $config[$name];
        }
        else {
            // If the configuration object does not exist in the configuration
            // storage, create a new object.
            $config = $this->createConfigObject($name, $immutable);
            if ($immutable) {
                // Get and apply any overrides.
                $overrides = $this->loadOverrides([
                    $name,
                ]);
                if (isset($overrides[$name])) {
                    $config->setModuleOverride($overrides[$name]);
                }
                // Apply any settings.php overrides.
                if (isset($GLOBALS['config'][$name])) {
                    $config->setSettingsOverride($GLOBALS['config'][$name]);
                }
            }
            foreach ($this->configFactoryOverrides as $override) {
                $config->addCacheableDependency($override->getCacheableMetadata($name));
            }
            return $config;
        }
    }
    
    /**
     * {@inheritdoc}
     */
    public function loadMultiple(array $names) {
        return $this->doLoadMultiple($names);
    }
    
    /**
     * Returns a list of configuration objects for the given names.
     *
     * @param array $names
     *   List of names of configuration objects.
     * @param bool $immutable
     *   (optional) Create an immutable configuration objects. Defaults to TRUE.
     *
     * @return \Drupal\Core\Config\Config[]|\Drupal\Core\Config\ImmutableConfig[]
     *   List of successfully loaded configuration objects, keyed by name.
     */
    protected function doLoadMultiple(array $names, $immutable = TRUE) {
        $list = [];
        foreach ($names as $key => $name) {
            $cache_key = $this->getConfigCacheKey($name, $immutable);
            if (isset($this->cache[$cache_key])) {
                $list[$name] = $this->cache[$cache_key];
                unset($names[$key]);
            }
        }
        // Pre-load remaining configuration files.
        if (!empty($names)) {
            // Initialize override information.
            $module_overrides = [];
            $storage_data = $this->storage
                ->readMultiple($names);
            if ($immutable && !empty($storage_data)) {
                // Only get module overrides if we have configuration to override.
                $module_overrides = $this->loadOverrides($names);
            }
            foreach ($storage_data as $name => $data) {
                $cache_key = $this->getConfigCacheKey($name, $immutable);
                $this->cache[$cache_key] = $this->createConfigObject($name, $immutable);
                $this->cache[$cache_key]
                    ->initWithData($data);
                if ($immutable) {
                    if (isset($module_overrides[$name])) {
                        $this->cache[$cache_key]
                            ->setModuleOverride($module_overrides[$name]);
                    }
                    if (isset($GLOBALS['config'][$name])) {
                        $this->cache[$cache_key]
                            ->setSettingsOverride($GLOBALS['config'][$name]);
                    }
                }
                $this->propagateConfigOverrideCacheability($cache_key, $name);
                $list[$name] = $this->cache[$cache_key];
            }
        }
        return $list;
    }
    
    /**
     * Get arbitrary overrides for the named configuration objects from modules.
     *
     * @param array $names
     *   The names of the configuration objects to get overrides for.
     *
     * @return array
     *   An array of overrides keyed by the configuration object name.
     */
    protected function loadOverrides(array $names) {
        $overrides = [];
        foreach ($this->configFactoryOverrides as $override) {
            // Existing overrides take precedence since these will have been added
            // by events with a higher priority.
            $overrides = NestedArray::mergeDeepArray([
                $override->loadOverrides($names),
                $overrides,
            ], TRUE);
        }
        return $overrides;
    }
    
    /**
     * Propagates cacheability of config overrides to cached config objects.
     *
     * @param string $cache_key
     *   The key of the cached config object to update.
     * @param string $name
     *   The name of the configuration object to construct.
     */
    protected function propagateConfigOverrideCacheability($cache_key, $name) {
        foreach ($this->configFactoryOverrides as $override) {
            $this->cache[$cache_key]
                ->addCacheableDependency($override->getCacheableMetadata($name));
        }
    }
    
    /**
     * {@inheritdoc}
     */
    public function reset($name = NULL) {
        if ($name) {
            // Clear all cached configuration for this name.
            foreach ($this->getConfigCacheKeys($name) as $cache_key) {
                unset($this->cache[$cache_key]);
            }
        }
        else {
            $this->cache = [];
        }
        // Clear the static list cache if supported by the storage.
        if ($this->storage instanceof StorageCacheInterface) {
            $this->storage
                ->resetListCache();
        }
        return $this;
    }
    
    /**
     * {@inheritdoc}
     */
    public function rename($old_name, $new_name) {
        Cache::invalidateTags($this->get($old_name)
            ->getCacheTags());
        $this->storage
            ->rename($old_name, $new_name);
        // Clear out the static cache of any references to the old name.
        foreach ($this->getConfigCacheKeys($old_name) as $old_cache_key) {
            unset($this->cache[$old_cache_key]);
        }
        // Prime the cache and load the configuration with the correct overrides.
        $config = $this->get($new_name);
        $event_name = $this->storage
            ->getCollectionName() === StorageInterface::DEFAULT_COLLECTION ? ConfigEvents::RENAME : ConfigCollectionEvents::RENAME_IN_COLLECTION;
        $this->eventDispatcher
            ->dispatch(new ConfigRenameEvent($config, $old_name), $event_name);
        return $this;
    }
    
    /**
     * {@inheritdoc}
     */
    public function getCacheKeys() {
        // Because get() adds overrides both from $GLOBALS and from
        // $this->configFactoryOverrides, add cache keys for each.
        $keys[] = 'global_overrides';
        foreach ($this->configFactoryOverrides as $override) {
            $keys[] = $override->getCacheSuffix();
        }
        return $keys;
    }
    
    /**
     * Gets the static cache key for a given config name.
     *
     * @param string $name
     *   The name of the configuration object.
     * @param bool $immutable
     *   Whether or not the object is mutable.
     *
     * @return string
     *   The cache key.
     */
    protected function getConfigCacheKey($name, $immutable) {
        $suffix = '';
        if ($immutable) {
            $suffix = ':' . implode(':', $this->getCacheKeys());
        }
        return $name . $suffix;
    }
    
    /**
     * Gets all the cache keys that match the provided config name.
     *
     * @param string $name
     *   The name of the configuration object.
     *
     * @return array
     *   An array of cache keys that match the provided config name.
     */
    protected function getConfigCacheKeys($name) {
        return array_filter(array_keys($this->cache), function ($key) use ($name) {
            // Return TRUE if the key is the name or starts with the configuration
            // name plus the delimiter.
            return $key === $name || str_starts_with($key, $name . ':');
        });
    }
    
    /**
     * {@inheritdoc}
     */
    public function clearStaticCache() {
        $this->cache = [];
        return $this;
    }
    
    /**
     * {@inheritdoc}
     */
    public function listAll($prefix = '') {
        return $this->storage
            ->listAll($prefix);
    }
    
    /**
     * Updates stale static cache entries when configuration is saved.
     *
     * @param ConfigCrudEvent $event
     *   The configuration event.
     */
    public function onConfigSave(ConfigCrudEvent $event) {
        $saved_config = $event->getConfig();
        // We are only concerned with config objects that belong to the collection
        // that matches the storage we depend on. Skip if the event was fired for a
        // config object belonging to a different collection.
        if ($saved_config->getStorage()
            ->getCollectionName() !== $this->storage
            ->getCollectionName()) {
            return;
        }
        // Ensure that the static cache contains up to date configuration objects by
        // replacing the data on any entries for the configuration object apart
        // from the one that references the actual config object being saved.
        foreach ($this->getConfigCacheKeys($saved_config->getName()) as $cache_key) {
            $cached_config = $this->cache[$cache_key];
            if ($cached_config !== $saved_config) {
                // We can not just update the data since other things about the object
                // might have changed. For example, whether or not it is new.
                $this->cache[$cache_key]
                    ->initWithData($saved_config->getRawData());
            }
        }
    }
    
    /**
     * Removes stale static cache entries when configuration is deleted.
     *
     * @param \Drupal\Core\Config\ConfigCrudEvent $event
     *   The configuration event.
     */
    public function onConfigDelete(ConfigCrudEvent $event) {
        $deleted_config = $event->getConfig();
        // We are only concerned with config objects that belong to the collection
        // that matches the storage we depend on. Skip if the event was fired for a
        // config object belonging to a different collection.
        if ($deleted_config->getStorage()
            ->getCollectionName() !== $this->storage
            ->getCollectionName()) {
            return;
        }
        // Ensure that the static cache does not contain deleted configuration.
        foreach ($this->getConfigCacheKeys($deleted_config->getName()) as $cache_key) {
            unset($this->cache[$cache_key]);
        }
    }
    
    /**
     * {@inheritdoc}
     */
    public static function getSubscribedEvents() : array {
        $events[ConfigEvents::SAVE][] = [
            'onConfigSave',
            255,
        ];
        $events[ConfigEvents::DELETE][] = [
            'onConfigDelete',
            255,
        ];
        return $events;
    }
    
    /**
     * {@inheritdoc}
     */
    public function addOverride(ConfigFactoryOverrideInterface $config_factory_override) {
        $this->configFactoryOverrides[] = $config_factory_override;
    }
    
    /**
     * Creates a configuration object.
     *
     * @param string $name
     *   Configuration object name.
     * @param bool $immutable
     *   Determines whether a mutable or immutable config object is returned.
     *
     * @return \Drupal\Core\Config\Config|\Drupal\Core\Config\ImmutableConfig
     *   The configuration object.
     */
    protected function createConfigObject($name, $immutable) {
        if ($immutable) {
            return new ImmutableConfig($name, $this->storage, $this->eventDispatcher, $this->typedConfigManager);
        }
        return new Config($name, $this->storage, $this->eventDispatcher, $this->typedConfigManager);
    }

}

Classes

Title Deprecated Summary
ConfigFactory Defines the configuration object factory.

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