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