Skip to content

Instantly share code, notes, and snippets.

Created June 26, 2013 15:38

Revisions

  1. @invalid-email-address Anonymous created this gist Jun 26, 2013.
    276 changes: 276 additions & 0 deletions gistfile1.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,276 @@
    <?php

    /**
    * @file
    * Contains \Drupal\Core\Config\TypedConfigManager.
    */

    namespace Drupal\Core\Config;

    use Drupal\Component\Plugin\PluginManagerBase;
    use Drupal\Component\Utility\NestedArray;

    /**
    * Manages config type plugins.
    */
    class TypedConfigManager extends PluginManagerBase {

    /**
    * A storage controller instance for reading configuration data.
    *
    * @var \Drupal\Core\Config\StorageInterface
    */
    protected $configStorage;

    /**
    * A storage controller 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 = array();

    /**
    * Creates a new typed configuration manager.
    *
    * @param \Drupal\Core\Config\StorageInterface $configStorage
    * The storage controller object to use for reading schema data
    * @param \Drupal\Core\Config\StorageInterface $schemaStorage
    * The storage controller object to use for reading schema data
    */
    public function __construct(StorageInterface $configStorage, StorageInterface $schemaStorage) {
    $this->configStorage = $configStorage;
    $this->schemaStorage = $schemaStorage;
    $this->loadAllSchema();
    }

    /**
    * Gets typed configuration data.
    *
    * @param string $name
    * Configuration object name.
    *
    * @return \Drupal\Core\Config\Schema\Element
    * Typed configuration element.
    */
    public function get($name) {
    $data = $this->configStorage->read($name);
    $definition = $this->getDefinition($name);
    return $this->create($definition, $data);
    }

    /**
    * Overrides \Drupal\Core\TypedData\TypedDataManager::create()
    *
    * Fills in default type and does variable replacement.
    */
    public function create(array $definition, $value = NULL, $name = NULL, $parent = NULL) {
    if (!isset($definition['type'])) {
    // Set default type 'string' if possible. If not it will be 'undefined'.
    if (is_string($value)) {
    $definition['type'] = 'string';
    }
    else {
    $definition['type'] = 'undefined';
    }
    }
    elseif (strpos($definition['type'], ']')) {
    // Replace variable names in definition.
    $replace = is_array($value) ? $value : array();
    if (isset($parent)) {
    $replace['%parent'] = $parent->getValue();
    }
    if (isset($name)) {
    $replace['%key'] = $name;
    }
    $definition['type'] = $this->replaceName($definition['type'], $replace);
    }
    // Create typed config object.
    $wrapper = $this->createInstance($definition['type'], $definition, $name, $parent);
    if (isset($value)) {
    $wrapper->setValue($value, FALSE);
    }
    return $wrapper;
    }

    /**
    * Overrides Drupal\Core\TypedData\TypedDataFactory::createInstance().
    */
    public function createInstance($plugin_id, array $configuration, $name = NULL, $parent = NULL) {
    $type_definition = $this->getDefinition($plugin_id);
    $configuration += $type_definition;
    if (!isset($type_definition)) {
    throw new InvalidArgumentException(format_string('Invalid data type %plugin_id has been given.', array('%plugin_id' => $plugin_id)));
    }

    // Allow per-data definition overrides of the used classes, i.e. take over
    // classes specified in the data definition.
    $key = empty($configuration['list']) ? 'class' : 'list class';
    if (isset($configuration[$key])) {
    $class = $configuration[$key];
    }
    elseif (isset($type_definition[$key])) {
    $class = $type_definition[$key];
    }

    if (!isset($class)) {
    throw new PluginException(sprintf('The plugin (%s) did not specify an instance class.', $plugin_id));
    }
    return new $class($configuration, $name, $parent);
    }

    /**
    * Implements Drupal\Component\Plugin\Discovery\DiscoveryInterface::getDefinition().
    */
    public function getDefinition($base_plugin_id) {
    if (isset($this->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 'default' element.
    // This should map to 'undefined' type by default, unless overridden.
    $type = 'default';
    }
    $definition = $this->definitions[$type];
    // Check whether this type is an extension of another one and compile it.
    if (isset($definition['type'])) {
    $merge = $this->getDefinition($definition['type']);
    $definition = NestedArray::mergeDeep($merge, $definition);
    // Unset type so we try the merge only once per type.
    unset($definition['type']);
    $this->definitions[$type] = $definition;
    }
    return $definition;
    }

    /**
    * Implements Drupal\Component\Plugin\Discovery\DiscoveryInterface::getDefinitions().
    */
    public function getDefinitions() {
    return $this->definitions;
    }

    /**
    * Load schema for module / theme.
    */
    protected function loadAllSchema() {
    foreach ($this->schemaStorage->listAll() as $name) {
    if ($schema = $this->schemaStorage->read($name)) {
    foreach ($schema as $type => $definition) {
    $this->definitions[$type] = $definition;
    }
    }
    }
    }

    /**
    * Gets fallback metadata name.
    *
    * @param string $name
    * Configuration name or key.
    *
    * @return null|string
    * Same name with the last part(s) replaced by the filesystem marker.
    * for example, breakpoint.breakpoint.module.toolbar.narrow check for
    * definition in below order:
    * breakpoint.breakpoint.module.toolbar.*
    * breakpoint.breakpoint.module.*.*
    * breakpoint.breakpoint.*.*.*
    * breakpoint.*.*.*.*
    * Returns null, if no matching element.
    */
    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(for example, breakpoint.breakpoint.*),
    // check for next level (which is, breakpoint.*.*).
    return self::getFallbackName($replaced);
    }
    }
    }

    /**
    * Replaces variables in configuration name.
    *
    * The configuration name may contain one or more variables to be replaced,
    * enclosed in square brackets like '[name]' and will follow the replacement
    * rules defined by the replaceVariable() method.
    *
    * @param string $name
    * Configuration name with variables in square brackets.
    * @param mixed $data
    * Configuration data for the element.
    * @return string
    * Configuration name with variables replaced.
    */
    protected static function replaceName($name, $data) {
    if (preg_match_all("/\[(.*)\]/U", $name, $matches)) {
    // Build our list of '[value]' => replacement.
    foreach (array_combine($matches[0], $matches[1]) as $key => $value) {
    $replace[$key] = self::replaceVariable($value, $data);
    }
    return strtr($name, $replace);
    }
    else {
    return $name;
    }
    }

    /**
    * Replaces variable values in included names with configuration data.
    *
    * Variable values are nested configuration keys that will be replaced by
    * their value or some of these special strings:
    * - '%key', will be replaced by the element's key.
    * - '%parent', to reference the parent element.
    *
    * There may be nested configuration keys separated by dots or more complex
    * patterns like '%parent.name' which references the 'name' value of the
    * parent element.
    *
    * Example patterns:
    * - 'name.subkey', indicates a nested value of the current element.
    * - '%parent.name', will be replaced by the 'name' value of the parent.
    * - '%parent.%key', will be replaced by the parent element's key.
    *
    * @param string $value
    * Variable value to be replaced.
    *
    * @return string
    * The replaced value if a replacement found or the original value if not.
    */
    protected static function replaceVariable($value, $data) {
    $parts = explode('.', $value);
    // Process each value part, one at a time.
    while ($name = array_shift($parts)) {
    if (!is_array($data) || !isset($data[$name])) {
    // Key not found, return original value
    return $value;
    }
    elseif (!$parts) {
    // If no more parts left, this is the final property.
    return (string)$data[$name];
    }
    else {
    // Get nested value and continue processing.
    $data = $data[$name];
    }
    }
    }

    }