diff --git a/core/lib/Drupal/Component/Plugin/ContextAwarePluginBase.php b/core/lib/Drupal/Component/Plugin/ContextAwarePluginBase.php
index b684630..a7bb9f9 100644
--- a/core/lib/Drupal/Component/Plugin/ContextAwarePluginBase.php
+++ b/core/lib/Drupal/Component/Plugin/ContextAwarePluginBase.php
@@ -58,19 +58,21 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition
    * {@inheritdoc}
    */
   public function getContextDefinitions() {
+    /** @var \Drupal\Component\Plugin\Definition\PluginContextDefinitionInterface $definition */
     $definition = $this->getPluginDefinition();
-    return !empty($definition['context']) ? $definition['context'] : array();
+    return $definition->getContextDefinitions();
   }
 
   /**
    * {@inheritdoc}
    */
   public function getContextDefinition($name) {
+    /** @var \Drupal\Component\Plugin\Definition\PluginContextDefinitionInterface $definition */
     $definition = $this->getPluginDefinition();
-    if (empty($definition['context'][$name])) {
+    if (!$definition->hasContextDefinition($name)) {
       throw new ContextException(sprintf("The %s context is not a valid context.", $name));
     }
-    return $definition['context'][$name];
+    return $definition->getContextDefinition($name);
   }
 
   /**
diff --git a/core/lib/Drupal/Component/Plugin/Definition/ArrayPluginDefinitionDecorator.php b/core/lib/Drupal/Component/Plugin/Definition/ArrayPluginDefinitionDecorator.php
new file mode 100644
index 0000000..6390160
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Definition/ArrayPluginDefinitionDecorator.php
@@ -0,0 +1,276 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Plugin\ArrayPluginDefinitionDecorator.
+ */
+
+namespace Drupal\Component\Plugin\Definition;
+
+use Drupal\Component\Plugin\Context\ContextDefinitionInterface;
+
+/**
+ * Decorates an array plugin definition.
+ *
+ * This class wraps array definitions in an objects for use in APIs that expect
+ * object definitions only. The arrays are decorated by reference.
+ *
+ * Definitions for new plugin types should be objects by default. This class
+ * exists for backwards compatibility with definitions of plugin types that were
+ * introduced before object support existed.
+ *
+ * @ingroup Plugin
+ */
+class ArrayPluginDefinitionDecorator implements PluginDefinitionInterface, PluginLabelDefinitionInterface, PluginDeriverDefinitionInterface, PluginContextDefinitionInterface {
+
+  /**
+   * The decorated array definition.
+   *
+   * @var mixed[]
+   */
+  protected $arrayDefinition = [];
+
+  /**
+   * Constructs a new instance.
+   *
+   * @param array $array_definition
+   *   The decorated array definition.
+   */
+  public function __construct(array $array_definition) {
+    if (isset($array_definition['class'])) {
+      $this->validateClass($array_definition['class']);
+    }
+    if (isset($array_definition['deriver'])) {
+      $this->validateDeriverClass($array_definition['deriver']);
+    }
+    if (isset($array_definition['context'])) {
+      $this->validateContextDefinitions($array_definition['context']);
+    }
+    $this->arrayDefinition = $array_definition;
+  }
+
+  /**
+   * Gets the array definition.
+   *
+   * @return mixed[]
+   */
+  public function getArrayDefinition() {
+    return $this->arrayDefinition;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setId($id) {
+    $this->arrayDefinition['id'] = $id;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getId() {
+    return isset($this->arrayDefinition['id']) ? $this->arrayDefinition['id'] : '';
+  }
+
+  /**
+   * Validates the class.
+   *
+   * @param string $class
+   *   A fully qualified class name.
+   *
+   * @throws \InvalidArgumentException
+   *   Thrown when the class is invalid.
+   */
+  protected function validateClass($class) {
+    if (!class_exists($class)) {
+      throw new \InvalidArgumentException(sprintf('Class %s does not exist.', $class));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setClass($class) {
+    $this->validateClass($class);
+    $this->arrayDefinition['class'] = $class;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getClass() {
+    return isset($this->arrayDefinition['class']) ? $this->arrayDefinition['class'] : '';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setProvider($provider) {
+    $this->arrayDefinition['provider'] = $provider;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getProvider() {
+    return isset($this->arrayDefinition['provider']) ? $this->arrayDefinition['provider'] : '';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setLabel($label) {
+    $this->arrayDefinition['label'] = $label;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLabel() {
+    return isset($this->arrayDefinition['label']) ? $this->arrayDefinition['label'] : '';
+  }
+
+  /**
+   * Validates the deriver class.
+   *
+   * @param string $class
+   *   A fully qualified class name.
+   *
+   * @throws \InvalidArgumentException
+   *   Thrown when the class is invalid.
+   */
+  protected function validateDeriverClass($class) {
+    if (!class_exists($class)) {
+      throw new \InvalidArgumentException(sprintf('Class %s does not exist.', $class));
+    }
+    elseif (!is_subclass_of($class, 'Drupal\Component\Plugin\Derivative\DeriverInterface')) {
+      throw new \InvalidArgumentException('Plugin deriver classes must implement \Drupal\Component\Plugin\Derivative\DeriverInterface.');
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setDeriverClass($class) {
+    $this->validateDeriverClass($class);
+    $this->arrayDefinition['deriver'] = $class;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDeriverClass() {
+    return isset($this->arrayDefinition['deriver']) ? $this->arrayDefinition['deriver'] : '';
+  }
+
+  /**
+   * Validates the context definitions.
+   *
+   * @param \Drupal\Component\Plugin\Context\ContextDefinitionInterface[] $context_definitions
+   *   The array of context definitions, keyed by context name.
+   *
+   * @throws \InvalidArgumentException
+   *   Thrown when the class is invalid.
+   */
+  protected function validateContextDefinitions(array $context_definitions) {
+    foreach ($context_definitions as $name => $context_definition) {
+      if (!($context_definition instanceof ContextDefinitionInterface)) {
+        throw new \InvalidArgumentException(sprintf('$context_definition[%s] does not implement \Drupal\Component\Plugin\Context\ContextDefinitionInterface', $name));
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setContextDefinitions(array $context_definitions) {
+    $this->validateContextDefinitions($context_definitions);
+    $this->arrayDefinition['context'] = $context_definitions;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContextDefinitions() {
+    return isset($this->arrayDefinition['context']) ? $this->arrayDefinition['context'] : [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setContextDefinition($name, ContextDefinitionInterface $context_definition) {
+    $this->arrayDefinition['context'][$name] = $context_definition;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getContextDefinition($name) {
+    if (isset($this->arrayDefinition['context'][$name])) {
+      return $this->arrayDefinition['context'][$name];
+    }
+    else {
+      throw new \InvalidArgumentException(sprintf('Context %s does not exist.', $name));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasContextDefinition($name) {
+    return isset($this->arrayDefinition['context'][$name]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function mergeDefinition(PluginDefinitionInterface $other_definition) {
+    if (!($other_definition instanceof $this)) {
+      throw new \InvalidArgumentException(sprintf('$other_definition must be an instance of %s, but %s was given.', get_class($this), get_class($other_definition)));
+    }
+
+    $this->arrayDefinition = array_merge($other_definition->arrayDefinition, $this->arrayDefinition);
+
+    return $this;
+  }
+
+  /**
+   * Ensures a plugin definition implements \Drupal\Component\Plugin\Definition\PluginDefinitionInterface.
+   *
+   * @param \Drupal\Component\Plugin\Definition\PluginDefinitionInterface|mixed[] $definition
+   *   Either an array plugin definition, or a definition that is an object already.
+   *
+   * @return \Drupal\Component\Plugin\Definition\PluginDefinitionInterface
+   *   The ensured object definition.
+   *
+   * @throws \InvalidArgumentException
+   *   Thrown when the definition is of the wrong type.
+   */
+  public static function ensureObjectDefinition($definition) {
+    if ($definition instanceof PluginDefinitionInterface) {
+      return $definition;
+    }
+    elseif (is_array($definition)) {
+      return new static($definition);
+    }
+    else {
+      $type = is_object($definition) ? get_class($definition) : gettype($definition);
+      throw new \InvalidArgumentException(sprintf('$definition must be an array or an instance of a class that implements \Drupal\Component\Plugin\Definition\PluginDefinitionInterface, but %s was given.', $type));
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/Definition/PluginContextDefinitionInterface.php b/core/lib/Drupal/Component/Plugin/Definition/PluginContextDefinitionInterface.php
new file mode 100644
index 0000000..0248d8b
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Definition/PluginContextDefinitionInterface.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Plugin\Definition\PluginContextDefinitionInterface.
+ */
+
+namespace Drupal\Component\Plugin\Definition;
+
+use Drupal\Component\Plugin\Context\ContextDefinitionInterface;
+
+/**
+ * Defines a plugin definition that includes contexts.
+ *
+ * @ingroup Plugin
+ */
+interface PluginContextDefinitionInterface extends PluginDefinitionInterface {
+
+  /**
+   * Sets the context definitions.
+   *
+   * @param \Drupal\Component\Plugin\Context\ContextDefinitionInterface[] $context_definitions
+   *   The array of context definitions, keyed by context name.
+   *
+   * @return $this
+   *
+   * @throws \InvalidArgumentException
+   *   Thrown if the definitions are invalid.
+   */
+  public function setContextDefinitions(array $context_definitions);
+
+  /**
+   * Gets the context definitions.
+   *
+   * @return \Drupal\Component\Plugin\Context\ContextDefinitionInterface[]
+   *   The array of context definitions, keyed by context name.
+   */
+  public function getContextDefinitions();
+
+  /**
+   * Sets a specific context definition.
+   *
+   * @param string $name
+   *   The name of the context in the plugin definition.
+   * @param \Drupal\Component\Plugin\Context\ContextDefinitionInterface $context_definition
+   *   The context definition to set.
+   *
+   * @return $this
+   */
+  public function setContextDefinition($name, ContextDefinitionInterface $context_definition);
+
+  /**
+   * Gets a specific context definition.
+   *
+   * @param string $name
+   *   The name of the context in the plugin definition.
+   *
+   * @throws \InvalidArgumentException
+   *   If the requested context does not exist.
+   *
+   * @return \Drupal\Component\Plugin\Context\ContextDefinitionInterface
+   *
+   * @see self::hasContextDefinition()
+   */
+  public function getContextDefinition($name);
+
+  /**
+   * Checks if a specific context definition exists.
+   *
+   * @param string $name
+   *   The name of the context in the plugin definition.
+   *
+   * @return bool
+   *   Whether the context definition exists.
+   */
+  public function hasContextDefinition($name);
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/Definition/PluginDefinition.php b/core/lib/Drupal/Component/Plugin/Definition/PluginDefinition.php
new file mode 100644
index 0000000..3b79a6d
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Definition/PluginDefinition.php
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Plugin\PluginDefinition.
+ */
+
+namespace Drupal\Component\Plugin\Definition;
+
+/**
+ * Provides a plugin definition.
+ *
+ * @ingroup Plugin
+ */
+class PluginDefinition implements PluginDefinitionInterface {
+
+  /**
+   * The plugin ID.
+   *
+   * @var string
+   */
+  protected $id;
+
+  /**
+   * The class.
+   *
+   * @var string
+   *   A class that implements
+   *   \Drupal\Component\Plugin\PluginInspectionInterface.
+   */
+  protected $class;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setId($id) {
+    $this->id = $id;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getId() {
+    return $this->id;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setClass($class) {
+    if (!class_exists($class)) {
+      throw new \InvalidArgumentException(sprintf('Class %s does not exist.', $class));
+    }
+
+    $this->class = $class;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getClass() {
+    return $this->class;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function mergeDefinition(PluginDefinitionInterface $other_definition) {
+    if (!($other_definition instanceof $this)) {
+      throw new \InvalidArgumentException(sprintf('$other_definition must be an instance of %s, but %s was given.', get_class($this), get_class($other_definition)));
+    }
+
+    return $this;
+  }
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/Definition/PluginDefinitionInterface.php b/core/lib/Drupal/Component/Plugin/Definition/PluginDefinitionInterface.php
new file mode 100644
index 0000000..01cf402
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Definition/PluginDefinitionInterface.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Plugin\Definition\PluginDefinitionInterface.
+ */
+
+namespace Drupal\Component\Plugin\Definition;
+
+/**
+ * Defines a plugin definition.
+ *
+ * @ingroup Plugin
+ */
+interface PluginDefinitionInterface {
+
+  /**
+   * Sets the plugin ID.
+   *
+   * @param string $id
+   *   The plugin ID.
+   *
+   * @return $this
+   */
+  public function setId($id);
+
+  /**
+   * Gets the plugin ID.
+   *
+   * @return string
+   *   The plugin ID.
+   */
+  public function getId();
+
+  /**
+   * Sets the class.
+   *
+   * @param string $class
+   *   A fully qualified class name.
+   *
+   * @return $this
+   *
+   * @throws \InvalidArgumentException
+   */
+  public function setClass($class);
+
+  /**
+   * Gets the class.
+   *
+   * @return string
+   *   A fully qualified class name.
+   */
+  public function getClass();
+
+  /**
+   * Merges another definition into this one.
+   *
+   * @param static $other_definition
+   *   The other definition to merge into $this.
+   *
+   * @return $this
+   *
+   * @throws \InvalidArgumentException
+   *   Thrown if $other_definition is not of the exact same type as $this.
+   */
+  public function mergeDefinition(PluginDefinitionInterface $other_definition);
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/Definition/PluginDeriverDefinitionInterface.php b/core/lib/Drupal/Component/Plugin/Definition/PluginDeriverDefinitionInterface.php
new file mode 100644
index 0000000..3d0d69c
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Definition/PluginDeriverDefinitionInterface.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Plugin\Definition\PluginDeriverDefinitionInterface.
+ */
+
+namespace Drupal\Component\Plugin\Definition;
+
+/**
+ * Defines a plugin definition that includes a deriver.
+ *
+ * @ingroup Plugin
+ */
+interface PluginDeriverDefinitionInterface extends PluginDefinitionInterface {
+
+  /**
+   * Sets the deriver class.
+   *
+   * @param string $class
+   *   The fully qualified name of a class that implements
+   *   \Drupal\Component\Plugin\Derivative\DeriverInterface.
+   *
+   * @return $this
+   *
+   * @throws \InvalidArgumentException
+   */
+  public function setDeriverClass($class);
+
+  /**
+   * Gets the deriver class.
+   *
+   * @return string|null
+   *   The fully qualified name of a class that implements
+   *   \Drupal\Component\Plugin\Derivative\DeriverInterface, or null if the
+   *   definition does not have a deriver.
+   */
+  public function getDeriverClass();
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/Definition/PluginDeriverDefinitionTrait.php b/core/lib/Drupal/Component/Plugin/Definition/PluginDeriverDefinitionTrait.php
new file mode 100644
index 0000000..25c133d
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Definition/PluginDeriverDefinitionTrait.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Plugin\PluginDeriverDefinitionTrait.
+ */
+
+namespace Drupal\Component\Plugin\Definition;
+
+/**
+ * Implements \Drupal\Component\Plugin\Definition\PluginDeriverDefinitionInterface.
+ *
+ * @ingroup Plugin
+ */
+trait PluginDeriverDefinitionTrait {
+
+  /**
+   * The deriver class.
+   *
+   * @var string|null
+   *   A class that implements
+   *   \Drupal\Component\Plugin\Derivative\DeriverInterface or null.
+   */
+  protected $deriverClass;
+
+  /**
+   * Implements \Drupal\Component\Plugin\Definition\PluginDeriverDefinitionInterface::setDeriverClass().
+   */
+  public function setDeriverClass($class) {
+    if (!class_exists($class)) {
+      throw new \InvalidArgumentException(sprintf('Class %s does not exist.', $class));
+    }
+    if (!is_subclass_of($class, 'Drupal\Component\Plugin\Derivative\DeriverInterface')) {
+      throw new \InvalidArgumentException('Plugin deriver classes must implement \Drupal\Component\Plugin\Derivative\DeriverInterface.');
+    }
+
+    $this->deriverClass = $class;
+
+    return $this;
+  }
+
+  /**
+   * Implements \Drupal\Component\Plugin\Definition\PluginDeriverDefinitionInterface::getDeriverClass().
+   */
+  public function getDeriverClass() {
+    return $this->deriverClass;
+  }
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/Definition/PluginLabelDefinitionInterface.php b/core/lib/Drupal/Component/Plugin/Definition/PluginLabelDefinitionInterface.php
new file mode 100644
index 0000000..6440bf7
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Definition/PluginLabelDefinitionInterface.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Plugin\PluginLabelDefinitionInterface .
+ */
+
+namespace Drupal\Component\Plugin\Definition;
+
+/**
+ * Defines a plugin definition that includes a human-readable label.
+ *
+ * @ingroup Plugin
+ */
+interface PluginLabelDefinitionInterface extends PluginDefinitionInterface {
+
+  /**
+   * Sets the human-readable plugin label.
+   *
+   * @param string $label
+   *   The label.
+   *
+   * @return $this
+   */
+  public function setLabel($label);
+
+  /**
+   * Gets the human-readable plugin label.
+   *
+   * @return string
+   *   The label.
+   */
+  public function getLabel();
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/Definition/PluginLabelDefinitionTrait.php b/core/lib/Drupal/Component/Plugin/Definition/PluginLabelDefinitionTrait.php
new file mode 100644
index 0000000..1fed30a
--- /dev/null
+++ b/core/lib/Drupal/Component/Plugin/Definition/PluginLabelDefinitionTrait.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Plugin\PluginLabelDefinitionTrait.
+ */
+
+namespace Drupal\Component\Plugin\Definition;
+
+/**
+ * Implements \Drupal\Component\Plugin\Definition\PluginLabelDefinitionInterface.
+ *
+ * @ingroup Plugin
+ */
+trait PluginLabelDefinitionTrait {
+
+  /**
+   * The human-readable plugin label.
+   *
+   * @var string
+   */
+  protected $label;
+
+  /**
+   * Implements \Drupal\Component\Plugin\Definition\PluginLabelDefinitionInterface::setLabel().
+   */
+  public function setLabel($label) {
+    $this->label = $label;
+
+    return $this;
+  }
+
+  /**
+   * Implements \Drupal\Component\Plugin\Definition\PluginLabelDefinitionInterface::getLabel().
+   */
+  public function getLabel() {
+    return $this->label;
+  }
+
+}
diff --git a/core/lib/Drupal/Component/Plugin/Discovery/DerivativeDiscoveryDecorator.php b/core/lib/Drupal/Component/Plugin/Discovery/DerivativeDiscoveryDecorator.php
index 04e360f..094e903 100644
--- a/core/lib/Drupal/Component/Plugin/Discovery/DerivativeDiscoveryDecorator.php
+++ b/core/lib/Drupal/Component/Plugin/Discovery/DerivativeDiscoveryDecorator.php
@@ -7,7 +7,8 @@
 
 namespace Drupal\Component\Plugin\Discovery;
 
-use Drupal\Component\Plugin\Exception\InvalidDeriverException;
+use Drupal\Component\Plugin\Definition\ArrayPluginDefinitionDecorator;
+use Drupal\Component\Plugin\Definition\PluginDeriverDefinitionInterface;
 
 /**
  * Base class providing the tools for a plugin discovery to be derivative aware.
@@ -68,7 +69,7 @@ public function getDefinition($plugin_id, $exception_on_invalid = TRUE) {
         // If a plugin defined itself as a derivative, merge in possible
         // defaults from the derivative.
         if ($derivative_id && isset($plugin_definition)) {
-          $plugin_definition = $this->mergeDerivativeDefinition($plugin_definition, $derivative_plugin_definition);
+          ArrayPluginDefinitionDecorator::ensureObjectDefinition($derivative_plugin_definition)->mergeDefinition(ArrayPluginDefinitionDecorator::ensureObjectDefinition($plugin_definition));
         }
         else {
           $plugin_definition = $derivative_plugin_definition;
@@ -108,7 +109,7 @@ protected function getDerivatives(array $base_plugin_definitions) {
           // Use this definition as defaults if a plugin already defined
           // itself as this derivative.
           if ($derivative_id && isset($base_plugin_definitions[$plugin_id])) {
-            $derivative_definition = $this->mergeDerivativeDefinition($base_plugin_definitions[$plugin_id], $derivative_definition);
+            ArrayPluginDefinitionDecorator::ensureObjectDefinition($derivative_definition)->mergeDefinition(ArrayPluginDefinitionDecorator::ensureObjectDefinition($base_plugin_definitions[$plugin_id]));
           }
           $plugin_definitions[$plugin_id] = $derivative_definition;
         }
@@ -170,7 +171,7 @@ protected function encodePluginId($base_plugin_id, $derivative_id) {
    *
    * @param string $base_plugin_id
    *   The base plugin id of the plugin.
-   * @param mixed $base_definition
+   * @param \Drupal\Component\Plugin\Definition\PluginDeriverDefinitionInterface $base_definition
    *   The base plugin definition to build derivatives.
    *
    * @return \Drupal\Component\Plugin\Derivative\DeriverInterface|null
@@ -180,10 +181,10 @@ protected function encodePluginId($base_plugin_id, $derivative_id) {
    *   Thrown if the 'deriver' class specified in the plugin definition
    *   does not implement \Drupal\Component\Plugin\Derivative\DeriverInterface.
    */
-  protected function getDeriver($base_plugin_id, $base_definition) {
+  protected function getDeriver($base_plugin_id, PluginDeriverDefinitionInterface $base_definition) {
     if (!isset($this->derivers[$base_plugin_id])) {
       $this->derivers[$base_plugin_id] = FALSE;
-      $class = $this->getDeriverClass($base_definition);
+      $class = $base_definition->getDeriverClass();
       if ($class) {
         $this->derivers[$base_plugin_id] = new $class($base_plugin_id);
       }
@@ -192,57 +193,10 @@ protected function getDeriver($base_plugin_id, $base_definition) {
   }
 
   /**
-   * Gets the deriver class name from the base plugin definition.
-   *
-   * @param array $base_definition
-   *   The base plugin definition to build derivatives.
-   *
-   * @return string|null
-   *   The name of a class implementing
-   *   \Drupal\Component\Plugin\Derivative\DeriverInterface.
-   *
-   * @throws \Drupal\Component\Plugin\Exception\InvalidDeriverException
-   *   Thrown if the 'deriver' class specified in the plugin definition
-   *   does not implement
-   *   \Drupal\Component\Plugin\Derivative\DerivativeInterface.
-   */
-  protected function getDeriverClass($base_definition) {
-    $class = NULL;
-    if ((is_array($base_definition) || ($base_definition = (array) $base_definition)) && (isset($base_definition['deriver']) && $class = $base_definition['deriver'])) {
-      if (!class_exists($class)) {
-        throw new InvalidDeriverException(sprintf('Plugin (%s) deriver "%s" does not exist.', $base_definition['id'], $class));
-      }
-      if (!is_subclass_of($class, '\Drupal\Component\Plugin\Derivative\DeriverInterface')) {
-        throw new InvalidDeriverException(sprintf('Plugin (%s) deriver "%s" must implement \Drupal\Component\Plugin\Derivative\DeriverInterface.', $base_definition['id'], $class));
-      }
-    }
-    return $class;
-  }
-
-  /**
-   * Merges a base and derivative definition, taking into account empty values.
-   *
-   * @param array $base_plugin_definition
-   *   The base plugin definition.
-   * @param array $derivative_definition
-   *   The derivative plugin definition.
-   *
-   * @return array
-   *   The merged definition.
-   */
-  protected function mergeDerivativeDefinition($base_plugin_definition, $derivative_definition) {
-    // Use this definition as defaults if a plugin already defined itself as
-    // this derivative, but filter out empty values first.
-    $filtered_base = array_filter($base_plugin_definition);
-    $derivative_definition = $filtered_base + ($derivative_definition ?: array());
-    // Add back any empty keys that the derivative didn't have.
-    return $derivative_definition + $base_plugin_definition;
-  }
-
-  /**
    * Passes through all unknown calls onto the decorated object.
    */
   public function __call($method, $args) {
     return call_user_func_array(array($this->decorated, $method), $args);
   }
+
 }
diff --git a/core/lib/Drupal/Component/Plugin/Factory/DefaultFactory.php b/core/lib/Drupal/Component/Plugin/Factory/DefaultFactory.php
index 2315a7f..d5de98f 100644
--- a/core/lib/Drupal/Component/Plugin/Factory/DefaultFactory.php
+++ b/core/lib/Drupal/Component/Plugin/Factory/DefaultFactory.php
@@ -6,6 +6,7 @@
 
 namespace Drupal\Component\Plugin\Factory;
 
+use Drupal\Component\Plugin\Definition\ArrayPluginDefinitionDecorator;
 use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
 use Drupal\Component\Plugin\Exception\PluginException;
 
@@ -77,18 +78,24 @@ public function createInstance($plugin_id, array $configuration = array()) {
    *
    */
   public static function getPluginClass($plugin_id, $plugin_definition = NULL, $required_interface = NULL) {
-    if (empty($plugin_definition['class'])) {
-      throw new PluginException(sprintf('The plugin (%s) did not specify an instance class.', $plugin_id));
+    if (is_null($plugin_definition)) {
+      return;
     }
 
-    $class = $plugin_definition['class'];
+    try {
+      $plugin_definition = ArrayPluginDefinitionDecorator::ensureObjectDefinition($plugin_definition);
+    }
+    catch (\InvalidArgumentException $e) {
+      throw new PluginException('The plugin definition is incorrect.', 0, $e);
+    }
+    $class = $plugin_definition->getClass();
 
-    if (!class_exists($class)) {
-      throw new PluginException(sprintf('Plugin (%s) instance class "%s" does not exist.', $plugin_id, $class));
+    if (!$class) {
+      throw new PluginException(sprintf('The plugin (%s) did not specify an instance class.', $plugin_id));
     }
 
-    if ($required_interface && !is_subclass_of($plugin_definition['class'], $required_interface)) {
-      throw new PluginException(sprintf('Plugin "%s" (%s) in %s should implement interface %s.', $plugin_id, $plugin_definition['class'], $plugin_definition['provider'], $required_interface));
+    if ($required_interface && !is_subclass_of($class, $required_interface)) {
+      throw new PluginException(sprintf('Plugin "%s" (%s) must implement interface %s.', $plugin_id, $class, $required_interface));
     }
 
     return $class;
diff --git a/core/lib/Drupal/Core/Block/BlockManager.php b/core/lib/Drupal/Core/Block/BlockManager.php
index 086016d..8a490a8 100644
--- a/core/lib/Drupal/Core/Block/BlockManager.php
+++ b/core/lib/Drupal/Core/Block/BlockManager.php
@@ -13,6 +13,7 @@
 use Drupal\Core\Plugin\CategorizingPluginManagerTrait;
 use Drupal\Core\Plugin\Context\ContextAwarePluginManagerTrait;
 use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinitionDecorator;
 
 /**
  * Manages discovery and instantiation of block plugins.
@@ -52,7 +53,7 @@ public function __construct(\Traversable $namespaces, CacheBackendInterface $cac
    */
   public function processDefinition(&$definition, $plugin_id) {
     parent::processDefinition($definition, $plugin_id);
-    $this->processDefinitionCategory($definition);
+    $this->processDefinitionCategory(new ArrayPluginDefinitionDecorator($definition));
   }
 
   /**
@@ -60,9 +61,17 @@ public function processDefinition(&$definition, $plugin_id) {
    */
   public function getSortedDefinitions(array $definitions = NULL) {
     // Sort the plugins first by category, then by label.
-    $definitions = $this->traitGetSortedDefinitions($definitions, 'admin_label');
+    /** @var \Drupal\Core\Plugin\CategorizingPluginManagerTrait|\Drupal\Component\Plugin\PluginManagerInterface $this */
+    $definitions = isset($definitions) ? $definitions : $this->getDefinitions();
+    uasort($definitions, function ($a, $b) {
+      if ($a['category'] != $b['category']) {
+        return strnatcasecmp($a['category'], $b['category']);
+      }
+      return strnatcasecmp($a['admin_label'], $b['admin_label']);
+    });
     // Do not display the 'broken' plugin in the UI.
     unset($definitions['broken']);
+
     return $definitions;
   }
 
@@ -70,7 +79,7 @@ public function getSortedDefinitions(array $definitions = NULL) {
    * {@inheritdoc}
    */
   public function getGroupedDefinitions(array $definitions = NULL) {
-    $definitions = $this->traitGetGroupedDefinitions($definitions, 'admin_label');
+    $definitions = $this->traitGetGroupedDefinitions($definitions);
     // Do not display the 'broken' plugin in the UI.
     unset($definitions[$this->t('Block')]['broken']);
     return $definitions;
diff --git a/core/lib/Drupal/Core/Entity/EntityType.php b/core/lib/Drupal/Core/Entity/EntityType.php
index e121d75..2dcb6be 100644
--- a/core/lib/Drupal/Core/Entity/EntityType.php
+++ b/core/lib/Drupal/Core/Entity/EntityType.php
@@ -7,9 +7,11 @@
 
 namespace Drupal\Core\Entity;
 
+use Drupal\Component\Plugin\Definition\PluginLabelDefinitionTrait;
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Entity\Exception\EntityTypeIdLengthException;
+use Drupal\Core\Plugin\Definition\PluginDefinition;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 
 /**
@@ -17,8 +19,9 @@
  *
  * @ingroup entity_api
  */
-class EntityType implements EntityTypeInterface {
+class EntityType extends PluginDefinition implements EntityTypeInterface {
 
+  use PluginLabelDefinitionTrait;
   use StringTranslationTrait;
 
   /**
@@ -50,27 +53,6 @@ class EntityType implements EntityTypeInterface {
   protected $entity_keys = array();
 
   /**
-   * The unique identifier of this entity type.
-   *
-   * @var string
-   */
-  protected $id;
-
-  /**
-   * The name of the provider of this entity type.
-   *
-   * @var string
-   */
-  protected $provider;
-
-  /**
-   * The name of the entity type class.
-   *
-   * @var string
-   */
-  protected $class;
-
-  /**
    * The name of the original entity type class.
    *
    * This is only set if the class name is changed.
@@ -172,13 +154,6 @@ class EntityType implements EntityTypeInterface {
   protected $translatable = FALSE;
 
   /**
-   * The human-readable name of the type.
-   *
-   * @var string
-   */
-  protected $label = '';
-
-  /**
    * A callable that can be used to provide the entity URI.
    *
    * @var callable|null
@@ -350,28 +325,25 @@ public function id() {
   /**
    * {@inheritdoc}
    */
-  public function getProvider() {
-    return $this->provider;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getClass() {
-    return $this->class;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function getOriginalClass() {
     return $this->originalClass ?: $this->class;
   }
 
   /**
-   * {@inheritdoc}
+   * Sets the class.
+   *
+   * @param string $class
+   *   A class that implements \Drupal\Core\Entity\EntityInterface.
+   *
+   * @return $this
+   *
+   * @throws \InvalidArgumentException
    */
   public function setClass($class) {
+    if (!is_subclass_of($class, 'Drupal\Core\Entity\EntityInterface')) {
+      throw new \InvalidArgumentException('Entity classes must implement \Drupal\Core\Entity\EntityInterface.');
+    }
+
     if (!$this->originalClass && $this->class) {
       // If the original class is currently not set, set it to the current
       // class, assume that is the original class name.
@@ -675,13 +647,6 @@ public function getDataTable() {
   /**
    * {@inheritdoc}
    */
-  public function getLabel() {
-    return (string) $this->label;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function getLowercaseLabel() {
     return Unicode::strtolower($this->getLabel());
   }
diff --git a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
index 46c2058..98ce423 100644
--- a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
@@ -7,6 +7,9 @@
 
 namespace Drupal\Core\Entity;
 
+use Drupal\Component\Plugin\Definition\PluginLabelDefinitionInterface;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
+
 /**
  * Provides an interface for an entity type and its metadata.
  *
@@ -15,7 +18,7 @@
  * implemented to alter existing data and fill-in defaults. Module-specific
  * properties should be documented in the hook implementations defining them.
  */
-interface EntityTypeInterface {
+interface EntityTypeInterface extends PluginDefinitionInterface, PluginLabelDefinitionInterface {
 
   /**
    * The maximum length of ID, in characters.
@@ -59,22 +62,6 @@ public function set($property, $value);
   public function id();
 
   /**
-   * Gets the name of the provider of this entity type.
-   *
-   * @return string
-   *   The name of the provider of this entity type.
-   */
-  public function getProvider();
-
-  /**
-   * Gets the name of the entity type class.
-   *
-   * @return string
-   *   The name of the entity type class.
-   */
-  public function getClass();
-
-  /**
    * Gets the name of the original entity type class.
    *
    * In case the class name was changed with setClass(), this will return
@@ -171,16 +158,6 @@ public function isRenderCacheable();
   public function isPersistentlyCacheable();
 
   /**
-   * Sets the name of the entity type class.
-   *
-   * @param string $class
-   *   The name of the entity type class.
-   *
-   * @return $this
-   */
-  public function setClass($class);
-
-  /**
    * Determines if there is a handler for a given type.
    *
    * @param string $handler_type
diff --git a/core/lib/Drupal/Core/Plugin/CategorizingPluginManagerTrait.php b/core/lib/Drupal/Core/Plugin/CategorizingPluginManagerTrait.php
index b3cc23b..d2f34ea 100644
--- a/core/lib/Drupal/Core/Plugin/CategorizingPluginManagerTrait.php
+++ b/core/lib/Drupal/Core/Plugin/CategorizingPluginManagerTrait.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\Core\Plugin;
 
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinitionDecorator;
+use Drupal\Core\Plugin\Definition\PluginCategoryDefinitionInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 
 /**
@@ -28,15 +30,15 @@
    *
    * If the definition lacks a category, it defaults to the providing module.
    *
-   * @param array $definition
+   * @param \Drupal\Core\Plugin\Definition\PluginCategoryDefinitionInterface $definition
    *   The plugin definition.
    */
-  protected function processDefinitionCategory(&$definition) {
+  protected function processDefinitionCategory(PluginCategoryDefinitionInterface $definition) {
     // Ensure that every plugin has a category.
-    if (empty($definition['category'])) {
+    if (!$definition->getCategory()) {
       // Default to the human readable module name if the provider is a module;
       // otherwise the provider machine name is used.
-      $definition['category'] = $this->getProviderName($definition['provider']);
+      $definition->setCategory($this->getProviderName($definition->getProvider()));
     }
   }
 
@@ -82,7 +84,11 @@ public function getCategories() {
     /** @var \Drupal\Core\Plugin\CategorizingPluginManagerTrait|\Drupal\Component\Plugin\PluginManagerInterface $this */
     // Fetch all categories from definitions and remove duplicates.
     $categories = array_unique(array_values(array_map(function ($definition) {
-      return $definition['category'];
+var_dump('X'.gettype($definition));
+      /** @var \Drupal\Core\Plugin\Definition\PluginCategoryDefinitionInterface $definition */
+      $definition = ArrayPluginDefinitionDecorator::ensureObjectDefinition($definition);
+var_dump('Y'.gettype($definition));
+      return $definition->getCategory();
     }, $this->getDefinitions())));
     natcasesort($categories);
     return $categories;
@@ -91,28 +97,35 @@ public function getCategories() {
   /**
    * Implements \Drupal\Component\Plugin\CategorizingPluginManagerInterface::getSortedDefinitions().
    */
-  public function getSortedDefinitions(array $definitions = NULL, $label_key = 'label') {
+  public function getSortedDefinitions(array $definitions = NULL) {
     // Sort the plugins first by category, then by label.
     /** @var \Drupal\Core\Plugin\CategorizingPluginManagerTrait|\Drupal\Component\Plugin\PluginManagerInterface $this */
     $definitions = isset($definitions) ? $definitions : $this->getDefinitions();
-    uasort($definitions, function ($a, $b) use ($label_key) {
-      if ($a['category'] != $b['category']) {
-        return strnatcasecmp($a['category'], $b['category']);
+    uasort($definitions, function ($a, $b) {
+      /** @var \Drupal\Core\Plugin\Definition\PluginCategoryDefinitionInterface $a */
+      $a = ArrayPluginDefinitionDecorator::ensureObjectDefinition($a);
+      /** @var \Drupal\Core\Plugin\Definition\PluginCategoryDefinitionInterface $b */
+      $b = ArrayPluginDefinitionDecorator::ensureObjectDefinition($b);
+      if ($a->getCategory() != $b->getCategory()) {
+        return strnatcasecmp($a->getCategory(), $b->getCategory());
       }
-      return strnatcasecmp($a[$label_key], $b[$label_key]);
+      return strnatcasecmp($a->getLabel(), $b->getLabel());
     });
+
     return $definitions;
   }
 
   /**
    * Implements \Drupal\Component\Plugin\CategorizingPluginManagerInterface::getGroupedDefinitions().
    */
-  public function getGroupedDefinitions(array $definitions = NULL, $label_key = 'label') {
+  public function getGroupedDefinitions(array $definitions = NULL) {
     /** @var \Drupal\Core\Plugin\CategorizingPluginManagerTrait|\Drupal\Component\Plugin\PluginManagerInterface $this */
-    $definitions = $this->getSortedDefinitions(isset($definitions) ? $definitions : $this->getDefinitions(), $label_key);
+    $definitions = $this->getSortedDefinitions(isset($definitions) ? $definitions : $this->getDefinitions());
     $grouped_definitions = array();
-    foreach ($definitions as $id => $definition) {
-      $grouped_definitions[(string) $definition['category']][$id] = $definition;
+    foreach ($definitions as $definition) {
+      /** @var \Drupal\Core\Plugin\Definition\PluginCategoryDefinitionInterface $object_definition */
+      $object_definition = ArrayPluginDefinitionDecorator::ensureObjectDefinition($definition);
+      $grouped_definitions[(string) $object_definition->getCategory()][$object_definition->getId()] = $definition;
     }
     return $grouped_definitions;
   }
diff --git a/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php b/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php
index fe9b78e..bbcf448 100644
--- a/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php
+++ b/core/lib/Drupal/Core/Plugin/Context/ContextHandler.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Plugin\Exception\ContextException;
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Plugin\ContextAwarePluginInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinitionDecorator;
 
 /**
  * Provides methods to handle sets of contexts.
@@ -21,13 +22,15 @@ class ContextHandler implements ContextHandlerInterface {
    */
   public function filterPluginDefinitionsByContexts(array $contexts, array $definitions) {
     return array_filter($definitions, function ($plugin_definition) use ($contexts) {
+      /** @var \Drupal\Component\Plugin\Definition\PluginContextDefinitionInterface $plugin_definition */
+      $plugin_definition = ArrayPluginDefinitionDecorator::ensureObjectDefinition($plugin_definition);
       // If this plugin doesn't need any context, it is available to use.
-      if (!isset($plugin_definition['context'])) {
+      if (!$plugin_definition->getContextDefinitions()) {
         return TRUE;
       }
 
       // Check the set of contexts against the requirements.
-      return $this->checkRequirements($contexts, $plugin_definition['context']);
+      return $this->checkRequirements($contexts, $plugin_definition->getContextDefinitions());
     });
   }
 
diff --git a/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php b/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php
index e4e05bd..33f99d9 100644
--- a/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php
+++ b/core/lib/Drupal/Core/Plugin/DefaultPluginManager.php
@@ -9,6 +9,8 @@
 
 use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface;
 use Drupal\Component\Plugin\Discovery\DiscoveryCachedTrait;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinitionDecorator;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
 use Drupal\Component\Plugin\PluginManagerBase;
 use Drupal\Component\Plugin\PluginManagerInterface;
@@ -77,6 +79,9 @@ class DefaultPluginManager extends PluginManagerBase implements PluginManagerInt
    * purposes.
    *
    * @var array
+   *
+   * @todo Make this work with
+   *   \Drupal\Component\Plugin\Definition\PluginDefinitionInterface.
    */
   protected $defaults = array();
 
@@ -246,7 +251,9 @@ protected function cacheSet($cid, $data, $expire = Cache::PERMANENT, array $tags
    * method.
    */
   public function processDefinition(&$definition, $plugin_id) {
-    if (!empty($this->defaults) && is_array($this->defaults)) {
+    // @todo Make this work with
+    //   \Drupal\Component\Plugin\Definition\PluginDefinitionInterface.
+    if (is_array($definition) && is_array($this->defaults)) {
       $definition = NestedArray::mergeDeep($this->defaults, $definition);
     }
   }
@@ -256,6 +263,9 @@ public function processDefinition(&$definition, $plugin_id) {
    *
    * @return array
    *   List of definitions to store in cache.
+   *
+   * @throws \Exception
+   *   Thrown when definitions do not implement the required interfaces.
    */
   protected function findDefinitions() {
     $definitions = $this->discovery->getDefinitions();
@@ -266,12 +276,13 @@ protected function findDefinitions() {
     // If this plugin was provided by a module that does not exist, remove the
     // plugin definition.
     foreach ($definitions as $plugin_id => $plugin_definition) {
-      // If the plugin definition is an object, attempt to convert it to an
-      // array, if that is not possible, skip further processing.
-      if (is_object($plugin_definition) && !($plugin_definition = (array) $plugin_definition)) {
-        continue;
+      $plugin_definition = ArrayPluginDefinitionDecorator::ensureObjectDefinition($plugin_definition);
+
+      if (!($plugin_definition instanceof PluginDefinitionInterface)) {
+        throw new \Exception(sprintf('The plugin definition (%s) does not implement \Drupal\Core\Plugin\Definition\PluginDefinitionInterface.'));
       }
-      if (isset($plugin_definition['provider']) && !in_array($plugin_definition['provider'], array('core', 'component')) && !$this->providerExists($plugin_definition['provider'])) {
+
+      if (!in_array($plugin_definition->getProvider(), array('core', 'component')) && !$this->providerExists($plugin_definition->getProvider())) {
         unset($definitions[$plugin_id]);
       }
     }
diff --git a/core/lib/Drupal/Core/Plugin/Definition/ArrayPluginDefinitionDecorator.php b/core/lib/Drupal/Core/Plugin/Definition/ArrayPluginDefinitionDecorator.php
new file mode 100644
index 0000000..b087eb9
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Definition/ArrayPluginDefinitionDecorator.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Plugin\ArrayPluginDefinitionDecorator.
+ */
+
+namespace Drupal\Core\Plugin\Definition;
+
+use Drupal\Component\Plugin\Definition\ArrayPluginDefinitionDecorator as ComponentArrayPluginDefinitionDecorator;
+
+/**
+ * Decorates an array plugin definition.
+ *
+ * This class wraps array definitions in an objects for use in APIs that expect
+ * object definitions only. The arrays are decorated by reference.
+ *
+ * Definitions for new plugin types should be objects by default. This class
+ * exists for backwards compatibility with definitions of plugin types that were
+ * introduced before object support existed.
+ *
+ * @ingroup Plugin
+ */
+class ArrayPluginDefinitionDecorator extends ComponentArrayPluginDefinitionDecorator implements PluginCategoryDefinitionInterface, PluginConfigDependenciesDefinitionInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setCategory($category) {
+    $this->arrayDefinition['category'] = $category;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCategory() {
+    return isset($this->arrayDefinition['category']) ? $this->arrayDefinition['category'] : '';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setConfigDependencies(array $dependencies) {
+    $this->arrayDefinition['config_dependencies'] = $dependencies;
+
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfigDependencies() {
+    return isset($this->arrayDefinition['config_dependencies']) ? $this->arrayDefinition['config_dependencies'] : [];
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/Definition/PluginCategoryDefinitionInterface.php b/core/lib/Drupal/Core/Plugin/Definition/PluginCategoryDefinitionInterface.php
new file mode 100644
index 0000000..ac55611
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Definition/PluginCategoryDefinitionInterface.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Plugin\Definition\PluginCategoryDefinitionInterface.
+ */
+
+namespace Drupal\Core\Plugin\Definition;
+
+use Drupal\Component\Plugin\Definition\PluginLabelDefinitionInterface;
+
+/**
+ * Defines a plugin definition that includes a category.
+ *
+ * @ingroup Plugin
+ */
+interface PluginCategoryDefinitionInterface extends PluginDefinitionInterface, PluginLabelDefinitionInterface {
+
+  /**
+   * Sets the category.
+   *
+   * @param \Drupal\Core\StringTranslation\TranslationWrapper|string $category
+   *   The category.
+   *
+   * @return $this
+   */
+  public function setCategory($category);
+
+  /**
+   * Gets the category.
+   *
+   * @return \Drupal\Core\StringTranslation\TranslationWrapper|string|null
+   *   The category.
+   */
+  public function getCategory();
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/Definition/PluginConfigDependenciesDefinitionInterface.php b/core/lib/Drupal/Core/Plugin/Definition/PluginConfigDependenciesDefinitionInterface.php
new file mode 100644
index 0000000..d02f730
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Definition/PluginConfigDependenciesDefinitionInterface.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Plugin\PluginConfigDependenciesDefinitionInterface.
+ */
+
+namespace Drupal\Core\Plugin\Definition;
+
+/**
+ * Defines a plugin definition that includes configuration dependencies.
+ *
+ * @ingroup Plugin
+ */
+interface PluginConfigDependenciesDefinitionInterface extends PluginDefinitionInterface {
+
+  /**
+   * Sets the dependencies.
+   *
+   * @param array[] $dependencies.
+   *   An array of dependencies keyed by the type of dependency. One example:
+   *   @code
+   *   array(
+   *     'module' => array(
+   *       'node',
+   *       'field',
+   *       'image',
+   *     ),
+   *   );
+   *   @endcode
+   *
+   * @return $this
+   */
+  public function setConfigDependencies(array $dependencies);
+
+  /**
+   * Gets the dependencies.
+   *
+   * @return array[]
+   *   See self::setConfigDependencies().
+   */
+  public function getConfigDependencies();
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/Definition/PluginDefinition.php b/core/lib/Drupal/Core/Plugin/Definition/PluginDefinition.php
new file mode 100644
index 0000000..2b961f5
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Definition/PluginDefinition.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Plugin\PluginDefinition.
+ */
+
+namespace Drupal\Core\Plugin\Definition;
+
+use Drupal\Component\Plugin\Definition\PluginDefinition as ComponentPluginDefinition;
+
+/**
+ * Provides a plugin definition.
+ *
+ * @ingroup Plugin
+ */
+class PluginDefinition extends ComponentPluginDefinition implements PluginDefinitionInterface {
+
+  /**
+   * The provider.
+   *
+   * @var string
+   */
+  protected $provider;
+
+  /**
+   * Implements \Drupal\Core\Plugin\Definition\PluginProviderDefinitionInterface::setProvider().
+   */
+  public function setProvider($provider) {
+    $this->provider = $provider;
+
+    return $this;
+  }
+
+  /**
+   * Implements \Drupal\Core\Plugin\Definition\PluginProviderDefinitionInterface::getProvider().
+   */
+  public function getProvider() {
+    return $this->provider;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/Definition/PluginDefinitionInterface.php b/core/lib/Drupal/Core/Plugin/Definition/PluginDefinitionInterface.php
new file mode 100644
index 0000000..399007e
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Definition/PluginDefinitionInterface.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Plugin\Definition\PluginDefinitionInterface.
+ */
+
+namespace Drupal\Core\Plugin\Definition;
+
+use Drupal\Component\Plugin\Definition\PluginDefinitionInterface as ComponentPluginDefinitionInterface;
+
+/**
+ * Defines a plugin definition.
+ *
+ * @ingroup Plugin
+ */
+interface PluginDefinitionInterface extends ComponentPluginDefinitionInterface {
+
+  /**
+   * Sets the plugin provider.
+   *
+   * The provider is the name of the module that provides the plugin.
+   *
+   * @param string $provider
+   *   The provider.
+   *
+   * @return $this
+   */
+  public function setProvider($provider);
+
+  /**
+   * Gets the plugin provider.
+   *
+   * The provider is the name of the module that provides the plugin.
+   *
+   * @return string
+   *   The provider.
+   */
+  public function getProvider();
+
+}
diff --git a/core/lib/Drupal/Core/Plugin/Discovery/ContainerDerivativeDiscoveryDecorator.php b/core/lib/Drupal/Core/Plugin/Discovery/ContainerDerivativeDiscoveryDecorator.php
index 6951c77..f705af3 100644
--- a/core/lib/Drupal/Core/Plugin/Discovery/ContainerDerivativeDiscoveryDecorator.php
+++ b/core/lib/Drupal/Core/Plugin/Discovery/ContainerDerivativeDiscoveryDecorator.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\Plugin\Discovery;
 
+use Drupal\Component\Plugin\Definition\PluginDeriverDefinitionInterface;
 use Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator;
 
 class ContainerDerivativeDiscoveryDecorator extends DerivativeDiscoveryDecorator {
@@ -14,10 +15,10 @@ class ContainerDerivativeDiscoveryDecorator extends DerivativeDiscoveryDecorator
   /**
    * {@inheritdoc}
    */
-  protected function getDeriver($base_plugin_id, $base_definition) {
+  protected function getDeriver($base_plugin_id, PluginDeriverDefinitionInterface $base_definition) {
     if (!isset($this->derivers[$base_plugin_id])) {
       $this->derivers[$base_plugin_id] = FALSE;
-      $class = $this->getDeriverClass($base_definition);
+      $class = $base_definition->getDeriverClass();
       if ($class) {
         // If the deriver provides a factory method, pass the container to it.
         if (is_subclass_of($class, '\Drupal\Core\Plugin\Discovery\ContainerDeriverInterface')) {
diff --git a/core/lib/Drupal/Core/Plugin/Discovery/HookDiscovery.php b/core/lib/Drupal/Core/Plugin/Discovery/HookDiscovery.php
index 9d1bfa3..1dc86a5 100644
--- a/core/lib/Drupal/Core/Plugin/Discovery/HookDiscovery.php
+++ b/core/lib/Drupal/Core/Plugin/Discovery/HookDiscovery.php
@@ -10,6 +10,8 @@
 use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
 use Drupal\Component\Plugin\Discovery\DiscoveryTrait;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinitionDecorator;
+use Drupal\Core\Plugin\Definition\PluginDefinitionInterface;
 
 /**
  * Provides a hook-based plugin discovery class.
@@ -54,7 +56,11 @@ public function getDefinitions() {
     foreach ($this->moduleHandler->getImplementations($this->hook) as $module) {
       $result = $this->moduleHandler->invoke($module, $this->hook);
       foreach ($result as $plugin_id => $definition) {
-        $definition['module'] = $module;
+        $object_definition = ArrayPluginDefinitionDecorator::ensureObjectDefinition($definition);
+        if (!($object_definition instanceof PluginDefinitionInterface)) {
+          throw new \Exception(sprintf('Module %s\'s hook_%s() implementation must plugin definitions that are either arrays or instances of classes that implement \Drupal\Core\Plugin\Definition\PluginDefinitionInterface', $module, $this->hook));
+        }
+        $object_definition->setProvider($module);
         $definitions[$plugin_id] = $definition;
       }
     }
diff --git a/core/lib/Drupal/Core/Plugin/PluginDependencyTrait.php b/core/lib/Drupal/Core/Plugin/PluginDependencyTrait.php
index f9e1ad3..9f24498 100644
--- a/core/lib/Drupal/Core/Plugin/PluginDependencyTrait.php
+++ b/core/lib/Drupal/Core/Plugin/PluginDependencyTrait.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Plugin\DependentPluginInterface;
 use Drupal\Component\Plugin\PluginInspectionInterface;
 use Drupal\Core\Entity\DependencyTrait;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinitionDecorator;
 
 /**
  * Provides a trait for calculating the dependencies of a plugin.
@@ -30,12 +31,11 @@
    *   The plugin instance.
    */
   protected function calculatePluginDependencies(PluginInspectionInterface $instance) {
-    $definition = $instance->getPluginDefinition();
-    $this->addDependency('module', $definition['provider']);
-    // Plugins can declare additional dependencies in their definition.
-    if (isset($definition['config_dependencies'])) {
-      $this->addDependencies($definition['config_dependencies']);
-    }
+    /** @var \Drupal\Core\Plugin\Definition\PluginDefinitionInterface $definition */
+    $definition = ArrayPluginDefinitionDecorator::ensureObjectDefinition($instance->getPluginDefinition());
+    $this->addDependency('module', $definition->getProvider());
+    // @todo Add PluginConfigDependenciesDefinitionInterface.
+    $this->addDependencies($definition->getConfigDependencies());
     // If a plugin is dependent, calculate its dependencies.
     if ($instance instanceof DependentPluginInterface && $plugin_dependencies = $instance->calculateDependencies()) {
       $this->addDependencies($plugin_dependencies);
diff --git a/core/tests/Drupal/Tests/Component/Plugin/DefaultFactoryTest.php b/core/tests/Drupal/Tests/Component/Plugin/DefaultFactoryTest.php
index 8d840d1..6cea820 100644
--- a/core/tests/Drupal/Tests/Component/Plugin/DefaultFactoryTest.php
+++ b/core/tests/Drupal/Tests/Component/Plugin/DefaultFactoryTest.php
@@ -40,7 +40,6 @@ public function testGetPluginClassWithMissingClass() {
    * Tests getPluginClass() with a not existing class definition.
    *
    * @expectedException \Drupal\Component\Plugin\Exception\PluginException
-   * @expectedExceptionMessage Plugin (kiwifruit) instance class "\Drupal\plugin_test\Plugin\plugin_test\fruit\Kiwifruit" does not exist.
    */
   public function testGetPluginClassWithNotExistingClass() {
     DefaultFactory::getPluginClass('kiwifruit', ['class' => '\Drupal\plugin_test\Plugin\plugin_test\fruit\Kiwifruit']);
@@ -60,7 +59,7 @@ public function testGetPluginClassWithInterface() {
    * Tests getPluginClass() with a required interface but no implementation.
    *
    * @expectedException \Drupal\Component\Plugin\Exception\PluginException
-   * @expectedExceptionMessage Plugin "cherry" (Drupal\plugin_test\Plugin\plugin_test\fruit\Kale) in core should implement interface \Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface.
+   * @expectedExceptionMessage Plugin "cherry" (Drupal\plugin_test\Plugin\plugin_test\fruit\Kale) must implement interface \Drupal\plugin_test\Plugin\plugin_test\fruit\FruitInterface.
    */
   public function testGetPluginClassWithInterfaceAndInvalidClass() {
     $plugin_class = 'Drupal\plugin_test\Plugin\plugin_test\fruit\Kale';
diff --git a/core/tests/Drupal/Tests/Component/Plugin/Definition/ArrayPluginDefinitionDecoratorTest.php b/core/tests/Drupal/Tests/Component/Plugin/Definition/ArrayPluginDefinitionDecoratorTest.php
new file mode 100644
index 0000000..86e7604
--- /dev/null
+++ b/core/tests/Drupal/Tests/Component/Plugin/Definition/ArrayPluginDefinitionDecoratorTest.php
@@ -0,0 +1,209 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Component\Plugin\Definition\ArrayPluginDefinitionDecoratorTest.
+ */
+
+namespace Drupal\Tests\Component\Plugin\Definition;
+
+use Drupal\Component\Plugin\Definition\ArrayPluginDefinitionDecorator;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Component\Plugin\Definition\ArrayPluginDefinitionDecorator
+ *
+ * @group Plugin
+ */
+class ArrayPluginDefinitionDecoratorTest extends UnitTestCase {
+
+  /**
+   * The decorated array plugin definition.
+   *
+   * @var mixed[]
+   */
+  protected $arrayDefinition = [];
+
+  /**
+   * The class under test.
+   *
+   * @var \Drupal\Component\Plugin\Definition\ArrayPluginDefinitionDecorator
+   */
+  protected $sut;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->sut = new ArrayPluginDefinitionDecorator($this->arrayDefinition);
+  }
+
+  /**
+   * @covers ::setId
+   * @covers ::getId
+   */
+  public function testGetId() {
+    $id = $this->randomMachineName();
+
+    $this->assertSame($this->sut, $this->sut->setId($id));
+    $this->assertSame($id, $this->sut->getId());
+    $this->assertSame($id, $this->sut->getArrayDefinition()['id']);
+  }
+
+  /**
+   * @covers ::setClass
+   * @covers ::getClass
+   */
+  public function testGetClass() {
+    $class = get_class($this->getMock('\Drupal\Component\Plugin\PluginInspectionInterface'));
+
+    $this->assertSame($this->sut, $this->sut->setClass($class));
+    $this->assertSame($class, $this->sut->getClass());
+    $this->assertSame($class, $this->sut->getArrayDefinition()['class']);
+  }
+
+  /**
+   * @covers ::setLabel
+   * @covers ::getLabel
+   */
+  public function testGetLabel() {
+    $label = $this->randomMachineName();
+
+    $this->assertSame($this->sut, $this->sut->setLabel($label));
+    $this->assertSame($label, $this->sut->getLabel());
+    $this->assertSame($label, $this->sut->getArrayDefinition()['label']);
+  }
+
+  /**
+   * @covers ::setDeriverClass
+   * @covers ::getDeriverClass
+   */
+  public function testGetDeriverClass() {
+    $class = get_class($this->getMock('\Drupal\Component\Plugin\Derivative\DeriverInterface'));
+
+    $this->assertSame($this->sut, $this->sut->setDeriverClass($class));
+    $this->assertSame($class, $this->sut->getDeriverClass());
+    $this->assertSame($class, $this->sut->getArrayDefinition()['deriver']);
+  }
+
+  /**
+   * @covers ::setDeriverClass
+   *
+   * @depends testGetDeriverClass
+   *
+   * @expectedException \InvalidArgumentException
+   */
+  public function testGetDeriverClassWithInvalidDeriverClass() {
+    $class = '\stdClass';
+
+    $this->sut->setDeriverClass($class);
+  }
+
+  /**
+   * @covers ::setProvider
+   * @covers ::getProvider
+   */
+  public function testGetProviderClass() {
+    $provider = $this->randomMachineName();
+
+    $this->assertSame($this->sut, $this->sut->setProvider($provider));
+    $this->assertSame($provider, $this->sut->getProvider());
+    $this->assertSame($provider, $this->sut->getArrayDefinition()['provider']);
+  }
+
+  /**
+   * @covers ::setContextDefinitions
+   * @covers ::getContextDefinitions
+   */
+  public function testGetContextDefinitions() {
+    $context_definition_name_a = $this->randomMachineName();
+    $context_definition_a = $this->getMock('\Drupal\Component\Plugin\Context\ContextDefinitionInterface');
+    $context_definition_name_b = $this->randomMachineName();
+    $context_definition_b = $this->getMock('\Drupal\Component\Plugin\Context\ContextDefinitionInterface');
+
+    $context_definitions = [
+      $context_definition_name_a => $context_definition_a,
+      $context_definition_name_b => $context_definition_b,
+    ];
+
+    $this->assertSame($this->sut, $this->sut->setContextDefinitions($context_definitions));
+    $this->assertSame($context_definitions, $this->sut->getContextDefinitions());
+    $this->assertSame($context_definitions, $this->sut->getArrayDefinition()['context']);
+  }
+
+  /**
+   * @covers ::setContextDefinitions
+   *
+   * @depends testGetContextDefinitions
+   *
+   * @expectedException \InvalidArgumentException
+   */
+  public function testSetContextDefinitionsWithInvalidDefinition() {
+    $context_definitions = [
+      $this->randomMachineName() => new \stdClass(),
+    ];
+
+    $this->sut->setContextDefinitions($context_definitions);
+  }
+
+  /**
+   * @covers ::setContextDefinition
+   * @covers ::getContextDefinition
+   * @covers ::hasContextDefinition
+   */
+  public function testGetContextDefinition() {
+    $name = $this->randomMachineName();
+    $context_definition = $this->getMock('\Drupal\Component\Plugin\Context\ContextDefinitionInterface');
+
+    $this->assertSame($this->sut, $this->sut->setContextDefinition($name, $context_definition));
+    $this->assertSame($context_definition, $this->sut->getContextDefinition($name));
+    $this->assertTrue($this->sut->hasContextDefinition($name));
+    $this->assertSame($context_definition, $this->sut->getArrayDefinition()['context'][$name]);
+  }
+
+  /**
+   * @covers ::getContextDefinition
+   * @covers ::hasContextDefinition
+   *
+   * @depends testGetContextDefinition
+   *
+   * @expectedException \InvalidArgumentException
+   */
+  public function testGetContextDefinitionWithNonExistentDefinition() {
+    $name = $this->randomMachineName();
+
+    $this->assertFalse($this->sut->hasContextDefinition($name));
+    $this->sut->getContextDefinition($name);
+  }
+
+  /**
+   * @covers ::mergeDefinition
+   */
+  public function testMergeDefinition() {
+    $other_array_definition = [
+      'foo' => $this->randomMachineName(),
+    ];
+    $other_definition = new ArrayPluginDefinitionDecorator($other_array_definition);
+
+    $this->assertSame($this->sut, $this->sut->mergeDefinition($other_definition));
+    $this->assertSame($other_array_definition['foo'], $this->sut->getArrayDefinition()['foo']);
+  }
+
+  /**
+   * @covers ::ensureObjectDefinition
+   */
+  public function testEnsureObjectDefinition() {
+    $array_definition = [
+      'foo' => $this->randomMachineName(),
+    ];
+    $object_definition = $this->getMock('\Drupal\Component\Plugin\Definition\PluginDefinitionInterface');
+
+    $sut = $this->sut;
+
+    $this->assertInstanceOf(get_class($sut), $sut::ensureObjectDefinition($array_definition));
+    $this->assertSame($object_definition, $sut::ensureObjectDefinition($object_definition));
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Component/Plugin/Definition/PluginDefinitionTest.php b/core/tests/Drupal/Tests/Component/Plugin/Definition/PluginDefinitionTest.php
new file mode 100644
index 0000000..3a28a72
--- /dev/null
+++ b/core/tests/Drupal/Tests/Component/Plugin/Definition/PluginDefinitionTest.php
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Component\Plugin\Definition\PluginDefinitionTest.
+ */
+
+namespace Drupal\Tests\Component\Plugin\Definition;
+
+use Drupal\Component\Plugin\Definition\PluginDefinition;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Component\Plugin\Definition\PluginDefinition
+ *
+ * @group Plugin
+ */
+class PluginDefinitionTest extends UnitTestCase {
+
+  /**
+   * The class under test.
+   *
+   * @var \Drupal\Component\Plugin\Definition\PluginDefinition
+   */
+  protected $sut;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->sut = new PluginDefinition();
+  }
+
+  /**
+   * @covers ::setId
+   * @covers ::getId
+   */
+  public function testGetId() {
+    $id = $this->randomMachineName();
+
+    $this->assertSame($this->sut, $this->sut->setId($id));
+    $this->assertSame($id, $this->sut->getId());
+  }
+
+  /**
+   * @covers ::setClass
+   * @covers ::getClass
+   */
+  public function testGetClass() {
+    $class = get_class($this->getMock('\Drupal\Component\Plugin\PluginInspectionInterface'));
+
+    $this->assertSame($this->sut, $this->sut->setClass($class));
+    $this->assertSame($class, $this->sut->getClass());
+  }
+
+  /**
+   * @covers ::mergeDefinition
+   */
+  public function testMergeDefinition() {
+    $other_definition = $this->getMock('\Drupal\Component\Plugin\Definition\PluginDefinition');
+
+    $this->assertSame($this->sut, $this->sut->mergeDefinition($other_definition));
+  }
+
+  /**
+   * @covers ::mergeDefinition
+   *
+   * @depends testMergeDefinition
+   *
+   * @expectedException \InvalidArgumentException
+   */
+  public function testMergeDefinitionWithInvalidOtherDefinition() {
+    $other_definition = $this->getMock('\Drupal\Component\Plugin\Definition\PluginDefinitionInterface');
+
+    $this->sut->mergeDefinition($other_definition);
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Component/Plugin/Definition/PluginDeriverDefinitionTraitTest.php b/core/tests/Drupal/Tests/Component/Plugin/Definition/PluginDeriverDefinitionTraitTest.php
new file mode 100644
index 0000000..af1bbda
--- /dev/null
+++ b/core/tests/Drupal/Tests/Component/Plugin/Definition/PluginDeriverDefinitionTraitTest.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Component\Plugin\Definition\PluginDeriverDefinitionTraitTest.
+ */
+
+namespace Drupal\Tests\Component\Plugin\Definition;
+
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Component\Plugin\Definition\PluginDeriverDefinitionTrait
+ *
+ * @group Plugin
+ */
+class PluginDeriverDefinitionTraitTest extends UnitTestCase {
+
+  /**
+   * The class under test.
+   *
+   * @var \Drupal\Component\Plugin\Definition\PluginDeriverDefinitionTrait
+   */
+  protected $sut;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->sut = $this->getMockForTrait('\Drupal\Component\Plugin\Definition\PluginDeriverDefinitionTrait');
+  }
+
+  /**
+   * @covers ::setDeriverClass
+   * @covers ::getDeriverClass
+   */
+  public function testGetDeriverClass() {
+    $class = get_class($this->getMock('\Drupal\Component\Plugin\Derivative\DeriverInterface'));
+
+    $this->assertSame($this->sut, $this->sut->setDeriverClass($class));
+    $this->assertSame($class, $this->sut->getDeriverClass());
+  }
+
+  /**
+   * @covers ::setDeriverClass
+   *
+   * @depends testGetDeriverClass
+   *
+   * @expectedException \InvalidArgumentException
+   */
+  public function testGetDeriverClassWithNonExistentDeriverClass() {
+    $class = $this->randomMachineName();
+
+    $this->sut->setDeriverClass($class);
+  }
+
+  /**
+   * @covers ::setDeriverClass
+   *
+   * @depends testGetDeriverClass
+   *
+   * @expectedException \InvalidArgumentException
+   */
+  public function testGetDeriverClassWithInvalidDeriverClass() {
+    $class = '\stdClass';
+
+    $this->sut->setDeriverClass($class);
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Component/Plugin/Definition/PluginLabelDefinitionTraitTest.php b/core/tests/Drupal/Tests/Component/Plugin/Definition/PluginLabelDefinitionTraitTest.php
new file mode 100644
index 0000000..4d6a5b1
--- /dev/null
+++ b/core/tests/Drupal/Tests/Component/Plugin/Definition/PluginLabelDefinitionTraitTest.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Component\Plugin\Definition\PluginLabelDefinitionTraitTest.
+ */
+
+namespace Drupal\Tests\Component\Plugin\Definition;
+
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Component\Plugin\Definition\PluginLabelDefinitionTrait
+ *
+ * @group Plugin
+ */
+class PluginLabelDefinitionTraitTest extends UnitTestCase {
+
+  /**
+   * The class under test.
+   *
+   * @var \Drupal\Component\Plugin\Definition\PluginLabelDefinitionTrait
+   */
+  protected $sut;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->sut = $this->getMockForTrait('\Drupal\Component\Plugin\Definition\PluginLabelDefinitionTrait');
+  }
+
+  /**
+   * @covers ::setLabel
+   * @covers ::getLabel
+   */
+  public function testGetLabel() {
+    $label = $this->randomMachineName();
+
+    $this->assertSame($this->sut, $this->sut->setLabel($label));
+    $this->assertSame($label, $this->sut->getLabel());
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php
index c350b48..83c4ebc 100644
--- a/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php
@@ -212,23 +212,30 @@ public function testgetOriginalClassUnchanged() {
   }
 
   /**
+   * @covers ::getClass
    * @covers ::setClass
    * @covers ::getOriginalClass
    */
   public function testgetOriginalClassChanged() {
-    $class = $this->randomMachineName();
-    $entity_type = $this->setUpEntityType(array('class' => $class));
-    $entity_type->setClass($this->randomMachineName());
-    $this->assertEquals($class, $entity_type->getOriginalClass());
+    $original_class = get_class($this->getMock('\Drupal\Core\Entity\EntityInterface'));
+    $new_class = get_class($this->getMock('\Drupal\Core\Entity\EntityInterface'));
+    $entity_type = $this->setUpEntityType(array('class' => $original_class));
+    $this->assertEquals($original_class, $entity_type->getClass());
+    $entity_type->setClass($new_class);
+    $this->assertEquals($original_class, $entity_type->getOriginalClass());
+    $this->assertEquals($new_class, $entity_type->getClass());
   }
 
   /**
    * @covers ::id
+   * @covers ::setId
+   * @covers ::getId
    */
   public function testId() {
     $id = $this->randomMachineName(32);
     $entity_type = $this->setUpEntityType(array('id' => $id));
     $this->assertEquals($id, $entity_type->id());
+    $this->assertEquals($id, $entity_type->getId());
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/Core/Plugin/CategorizingPluginManagerTraitTest.php b/core/tests/Drupal/Tests/Core/Plugin/CategorizingPluginManagerTraitTest.php
index db67ad7..c738e6e 100644
--- a/core/tests/Drupal/Tests/Core/Plugin/CategorizingPluginManagerTraitTest.php
+++ b/core/tests/Drupal/Tests/Core/Plugin/CategorizingPluginManagerTraitTest.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Plugin\CategorizingPluginManagerTrait;
 use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinitionDecorator;
 use Drupal\Tests\UnitTestCase;
 
 /**
@@ -22,7 +23,7 @@ class CategorizingPluginManagerTraitTest extends UnitTestCase {
   /**
    * The plugin manager to test.
    *
-   * @var \Drupal\Component\Plugin\CategorizingPluginManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+   * @var \Drupal\Tests\Core\Plugin\CategorizingPluginManager|\PHPUnit_Framework_MockObject_MockObject
    */
   protected $pluginManager;
 
@@ -128,14 +129,17 @@ public function __construct(ModuleHandlerInterface $module_handler) {
   public function getDefinitions() {
     return [
       'cucumber' => [
+        'id' => 'cucumber',
         'label' => 'cucumber',
         'category' => 'vegetables',
       ],
       'apple' => [
+        'id' => 'apple',
         'label' => 'apple',
         'category' => 'fruits',
       ],
       'mango' => [
+        'id' => 'mango',
         'label' => 'mango',
         'category' => 'fruits',
       ],
@@ -147,7 +151,9 @@ public function getDefinitions() {
    */
   public function processDefinition(&$definition, $plugin_id) {
     parent::processDefinition($definition, $plugin_id);
-    $this->processDefinitionCategory($definition);
+    $object_definition = ArrayPluginDefinitionDecorator::ensureObjectDefinition($definition);
+    $this->processDefinitionCategory($object_definition);
+    $definition = $object_definition->getA
   }
 
 }
diff --git a/core/tests/Drupal/Tests/Core/Plugin/Definition/ArrayPluginDefinitionDecoratorTest.php b/core/tests/Drupal/Tests/Core/Plugin/Definition/ArrayPluginDefinitionDecoratorTest.php
new file mode 100644
index 0000000..7d3f2f0
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Plugin/Definition/ArrayPluginDefinitionDecoratorTest.php
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Plugin\Definition\ArrayPluginDefinitionDecoratorTest.
+ */
+
+namespace Drupal\Tests\Core\Plugin\Definition;
+
+use Drupal\Core\Plugin\Definition\ArrayPluginDefinitionDecorator;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Plugin\Definition\ArrayPluginDefinitionDecorator
+ *
+ * @group Plugin
+ */
+class ArrayPluginDefinitionDecoratorTest extends UnitTestCase {
+
+  /**
+   * The decorated array plugin definition.
+   *
+   * @var mixed[]
+   */
+  protected $arrayDefinition = [];
+
+  /**
+   * The class under test.
+   *
+   * @var \Drupal\Core\Plugin\Definition\ArrayPluginDefinitionDecorator
+   */
+  protected $sut;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->sut = new ArrayPluginDefinitionDecorator($this->arrayDefinition);
+  }
+
+  /**
+   * @covers ::setCategory
+   * @covers ::getCategory
+   */
+  public function testGetCategory() {
+    $category = $this->randomMachineName();
+
+    $this->assertSame($this->sut, $this->sut->setCategory($category));
+    $this->assertSame($category, $this->sut->getCategory());
+    $this->assertSame($category, $this->arrayDefinition['category']);
+  }
+
+  /**
+   * @covers ::setConfigDependencies
+   * @covers ::getConfigDependencies
+   */
+  public function testGetConfigDependencies() {
+    $dependencies = [
+      'module' => [$this->randomMachineName()],
+    ];
+
+    $this->assertSame($this->sut, $this->sut->setConfigDependencies($dependencies));
+    $this->assertSame($dependencies, $this->sut->getConfigDependencies());
+    $this->assertSame($dependencies, $this->arrayDefinition['config_dependencies']);
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Plugin/Definition/PluginDefinitionTest.php b/core/tests/Drupal/Tests/Core/Plugin/Definition/PluginDefinitionTest.php
new file mode 100644
index 0000000..9cf5e3d
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Plugin/Definition/PluginDefinitionTest.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Plugin\Definition\PluginDefinitionTest.
+ */
+
+namespace Drupal\Tests\Core\Plugin\Definition;
+
+use Drupal\Core\Plugin\Definition\PluginDefinition;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Plugin\Definition\PluginDefinition
+ *
+ * @group Plugin
+ */
+class PluginDefinitionTest extends UnitTestCase {
+
+  /**
+   * The class under test.
+   *
+   * @var \Drupal\Core\Plugin\Definition\PluginDefinition
+   */
+  protected $sut;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->sut = new PluginDefinition();
+  }
+
+  /**
+   * @covers ::setProvider
+   * @covers ::getProvider
+   */
+  public function testGetProvider() {
+    $provider = $this->randomMachineName();
+
+    $this->assertSame($this->sut, $this->sut->setProvider($provider));
+    $this->assertSame($provider, $this->sut->getProvider());
+  }
+
+}
