From 109dfd58bd175edc48eac854b21364e7ce689939 Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Fri, 12 Apr 2019 14:23:23 -0700
Subject: [PATCH] Issue #2852463 by mikelutz, tim.plunkett, heddn, xjm,
 MegaChriz, joachim, phenaproxima, dawehner, benjifisher: Create a trait to
 implement \Drupal\Component\Plugin\ConfigurableInterface

---
 .../Component/Plugin/ConfigurableTrait.php    |  61 ++++++
 .../Drupal/Component/Plugin/PluginBase.php    |  13 +-
 .../Core/Action/ConfigurableActionBase.php    |  31 +--
 core/lib/Drupal/Core/Block/BlockBase.php      |  24 +--
 .../Core/Condition/ConditionPluginBase.php    |  19 +-
 core/lib/Drupal/Core/Display/VariantBase.php  |  19 +-
 core/lib/Drupal/Core/Layout/LayoutDefault.php |  33 +--
 .../Kernel/Migrate/d6/MigrateBlockTest.php    |  12 +-
 core/modules/image/src/ImageEffectBase.php    |  11 +-
 .../Plugin/ConfigurableSearchPluginBase.php   |  34 +--
 .../workflows/src/Plugin/WorkflowTypeBase.php |  17 +-
 .../Plugin/ConfigurableTraitTest.php          | 196 ++++++++++++++++++
 .../DefaultSingleLazyPluginCollectionTest.php |  19 +-
 .../Fixtures/TestConfigurablePlugin.php       |  25 +--
 14 files changed, 301 insertions(+), 213 deletions(-)
 create mode 100644 core/lib/Drupal/Component/Plugin/ConfigurableTrait.php
 create mode 100644 core/tests/Drupal/Tests/Component/Plugin/ConfigurableTraitTest.php

diff --git a/core/lib/Drupal/Component/Plugin/ConfigurableTrait.php b/core/lib/Drupal/Component/Plugin/ConfigurableTrait.php
new file mode 100644
index 000000000000..c532f4e9f0d3
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/ConfigurableTrait.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace Drupal\Component\Plugin;
+
+use Drupal\Component\Utility\NestedArray;
+
+/**
+ * Implements \Drupal\Component\Plugin\ConfigurableInterface.
+ *
+ * In order for configurable plugins to maintain their configuration, the
+ * default configuration must be merged into any explicitly defined
+ * configuration. This trait provides the appropriate getters and setters to
+ * handle this logic, removing the need for excess boilerplate.
+ *
+ * @ingroup Plugin
+ *
+ * @todo Add protected $configuration property when PHP 5 is no longer
+ *   supported. See https://www.drupal.org/project/drupal/issues/3029004.
+ */
+trait ConfigurableTrait {
+
+  /**
+   * Gets this plugin's configuration.
+   *
+   * @return array
+   *   An array of this plugin's configuration.
+   *
+   * @see \Drupal\Component\Plugin\ConfigurableInterface::getConfiguration()
+   */
+  public function getConfiguration() {
+    return $this->configuration;
+  }
+
+  /**
+   * Sets the configuration for this plugin instance.
+   *
+   * @param array $configuration
+   *   An associative array containing the plugin's configuration.
+   *
+   * @return $this
+   *
+   * @see \Drupal\Component\Plugin\ConfigurableInterface::setConfiguration()
+   */
+  public function setConfiguration(array $configuration) {
+    $this->configuration = NestedArray::mergeDeepArray([$this->defaultConfiguration(), $configuration], TRUE);
+    return $this;
+  }
+
+  /**
+   * Gets default configuration for this plugin.
+   *
+   * @return array
+   *   An associative array with the default configuration.
+   *
+   * @see \Drupal\Component\Plugin\ConfigurableInterface::defaultConfiguration()
+   */
+  public function defaultConfiguration() {
+    return [];
+  }
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/PluginBase.php b/core/lib/Drupal/Component/Plugin/PluginBase.php
index e7e980ade48f..07c8c4b15f87 100644
--- a/core/lib/Drupal/Component/Plugin/PluginBase.php
+++ b/core/lib/Drupal/Component/Plugin/PluginBase.php
@@ -51,9 +51,20 @@ abstract class PluginBase implements PluginInspectionInterface, DerivativeInspec
    *   The plugin implementation definition.
    */
   public function __construct(array $configuration, $plugin_id, $plugin_definition) {
-    $this->configuration = $configuration;
     $this->pluginId = $plugin_id;
     $this->pluginDefinition = $plugin_definition;
+    if ($this->isConfigurable()) {
+      // @todo Some plugins use getConfiguration() and setConfiguration() to
+      //   manipulate external configuration storage but still expect
+      //   $this->configuration to be set. Deprecate this in
+      //   https://www.drupal.org/project/drupal/issues/3029077.
+      $this->configuration = $configuration;
+
+      $this->setConfiguration($configuration);
+    }
+    else {
+      $this->configuration = $configuration;
+    }
 
     if ($this instanceof ConfigurablePluginInterface && !$this instanceof ConfigurableInterface) {
       @trigger_error('Drupal\Component\Plugin\ConfigurablePluginInterface is deprecated in Drupal 8.7.0 and will be removed before Drupal 9.0.0. You should implement ConfigurableInterface and/or DependentPluginInterface directly as needed. If you implement ConfigurableInterface you may choose to implement ConfigurablePluginInterface in Drupal 8 as well for maximum compatibility, however this must be removed prior to Drupal 9. See https://www.drupal.org/node/2946161', E_USER_DEPRECATED);
diff --git a/core/lib/Drupal/Core/Action/ConfigurableActionBase.php b/core/lib/Drupal/Core/Action/ConfigurableActionBase.php
index 49c40800c28a..32d4ab71f82b 100644
--- a/core/lib/Drupal/Core/Action/ConfigurableActionBase.php
+++ b/core/lib/Drupal/Core/Action/ConfigurableActionBase.php
@@ -6,6 +6,7 @@
 use Drupal\Component\Plugin\ConfigurablePluginInterface;
 use Drupal\Component\Plugin\DependentPluginInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Component\Plugin\ConfigurableTrait;
 use Drupal\Core\Plugin\PluginFormInterface;
 
 /**
@@ -13,35 +14,7 @@
  */
 abstract class ConfigurableActionBase extends ActionBase implements ConfigurableInterface, DependentPluginInterface, ConfigurablePluginInterface, PluginFormInterface {
 
-  /**
-   * {@inheritdoc}
-   */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
-    parent::__construct($configuration, $plugin_id, $plugin_definition);
-
-    $this->setConfiguration($configuration);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function defaultConfiguration() {
-    return [];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getConfiguration() {
-    return $this->configuration;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setConfiguration(array $configuration) {
-    $this->configuration = $configuration + $this->defaultConfiguration();
-  }
+  use ConfigurableTrait;
 
   /**
    * {@inheritdoc}
diff --git a/core/lib/Drupal/Core/Block/BlockBase.php b/core/lib/Drupal/Core/Block/BlockBase.php
index f5324bfb88d1..94bae6c1eb72 100644
--- a/core/lib/Drupal/Core/Block/BlockBase.php
+++ b/core/lib/Drupal/Core/Block/BlockBase.php
@@ -5,6 +5,7 @@
 use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Messenger\MessengerTrait;
+use Drupal\Component\Plugin\ConfigurableTrait;
 use Drupal\Core\Plugin\ContextAwarePluginAssignmentTrait;
 use Drupal\Core\Plugin\ContextAwarePluginBase;
 use Drupal\Component\Utility\NestedArray;
@@ -26,6 +27,7 @@
  */
 abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginInterface, PluginWithFormsInterface, PreviewFallbackInterface {
 
+  use ConfigurableTrait;
   use ContextAwarePluginAssignmentTrait;
   use MessengerTrait;
   use PluginWithFormsTrait;
@@ -51,21 +53,6 @@ public function label() {
     return (string) $definition['admin_label'];
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
-    parent::__construct($configuration, $plugin_id, $plugin_definition);
-    $this->setConfiguration($configuration);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getConfiguration() {
-    return $this->configuration;
-  }
-
   /**
    * {@inheritdoc}
    */
@@ -92,13 +79,6 @@ protected function baseConfigurationDefaults() {
     ];
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function defaultConfiguration() {
-    return [];
-  }
-
   /**
    * {@inheritdoc}
    */
diff --git a/core/lib/Drupal/Core/Condition/ConditionPluginBase.php b/core/lib/Drupal/Core/Condition/ConditionPluginBase.php
index b5134fb51dae..b0eaa085dbb9 100644
--- a/core/lib/Drupal/Core/Condition/ConditionPluginBase.php
+++ b/core/lib/Drupal/Core/Condition/ConditionPluginBase.php
@@ -6,6 +6,7 @@
 use Drupal\Core\Executable\ExecutablePluginBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Form\SubformStateInterface;
+use Drupal\Component\Plugin\ConfigurableTrait;
 use Drupal\Core\Plugin\ContextAwarePluginAssignmentTrait;
 
 /**
@@ -19,6 +20,7 @@
  */
 abstract class ConditionPluginBase extends ExecutablePluginBase implements ConditionInterface {
 
+  use ConfigurableTrait;
   use ContextAwarePluginAssignmentTrait;
 
   /**
@@ -28,15 +30,6 @@ abstract class ConditionPluginBase extends ExecutablePluginBase implements Condi
    */
   protected $executableManager;
 
-  /**
-   * {@inheritdoc}
-   */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
-    parent::__construct($configuration, $plugin_id, $plugin_definition);
-
-    $this->setConfiguration($configuration);
-  }
-
   /**
    * {@inheritdoc}
    */
@@ -93,14 +86,6 @@ public function getConfiguration() {
     ] + $this->configuration;
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function setConfiguration(array $configuration) {
-    $this->configuration = $configuration + $this->defaultConfiguration();
-    return $this;
-  }
-
   /**
    * {@inheritdoc}
    */
diff --git a/core/lib/Drupal/Core/Display/VariantBase.php b/core/lib/Drupal/Core/Display/VariantBase.php
index 4f7ce9e9ffc4..2cc8c485f4fa 100644
--- a/core/lib/Drupal/Core/Display/VariantBase.php
+++ b/core/lib/Drupal/Core/Display/VariantBase.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Core\Display;
 
+use Drupal\Component\Plugin\ConfigurableTrait;
 use Drupal\Core\Cache\RefinableCacheableDependencyTrait;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Plugin\PluginBase;
@@ -18,18 +19,10 @@
  */
 abstract class VariantBase extends PluginBase implements VariantInterface {
 
+  use ConfigurableTrait;
   use PluginDependencyTrait;
   use RefinableCacheableDependencyTrait;
 
-  /**
-   * {@inheritdoc}
-   */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
-    parent::__construct($configuration, $plugin_id, $plugin_definition);
-
-    $this->setConfiguration($configuration);
-  }
-
   /**
    * {@inheritdoc}
    */
@@ -74,14 +67,6 @@ public function getConfiguration() {
     ] + $this->configuration;
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function setConfiguration(array $configuration) {
-    $this->configuration = $configuration + $this->defaultConfiguration();
-    return $this;
-  }
-
   /**
    * {@inheritdoc}
    */
diff --git a/core/lib/Drupal/Core/Layout/LayoutDefault.php b/core/lib/Drupal/Core/Layout/LayoutDefault.php
index 6e53d00f444b..de252cc81b99 100644
--- a/core/lib/Drupal/Core/Layout/LayoutDefault.php
+++ b/core/lib/Drupal/Core/Layout/LayoutDefault.php
@@ -2,7 +2,7 @@
 
 namespace Drupal\Core\Layout;
 
-use Drupal\Component\Utility\NestedArray;
+use Drupal\Component\Plugin\ConfigurableTrait;
 use Drupal\Core\Plugin\PluginBase;
 
 /**
@@ -10,6 +10,8 @@
  */
 class LayoutDefault extends PluginBase implements LayoutInterface {
 
+  use ConfigurableTrait;
+
   /**
    * The layout definition.
    *
@@ -17,14 +19,6 @@ class LayoutDefault extends PluginBase implements LayoutInterface {
    */
   protected $pluginDefinition;
 
-  /**
-   * {@inheritdoc}
-   */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
-    parent::__construct($configuration, $plugin_id, $plugin_definition);
-    $this->setConfiguration($configuration);
-  }
-
   /**
    * {@inheritdoc}
    */
@@ -45,27 +39,6 @@ public function build(array $regions) {
     return $build;
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function getConfiguration() {
-    return $this->configuration;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setConfiguration(array $configuration) {
-    $this->configuration = NestedArray::mergeDeep($this->defaultConfiguration(), $configuration);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function defaultConfiguration() {
-    return [];
-  }
-
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/block/tests/src/Kernel/Migrate/d6/MigrateBlockTest.php b/core/modules/block/tests/src/Kernel/Migrate/d6/MigrateBlockTest.php
index 6ea998b0c9ea..ec41180b27af 100644
--- a/core/modules/block/tests/src/Kernel/Migrate/d6/MigrateBlockTest.php
+++ b/core/modules/block/tests/src/Kernel/Migrate/d6/MigrateBlockTest.php
@@ -101,8 +101,8 @@ public function testBlockMigration() {
     $visibility = [
       'request_path' => [
         'id' => 'request_path',
-        'negate' => TRUE,
         'pages' => "<front>\n/node/1\n/blog/*",
+        'negate' => TRUE,
       ],
     ];
     $settings = [
@@ -131,10 +131,10 @@ public function testBlockMigration() {
         'roles' => [
           'authenticated' => 'authenticated',
         ],
+        'negate' => FALSE,
         'context_mapping' => [
           'user' => '@user.current_user_context:current_user',
         ],
-        'negate' => FALSE,
       ],
     ];
     $settings = [
@@ -152,10 +152,10 @@ public function testBlockMigration() {
         'roles' => [
           'migrate_test_role_1' => 'migrate_test_role_1',
         ],
+        'negate' => FALSE,
         'context_mapping' => [
           'user' => '@user.current_user_context:current_user',
         ],
-        'negate' => FALSE,
       ],
     ];
     $settings = [
@@ -171,8 +171,8 @@ public function testBlockMigration() {
     $visibility = [
       'request_path' => [
         'id' => 'request_path',
-        'negate' => TRUE,
         'pages' => '/node/1',
+        'negate' => TRUE,
       ],
     ];
     $settings = [
@@ -254,8 +254,8 @@ public function testBlockMigration() {
     $visibility = [
       'request_path' => [
         'id' => 'request_path',
-        'negate' => FALSE,
         'pages' => '<front>',
+        'negate' => FALSE,
       ],
     ];
     $settings = [
@@ -272,8 +272,8 @@ public function testBlockMigration() {
     $visibility = [
       'request_path' => [
         'id' => 'request_path',
-        'negate' => FALSE,
         'pages' => '/node',
+        'negate' => FALSE,
       ],
     ];
     $settings = [
diff --git a/core/modules/image/src/ImageEffectBase.php b/core/modules/image/src/ImageEffectBase.php
index 58be370c1e6e..d0de4d674ac1 100644
--- a/core/modules/image/src/ImageEffectBase.php
+++ b/core/modules/image/src/ImageEffectBase.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\image;
 
+use Drupal\Component\Plugin\ConfigurableTrait;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\Core\Plugin\PluginBase;
 use Psr\Log\LoggerInterface;
@@ -19,6 +20,8 @@
  */
 abstract class ImageEffectBase extends PluginBase implements ImageEffectInterface, ContainerFactoryPluginInterface {
 
+  use ConfigurableTrait;
+
   /**
    * The image effect ID.
    *
@@ -46,7 +49,6 @@ abstract class ImageEffectBase extends PluginBase implements ImageEffectInterfac
   public function __construct(array $configuration, $plugin_id, $plugin_definition, LoggerInterface $logger) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
 
-    $this->setConfiguration($configuration);
     $this->logger = $logger;
   }
 
@@ -151,13 +153,6 @@ public function setConfiguration(array $configuration) {
     return $this;
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function defaultConfiguration() {
-    return [];
-  }
-
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/search/src/Plugin/ConfigurableSearchPluginBase.php b/core/modules/search/src/Plugin/ConfigurableSearchPluginBase.php
index 7ad95f16823b..a1af17653015 100644
--- a/core/modules/search/src/Plugin/ConfigurableSearchPluginBase.php
+++ b/core/modules/search/src/Plugin/ConfigurableSearchPluginBase.php
@@ -2,7 +2,7 @@
 
 namespace Drupal\search\Plugin;
 
-use Drupal\Component\Utility\NestedArray;
+use Drupal\Component\Plugin\ConfigurableTrait;
 use Drupal\Core\Form\FormStateInterface;
 
 /**
@@ -10,6 +10,8 @@
  */
 abstract class ConfigurableSearchPluginBase extends SearchPluginBase implements ConfigurableSearchPluginInterface {
 
+  use ConfigurableTrait;
+
   /**
    * The unique ID for the search page using this plugin.
    *
@@ -17,36 +19,6 @@ abstract class ConfigurableSearchPluginBase extends SearchPluginBase implements
    */
   protected $searchPageId;
 
-  /**
-   * {@inheritdoc}
-   */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
-    parent::__construct($configuration, $plugin_id, $plugin_definition);
-
-    $this->setConfiguration($configuration);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function defaultConfiguration() {
-    return [];
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getConfiguration() {
-    return $this->configuration;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setConfiguration(array $configuration) {
-    $this->configuration = NestedArray::mergeDeep($this->defaultConfiguration(), $configuration);
-  }
-
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/workflows/src/Plugin/WorkflowTypeBase.php b/core/modules/workflows/src/Plugin/WorkflowTypeBase.php
index 959df2ebb5c3..2f0ba1ffb0eb 100644
--- a/core/modules/workflows/src/Plugin/WorkflowTypeBase.php
+++ b/core/modules/workflows/src/Plugin/WorkflowTypeBase.php
@@ -3,6 +3,7 @@
 namespace Drupal\workflows\Plugin;
 
 use Drupal\Component\Plugin\PluginBase;
+use Drupal\Component\Plugin\ConfigurableTrait;
 use Drupal\Core\Plugin\PluginWithFormsTrait;
 use Drupal\workflows\State;
 use Drupal\workflows\StateInterface;
@@ -18,6 +19,7 @@
  */
 abstract class WorkflowTypeBase extends PluginBase implements WorkflowTypeInterface {
 
+  use ConfigurableTrait;
   use PluginWithFormsTrait;
 
   /**
@@ -25,14 +27,6 @@ abstract class WorkflowTypeBase extends PluginBase implements WorkflowTypeInterf
    */
   const VALID_ID_REGEX = '/[^a-z0-9_]+/';
 
-  /**
-   * {@inheritdoc}
-   */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
-    parent::__construct($configuration, $plugin_id, $plugin_definition);
-    $this->setConfiguration($configuration);
-  }
-
   /**
    * {@inheritdoc}
    */
@@ -57,13 +51,6 @@ public function workflowStateHasData(WorkflowInterface $workflow, StateInterface
     return FALSE;
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function getConfiguration() {
-    return $this->configuration;
-  }
-
   /**
    * {@inheritdoc}
    */
diff --git a/core/tests/Drupal/Tests/Component/Plugin/ConfigurableTraitTest.php b/core/tests/Drupal/Tests/Component/Plugin/ConfigurableTraitTest.php
new file mode 100644
index 000000000000..7aaed8538270
--- /dev/null
+++ b/core/tests/Drupal/Tests/Component/Plugin/ConfigurableTraitTest.php
@@ -0,0 +1,196 @@
+<?php
+
+namespace Drupal\Tests\Component\Plugin;
+
+use Drupal\Component\Plugin\ConfigurableInterface;
+use Drupal\Component\Plugin\ConfigurableTrait;
+use Drupal\Component\Plugin\PluginBase;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Tests for ConfigurableTrait.
+ *
+ * @coversDefaultClass \Drupal\Component\Plugin\ConfigurableTrait
+ *
+ * @group Plugin
+ */
+class ConfigurableTraitTest extends TestCase {
+
+  /**
+   * Tests ConfigurableTrait::defaultConfiguration.
+   *
+   * @covers ::defaultConfiguration
+   */
+  public function testDefaultConfiguration() {
+    /** @var \Drupal\Component\Plugin\ConfigurableInterface $configurable_plugin */
+    $configurable_plugin = $this->getMockForTrait(ConfigurableTrait::class);
+    $this->assertSame([], $configurable_plugin->defaultConfiguration());
+  }
+
+  /**
+   * Tests ConfigurableTrait::getConfiguration.
+   *
+   * @covers ::getConfiguration
+   */
+  public function testGetConfiguration() {
+    $test_configuration = [
+      'config_key_1' => 'config_value_1',
+      'config_key_2' => [
+        'nested_key_1' => 'nested_value_1',
+        'nested_key_2' => 'nested_value_2',
+      ],
+    ];
+    $configurable_plugin = new ConfigurableTestClass($test_configuration);
+    $this->assertSame($test_configuration, $configurable_plugin->getConfiguration());
+  }
+
+  /**
+   * Tests configurableTrait::setConfiguration.
+   *
+   * Specifically test the way default and provided configurations are merged.
+   *
+   * @param array $default_configuration
+   *   The default configuration to use for the trait.
+   * @param array $test_configuration
+   *   The configuration to test.
+   * @param array $final_configuration
+   *   The expected final plugin configuration.
+   *
+   * @covers ::setConfiguration
+   *
+   * @dataProvider setConfigurationDataProvider
+   */
+  public function testSetConfiguration(array $default_configuration, array $test_configuration, array $final_configuration) {
+    $test_object = new ConfigurableTestClass($default_configuration);
+    $test_object->setConfiguration($test_configuration);
+    $this->assertSame($final_configuration, $test_object->getConfiguration());
+  }
+
+  /**
+   * Provides data for testSetConfiguration.
+   *
+   * @return array
+   *   The data.
+   */
+  public function setConfigurationDataProvider() {
+    return [
+      'Direct Override' => [
+        'default_configuration' => [
+          'default_key_1' => 'default_value_1',
+          'default_key_2' => [
+            'default_nested_key_1' => 'default_nested_value_1',
+            'default_nested_key_2' => 'default_nested_value_2',
+          ],
+        ],
+        'test_configuration' => [
+          'default_key_1' => 'override_value_1',
+          'default_key_2' => [
+            'default_nested_key_1' => 'override_nested_value_1',
+            'default_nested_key_2' => 'override_nested_value_2',
+          ],
+        ],
+        'final_configuration' => [
+          'default_key_1' => 'override_value_1',
+          'default_key_2' => [
+            'default_nested_key_1' => 'override_nested_value_1',
+            'default_nested_key_2' => 'override_nested_value_2',
+          ],
+        ],
+      ],
+      'Mixed Override' => [
+        'default_configuration' => [
+          'default_key_1' => 'default_value_1',
+          'default_key_2' => [
+            'default_nested_key_1' => 'default_nested_value_1',
+            'default_nested_key_2' => 'default_nested_value_2',
+          ],
+        ],
+        'test_configuration' => [
+          'override_key_1' => 'config_value_1',
+          'default_key_2' => [
+            'default_nested_key_1' => 'override_value_1',
+            'override_nested_key' => 'override_value',
+          ],
+        ],
+        'final_configuration' => [
+          'default_key_1' => 'default_value_1',
+          'default_key_2' => [
+            'default_nested_key_1' => 'override_value_1',
+            'default_nested_key_2' => 'default_nested_value_2',
+            'override_nested_key' => 'override_value',
+          ],
+          'override_key_1' => 'config_value_1',
+        ],
+      ],
+      'indexed_override' => [
+        'default_configuration' => [
+          'config_value_1',
+          'config_value_2',
+          'config_value_3',
+        ],
+        'test_configuration' => [
+          'override_value_1',
+          'override_value_2',
+        ],
+        'final_configuration' => [
+          'override_value_1',
+          'override_value_2',
+          'config_value_3',
+        ],
+      ],
+      'indexed_override_complex' => [
+        'default_configuration' => [
+          'config_value_1',
+          'config_value_2',
+          'config_value_3',
+        ],
+        'test_configuration' => [
+          0 => 'override_value_1',
+          2 => 'override_value_3',
+        ],
+        'final_configuration' => [
+          'override_value_1',
+          'config_value_2',
+          'override_value_3',
+        ],
+      ],
+    ];
+  }
+
+}
+
+/**
+ * A test class using ConfigurablePluginTrait.
+ */
+class ConfigurableTestClass extends PluginBase implements ConfigurableInterface {
+  use ConfigurableTrait;
+
+  /**
+   * A default configuration for the test class to return.
+   *
+   * @var array
+   */
+  protected $defaultConfiguration;
+
+  /**
+   * Constructs a ConfigurablePluginTestClass object.
+   *
+   * @param array $default_configuration
+   *   The default configuration to return.
+   */
+  public function __construct(array $default_configuration) {
+    $this->defaultConfiguration = $default_configuration;
+    parent::__construct([], '', []);
+  }
+
+  /**
+   * Returns the provided test defaults.
+   *
+   * @return array
+   *   The default configuration.
+   */
+  public function defaultConfiguration() {
+    return $this->defaultConfiguration;
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Plugin/DefaultSingleLazyPluginCollectionTest.php b/core/tests/Drupal/Tests/Core/Plugin/DefaultSingleLazyPluginCollectionTest.php
index cd8b384614b5..ae7015ccfa5b 100644
--- a/core/tests/Drupal/Tests/Core/Plugin/DefaultSingleLazyPluginCollectionTest.php
+++ b/core/tests/Drupal/Tests/Core/Plugin/DefaultSingleLazyPluginCollectionTest.php
@@ -4,6 +4,7 @@
 
 use Drupal\Component\Plugin\ConfigurableInterface;
 use Drupal\Component\Plugin\PluginBase;
+use Drupal\Component\Plugin\ConfigurableTrait;
 use Drupal\Core\Plugin\DefaultSingleLazyPluginCollection;
 
 /**
@@ -73,22 +74,6 @@ public function testGetInstanceIds() {
 
 class ConfigurablePlugin extends PluginBase implements ConfigurableInterface {
 
-  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
-    parent::__construct($configuration, $plugin_id, $plugin_definition);
-
-    $this->configuration = $configuration + $this->defaultConfiguration();
-  }
-
-  public function defaultConfiguration() {
-    return [];
-  }
-
-  public function getConfiguration() {
-    return $this->configuration;
-  }
-
-  public function setConfiguration(array $configuration) {
-    $this->configuration = $configuration;
-  }
+  use ConfigurableTrait;
 
 }
diff --git a/core/tests/Drupal/Tests/Core/Plugin/Fixtures/TestConfigurablePlugin.php b/core/tests/Drupal/Tests/Core/Plugin/Fixtures/TestConfigurablePlugin.php
index e31fba7b59df..b52dcee17f12 100644
--- a/core/tests/Drupal/Tests/Core/Plugin/Fixtures/TestConfigurablePlugin.php
+++ b/core/tests/Drupal/Tests/Core/Plugin/Fixtures/TestConfigurablePlugin.php
@@ -5,29 +5,14 @@
 use Drupal\Component\Plugin\ConfigurableInterface;
 use Drupal\Component\Plugin\DependentPluginInterface;
 use Drupal\Component\Plugin\PluginBase;
+use Drupal\Component\Plugin\ConfigurableTrait;
 
+/**
+ * A fixture to test Configurable Plugins.
+ */
 class TestConfigurablePlugin extends PluginBase implements ConfigurableInterface, DependentPluginInterface {
 
-  /**
-   * {@inheritdoc}
-   */
-  public function getConfiguration() {
-    return $this->configuration;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setConfiguration(array $configuration) {
-    $this->configuration = $configuration;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function defaultConfiguration() {
-    return [];
-  }
+  use ConfigurableTrait;
 
   /**
    * {@inheritdoc}
-- 
GitLab