ConfigFactory.php 14.9 KB
Newer Older
1 2 3 4 5 6 7 8 9
<?php

/**
 * @file
 * Definition of Drupal\Core\Config\ConfigFactory.
 */

namespace Drupal\Core\Config;

10
use Drupal\Core\Language\Language;
11
use Drupal\Core\Language\LanguageDefault;
12 13
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
14

15 16 17 18 19 20
/**
 * 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.
 *
21
 * @see \Drupal\Core\Config\Config
22
 *
23 24
 * Each configuration object gets a storage controller object injected, which
 * is used for reading and writing the configuration data.
25
 *
26
 * @see \Drupal\Core\Config\StorageInterface
27
 */
28 29 30 31 32 33
class ConfigFactory implements EventSubscriberInterface {

  /**
   * Prefix for all language configuration files.
   */
  const LANGUAGE_CONFIG_PREFIX = 'language.config';
34

35
  /**
36
   * A storage controller instance for reading and writing configuration data.
37
   *
38
   * @var \Drupal\Core\Config\StorageInterface
39
   */
40
  protected $storage;
41

42
  /**
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
   * An event dispatcher instance to use for configuration events.
   *
   * @var \Symfony\Component\EventDispatcher\EventDispatcher
   */
  protected $eventDispatcher;

  /**
   * A flag indicating if we should use overrides.
   *
   * @var boolean
   */
  protected $useOverrides = TRUE;

  /**
   * The language object used to override configuration data.
58
   *
59
   * @var \Drupal\Core\Language\Language
60
   */
61
  protected $language;
62

63 64 65
  /**
   * Cached configuration objects.
   *
66
   * @var \Drupal\Core\Config\Config[]
67 68 69
   */
  protected $cache = array();

70 71 72 73 74 75 76
  /**
   * The typed config manager.
   *
   * @var \Drupal\Core\Config\TypedConfigManager
   */
  protected $typedConfigManager;

77 78 79
  /**
   * Constructs the Config factory.
   *
80
   * @param \Drupal\Core\Config\StorageInterface $storage
81
   *   The configuration storage engine.
82 83
   * @param \Symfony\Component\EventDispatcher\EventDispatcher $event_dispatcher
   *   An event dispatcher instance to use for configuration events.
84 85
   * @param \Drupal\Core\Config\TypedConfigManager $typed_config
   *   The typed configuration manager.
86
   */
87
  public function __construct(StorageInterface $storage, EventDispatcher $event_dispatcher, TypedConfigManager $typed_config) {
88
    $this->storage = $storage;
89
    $this->eventDispatcher = $event_dispatcher;
90
    $this->typedConfigManager = $typed_config;
91 92 93
  }

  /**
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
   * Disable overrides when loading configuration objects.
   *
   * @return \Drupal\Core\Config\ConfigFactory
   *   The config factory object.
   */
  public function disableOverrides() {
    $this->useOverrides = FALSE;
    return $this;
  }

  /**
   * Enable overrides when loading configuration objects.
   *
   * @return \Drupal\Core\Config\ConfigFactory
   *   The config factory object.
   */
  public function enableOverrides() {
    $this->useOverrides = TRUE;
    return $this;
  }

  /**
   * Returns a configuration object for a given name.
117 118 119
   *
   * @param string $name
   *   The name of the configuration object to construct.
120 121 122
   *
   * @return \Drupal\Core\Config\Config
   *   A configuration object.
123 124
   */
  public function get($name) {
125 126 127 128
    global $conf;

    if ($config = $this->loadMultiple(array($name))) {
      return $config[$name];
129
    }
130 131 132 133 134 135 136 137 138 139 140
    else {
      $cache_key = $this->getCacheKey($name);
      // If the config object has been deleted it will already exist in the
      // cache but self::loadMultiple does not return such objects.
      // @todo Explore making ConfigFactory a listener to the config.delete
      //   event to reset the static cache when this occurs.
      if (!isset($this->cache[$cache_key])) {
        // If the configuration object does not exist in the configuration
        // storage or static cache create a new object and add it to the static
        // cache.
        $this->cache[$cache_key] = new Config($name, $this->storage, $this->eventDispatcher, $this->typedConfigManager, $this->language);
141

142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
        if ($this->canOverride($name)) {
          // Get and apply any language overrides.
          if ($this->language) {
            $language_overrides = $this->storage->read($this->getLanguageConfigName($this->language->id, $name));
          }
          else {
            $language_overrides = FALSE;
          }
          if (is_array($language_overrides)) {
            $this->cache[$cache_key]->setLanguageOverride($language_overrides);
          }
          // Get and apply any module overrides.
          $module_overrides = $this->loadModuleOverrides(array($name));
          if (isset($module_overrides[$name])) {
            $this->cache[$cache_key]->setModuleOverride($module_overrides[$name]);
          }
          // Apply any settings.php overrides.
          if (isset($conf[$name])) {
            $this->cache[$cache_key]->setSettingsOverride($conf[$name]);
          }
        }
      }
      return $this->cache[$cache_key];
    }
166
  }
167

168
  /**
169
   * Returns a list of configuration objects for the given names.
170 171 172 173 174 175 176 177 178 179 180
   *
   * This will pre-load all requested configuration objects does not create
   * new configuration objects.
   *
   * @param array $names
   *   List of names of configuration objects.
   *
   * @return array
   *   List of successfully loaded configuration objects, keyed by name.
   */
  public function loadMultiple(array $names) {
181
    global $conf;
182 183

    $list = array();
184

185 186
    foreach ($names as $key => $name) {
      // @todo: Deleted configuration stays in $this->cache, only return
187 188
      //   configuration objects that are not new.
      $cache_key = $this->getCacheKey($name);
189 190 191 192 193 194 195 196
      if (isset($this->cache[$cache_key]) && !$this->cache[$cache_key]->isNew()) {
        $list[$name] = $this->cache[$cache_key];
        unset($names[$key]);
      }
    }

    // Pre-load remaining configuration files.
    if (!empty($names)) {
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
      // Initialise override information.
      $module_overrides = array();
      $language_names = array();

      if ($this->useOverrides) {
        // In order to make just one call to storage, add in language names.
        // Keep track of them separately, so we can get language override data
        // returned from storage and set it on new Config objects.
        $language_names = $this->getLanguageConfigNames($names);
      }

      $storage_data = $this->storage->readMultiple(array_merge($names, array_values($language_names)));

      if ($this->useOverrides && !empty($storage_data)) {
        // Only fire module override event if we have configuration to override.
        $module_overrides = $this->loadModuleOverrides($names);
      }

215
      foreach ($storage_data as $name => $data) {
216 217 218 219 220 221 222 223 224 225
        if (in_array($name, $language_names)) {
          // Language override configuration is used to override other
          // configuration. Therefore, when it has been added to the
          // $storage_data it is not statically cached in the config factory or
          // overridden in any way.
          continue;
        }
        $cache_key = $this->getCacheKey($name);

        $this->cache[$cache_key] = new Config($name, $this->storage, $this->eventDispatcher, $this->typedConfigManager, $this->language);
226
        $this->cache[$cache_key]->initWithData($data);
227 228 229 230 231 232 233 234 235 236 237
        if ($this->canOverride($name)) {
          if (isset($language_names[$name]) && isset($storage_data[$language_names[$name]])) {
            $this->cache[$cache_key]->setLanguageOverride($storage_data[$language_names[$name]]);
          }
          if (isset($module_overrides[$name])) {
            $this->cache[$cache_key]->setModuleOverride($module_overrides[$name]);
          }
          if (isset($conf[$name])) {
            $this->cache[$cache_key]->setSettingsOverride($conf[$name]);
          }
        }
238 239 240
        $list[$name] = $this->cache[$cache_key];
      }
    }
241

242 243 244
    return $list;
  }

245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
  /**
   * 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 loadModuleOverrides(array $names) {
    $configOverridesEvent = new ConfigModuleOverridesEvent($names, $this->language);
    $this->eventDispatcher->dispatch('config.module.overrides', $configOverridesEvent);
    return $configOverridesEvent->getOverrides();
  }

260 261 262 263 264 265
  /**
   * Resets and re-initializes configuration objects. Internal use only.
   *
   * @param string $name
   *   (optional) The name of the configuration object to reset. If omitted, all
   *   configuration objects are reset.
266 267 268
   *
   * @return \Drupal\Core\Config\ConfigFactory
   *   The config factory object.
269 270 271
   */
  public function reset($name = NULL) {
    if ($name) {
272
      // Clear all cached configuration for this name.
273
      foreach ($this->getCacheKeys($name) as $cache_key) {
274
        unset($this->cache[$cache_key]);
275 276 277
      }
    }
    else {
278
      $this->cache = array();
279
    }
280 281 282 283 284

    // Clear the static list cache if supported by the storage.
    if ($this->storage instanceof StorageCacheInterface) {
      $this->storage->resetListCache();
    }
285
    return $this;
286 287 288
  }

  /**
289
   * Renames a configuration object using the storage controller.
290 291 292 293 294 295
   *
   * @param string $old_name
   *   The old name of the configuration object.
   * @param string $new_name
   *   The new name of the configuration object.
   *
296 297
   * @return \Drupal\Core\Config\Config
   *   The renamed config object.
298 299
   */
  public function rename($old_name, $new_name) {
300 301
    $this->storage->rename($old_name, $new_name);
    $old_cache_key = $this->getCacheKey($old_name);
302
    if (isset($this->cache[$old_cache_key])) {
303 304
      unset($this->cache[$old_cache_key]);
    }
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326

    $new_cache_key = $this->getCacheKey($new_name);
    $this->cache[$new_cache_key] = new Config($new_name, $this->storage, $this->eventDispatcher, $this->typedConfigManager, $this->language);
    $this->cache[$new_cache_key]->load();
    return $this->cache[$new_cache_key];
  }

  /**
   * Gets the cache key for a given config name.
   *
   * @param string $name
   *   The name of the configuration object.
   *
   * @return string
   *   The cache key.
   */
  public function getCacheKey($name) {
    $can_override = $this->canOverride($name);
    $cache_key = $name . ':' . ($can_override ? 'overrides' : 'raw');

    if ($can_override && isset($this->language)) {
      $cache_key =  $cache_key . ':' . $this->language->id;
327
    }
328 329
    return $cache_key;
  }
330

331 332 333 334 335 336 337 338 339 340 341 342 343 344
  /**
   * 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.
   */
  public function getCacheKeys($name) {
    return array_filter(array_keys($this->cache), function($key) use ($name) {
      // Return TRUE if the key starts with the configuration name.
      return strpos($key, $name . ':') === 0;
    });
345
  }
346 347

  /**
348
   * Clears the config factory static cache.
349
   *
350 351 352 353 354 355 356 357 358
   * @return \Drupal\Core\Config\ConfigFactory
   *   The config factory object.
   */
  public function clearStaticCache() {
    $this->cache = array();
    return $this;
  }

  /**
359
   * Sets the language to be used in configuration overrides.
360 361 362 363
   *
   * @param \Drupal\Core\Language\Language $language
   *   The language object to be set on the config factory. Used to override
   *   configuration by language.
364 365 366 367
   *
   * @return \Drupal\Core\Config\ConfigFactory
   *   The config factory object.
   */
368 369
  public function setLanguage(Language $language = NULL) {
    $this->language = $language;
370 371 372
    return $this;
  }

373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
  /**
   * Sets the language for configuration overrides using the default language.
   *
   * @param \Drupal\Core\Language\LanguageDefault $language_default
   *   The default language service. This sets the initial language on the
   *   config factory to the site's default. The language can be used to
   *   override configuration data if language overrides are available.
   *
   * @return \Drupal\Core\Config\ConfigFactory
   *   The config factory object.
   */
  public function setLanguageFromDefault(LanguageDefault $language_default) {
    $this->language = $language_default->get();
    return $this;
  }

389
  /**
390
   * Gets the language Used to override configuration.
391
   *
392
   * @return \Drupal\Core\Language\Language
393
   */
394 395
  public function getLanguage() {
    return $this->language;
396 397 398
  }

  /**
399
   * Gets configuration names for this language.
400
   *
401 402 403 404 405 406 407 408
   * It will be the same name with a prefix depending on language code:
   * language.config.LANGCODE.NAME
   *
   * @param array $names
   *   A list of configuration object names.
   *
   * @return array
   *   The localized config names, keyed by configuration object name.
409
   */
410 411 412 413 414 415 416 417
  public function getLanguageConfigNames(array $names) {
    $language_names = array();
    if (isset($this->language)) {
      foreach ($names as $name) {
        if ($language_name = $this->getLanguageConfigName($this->language->id, $name)) {
          $language_names[$name] = $language_name;
        }
      }
418
    }
419
    return $language_names;
420 421
  }

422
  /**
423 424 425 426
   * Gets configuration name for the provided language.
   *
   * The name will be the same name with a prefix depending on language code:
   * language.config.LANGCODE.NAME
427
   *
428 429
   * @param string $langcode
   *   The language code.
430 431 432
   * @param string $name
   *   The name of the configuration object.
   *
433 434 435
   * @return bool|string
   *   The configuration name for configuration object providing overrides.
   *   Returns false if the name already starts with the language config prefix.
436
   */
437 438 439 440 441
  public function getLanguageConfigName($langcode, $name) {
    if (strpos($name, static::LANGUAGE_CONFIG_PREFIX) === 0) {
      return FALSE;
    }
    return static::LANGUAGE_CONFIG_PREFIX . '.' . $langcode . '.' . $name;
442 443 444
  }

  /**
445 446 447
   * Determines if a particular configuration object can be overridden.
   *
   * Language override configuration should not be overridden.
448 449 450 451
   *
   * @param string $name
   *   The name of the configuration object.
   *
452 453
   * @return bool
   *   TRUE if the configuration object can be overridden.
454
   */
455 456
  protected function canOverride($name) {
    return $this->useOverrides && !(strpos($name, static::LANGUAGE_CONFIG_PREFIX) === 0);
457
  }
458 459

  /**
460
   * Removes stale static cache entries when configuration is saved.
461
   *
462 463
   * @param ConfigEvent $event
   *   The configuration event.
464
   */
465 466 467 468 469 470 471 472 473 474 475
  public function onConfigSave(ConfigEvent $event) {
    // 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.
    $saved_config = $event->getConfig();
    foreach ($this->getCacheKeys($saved_config->getName()) as $cache_key) {
      $cached_config = $this->cache[$cache_key];
      if ($cached_config !== $saved_config) {
        $this->cache[$cache_key]->setData($saved_config->getRawData());
      }
    }
476
  }
477 478 479 480 481 482 483 484 485

  /**
   * {@inheritdoc}
   */
  static function getSubscribedEvents() {
    $events['config.save'][] = array('onConfigSave', 255);
    return $events;
  }

486
}