You are here

ConfigFactory.php in Drupal 8

Same filename and directory in other branches
  1. 9 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\Component\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\Component\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\Component\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);
    $this->eventDispatcher
      ->dispatch(ConfigEvents::RENAME, new ConfigRenameEvent($config, $old_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 || strpos($key, $name . ':') === 0;
    });
  }

  /**
   * {@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() {
    $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

Namesort descending Description
ConfigFactory Defines the configuration object factory.