From 0daa2b5227a8f785f450b50016730101d2ca373e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=CC=81bor=20Hojtsy?= <gabor@hojtsy.hu> Date: Fri, 8 Jun 2018 14:18:59 +0200 Subject: [PATCH] Issue #1818574 by tim.plunkett, alexpott, tedbow, dawehner, EclipseGc, fago, Wim Leers, andypost, Berdir, xjm, yched, catch, jibran, effulgentsia: Support config entities in typed data EntityAdapter --- .../Plugin/DataType/ConfigEntityAdapter.php | 103 ++++++++++ .../Plugin/DataType/Deriver/EntityDeriver.php | 6 + .../Entity/Plugin/DataType/EntityAdapter.php | 6 - .../Core/Entity/ConfigEntityAdapterTest.php | 176 ++++++++++++++++++ .../Entity/EntityTypedDataDefinitionTest.php | 2 + .../TypedData/EntityAdapterUnitTest.php | 23 --- 6 files changed, 287 insertions(+), 29 deletions(-) create mode 100644 core/lib/Drupal/Core/Entity/Plugin/DataType/ConfigEntityAdapter.php create mode 100644 core/tests/Drupal/KernelTests/Core/Entity/ConfigEntityAdapterTest.php diff --git a/core/lib/Drupal/Core/Entity/Plugin/DataType/ConfigEntityAdapter.php b/core/lib/Drupal/Core/Entity/Plugin/DataType/ConfigEntityAdapter.php new file mode 100644 index 000000000000..093c77dd16b1 --- /dev/null +++ b/core/lib/Drupal/Core/Entity/Plugin/DataType/ConfigEntityAdapter.php @@ -0,0 +1,103 @@ +<?php + +namespace Drupal\Core\Entity\Plugin\DataType; + +use Drupal\Core\TypedData\Exception\MissingDataException; + +/** + * Enhances EntityAdapter for config entities. + */ +class ConfigEntityAdapter extends EntityAdapter { + + /** + * The wrapped entity object. + * + * @var \Drupal\Core\Config\Entity\ConfigEntityInterface + */ + protected $entity; + + /** + * {@inheritdoc} + */ + public function get($property_name) { + if (!isset($this->entity)) { + throw new MissingDataException("Unable to get property $property_name as no entity has been provided."); + } + return $this->getConfigTypedData()->get($property_name); + } + + /** + * {@inheritdoc} + */ + public function set($property_name, $value, $notify = TRUE) { + if (!isset($this->entity)) { + throw new MissingDataException("Unable to set property $property_name as no entity has been provided."); + } + $this->entity->set($property_name, $value, $notify); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getProperties($include_computed = FALSE) { + if (!isset($this->entity)) { + throw new MissingDataException('Unable to get properties as no entity has been provided.'); + } + return $this->getConfigTypedData()->getProperties($include_computed); + } + + /** + * {@inheritdoc} + */ + public function onChange($property_name) { + if (isset($this->entity)) { + // Let the entity know of any changes. + $this->getConfigTypedData()->onChange($property_name); + } + } + + /** + * {@inheritdoc} + */ + public function getIterator() { + if (isset($this->entity)) { + return $this->getConfigTypedData()->getIterator(); + } + return new \ArrayIterator([]); + } + + /** + * Gets the typed data manager. + * + * @return \Drupal\Core\Config\TypedConfigManagerInterface + * The typed data manager. + */ + public function getTypedDataManager() { + if (empty($this->typedDataManager)) { + $this->typedDataManager = \Drupal::service('config.typed'); + } + + return $this->typedDataManager; + } + + /** + * {@inheritdoc} + */ + public function applyDefaultValue($notify = TRUE) { + // @todo Figure out what to do for this method, see + // https://www.drupal.org/project/drupal/issues/2945635. + throw new \BadMethodCallException('Method not supported'); + } + + /** + * Gets typed data for config entity. + * + * @return \Drupal\Core\TypedData\ComplexDataInterface + * The typed data. + */ + protected function getConfigTypedData() { + return $this->getTypedDataManager()->createFromNameAndData($this->entity->getConfigDependencyName(), $this->entity->toArray()); + } + +} diff --git a/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php b/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php index d058eb4372fd..38ac960cc17a 100644 --- a/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php +++ b/core/lib/Drupal/Core/Entity/Plugin/DataType/Deriver/EntityDeriver.php @@ -2,8 +2,11 @@ namespace Drupal\Core\Entity\Plugin\DataType\Deriver; +use Drupal\Core\Config\Entity\ConfigEntityInterface; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityTypeBundleInfoInterface; +use Drupal\Core\Entity\Plugin\DataType\ConfigEntityAdapter; +use Drupal\Core\Entity\Plugin\DataType\EntityAdapter; use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -86,7 +89,9 @@ public function getDerivativeDefinitions($base_plugin_definition) { $this->derivatives[''] = $base_plugin_definition; // Add definitions for each entity type and bundle. foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) { + $class = $entity_type->entityClassImplements(ConfigEntityInterface::class) ? ConfigEntityAdapter::class : EntityAdapter::class; $this->derivatives[$entity_type_id] = [ + 'class' => $class, 'label' => $entity_type->getLabel(), 'constraints' => $entity_type->getConstraints(), 'internal' => $entity_type->isInternal(), @@ -96,6 +101,7 @@ public function getDerivativeDefinitions($base_plugin_definition) { foreach ($this->bundleInfoService->getBundleInfo($entity_type_id) as $bundle => $bundle_info) { if ($bundle !== $entity_type_id) { $this->derivatives[$entity_type_id . ':' . $bundle] = [ + 'class' => $class, 'label' => $bundle_info['label'], 'constraints' => $this->derivatives[$entity_type_id]['constraints'], ] + $base_plugin_definition; diff --git a/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityAdapter.php b/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityAdapter.php index b0b94b8dbb93..1b54d7004706 100644 --- a/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityAdapter.php +++ b/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityAdapter.php @@ -78,8 +78,6 @@ public function get($property_name) { throw new MissingDataException("Unable to get property $property_name as no entity has been provided."); } if (!$this->entity instanceof FieldableEntityInterface) { - // @todo: Add support for config entities in - // https://www.drupal.org/node/1818574. throw new \InvalidArgumentException("Unable to get unknown property $property_name."); } // This will throw an exception for unknown fields. @@ -94,8 +92,6 @@ public function set($property_name, $value, $notify = TRUE) { throw new MissingDataException("Unable to set property $property_name as no entity has been provided."); } if (!$this->entity instanceof FieldableEntityInterface) { - // @todo: Add support for config entities in - // https://www.drupal.org/node/1818574. throw new \InvalidArgumentException("Unable to set unknown property $property_name."); } // This will throw an exception for unknown fields. @@ -111,8 +107,6 @@ public function getProperties($include_computed = FALSE) { throw new MissingDataException('Unable to get properties as no entity has been provided.'); } if (!$this->entity instanceof FieldableEntityInterface) { - // @todo: Add support for config entities in - // https://www.drupal.org/node/1818574. return []; } return $this->entity->getFields($include_computed); diff --git a/core/tests/Drupal/KernelTests/Core/Entity/ConfigEntityAdapterTest.php b/core/tests/Drupal/KernelTests/Core/Entity/ConfigEntityAdapterTest.php new file mode 100644 index 000000000000..af9403c320aa --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Entity/ConfigEntityAdapterTest.php @@ -0,0 +1,176 @@ +<?php + +namespace Drupal\KernelTests\Core\Entity; + +use Drupal\Core\Config\Schema\Mapping; +use Drupal\Core\Entity\Plugin\DataType\ConfigEntityAdapter; +use Drupal\Core\TypedData\Plugin\DataType\BooleanData; +use Drupal\Core\TypedData\Plugin\DataType\IntegerData; +use Drupal\Core\TypedData\Plugin\DataType\StringData; +use Drupal\KernelTests\KernelTestBase; + +/** + * Tests entity adapter for configuration entities. + * + * @see \Drupal\Core\Entity\Plugin\DataType\ConfigEntityAdapter + * + * @group Entity + * + * @coversDefaultClass \Drupal\Core\Entity\Plugin\DataType\ConfigEntityAdapter + */ +class ConfigEntityAdapterTest extends KernelTestBase { + + /** + * {@inheritdoc} + */ + public static $modules = ['config_test']; + + /** + * The config entity. + * + * @var \Drupal\config_test\Entity\ConfigTest + */ + protected $entity; + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + $this->installConfig(static::$modules); + + // ConfigTest::create doesn't work with the following exception: + // "Multiple entity types found for Drupal\config_test\Entity\ConfigTest." + $this->entity = \Drupal::entityTypeManager()->getStorage('config_test')->create([ + 'id' => 'system', + 'label' => 'foobar', + 'weight' => 1, + ]); + } + + /** + * @covers \Drupal\Core\Entity\Plugin\DataType\Deriver\EntityDeriver::getDerivativeDefinitions + */ + public function testEntityDeriver() { + $definition = \Drupal::typedDataManager()->getDefinition('entity:config_test'); + $this->assertEquals(ConfigEntityAdapter::class, $definition['class']); + } + + /** + * @covers ::validate + */ + public function testValidate() { + $adapter = ConfigEntityAdapter::createFromEntity($this->entity); + $violations = $adapter->validate(); + $this->assertEmpty($violations); + $this->entity = \Drupal::entityTypeManager()->getStorage('config_test')->create([ + 'id' => 'system', + 'label' => 'foobar', + // Set weight to be a string which should not validate. + 'weight' => 'very heavy', + ]); + $adapter = ConfigEntityAdapter::createFromEntity($this->entity); + $violations = $adapter->validate(); + $this->assertCount(1, $violations); + $violation = $violations->get(0); + $this->assertEquals('This value should be of the correct primitive type.', $violation->getMessage()); + $this->assertEquals('weight', $violation->getPropertyPath()); + } + + /** + * @covers ::getProperties + */ + public function testGetProperties() { + $expected_properties = [ + 'uuid' => StringData::class, + 'langcode' => StringData::class, + 'status' => BooleanData::class, + 'dependencies' => Mapping::class, + 'id' => StringData::class, + 'label' => StringData::class, + 'weight' => IntegerData::class, + 'style' => StringData::class, + 'size' => StringData::class, + 'size_value' => StringData::class, + 'protected_property' => StringData::class, + ]; + $properties = ConfigEntityAdapter::createFromEntity($this->entity)->getProperties(); + $keys = []; + foreach ($properties as $key => $property) { + $keys[] = $key; + $this->assertInstanceOf($expected_properties[$key], $property); + } + $this->assertSame(array_keys($expected_properties), $keys); + } + + /** + * @covers ::getValue + */ + public function testGetValue() { + $adapter = ConfigEntityAdapter::createFromEntity($this->entity); + $this->assertEquals($this->entity->weight, $adapter->get('weight')->getValue()); + $this->assertEquals($this->entity->id(), $adapter->get('id')->getValue()); + $this->assertEquals($this->entity->label, $adapter->get('label')->getValue()); + } + + /** + * @covers ::set + */ + public function testSet() { + $adapter = ConfigEntityAdapter::createFromEntity($this->entity); + // Get the value via typed data to ensure that the typed representation is + // updated correctly when the value is set. + $this->assertEquals(1, $adapter->get('weight')->getValue()); + + $return = $adapter->set('weight', 2); + $this->assertSame($adapter, $return); + $this->assertEquals(2, $this->entity->weight); + // Ensure the typed data is updated via the set too. + $this->assertEquals(2, $adapter->get('weight')->getValue()); + } + + /** + * @covers ::getString + */ + public function testGetString() { + $adapter = ConfigEntityAdapter::createFromEntity($this->entity); + $this->assertEquals('foobar', $adapter->getString()); + } + + /** + * @covers ::applyDefaultValue + */ + public function testApplyDefaultValue() { + $this->setExpectedException(\BadMethodCallException::class, 'Method not supported'); + $adapter = ConfigEntityAdapter::createFromEntity($this->entity); + $adapter->applyDefaultValue(); + } + + /** + * @covers ::getIterator + */ + public function testGetIterator() { + $adapter = ConfigEntityAdapter::createFromEntity($this->entity); + $iterator = $adapter->getIterator(); + $fields = iterator_to_array($iterator); + $expected_fields = [ + 'uuid', + 'langcode', + 'status', + 'dependencies', + 'id', + 'label', + 'weight', + 'style', + 'size', + 'size_value', + 'protected_property', + ]; + $this->assertEquals($expected_fields, array_keys($fields)); + $this->assertEquals($this->entity->id(), $fields['id']->getValue()); + + $adapter->setValue(NULL); + $this->assertEquals(new \ArrayIterator([]), $adapter->getIterator()); + } + +} diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityTypedDataDefinitionTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityTypedDataDefinitionTest.php index ece711b1b03d..5426da7444f3 100644 --- a/core/tests/Drupal/KernelTests/Core/Entity/EntityTypedDataDefinitionTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityTypedDataDefinitionTest.php @@ -2,6 +2,7 @@ namespace Drupal\KernelTests\Core\Entity; +use Drupal\Core\Config\Entity\ConfigEntityInterface; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\TypedData\EntityDataDefinition; @@ -149,6 +150,7 @@ public function testEntityDefinitionIsInternal($internal, $expected) { $entity_type_id = $this->randomMachineName(); $entity_type = $this->prophesize(EntityTypeInterface::class); + $entity_type->entityClassImplements(ConfigEntityInterface::class)->willReturn(FALSE); $entity_type->getLabel()->willReturn($this->randomString()); $entity_type->getConstraints()->willReturn([]); $entity_type->isInternal()->willReturn($internal); diff --git a/core/tests/Drupal/Tests/Core/Entity/TypedData/EntityAdapterUnitTest.php b/core/tests/Drupal/Tests/Core/Entity/TypedData/EntityAdapterUnitTest.php index 8e909d24bd4f..661bb2afc827 100644 --- a/core/tests/Drupal/Tests/Core/Entity/TypedData/EntityAdapterUnitTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/TypedData/EntityAdapterUnitTest.php @@ -35,13 +35,6 @@ class EntityAdapterUnitTest extends UnitTestCase { */ protected $entity; - /** - * The config entity used for testing. - * - * @var \Drupal\Core\Entity\ConfigtEntityBase|\PHPUnit_Framework_MockObject_MockObject - */ - protected $configEntity; - /** * The content entity adapter under test. * @@ -49,13 +42,6 @@ class EntityAdapterUnitTest extends UnitTestCase { */ protected $entityAdapter; - /** - * The config entity adapter under test. - * - * @var \Drupal\Core\Entity\Plugin\DataType\EntityAdapter - */ - protected $configEntityAdapter; - /** * The entity type used for testing. * @@ -242,10 +228,6 @@ protected function setUp() { $this->entity = $this->getMockForAbstractClass('\Drupal\Core\Entity\ContentEntityBase', [$values, $this->entityTypeId, $this->bundle]); $this->entityAdapter = EntityAdapter::createFromEntity($this->entity); - - $this->configEntity = $this->getMockForAbstractClass('\Drupal\Core\Config\Entity\ConfigEntityBase', [$values, $this->entityTypeId, $this->bundle]); - - $this->configEntityAdapter = EntityAdapter::createFromEntity($this->configEntity); } /** @@ -455,11 +437,6 @@ public function testGetIterator() { $this->entityAdapter->setValue(NULL); $this->assertEquals(new \ArrayIterator([]), $this->entityAdapter->getIterator()); - - // Config entity test. - $iterator = $this->configEntityAdapter->getIterator(); - $this->configEntityAdapter->setValue(NULL); - $this->assertEquals(new \ArrayIterator([]), $this->entityAdapter->getIterator()); } } -- GitLab