Commit 2c2f5f59 authored by alexpott's avatar alexpott

Issue #1867856 by fubhy the cat, EclipseGc, Berdir: Use annotation discovery for data type plugins.

parent 9144713d
......@@ -198,6 +198,7 @@ services:
arguments: [slave]
typed_data:
class: Drupal\Core\TypedData\TypedDataManager
arguments: ['@container.namespaces', '@cache.cache', '@language_manager', '@module_handler']
calls:
- [setValidationConstraintManager, ['@validation.constraint']]
validation.constraint:
......
......@@ -583,13 +583,13 @@ function hook_entity_operation_alter(array &$operations, \Drupal\Core\Entity\Ent
/**
* Control access to fields.
*
* This hook is invoked from \Drupal\Core\Entity\Field\Type\Field::access() to
* This hook is invoked from \Drupal\Core\Entity\Field\Field::access() to
* let modules grant or deny operations on fields.
*
* @param string $operation
* The operation to be performed. See
* \Drupal\Core\TypedData\AccessibleInterface::access() for possible values.
* @param \Drupal\Core\Entity\Field\Type\Field $field
* @param \Drupal\Core\Entity\Field\Field $field
* The entity field object on which the operation is to be performed.
* @param \Drupal\Core\Session\AccountInterface $account
* The user account to check.
......@@ -617,7 +617,7 @@ function hook_entity_field_access($operation, $field, \Drupal\Core\Session\Accou
* @param array $context
* Context array on the performed operation with the following keys:
* - operation: The operation to be performed (string).
* - field: The entity field object (\Drupal\Core\Entity\Field\Type\Field).
* - field: The entity field object (\Drupal\Core\Entity\Field\Field).
* - account: The user account to check access for
* (Drupal\user\Plugin\Core\Entity\User).
*/
......
<?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;
$this->loadAllSchema();
}
/**
* 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->storage->listAll() as $name) {
if ($schema = $this->storage->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);
}
}
}
}
<?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);
}
}
......@@ -7,13 +7,13 @@
namespace Drupal\Core\Config;
use Drupal\Core\Config\Schema\SchemaDiscovery;
use Drupal\Core\TypedData\TypedDataManager;
use Drupal\Component\Plugin\PluginManagerBase;
use Drupal\Component\Utility\NestedArray;
/**
* Manages config type plugins.
*/
class TypedConfigManager extends TypedDataManager {
class TypedConfigManager extends PluginManagerBase {
/**
* A storage controller instance for reading configuration data.
......@@ -22,6 +22,20 @@ class TypedConfigManager extends TypedDataManager {
*/
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.
*
......@@ -32,8 +46,8 @@ class TypedConfigManager extends TypedDataManager {
*/
public function __construct(StorageInterface $configStorage, StorageInterface $schemaStorage) {
$this->configStorage = $configStorage;
$this->discovery = new SchemaDiscovery($schemaStorage);
$this->factory = new TypedConfigElementFactory($this->discovery);
$this->schemaStorage = $schemaStorage;
$this->loadAllSchema();
}
/**
......@@ -78,7 +92,116 @@ public function create(array $definition, $value = NULL, $name = NULL, $parent =
$definition['type'] = $this->replaceName($definition['type'], $replace);
}
// Create typed config object.
return parent::create($definition, $value, $name, $parent);
$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);
}
}
}
/**
......
......@@ -369,7 +369,7 @@ public function onChange($property_name) {
/**
* Implements \Drupal\Core\TypedData\TranslatableInterface::getTranslation().
*
* @return \Drupal\Core\Entity\Field\Type\EntityTranslation
* @return \Drupal\Core\Entity\Plugin\DataType\EntityTranslation
*/
public function getTranslation($langcode, $strict = TRUE) {
// If the default language is Language::LANGCODE_NOT_SPECIFIED, the entity is not
......
......@@ -2,10 +2,10 @@
/**
* @file
* Contains \Drupal\Core\Entity\Field\Type\Field.
* Contains \Drupal\Core\Entity\Field\Field.
*/
namespace Drupal\Core\Entity\Field\Type;
namespace Drupal\Core\Entity\Field;
use Drupal\Core\Entity\Field\FieldInterface;
use Drupal\Core\Session\AccountInterface;
......
......@@ -8,7 +8,7 @@
namespace Drupal\Core\Entity\Field;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\TypedData\Type\Map;
use Drupal\Core\TypedData\Plugin\DataType\Map;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\user;
......@@ -80,7 +80,7 @@ public function __get($name) {
}
/**
* Overrides \Drupal\Core\TypedData\Type\Map::set().
* {@inheritdoc}
*/
public function set($property_name, $value, $notify = TRUE) {
// Notify the parent of any changes to be made.
......
......@@ -2,15 +2,24 @@
/**
* @file
* Contains \Drupal\Core\Entity\Field\Type\BooleanItem.
* Contains \Drupal\Core\Entity\Plugin\DataType\BooleanItem.
*/
namespace Drupal\Core\Entity\Field\Type;
namespace Drupal\Core\Entity\Plugin\DataType;
use Drupal\Core\TypedData\Annotation\DataType;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\Entity\Field\FieldItemBase;
/**
* Defines the 'boolean_field' entity field item.
*
* @DataType(
* id = "boolean_field",
* label = @Translation("Boolean field item"),
* description = @Translation("An entity field containing a boolean value."),
* list_class = "\Drupal\Core\Entity\Field\Field"
* )
*/
class BooleanItem extends FieldItemBase {
......
......@@ -2,15 +2,24 @@
/**
* @file
* Contains \Drupal\Core\Entity\Field\Type\DateItem.
* Contains \Drupal\Core\Entity\Plugin\DataType\DateItem.
*/
namespace Drupal\Core\Entity\Field\Type;
namespace Drupal\Core\Entity\Plugin\DataType;
use Drupal\Core\TypedData\Annotation\DataType;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\Entity\Field\FieldItemBase;
/**
* Defines the 'date_field' entity field item.
*
* @DataType(
* id = "date_field",
* label = @Translation("Date field item"),
* description = @Translation("An entity field containing a date value."),
* list_class = "\Drupal\Core\Entity\Field\Field"
* )
*/
class DateItem extends FieldItemBase {
......
......@@ -2,15 +2,25 @@
/**
* @file
* Contains \Drupal\Core\Entity\Field\Type\EmailItem.
* Contains \Drupal\Core\Entity\Plugin\DataType\EmailItem.
*/
namespace Drupal\Core\Entity\Field\Type;
namespace Drupal\Core\Entity\Plugin\DataType;
use Drupal\Core\TypedData\Annotation\DataType;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\Entity\Field\FieldItemBase;
use Drupal\field\Plugin\field\field_type\LegacyConfigFieldItem;
/**
* Defines the 'email_field' entity field item.
*
* @DataType(
* id = "email_field",
* label = @Translation("E-mail field item"),
* description = @Translation("An entity field containing an e-mail value."),
* list_class = "\Drupal\Core\Entity\Field\Field"
* )
*/
class EmailItem extends LegacyConfigFieldItem {
......@@ -37,6 +47,7 @@ public function getPropertyDefinitions() {
return static::$propertyDefinitions;
}
/**
* {@inheritdoc}
*/
......
......@@ -2,11 +2,13 @@
/**
* @file
* Contains \Drupal\Core\Entity\Field\Type\EntityReferenceItem.
* Contains \Drupal\Core\Entity\Plugin\DataType\EntityReferenceItem.
*/
namespace Drupal\Core\Entity\Field\Type;
namespace Drupal\Core\Entity\Plugin\DataType;
use Drupal\Core\TypedData\Annotation\DataType;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\Entity\Field\FieldItemBase;
use Drupal\Core\TypedData\TypedDataInterface;
......@@ -15,6 +17,13 @@
*
* Required settings (below the definition's 'settings' key) are:
* - target_type: The entity type to reference.
*
* @DataType(
* id = "entity_reference_field",
* label = @Translation("Entity reference field item"),
* description = @Translation("An entity field containing an entity reference."),
* list_class = "\Drupal\Core\Entity\Field\Field"
* )
*/
class EntityReferenceItem extends FieldItemBase {
......
......@@ -2,17 +2,18 @@
/**
* @file
* Contains \Drupal\Core\Entity\Type\EntityTranslation.
* Contains \Drupal\Core\Entity\Plugin\DataType\EntityTranslation.
*/
namespace Drupal\Core\Entity\Field\Type;
namespace Drupal\Core\Entity\Plugin\DataType;
use Drupal\Core\TypedData\Annotation\DataType;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\TypedData\AccessibleInterface;
use Drupal\Core\TypedData\ComplexDataInterface;
use Drupal\Core\TypedData\TypedData;
use ArrayIterator;
use Drupal\Core\TypedData\TypedDataInterface;
use IteratorAggregate;
use InvalidArgumentException;
......@@ -21,6 +22,12 @@
*
* Via this object translated entity fields may be read and updated in the same
* way as untranslatable entity fields on the entity object.
*
* @DataType(
* id = "entity_translation",
* label = @Translation("Entity translation"),
* description = @Translation("A translation of an entity.")
* )
*/
class EntityTranslation extends TypedData implements IteratorAggregate, AccessibleInterface, ComplexDataInterface {
......
......@@ -2,11 +2,13 @@
/**
* @file
* Contains \Drupal\Core\Entity\Field\Type\EntityWrapper.
* Contains \Drupal\Core\Entity\Plugin\DataType\EntityWrapper.
*/
namespace Drupal\Core\Entity\Field\Type;
namespace Drupal\Core\Entity\Plugin\DataType;
use Drupal\Core\TypedData\Annotation\DataType;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\TypedData\ComplexDataInterface;
use Drupal\Core\TypedData\TypedData;
......@@ -34,6 +36,12 @@
* Supported settings (below the definition's 'settings' key) are:
* - id source: If used as computed property, the ID property used to load
* the entity object.
*
* @DataType(
* id = "entity",
* label = @Translation("Entity"),
* description = @Translation("All kind of entities, e.g. nodes, comments or users.")
* )
*/
class EntityWrapper extends TypedData implements IteratorAggregate, ComplexDataInterface {
......
......@@ -38,11 +38,6 @@ public function getDerivativeDefinition($derivative_id, array $base_plugin_defin
*/
public function getDerivativeDefinitions(array $base_plugin_definition) {
foreach (\Drupal::service('plugin.manager.entity.field.field_type')->getDefinitions() as $plugin_id => $definition) {
// Typed data API expects a 'list class' property, but annotations do not
// support spaces in property names.
$definition['list class'] = $definition['list_class'];
unset($definition['list_class']);
$this->derivatives[$plugin_id] = $definition;
}
return $this->derivatives;
......
<?php
/**
* @file
* Contains \Drupal\Core\Entity\Plugin\DataType\FieldItem.
*/
namespace Drupal\Core\Entity\Plugin\DataType;
use Drupal\Core\TypedData\Annotation\DataType;
use Drupal\Core\Annotation\Translation;
use Drupal\Component\Plugin\PluginBase;
/**
* Defines the base plugin definition for field type typed data types.
*
* @DataType(
* id = "field_item",
* label = @Translation("Field item"),
* list_class = "\Drupal\Core\Entity\Field\Field",
* derivative = "Drupal\Core\Entity\Plugin\DataType\FieldDataTypeDerivative"
* )
*/
class FieldItem extends PluginBase {
}
......@@ -2,15 +2,24 @@
/**
* @file
* Contains \Drupal\Core\Entity\Field\Type\IntegerItem.
* Contains \Drupal\Core\Entity\Plugin\DataType\IntegerItem.
*/
namespace Drupal\Core\Entity\Field\Type;
namespace Drupal\Core\Entity\Plugin\DataType;
use Drupal\Core\TypedData\Annotation\DataType;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\Entity\Field\FieldItemBase;
/**
* Defines the 'integer_field' entity field item.
*
* @DataType(
* id = "integer_field",
* label = @Translation("Integer field item"),
* description = @Translation("An entity field containing an integer value."),
* list_class = "\Drupal\Core\Entity\Field\Field"
* )
*/
class IntegerItem extends FieldItemBase {
......
......@@ -2,16 +2,30 @@
/**
* @file
* Contains \Drupal\Core\Entity\Field\Type\LanguageItem.
* Contains \Drupal\Core\Entity\Plugin\DataType\LanguageItem.
*/
namespace Drupal\Core\Entity\Field\Type;
namespace Drupal\Core\Entity\Plugin\DataType;
use Drupal\Core\TypedData\Annotation\DataType;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\Entity\Field\FieldItemBase;
use Drupal\Core\Language\Language;
/**
* Defines the 'language_field' entity field item.
*
* @DataType(
* id = "language_field",
* label = @Translation("Language field item"),
* description = @Translation("An entity field referencing a language."),
* list_class = "\Drupal\Core\Entity\Field\Field",
* constraints = {
* "ComplexData" = {
* "value" = {"Length" = {"max" = 12}}
* }
* }
* )
*/
class LanguageItem extends FieldItemBase {
......
......@@ -2,15 +2,24 @@
/**
* @file
* Contains \Drupal\Core\Entity\Field\Type\StringItem.
* Contains \Drupal\Core\Entity\Plugin\DataType\StringItem.
*/
namespace Drupal\Core\Entity\Field\Type;
namespace Drupal\Core\Entity\Plugin\DataType;
use Drupal\Core\TypedData\Annotation\DataType;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\Entity\Field\FieldItemBase;
/**
* Defines the 'string_field' entity field item.
*
* @DataType(
* id = "string_field",
* label = @Translation("String field item"),
* description = @Translation("An entity field containing a string value."),
* list_class = "\Drupal\Core\Entity\Field\Field"
* )
*/
class StringItem extends FieldItemBase {
......