diff --git a/core/core.services.yml b/core/core.services.yml index 95177ff6395da17c64cb0b1bfda42061ea24519d..c733180b04710b6d8fc1ba1ff4c6a0543e317798 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -382,7 +382,7 @@ services: parent: default_plugin_manager plugin.manager.field.field_type: class: Drupal\Core\Field\FieldTypePluginManager - arguments: ['@container.namespaces', '@cache.discovery', '@module_handler'] + arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@typed_data_manager'] plugin.manager.field.widget: class: Drupal\Core\Field\WidgetPluginManager arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@plugin.manager.field.field_type'] diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php index f6122cfbac51f04e951db433819aaaef6a35c953..a11d56049c40b3ee8078257570d2da4fafe3f5d9 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php @@ -184,15 +184,6 @@ public function __construct(array $values, $entity_type, $bundle = FALSE, $trans } } - /** - * Returns the typed data manager. - * - * @return \Drupal\Core\TypedData\TypedDataManager - */ - protected function typedDataManager() { - return \Drupal::typedDataManager(); - } - /** * {@inheritdoc} */ @@ -378,8 +369,7 @@ protected function getTranslatedField($name, $langcode) { if (isset($this->values[$name][$langcode])) { $value = $this->values[$name][$langcode]; } - $entity_adapter = $this->getTypedData(); - $field = \Drupal::typedDataManager()->getPropertyInstance($entity_adapter, $name, $value); + $field = \Drupal::service('plugin.manager.field.field_type')->createFieldItemList($this, $name, $value); if ($default) { // $this->defaultLangcode might not be set if we are initializing the // default language code cache, in which case there is no valid @@ -894,7 +884,8 @@ public function __clone() { // Avoid deep-cloning when we are initializing a translation object, since // it will represent the same entity, only with a different active language. if (!$this->translationInitialize) { - // The translation is a different object, and needs its own TypedData object. + // The translation is a different object, and needs its own TypedData + // adapter object. $this->typedData = NULL; $definitions = $this->getFieldDefinitions(); foreach ($this->fields as $name => $values) { diff --git a/core/lib/Drupal/Core/Field/BaseFieldDefinition.php b/core/lib/Drupal/Core/Field/BaseFieldDefinition.php index 1d2bf6de2132479a0ee6841398d9a7b8688ca7ae..244f25b670e35fb7197341368251d78f57c6f676 100644 --- a/core/lib/Drupal/Core/Field/BaseFieldDefinition.php +++ b/core/lib/Drupal/Core/Field/BaseFieldDefinition.php @@ -515,7 +515,7 @@ public function getOptionsProvider($property_name, FieldableEntityInterface $ent // without modifying the entity being worked on. if (is_subclass_of($this->getFieldItemClass(), '\Drupal\Core\TypedData\OptionsProviderInterface')) { $items = $entity->get($this->getName()); - return \Drupal::typedDataManager()->getPropertyInstance($items, 0); + return \Drupal::service('plugin.manager.field.field_type')->createFieldItem($items, 0); } // @todo: Allow setting custom options provider, see // https://www.drupal.org/node/2002138. diff --git a/core/lib/Drupal/Core/Field/FieldItemBase.php b/core/lib/Drupal/Core/Field/FieldItemBase.php index b2119e37425e6c018981a37fdfd245592b5f7750..1bb5486c41a35041d6370f602c96de29a2bc4945 100644 --- a/core/lib/Drupal/Core/Field/FieldItemBase.php +++ b/core/lib/Drupal/Core/Field/FieldItemBase.php @@ -78,7 +78,7 @@ public function getLangcode() { * {@inheritdoc} */ public function getFieldDefinition() { - return $this->getParent()->getFieldDefinition(); + return $this->definition->getFieldDefinition(); } /** diff --git a/core/lib/Drupal/Core/Field/FieldItemList.php b/core/lib/Drupal/Core/Field/FieldItemList.php index cfb5fdc9e66182392b8cdd79a8b1edc139b26863..930b4e17aeeed7afaa113268f4b470a91175cd3b 100644 --- a/core/lib/Drupal/Core/Field/FieldItemList.php +++ b/core/lib/Drupal/Core/Field/FieldItemList.php @@ -40,6 +40,13 @@ class FieldItemList extends ItemList implements FieldItemListInterface { */ protected $langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED; + /** + * {@inheritdoc} + */ + protected function createItem($offset = 0, $value = NULL) { + return \Drupal::service('plugin.manager.field.field_type')->createFieldItem($this, $offset, $value); + } + /** * {@inheritdoc} */ diff --git a/core/lib/Drupal/Core/Field/FieldTypePluginManager.php b/core/lib/Drupal/Core/Field/FieldTypePluginManager.php index e355985c70cdacc460a43f17d5910a107f7a8bc6..81fd39464b38364344633e553458bfb8f0afae32 100644 --- a/core/lib/Drupal/Core/Field/FieldTypePluginManager.php +++ b/core/lib/Drupal/Core/Field/FieldTypePluginManager.php @@ -9,8 +9,10 @@ use Drupal\Component\Plugin\Factory\DefaultFactory; use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Plugin\DefaultPluginManager; +use Drupal\Core\TypedData\TypedDataManager; /** * Plugin manager for 'field type' plugins. @@ -19,6 +21,13 @@ */ class FieldTypePluginManager extends DefaultPluginManager implements FieldTypePluginManagerInterface { + /** + * The typed data manager. + * + * @var \Drupal\Core\TypedData\TypedDataManager + */ + protected $typedDataManager; + /** * Constructs the FieldTypePluginManager object * @@ -29,11 +38,50 @@ class FieldTypePluginManager extends DefaultPluginManager implements FieldTypePl * Cache backend instance to use. * @param \Drupal\Core\Extension\ModuleHandlerInterface * The module handler. + * @param \Drupal\Core\TypedData\TypedDataManager $typed_data_manager + * The typed data manager. */ - public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) { + public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, TypedDataManager $typed_data_manager) { parent::__construct('Plugin/Field/FieldType', $namespaces, $module_handler, 'Drupal\Core\Field\FieldItemInterface', 'Drupal\Core\Field\Annotation\FieldType'); $this->alterInfo('field_info'); $this->setCacheBackend($cache_backend, 'field_types_plugins'); + $this->typedDataManager = $typed_data_manager; + } + + /** + * {@inheritdoc} + * + * Creates a field item, which is not part of an entity or field item list. + * + * @param string $field_type + * The field type, for which a field item should be created. + * @param array $configuration + * The plugin configuration array, i.e. an array with the following keys: + * - field_definition: The field definition object, i.e. an instance of + * Drupal\Core\Field\FieldDefinitionInterface. + * + * @return \Drupal\Core\Field\FieldItemInterface + * The instantiated object. + */ + public function createInstance($field_type, array $configuration = array()) { + $configuration['data_definition'] = $configuration['field_definition']->getItemDefinition(); + return $this->typedDataManager->createInstance("field_item:$field_type", $configuration); + } + + /** + * {@inheritdoc} + */ + public function createFieldItemList(FieldableEntityInterface $entity, $field_name, $values = NULL) { + // Leverage prototyping of the Typed Data API for fast instantiation. + return $this->typedDataManager->getPropertyInstance($entity->getTypedData(), $field_name, $values); + } + + /** + * {@inheritdoc} + */ + public function createFieldItem(FieldItemListInterface $items, $index, $values = NULL) { + // Leverage prototyping of the Typed Data API for fast instantiation. + return $this->typedDataManager->getPropertyInstance($items, $index, $values); } /** diff --git a/core/lib/Drupal/Core/Field/FieldTypePluginManagerInterface.php b/core/lib/Drupal/Core/Field/FieldTypePluginManagerInterface.php index c3e4bcc79ae9b8441adbae8010cba6f77c0883e1..0d48b594a8dab809ef9c1fbb10ad7d161d45bbe4 100644 --- a/core/lib/Drupal/Core/Field/FieldTypePluginManagerInterface.php +++ b/core/lib/Drupal/Core/Field/FieldTypePluginManagerInterface.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Field; use Drupal\Component\Plugin\PluginManagerInterface; +use Drupal\Core\Entity\FieldableEntityInterface; /** * Defines an interface for the field type plugin manager. @@ -16,6 +17,45 @@ */ interface FieldTypePluginManagerInterface extends PluginManagerInterface { + /** + * Creates a new field item list. + * + * The provided entity is assigned as the parent of the created item list. + * However, it is the responsibility of the caller (usually the parent entity + * itself) to make the parent aware of the field as a new child. + * + * @param \Drupal\Core\Entity\FieldableEntityInterface $entity + * The entity this field item list will be part of. + * @param string $field_name + * The name of the field. + * @param mixed $values + * (optional) The data value. If set, it has to match one of the supported + * data type format as documented for the data type classes. + * + * @return \Drupal\Core\Field\FieldItemListInterface + * The instantiated object. + */ + public function createFieldItemList(FieldableEntityInterface $entity, $field_name, $values = NULL); + + /** + * Creates a new field item as part of a field item list. + * + * The provided item list is assigned as the parent of the created item. It + * However, it is the responsibility of the caller (usually the parent list + * itself) to have the parent aware of the item as a new child. + * + * @param \Drupal\Core\Field\FieldItemListInterface $items + * The field item list, for which to create a new item. + * @param int $index + * The list index at which the item is created. + * @param array|null $values + * (optional) The values to assign to the field item properties. + * + * @return \Drupal\Core\Field\FieldItemInterface + * The instantiated object. + */ + public function createFieldItem(FieldItemListInterface $items, $index, $values = NULL); + /** * Returns the default field-level settings for a field type. * diff --git a/core/lib/Drupal/Core/Field/TypedData/FieldItemDataDefinition.php b/core/lib/Drupal/Core/Field/TypedData/FieldItemDataDefinition.php index 7b85e8c7bcaac1e5822389179658ff11978746cc..510c027a62fb09b218d086c1c051164c6d26013b 100644 --- a/core/lib/Drupal/Core/Field/TypedData/FieldItemDataDefinition.php +++ b/core/lib/Drupal/Core/Field/TypedData/FieldItemDataDefinition.php @@ -78,4 +78,14 @@ public function getMainPropertyName() { return $this->fieldDefinition->getFieldStorageDefinition()->getMainPropertyName(); } + /** + * Gets the field item's field definition. + * + * @return \Drupal\Core\Field\FieldDefinitionInterface + * The field definition for this field item. + */ + public function getFieldDefinition() { + return $this->fieldDefinition; + } + } diff --git a/core/modules/field/src/Entity/FieldStorageConfig.php b/core/modules/field/src/Entity/FieldStorageConfig.php index a1c2d589b7b7880a5b60bc1e36064689837e5581..fd3bd14555fdc7cccd60f6177dce5ca6314a6107 100644 --- a/core/modules/field/src/Entity/FieldStorageConfig.php +++ b/core/modules/field/src/Entity/FieldStorageConfig.php @@ -630,7 +630,7 @@ public function getOptionsProvider($property_name, FieldableEntityInterface $ent // without modifying the entity being worked on. if (is_subclass_of($this->getFieldItemClass(), '\Drupal\Core\TypedData\OptionsProviderInterface')) { $items = $entity->get($this->getName()); - return \Drupal::typedDataManager()->getPropertyInstance($items, 0); + return \Drupal::service('plugin.manager.field.field_type')->createFieldItem($items, 0); } // @todo: Allow setting custom options provider, see // https://www.drupal.org/node/2002138. diff --git a/core/modules/field/src/Tests/FieldTypePluginManagerTest.php b/core/modules/field/src/Tests/FieldTypePluginManagerTest.php index ea12c40626371847a45992ecb8c8fbc8d53cbcac..758b2f566a23010cfefaa3653237f54993cef3eb 100644 --- a/core/modules/field/src/Tests/FieldTypePluginManagerTest.php +++ b/core/modules/field/src/Tests/FieldTypePluginManagerTest.php @@ -7,6 +7,10 @@ namespace Drupal\field\Tests; +use Drupal\Component\Utility\String; +use Drupal\Core\Field\BaseFieldDefinition; +use Drupal\entity_test\Entity\EntityTest; + /** * Tests the field type manager. * @@ -26,4 +30,63 @@ function testDefaultSettings() { } } + /** + * Tests creation of field item instances. + */ + public function testCreateInstance() { + /** @var \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager */ + $field_type_manager = \Drupal::service('plugin.manager.field.field_type'); + foreach (array('test_field', 'shape', 'hidden_test_field') as $type) { + $definition = $field_type_manager->getDefinition($type); + + $class = $definition['class']; + $field_name = 'field_' . $type; + + $field_definition = BaseFieldDefinition::create($type); + + $configuration = array( + 'field_definition' => $field_definition, + 'name' => $field_name, + 'parent' => NULL, + ); + + $instance = $field_type_manager->createInstance($type, $configuration); + + $this->assertTrue($instance instanceof $class, String::format('Created a @class instance', array('@class' => $class))); + $this->assertEqual($field_name, $instance->getName(), String::format('Instance name is @name', array('@name' => $field_name))); + } + } + + /** + * Tests creation of field item instances. + */ + public function testCreateInstanceWithConfig() { + /** @var \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager */ + $field_type_manager = \Drupal::service('plugin.manager.field.field_type'); + $type = 'test_field'; + $definition = $field_type_manager->getDefinition($type); + + $class = $definition['class']; + $field_name = 'field_' . $type; + + $field_definition = BaseFieldDefinition::create($type) + ->setLabel('Jenny') + ->setDefaultValue(8675309); + + $configuration = array( + 'field_definition' => $field_definition, + 'name' => $field_name, + 'parent' => NULL, + ); + + $entity = EntityTest::create(); + + $instance = $field_type_manager->createInstance($type, $configuration); + + $this->assertTrue($instance instanceof $class, String::format('Created a @class instance', array('@class' => $class))); + $this->assertEqual($field_name, $instance->getName(), String::format('Instance name is @name', array('@name' => $field_name))); + $this->assertEqual($instance->getFieldDefinition()->getLabel(), 'Jenny', 'Instance label is Jenny'); + $this->assertEqual($instance->getFieldDefinition()->getDefaultValue($entity), [['value' => 8675309]], 'Instance default_value is 8675309'); + } + } diff --git a/core/tests/Drupal/Tests/Core/Entity/ContentEntityBaseUnitTest.php b/core/tests/Drupal/Tests/Core/Entity/ContentEntityBaseUnitTest.php index 2b1db8be7e7e8fe6a31873a6bacba1a4ad3ebdab..2b9ef09d66d9a6deba169877267e0e56aa39b240 100644 --- a/core/tests/Drupal/Tests/Core/Entity/ContentEntityBaseUnitTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/ContentEntityBaseUnitTest.php @@ -229,9 +229,9 @@ public function testIsNewRevision() { ->disableOriginalConstructor() ->getMockForAbstractClass(); - $this->typedDataManager->expects($this->any()) - ->method('getPropertyInstance') - ->with($this->entity->getTypedData(), 'revision_id', NULL) + $this->fieldTypePluginManager->expects($this->any()) + ->method('createFieldItemList') + ->with($this->entity, 'revision_id', NULL) ->will($this->returnValue($field_item_list)); $this->fieldDefinitions['revision_id']->getItemDefinition()->setClass(get_class($field_item)); diff --git a/core/tests/Drupal/Tests/Core/Entity/TypedData/EntityAdapterUnitTest.php b/core/tests/Drupal/Tests/Core/Entity/TypedData/EntityAdapterUnitTest.php index 0628c89945a32758b38101d03a6f933e101bedf7..79eb3b2b7d8c4a1000d68904ba92046de377a5a1 100644 --- a/core/tests/Drupal/Tests/Core/Entity/TypedData/EntityAdapterUnitTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/TypedData/EntityAdapterUnitTest.php @@ -164,11 +164,6 @@ protected function setUp() { ->method('getValidationConstraintManager') ->willReturn($validation_constraint_manager); - $this->fieldItemList = $this->getMock('\Drupal\Core\Field\FieldItemListInterface'); - $this->typedDataManager->expects($this->any()) - ->method('getPropertyInstance') - ->willReturn($this->fieldItemList); - $not_specified = new Language(array('id' => LanguageInterface::LANGCODE_NOT_SPECIFIED, 'locked' => TRUE)); $this->languageManager = $this->getMock('\Drupal\Core\Language\LanguageManagerInterface'); $this->languageManager->expects($this->any()) @@ -190,6 +185,11 @@ protected function setUp() { ->method('getDefaultFieldSettings') ->will($this->returnValue(array())); + $this->fieldItemList = $this->getMock('\Drupal\Core\Field\FieldItemListInterface'); + $this->fieldTypePluginManager->expects($this->any()) + ->method('createFieldItemList') + ->willReturn($this->fieldItemList); + $container = new ContainerBuilder(); $container->set('entity.manager', $this->entityManager); $container->set('uuid', $this->uuid); diff --git a/core/tests/Drupal/Tests/Core/Field/BaseFieldDefinitionTestBase.php b/core/tests/Drupal/Tests/Core/Field/BaseFieldDefinitionTestBase.php index 1fffe450af025c78a033abb51f914a6518aa6afc..97354872ceb446993a9b926089a9ff6acfc67f5e 100644 --- a/core/tests/Drupal/Tests/Core/Field/BaseFieldDefinitionTestBase.php +++ b/core/tests/Drupal/Tests/Core/Field/BaseFieldDefinitionTestBase.php @@ -40,10 +40,14 @@ protected function setUp() { ->method('moduleExists') ->with($module_name) ->will($this->returnValue(TRUE)); + $typed_data_manager = $this->getMockBuilder('\Drupal\Core\TypedData\TypedDataManager') + ->disableOriginalConstructor() + ->getMock(); $plugin_manager = new FieldTypePluginManager( $namespaces, $this->getMock('Drupal\Core\Cache\CacheBackendInterface'), - $module_handler + $module_handler, + $typed_data_manager ); $container = new ContainerBuilder();