Issue #1866610 by Jose Reyero, Gábor Hojtsy, effulgentsia, alexpott, aspilicious, YesCT, fago, sun, dawehner, heyrocker, yched, spearhead93: Introduce Kwalify-inspired schema format for configuration.
parent
2735dbb481
commit
94278ddd5a
|
@ -317,3 +317,16 @@ function config_get_entity_type_by_name($name) {
|
|||
});
|
||||
return key($entities);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the typed config manager service.
|
||||
*
|
||||
* Use the typed data manager service for creating typed configuration objects.
|
||||
*
|
||||
* @see Drupal\Core\TypedData\TypedDataManager::create()
|
||||
*
|
||||
* @return Drupal\Core\TypedData\TypedConfigManager
|
||||
*/
|
||||
function config_typed() {
|
||||
return drupal_container()->get('config.typed');
|
||||
}
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Schema\ArrayElement.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Schema;
|
||||
|
||||
use \ArrayAccess;
|
||||
use \ArrayIterator;
|
||||
use \Countable;
|
||||
use \IteratorAggregate;
|
||||
use \Traversable;
|
||||
|
||||
/**
|
||||
* Defines a generic configuration element that contains multiple properties.
|
||||
*/
|
||||
abstract class ArrayElement extends Element implements IteratorAggregate, ArrayAccess, Countable {
|
||||
|
||||
/**
|
||||
* Parsed elements.
|
||||
*/
|
||||
protected $elements;
|
||||
|
||||
/**
|
||||
* Gets an array of contained elements.
|
||||
*
|
||||
* @return array
|
||||
* Array of \Drupal\Core\Config\Schema\ArrayElement objects.
|
||||
*/
|
||||
protected function getElements() {
|
||||
if (!isset($this->elements)) {
|
||||
$this->elements = $this->parse();
|
||||
}
|
||||
return $this->elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets valid configuration data keys.
|
||||
*
|
||||
* @return array
|
||||
* Array of valid configuration data keys.
|
||||
*/
|
||||
protected function getAllKeys() {
|
||||
return is_array($this->value) ? array_keys($this->value) : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an array of contained elements.
|
||||
*
|
||||
* @return array
|
||||
* Array of \Drupal\Core\Config\Schema\ArrayElement objects.
|
||||
*/
|
||||
protected abstract function parse();
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::validate().
|
||||
*/
|
||||
public function validate() {
|
||||
foreach ($this->getElements() as $element) {
|
||||
if (!$element->validate()) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ArrayAccess::offsetExists().
|
||||
*/
|
||||
public function offsetExists($offset) {
|
||||
return array_key_exists($offset, $this->getElements());
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ArrayAccess::offsetGet().
|
||||
*/
|
||||
public function offsetGet($offset) {
|
||||
$elements = $this->getElements();
|
||||
return $elements[$offset];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ArrayAccess::offsetSet().
|
||||
*/
|
||||
public function offsetSet($offset, $value) {
|
||||
if ($value instanceof TypedDataInterface) {
|
||||
$value = $value->getValue();
|
||||
}
|
||||
$this->value[$offset] = $value;
|
||||
unset($this->elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements ArrayAccess::offsetUnset().
|
||||
*/
|
||||
public function offsetUnset($offset) {
|
||||
unset($this->value[$offset]);
|
||||
unset($this->elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Countable::count().
|
||||
*/
|
||||
public function count() {
|
||||
return count($this->getElements());
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements IteratorAggregate::getIterator();
|
||||
*/
|
||||
public function getIterator() {
|
||||
return new ArrayIterator($this->getElements());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Schema\Element.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Schema;
|
||||
|
||||
use Drupal\Core\TypedData\ContextAwareTypedData;
|
||||
|
||||
/**
|
||||
* Defines a generic configuration element.
|
||||
*/
|
||||
abstract class Element extends ContextAwareTypedData {
|
||||
|
||||
/**
|
||||
* The configuration value.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* Create typed config object.
|
||||
*/
|
||||
protected function parseElement($key, $data, $definition) {
|
||||
return config_typed()->create($definition, $data, $key, $this);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Schema\Mapping.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Schema;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\TypedData\ComplexDataInterface;
|
||||
use \InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Defines a mapping configuration element.
|
||||
*
|
||||
* Wraps configuration data and metadata allowing access to configuration data
|
||||
* using the ComplexDataInterface API. This object may contain any number and
|
||||
* type of nested properties.
|
||||
*/
|
||||
class Mapping extends ArrayElement implements ComplexDataInterface {
|
||||
|
||||
/**
|
||||
* Overrides ArrayElement::parse()
|
||||
*/
|
||||
protected function parse() {
|
||||
$elements = array();
|
||||
foreach ($this->definition['mapping'] as $key => $definition) {
|
||||
if (isset($this->value[$key])) {
|
||||
$elements[$key] = $this->parseElement($key, $this->value[$key], $definition);
|
||||
}
|
||||
}
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\TypedData\ComplexDataInterface::get().
|
||||
*/
|
||||
public function get($property_name) {
|
||||
$elements = $this->getElements();
|
||||
if (isset($elements[$property_name])) {
|
||||
return $elements[$property_name];
|
||||
}
|
||||
else {
|
||||
throw new InvalidArgumentException(format_string("The configuration property @key doesn't exist.", array(
|
||||
'@key' => $property_name,
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\TypedData\ComplexDataInterface::set().
|
||||
*/
|
||||
public function set($property_name, $value) {
|
||||
// Set the data into the configuration array but behave according to the
|
||||
// interface specification when we've got a null value.
|
||||
if (isset($value)) {
|
||||
$this->value[$property_name] = $value;
|
||||
return $this->get($property_name);
|
||||
}
|
||||
else {
|
||||
// In these objects, when clearing the value, the property is gone.
|
||||
// As this needs to return a property, we get it before we delete it.
|
||||
$property = $this->get($property_name);
|
||||
unset($this->value[$property_name]);
|
||||
$property->setValue($value);
|
||||
return $property;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\TypedData\ComplexDataInterface::getProperties().
|
||||
*/
|
||||
public function getProperties($include_computed = FALSE) {
|
||||
return $this->getElements();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\TypedData\ComplexDataInterface::getPropertyValues().
|
||||
*/
|
||||
public function getPropertyValues() {
|
||||
return $this->getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\TypedData\ComplexDataInterface::setPropertyValues().
|
||||
*/
|
||||
public function setPropertyValues($values) {
|
||||
foreach ($values as $name => $value) {
|
||||
$this->value[$name] = $value;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinition().
|
||||
*/
|
||||
public function getPropertyDefinition($name) {
|
||||
if (isset($this->definition['mapping'][$name])) {
|
||||
return $this->definition['mapping'][$name];
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinitions().
|
||||
*/
|
||||
public function getPropertyDefinitions() {
|
||||
$list = array();
|
||||
foreach ($this->getAllKeys() as $key) {
|
||||
$list[$key] = $this->getPropertyDefinition($key);
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\TypedData\ComplexDataInterface::isEmpty().
|
||||
*/
|
||||
public function isEmpty() {
|
||||
return empty($this->value);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Schema\Parser.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Schema;
|
||||
|
||||
/**
|
||||
* Parser.
|
||||
*/
|
||||
class Parser {
|
||||
|
||||
/**
|
||||
* Parse configuration data against schema data.
|
||||
*/
|
||||
static function parse($data, $definition, $name = NULL, $parent = NULL) {
|
||||
// Set default type depending on data and context.
|
||||
if (!isset($definition['type'])) {
|
||||
if (is_array($data) || !$context) {
|
||||
$definition += array('type' => 'any');
|
||||
}
|
||||
else {
|
||||
$definition += array('type' => 'str');
|
||||
}
|
||||
}
|
||||
// Create typed data object.
|
||||
config_typed()->create($definition, $data, $name, $parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate configuration data against schema data.
|
||||
*/
|
||||
static function validate($config_data, $schema_data) {
|
||||
return self::parse($config_data, $schema_data)->validate();
|
||||
}
|
||||
|
||||
static function getDefinition($type, $data) {
|
||||
return config_definition($type);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Schema\Sequence.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Schema;
|
||||
|
||||
/**
|
||||
* Generic configuration property.
|
||||
*/
|
||||
class Property extends Element {
|
||||
|
||||
/**
|
||||
* Implements TypedDataInterface::validate().
|
||||
*/
|
||||
public function validate() {
|
||||
return isset($this->value);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Config\Schema\SchemaDiscovery.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Schema;
|
||||
|
||||
use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
|
||||
/**
|
||||
* A discovery mechanism that reads plugin definitions from schema data
|
||||
* in YAML format.
|
||||
*/
|
||||
class SchemaDiscovery implements DiscoveryInterface {
|
||||
|
||||
/**
|
||||
* A storage controller instance for reading configuration schema data.
|
||||
*
|
||||
* @var Drupal\Core\Config\StorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* The array of plugin definitions, keyed by plugin id.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $definitions = array();
|
||||
|
||||
/**
|
||||
* Public constructor.
|
||||
*
|
||||
* @param Drupal\Core\Config\StorageInterface $storage
|
||||
* The storage controller object to use for reading schema data
|
||||
*/
|
||||
public function __construct($storage) {
|
||||
$this->storage = $storage;
|
||||
// Load definitions for all enabled modules.
|
||||
foreach (module_list() as $module) {
|
||||
$this->loadSchema($module);
|
||||
}
|
||||
// @todo Load definitions for all enabled themes.
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)) && isset($this->definitions[$name])) {
|
||||
// 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 loadSchema($component) {
|
||||
if ($schema = $this->storage->read($component . '.schema')) {
|
||||
foreach ($schema as $type => $definition) {
|
||||
$this->definitions[$type] = $definition;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets fallback metadata name.
|
||||
*
|
||||
* @param string $name
|
||||
* Configuration name or key.
|
||||
*
|
||||
* @return string
|
||||
* Same name with the last part replaced by the filesystem marker.
|
||||
*/
|
||||
protected static function getFallbackName($name) {
|
||||
$replaced = preg_replace('/\.[^.]+$/', '.' . '*', $name);
|
||||
if ($replaced != $name) {
|
||||
return $replaced;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\Schema\Sequence.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config\Schema;
|
||||
|
||||
use Drupal\Core\TypedData\ListInterface;
|
||||
|
||||
/**
|
||||
* Defines a configuration element of type Sequence.
|
||||
*/
|
||||
class Sequence extends ArrayElement implements ListInterface {
|
||||
|
||||
/**
|
||||
* Overrides ArrayElement::parse()
|
||||
*/
|
||||
protected function parse() {
|
||||
$definition = $definition = $this->getItemDefinition();
|
||||
$elements = array();
|
||||
foreach ($this->value as $key => $value) {
|
||||
$elements[$key] = $this->parseElement($key, $value, $definition);
|
||||
}
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\TypedData\ListInterface::isEmpty().
|
||||
*/
|
||||
public function isEmpty() {
|
||||
return empty($this->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\TypedData\ListInterface::getItemDefinition().
|
||||
*/
|
||||
public function getItemDefinition() {
|
||||
return $this->definition['sequence'][0];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\TypedConfigElementFactory.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Core\TypedData\TypedDataFactory;
|
||||
|
||||
/**
|
||||
* A factory for typed config element objects.
|
||||
*
|
||||
* This factory merges the type definition into the element definition prior to
|
||||
* creating the instance.
|
||||
*/
|
||||
class TypedConfigElementFactory extends TypedDataFactory {
|
||||
|
||||
/**
|
||||
* Overrides Drupal\Core\TypedData\TypedDataFactory::createInstance().
|
||||
*/
|
||||
public function createInstance($plugin_id, array $configuration, $name = NULL, $parent = NULL) {
|
||||
$type_definition = $this->discovery->getDefinition($plugin_id);
|
||||
$configuration += $type_definition;
|
||||
return parent::createInstance($plugin_id, $configuration, $name, $parent);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Config\TypedConfigManager.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Config;
|
||||
|
||||
use Drupal\Core\Config\Schema\SchemaDiscovery;
|
||||
use Drupal\Core\TypedData\TypedDataManager;
|
||||
|
||||
/**
|
||||
* Manages config type plugins.
|
||||
*/
|
||||
class TypedConfigManager extends TypedDataManager {
|
||||
|
||||
/**
|
||||
* A storage controller instance for reading configuration data.
|
||||
*
|
||||
* @var Drupal\Core\Config\StorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* Creates a new typed configuration manager.
|
||||
*
|
||||
* @param Drupal\Core\Config\StorageInterface $storage
|
||||
* The storage controller object to use for reading schema data
|
||||
*/
|
||||
public function __construct($storage) {
|
||||
$this->storage = $storage;
|
||||
$this->discovery = new SchemaDiscovery($storage);
|
||||
$this->factory = new TypedConfigElementFactory($this->discovery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->storage->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 'str' if possible. If not it will be 'any'.
|
||||
if (is_string($value)) {
|
||||
$definition['type'] = 'str';
|
||||
}
|
||||
else {
|
||||
$definition['type'] = 'any';
|
||||
}
|
||||
}
|
||||
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.
|
||||
return parent::create($definition, $value, $name, $parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -60,6 +60,10 @@ class CoreBundle extends Bundle {
|
|||
->register('config.storage.staging', 'Drupal\Core\Config\FileStorage')
|
||||
->addArgument(config_get_config_directory(CONFIG_STAGING_DIRECTORY));
|
||||
|
||||
// Register the typed configuration data manager.
|
||||
$container->register('config.typed', 'Drupal\Core\Config\TypedConfigManager')
|
||||
->addArgument(new Reference('config.storage'));
|
||||
|
||||
// Register the service for the default database connection.
|
||||
$container->register('database', 'Drupal\Core\Database\Connection')
|
||||
->setFactoryClass('Drupal\Core\Database\Database')
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\config\Tests\ConfigSchemaTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\config\Tests;
|
||||
|
||||
use Drupal\Core\Config\TypedConfig;
|
||||
use Drupal\simpletest\DrupalUnitTestBase;
|
||||
|
||||
/**
|
||||
* Tests schema for configuration objects.
|
||||
*/
|
||||
class ConfigSchemaTest extends DrupalUnitTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('system', 'locale', 'image');
|
||||
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Configuration schema',
|
||||
'description' => 'Tests Metadata for configuration objects.',
|
||||
'group' => 'Configuration',
|
||||
);
|
||||
}
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
config_install_default_config('module', 'system');
|
||||
config_install_default_config('module', 'image');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the basic metadata retrieval layer.
|
||||
*/
|
||||
function testSchemaMapping() {
|
||||
// Simple case, straight metadata.
|
||||
$definition = config_typed()->getDefinition('system.maintenance');
|
||||
$expected = array();
|
||||
$expected['label'] = 'Maintenance mode';
|
||||
$expected['class'] = '\Drupal\Core\Config\Schema\Mapping';
|
||||
$expected['mapping']['enabled'] = array(
|
||||
'label' => 'Put site into maintenance mode',
|
||||
'type' => 'boolean'
|
||||
);
|
||||
$expected['mapping']['message'] = array(
|
||||
'label' => 'Message to display when in maintenance mode',
|
||||
'type' => 'text',
|
||||
);
|
||||
$this->assertEqual($definition, $expected, 'Retrieved the right metadata for system.maintenance');
|
||||
|
||||
// More complex case, generic type. Metadata for image style.
|
||||
$definition = config_typed()->getDefinition('image.style.large');
|
||||
$expected = array();
|
||||
$expected['label'] = 'Image style';
|
||||
$expected['class'] = '\Drupal\Core\Config\Schema\Mapping';
|
||||
$expected['mapping']['name']['type'] = 'string';
|
||||
$expected['mapping']['label']['type'] = 'label';
|
||||
$expected['mapping']['effects']['type'] = 'sequence';
|
||||
$expected['mapping']['effects']['sequence'][0]['type'] = 'mapping';
|
||||
$expected['mapping']['effects']['sequence'][0]['mapping']['name']['type'] = 'string';
|
||||
$expected['mapping']['effects']['sequence'][0]['mapping']['data']['type'] = 'image.effect.[%parent.name]';
|
||||
$expected['mapping']['effects']['sequence'][0]['mapping']['weight']['type'] = 'integer';
|
||||
$expected['mapping']['effects']['sequence'][0]['mapping']['ieid']['type'] = 'string';
|
||||
|
||||
$this->assertEqual($definition, $expected, 'Retrieved the right metadata for image.style.large');
|
||||
|
||||
// More complex, type based on a complex one.
|
||||
$definition = config_typed()->getDefinition('image.effect.image_scale');
|
||||
// This should be the schema for image.effect.image_scale.
|
||||
$expected = array();
|
||||
$expected['label'] = 'Image scale';
|
||||
$expected['class'] = '\Drupal\Core\Config\Schema\Mapping';
|
||||
$expected['mapping']['width']['type'] = 'integer';
|
||||
$expected['mapping']['width']['label'] = 'Width';
|
||||
$expected['mapping']['height']['type'] = 'integer';
|
||||
$expected['mapping']['height']['label'] = 'Height';
|
||||
$expected['mapping']['upscale']['type'] = 'boolean';
|
||||
$expected['mapping']['upscale']['label'] = 'Upscale';
|
||||
|
||||
$this->assertEqual($definition, $expected, 'Retrieved the right metadata for image.effect.image_scale');
|
||||
|
||||
// Most complex case, get metadata for actual configuration element.
|
||||
$effects = config_typed()->get('image.style.medium')->get('effects');
|
||||
$definition = $effects['bddf0d06-42f9-4c75-a700-a33cafa25ea0']['data']->getDefinition();
|
||||
// This should be the schema for image.effect.image_scale, reuse previous one.
|
||||
$expected['type'] = 'image.effect.image_scale';
|
||||
|
||||
$this->assertEqual($definition, $expected, 'Retrieved the right metadata for the first effect of image.style.medium');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests metadata applied to configuration objects.
|
||||
*/
|
||||
function testSchemaData() {
|
||||
// Try some simple properties.
|
||||
$meta = config_typed()->get('system.site');
|
||||
$property = $meta->get('name');
|
||||
$this->assertTrue(is_a($property, 'Drupal\Core\TypedData\Type\String'), 'Got the right wrapper fo the site name property.');
|
||||
$this->assertEqual($property->getType(), 'label', 'Got the right string type for site name data.');
|
||||
$this->assertEqual($property->getValue(), 'Drupal', 'Got the right string value for site name data.');
|
||||
|
||||
$property = $meta->get('page')->get('front');
|
||||
$this->assertTrue(is_a($property, 'Drupal\Core\TypedData\Type\String'), 'Got the right wrapper fo the page.front property.');
|
||||
$this->assertEqual($property->getType(), 'path', 'Got the right type for page.front data (undefined).');
|
||||
$this->assertEqual($property->getValue(), 'user', 'Got the right value for page.front data.');
|
||||
|
||||
// Check nested array of properties.
|
||||
$list = $meta->get('page');
|
||||
$this->assertEqual(count($list), 3, 'Got a list with the right number of properties for site page data');
|
||||
$this->assertTrue(isset($list['front']) && isset($list['403']) && isset($list['404']), 'Got a list with the right properties for site page data.');
|
||||
$this->assertEqual($list['front']->getValue(), 'user', 'Got the right value for page.front data from the list.');
|
||||
|
||||
// And test some ComplexDataInterface methods.
|
||||
$properties = $list->getProperties();
|
||||
$this->assertTrue(count($properties) == 3 && $properties['front'] == $list['front'], 'Got the right properties for site page.');
|
||||
$values = $list->getPropertyValues();
|
||||
$this->assertTrue(count($values) == 3 && $values['front'] == 'user', 'Got the right property values for site page.');
|
||||
|
||||
// Now let's try something more complex, with nested objects.
|
||||
$wrapper = config_typed()->get('image.style.large');
|
||||
$effects = $wrapper->get('effects');
|
||||
|
||||
// The function is_array() doesn't work with ArrayAccess, so we use count().
|
||||
$this->assertTrue(count($effects) == 1, 'Got an array with effects for image.style.large data');
|
||||
$ieid = key($effects->getValue());
|
||||
$effect = $effects[$ieid];
|
||||
$this->assertTrue(count($effect['data']) && $effect['name']->getValue() == 'image_scale', 'Got data for the image scale effect from metadata.');
|
||||
$this->assertEqual($effect['data']['width']->getType(), 'integer', 'Got the right type for the scale effect width.');
|
||||
$this->assertEqual($effect['data']['width']->getValue(), 480, 'Got the right value for the scale effect width.' );
|
||||
|
||||
// Finally update some object using a configuration wrapper.
|
||||
$new_slogan = 'Site slogan for testing configuration metadata';
|
||||
$wrapper = config_typed()->get('system.site');
|
||||
$wrapper->set('slogan', $new_slogan);
|
||||
$site_slogan = $wrapper->get('slogan');
|
||||
$this->assertEqual($site_slogan->getValue(), $new_slogan, 'Successfully updated the contained configuration data');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
# Configuration schema: contact.schema.yml
|
||||
|
||||
# Schema for contact category (multiple)
|
||||
contact.category.*:
|
||||
type: mapping
|
||||
mapping:
|
||||
"id":
|
||||
type: string
|
||||
"label":
|
||||
type: string
|
||||
"recipients":
|
||||
type: sequence
|
||||
sequence:
|
||||
- type: email
|
||||
"reply":
|
||||
type: string
|
||||
"weight":
|
||||
type: integer
|
||||
|
||||
# Module settings
|
||||
contact.settings:
|
||||
type: mapping
|
||||
mapping:
|
||||
"default_category":
|
||||
type: string
|
||||
required: yes
|
||||
"flood":
|
||||
type: mapping
|
||||
mapping:
|
||||
"limit":
|
||||
type: integer
|
||||
"interval":
|
||||
type: integer
|
||||
"user_default_enabled":
|
||||
type: boolean
|
|
@ -0,0 +1,82 @@
|
|||
# Image module schema: image.schema.yml
|
||||
|
||||
# Data types for image module.
|
||||
image.size:
|
||||
type: mapping
|
||||
mapping:
|
||||
"width":
|
||||
type: integer
|
||||
label: "Width"
|
||||
"height":
|
||||
type: integer
|
||||
label: "Height"
|
||||
|
||||
# Image styles (multiple).
|
||||
# Plugin \Drupal\image\Plugin\Core\Entity\ImageStyle
|
||||
image.style.*:
|
||||
type: mapping
|
||||
label: "Image style"
|
||||
mapping:
|
||||
"name":
|
||||
type: string
|
||||
"label":
|
||||
type: label
|
||||
"effects":
|
||||
type: sequence
|
||||
sequence:
|
||||
- type: mapping
|
||||
mapping:
|
||||
"name":
|
||||
type: string
|
||||
"data":
|
||||
type: image.effect.[%parent.name]
|
||||
"weight":
|
||||
type: integer
|
||||
"ieid":
|
||||
type: string
|
||||
|
||||
# Image effects plugins: image.effect.%
|
||||
# These are used in image styles.
|
||||
image.effect.image_crop:
|
||||
type: image.size
|
||||
label: "Image crop"
|
||||
mapping:
|
||||
"anchor":
|
||||
label: "Anchor"
|
||||
|
||||
image.effect.image_resize:
|
||||
type: image.size
|
||||
label: "Image resize"
|
||||
|
||||
image.effect.image_rotate:
|
||||
type: mapping
|
||||
label: "Image rotate"
|
||||
mapping:
|
||||
degrees:
|
||||
type: integer
|
||||
label: 'Rotation angle'
|
||||
bgcolor:
|
||||
label: 'Background color'
|
||||
random:
|
||||
type: boolean
|
||||
label: 'Randomize'
|
||||
|
||||
image.effect.image_scale:
|
||||
type: image.size
|
||||
label: "Image scale"
|
||||
mapping:
|
||||
"upscale":
|
||||
type: boolean
|
||||
label: "Upscale"
|
||||
|
||||
image.effect.image_scale_and_crop:
|
||||
type: image.size
|
||||
label: "Image scale and crop"
|
||||
|
||||
# Schema for configuration files of image module.
|
||||
image.settings:
|
||||
type: mapping
|
||||
mapping:
|
||||
"preview_image":
|
||||
type: string
|
||||
label: "Preview image"
|
|
@ -0,0 +1,104 @@
|
|||
# Basic scalar data types from typed data.
|
||||
boolean:
|
||||
label: 'Boolean'
|
||||
class: '\Drupal\Core\TypedData\Type\Boolean'
|
||||
email:
|
||||
label: 'Email'
|
||||
class: '\Drupal\Core\TypedData\Type\Email'
|
||||
integer:
|
||||
label: 'Integer'
|
||||
class: '\Drupal\Core\TypedData\Type\Integer'
|
||||
string:
|
||||
label: 'String'
|
||||
class: '\Drupal\Core\TypedData\Type\String'
|
||||
uri:
|
||||
label: 'Uri'
|
||||
class: '\Drupal\Core\TypedData\Type\Uri'
|
||||
|
||||
# Basic data types for configuration.
|
||||
undefined:
|
||||
label: 'Undefined'
|
||||
class: '\Drupal\Core\Config\Schema\Property'
|
||||
mapping:
|
||||
label: Mapping
|
||||
class: '\Drupal\Core\Config\Schema\Mapping'
|
||||
sequence:
|
||||
label: Sequence
|
||||
class: '\Drupal\Core\Config\Schema\Sequence'
|
||||
|
||||
# Default mapping for unknown types or types not found.
|
||||
default:
|
||||
type: undefined
|
||||
label: 'Unknown'
|
||||
|
||||
# Simple extended data types:
|
||||
|
||||
# Human readable string that must be plain text and editable with a text field.
|
||||
label:
|
||||
type: string
|
||||
label: 'Label'
|
||||
|
||||
# Internal Drupal path
|
||||
path:
|
||||
type: string
|
||||
label: 'Path'
|
||||
|
||||
# Human readable string that can contain multiple lines of text or HTML.
|
||||
text:
|
||||
type: string
|
||||
label: 'Text'
|
||||
|
||||
# Complex extended data types:
|
||||
|
||||
# Mail text with subject and body parts.
|
||||
mail:
|
||||
type: mapping
|
||||
label: "Mail"
|
||||
mapping:
|
||||
"subject":
|
||||
type: text
|
||||
label: "Subject"
|
||||
"body":
|
||||
type: text
|
||||
label: "Body"
|
||||
|
||||
# Schema for configuration files of system module:
|
||||
|
||||
system.site:
|
||||
type: mapping
|
||||
label: 'Site information'
|
||||
mapping:
|
||||
"name":
|
||||
label: "Site name"
|
||||
type: label
|
||||
"mail":
|
||||
label: "Site mail"
|
||||
type: email
|
||||
"slogan":
|
||||
label: "Site slogan"
|
||||
type: text
|
||||
"page":
|
||||
type: mapping
|
||||
mapping:
|
||||
"403":
|
||||
type: path
|
||||
"404":
|
||||
type: path
|
||||
"front":
|
||||
type: path
|
||||
label: "Front page path"
|
||||
"admin_compact_mode":
|
||||
type: boolean
|
||||
"weight_select_max":
|
||||
type: integer
|
||||
|
||||
system.maintenance:
|
||||
type: mapping
|
||||
label: 'Maintenance mode'
|
||||
mapping:
|
||||
"enabled":
|
||||
type: boolean
|
||||
label: "Put site into maintenance mode"
|
||||
"message":
|
||||
type: text
|
||||
label: "Message to display when in maintenance mode"
|
|
@ -0,0 +1,75 @@
|
|||
# User module schema: user.schema.yml
|
||||
|
||||
# User mails.
|
||||
user.mail:
|
||||
type: mapping
|
||||
mapping:
|
||||
"cancel_confirm":
|
||||
type: mail
|
||||
label: "Account cancellation confirmation"
|
||||
"password_reset":
|
||||
type: mail
|
||||
label: "Password reset email"
|
||||
"register_admin_created":
|
||||
type: mail
|
||||
label: "Account created by administrator"
|
||||
"register_no_approval_required":
|
||||
type: mail
|
||||
label: "Registration confirmation (No approval required)"
|
||||
"register_pending_approval":
|
||||
type: mail
|
||||
label: "Registration confirmation (Pending approval)"
|
||||
"status_activated":
|
||||
type: mail
|
||||
label: "Account activation"
|
||||
"status_blocked":
|
||||
type: mail
|
||||
label: "Account blocked"
|
||||
"status_canceled":
|
||||
type: mail
|
||||
label: "Account cancelled"
|
||||
|
||||
# User settings.
|
||||
user.settings:
|
||||
type: mapping
|
||||
mapping:
|
||||
"admin_role":
|
||||
type: string
|
||||
label: "Administrator role"
|
||||
"anonymous":
|
||||
type: label
|
||||
label: "Anonymous name"
|
||||
"verify_mail":
|
||||
type: boolean
|
||||
label: "Verify mail"
|
||||
"notify":
|
||||
type: mapping
|
||||
label: "Notify user"
|
||||
mapping:
|
||||
"cancel_confirm":
|
||||
type: boolean
|
||||
"password_reset":
|
||||
type: boolean
|
||||
"status_activated":
|
||||
type: boolean
|
||||
"status_blocked":
|
||||
type: boolean
|
||||
"status_cancelled":
|
||||
type: boolean
|
||||
"register_admin_created":
|
||||
type: boolean
|
||||
"register_no_approval_required":
|
||||
type: boolean
|
||||
"register_pending_approval":
|
||||
type: boolean
|
||||
"register":
|
||||
type: string
|
||||
enum: [visitors, admin_only, visitors_admin_approval]
|
||||
"signatures":
|
||||
type: boolean
|
||||
label: "User signatures"
|
||||
"cancel_method":
|
||||
type: string
|
||||
label: "User cancel method"
|
||||
"password_reset_timeout":
|
||||
type: integer
|
|
@ -0,0 +1,232 @@
|
|||
# View definition (multiple)
|
||||
views.view.*:
|
||||
type: mapping
|
||||
label: 'View'
|
||||
mapping:
|
||||
disabled:
|
||||
type: boolean
|
||||
label: 'Disabled'
|
||||
api_version:
|
||||
label: 'API version'
|
||||
module:
|
||||
label: 'Module'
|
||||
name:
|
||||
label: 'Machine name'
|
||||
description:
|
||||
type: text
|
||||
label: 'Administrative description'
|
||||
tag:
|
||||
label: 'Tag'
|
||||
base_table:
|
||||
label: 'Base table'
|
||||
base_field:
|
||||
label: 'Base field'
|
||||
human_name:
|
||||
type: label
|
||||
label: 'Human readable name'
|
||||
core:
|
||||
label: 'Drupal version'
|
||||
uuid:
|
||||
label: 'UUID'
|
||||
display:
|
||||
type: sequence
|
||||
label: 'Displays'
|
||||
sequence:
|
||||
- type: mapping
|
||||
label: 'Display settings'
|
||||
mapping:
|
||||
id:
|
||||
label: 'Machine name'
|
||||
display_title:
|
||||
type: text
|
||||
label: 'Title'
|
||||
display_plugin:
|
||||
label: 'Display plugin'
|
||||
position:
|
||||
type: integer
|
||||
label: 'Position'
|
||||
display_options:
|
||||
type: 'views.display.[%parent.display_plugin]'
|
||||
|
||||
# Views display: common
|
||||
# Options for Drupal\views\Plugin\views\display\DisplayPluginBase
|
||||
views.display.*:
|
||||
type: mapping
|
||||
label: 'Display options'
|
||||
mapping:
|
||||
title:
|
||||
type: text
|
||||
label: 'Display title'
|
||||
format:
|
||||
label: 'Format'
|
||||
fields:
|
||||
type: sequence
|
||||
label: 'Fields'
|
||||
sequence:
|
||||
- type: 'views.field.[table]-[field]'
|
||||
pager:
|
||||
type: mapping
|
||||
label: 'Pager'
|
||||
mapping:
|
||||
type:
|
||||
label: 'Pager type'
|
||||
options:
|
||||
type: mapping
|
||||
label: 'Options'
|
||||
mapping:
|
||||
offset:
|
||||
type: integer
|
||||
label: 'Offset'
|
||||
|
||||
exposed_form:
|
||||
type: mapping
|
||||
label: 'Exposed form'
|
||||
mapping:
|
||||
type:
|
||||
label: 'Exposed form type'
|
||||
access:
|
||||
type: mapping
|
||||
label: 'Access'
|
||||
mapping:
|
||||
type:
|
||||
label: 'Access type'
|
||||
other:
|
||||
label: 'Other'
|
||||
cache:
|
||||
type: mapping
|
||||
label: 'Cache'
|
||||
mapping:
|
||||
type:
|
||||
label: 'Cache type'
|
||||
sorts:
|
||||
type: sequence
|
||||
label: 'Sorts'
|
||||
sequence:
|
||||
- type: 'views.sort.[table]-[field]'
|
||||
arguments:
|
||||
type: sequence
|
||||
label: 'Arguments'
|
||||
sequence:
|
||||
- type: 'views.argument.[table]-[field]'
|
||||
filters:
|
||||
type: sequence
|
||||
label: 'Filters'
|
||||
sequence:
|
||||
- type: 'views.filter.[table]-[field]'
|
||||
style:
|
||||
type: mapping
|
||||
label: 'Style'
|
||||
mapping:
|
||||
type:
|
||||
label: 'Type'
|
||||
row:
|
||||
type: mapping
|
||||
label: 'Row'
|
||||
mapping:
|
||||
type:
|
||||
label: 'Row type'
|
||||
options:
|
||||
include: 'views.row.[%parent.type]'
|
||||
query:
|
||||
type: mapping
|
||||
label: 'Query'
|
||||
mapping:
|
||||
type:
|
||||
label: 'Query type'
|
||||
options:
|
||||
type: mapping
|
||||
label: 'Query options'
|
||||
mapping:
|
||||
query_comment:
|
||||
type: boolean
|
||||
label: 'Query comment'
|
||||
defaults:
|
||||
type: mapping
|
||||
label: 'Defaults'
|
||||
mapping:
|
||||
style_plugin:
|
||||
label: 'Style plugin'
|
||||
style_options:
|
||||
type: 'views.style.[%parent.style_plugin]'
|
||||
row_plugin:
|
||||
label: 'Row plugin'
|
||||
row_options:
|
||||
type: 'views.style.[%parent.row_plugin]'
|
||||
relationships:
|
||||
type: sequence
|
||||
label: 'Relationships'
|
||||
sequence:
|
||||
- type: 'views.relationship.[table]-[field]'
|
||||
|
||||
# Options for Drupal\views\Plugin\views\display\PathPluginBase
|
||||
views.display.PathPluginBase:
|
||||
include: 'views.display.%'
|
||||
type: mapping
|
||||
mapping:
|
||||
path:
|
||||
type: string
|
||||
label: 'Page path'
|
||||
|
||||
# Views display plugin: Drupal\views\Plugin\views\display\Page
|
||||
views.display.page:
|
||||
type: 'views.display.PathPluginBase'
|
||||
label: 'Page display options'
|
||||
mapping:
|
||||
menu:
|
||||
type: mapping
|
||||
label: 'Menu'
|
||||
mapping:
|
||||
type:
|
||||
label: 'Type'
|
||||
title:
|
||||
type: text
|
||||
label: 'Title'
|
||||
description:
|
||||
type: text
|
||||
label: 'Description'
|
||||
weight:
|
||||
type: integer
|
||||
label: 'Weight'
|
||||
name:
|
||||
label: 'Menu name'
|
||||
context:
|
||||
label: 'Context'
|
||||
tab_options:
|
||||
type: mapping
|
||||
label: 'Tab options'
|
||||
mapping:
|
||||
type:
|
||||
label: 'Type'
|
||||
title:
|
||||
type: text
|
||||
label: 'Title'
|
||||
description:
|
||||
type: text
|
||||
label: 'Description'
|
||||
weight:
|
||||
type: integer
|
||||
label: 'Weight'
|
||||
name:
|
||||
label: 'Menu name'
|
||||
|
||||
# Views display plugin: Drupal\views\Plugin\views\display\Block
|
||||
views.display.block:
|
||||
type: 'views.display.%'
|
||||
label: 'Block display options'
|
||||
mapping:
|
||||
block_description:
|
||||
type: text
|
||||
label: 'Block name'
|
||||
block_caching:
|
||||
label: 'Block caching'
|
||||
|
||||
# Views display plugin: Drupal\views\Plugin\views\display\Feed
|
||||
views.display.feed:
|
||||
type: 'views.display.PathPluginBase'
|
||||
label: 'Feed display options'
|
||||
mapping:
|
||||
sitename_title:
|
||||
type: boolean
|
||||
label: 'Use the site name for the title'
|
||||
displays:
|
||||
label: 'The feed icon will be available only to the selected displays.'
|
Loading…
Reference in New Issue