Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
60.00% covered (warning)
60.00%
6 / 10
CRAP
92.16% covered (success)
92.16%
47 / 51
DerivativeDiscoveryDecorator
0.00% covered (danger)
0.00%
0 / 1
60.00% covered (warning)
60.00%
6 / 10
32.49
92.16% covered (success)
92.16%
47 / 51
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 getDefinition
0.00% covered (danger)
0.00%
0 / 1
5.02
90.91% covered (success)
90.91%
10 / 11
 getDefinitions
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 getDerivatives
100.00% covered (success)
100.00%
1 / 1
7
100.00% covered (success)
100.00%
13 / 13
 decodePluginId
0.00% covered (danger)
0.00%
0 / 1
2.15
66.67% covered (warning)
66.67%
2 / 3
 encodePluginId
0.00% covered (danger)
0.00%
0 / 1
2.15
66.67% covered (warning)
66.67%
2 / 3
 getDeriver
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
6 / 6
 getDeriverClass
100.00% covered (success)
100.00%
1 / 1
7
100.00% covered (success)
100.00%
7 / 7
 mergeDerivativeDefinition
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 __call
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
<?php
/**
 * @file
 * Contains \Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator.
 */
namespace Drupal\Component\Plugin\Discovery;
use Drupal\Component\Plugin\Exception\InvalidDeriverException;
/**
 * Base class providing the tools for a plugin discovery to be derivative aware.
 *
 * Provides a decorator that allows the use of plugin derivatives for normal
 * implementations DiscoveryInterface.
 */
class DerivativeDiscoveryDecorator implements DiscoveryInterface {
  use DiscoveryTrait;
  /**
   * Plugin derivers.
   *
   * @var \Drupal\Component\Plugin\Derivative\DeriverInterface[]
   *   Keys are base plugin IDs.
   */
  protected $derivers = array();
  /**
   * The decorated plugin discovery.
   *
   * @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface
   */
  protected $decorated;
  /**
   * Creates a new instance.
   *
   * @param \Drupal\Component\Plugin\Discovery\DiscoveryInterface $decorated
   *   The parent object implementing DiscoveryInterface that is being
   *   decorated.
   */
  public function __construct(DiscoveryInterface $decorated) {
    $this->decorated = $decorated;
  }
  /**
   * {@inheritdoc}
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidDeriverException
   *   Thrown if the 'deriver' class specified in the plugin definition
   *   does not implement \Drupal\Component\Plugin\Derivative\DeriverInterface.
   */
  public function getDefinition($plugin_id, $exception_on_invalid = TRUE) {
    // This check is only for derivative plugins that have explicitly provided
    // an ID. This is not common, and can be expected to fail. Therefore, opt
    // out of the thrown exception, which will be handled when checking the
    // $base_plugin_id.
    $plugin_definition = $this->decorated->getDefinition($plugin_id, FALSE);
    list($base_plugin_id, $derivative_id) = $this->decodePluginId($plugin_id);
    $base_plugin_definition = $this->decorated->getDefinition($base_plugin_id, $exception_on_invalid);
    if ($base_plugin_definition) {
      $deriver = $this->getDeriver($base_plugin_id, $base_plugin_definition);
      if ($deriver) {
        $derivative_plugin_definition = $deriver->getDerivativeDefinition($derivative_id, $base_plugin_definition);
        // 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);
        }
        else {
          $plugin_definition = $derivative_plugin_definition;
        }
      }
    }
    return $plugin_definition;
  }
  /**
   * {@inheritdoc}
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidDeriverException
   *   Thrown if the 'deriver' class specified in the plugin definition
   *   does not implement \Drupal\Component\Plugin\Derivative\DeriverInterface.
   */
  public function getDefinitions() {
    $plugin_definitions = $this->decorated->getDefinitions();
    return $this->getDerivatives($plugin_definitions);
  }
  /**
   * Adds derivatives to a list of plugin definitions.
   *
   * This should be called by the class extending this in
   * DiscoveryInterface::getDefinitions().
   */
  protected function getDerivatives(array $base_plugin_definitions) {
    $plugin_definitions = array();
    foreach ($base_plugin_definitions as $base_plugin_id => $plugin_definition) {
      $deriver = $this->getDeriver($base_plugin_id, $plugin_definition);
      if ($deriver) {
        $derivative_definitions = $deriver->getDerivativeDefinitions($plugin_definition);
        foreach ($derivative_definitions as $derivative_id => $derivative_definition) {
          $plugin_id = $this->encodePluginId($base_plugin_id, $derivative_id);
          // 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);
          }
          $plugin_definitions[$plugin_id] = $derivative_definition;
        }
      }
      // If a plugin already defined itself as a derivative it might already
      // be merged into the definitions.
      elseif (!isset($plugin_definitions[$base_plugin_id])) {
        $plugin_definitions[$base_plugin_id] = $plugin_definition;
      }
    }
    return $plugin_definitions;
  }
  /**
   * Decodes derivative id and plugin id from a string.
   *
   * @param string $plugin_id
   *   Plugin identifier that may point to a derivative plugin.
   *
   * @return array
   *   An array with the base plugin id as the first index and the derivative id
   *   as the second. If there is no derivative id it will be null.
   */
  protected function decodePluginId($plugin_id) {
    // Try and split the passed plugin definition into a plugin and a
    // derivative id. We don't need to check for !== FALSE because a leading
    // colon would break the derivative system and doesn't makes sense.
    if (strpos($plugin_id, ':')) {
      return explode(':', $plugin_id, 2);
    }
    return array($plugin_id, NULL);
  }
  /**
   * Encodes plugin and derivative id's into a string.
   *
   * @param string $base_plugin_id
   *   The base plugin identifier.
   * @param string $derivative_id
   *   The derivative identifier.
   *
   * @return string
   *   A uniquely encoded combination of the $base_plugin_id and $derivative_id.
   */
  protected function encodePluginId($base_plugin_id, $derivative_id) {
    if ($derivative_id) {
      return "$base_plugin_id:$derivative_id";
    }
    // By returning the unmerged plugin_id, we are able to support derivative
    // plugins that support fetching the base definitions.
    return $base_plugin_id;
  }
  /**
   * Gets a deriver for a base plugin.
   *
   * @param string $base_plugin_id
   *   The base plugin id of the plugin.
   * @param mixed $base_definition
   *   The base plugin definition to build derivatives.
   *
   * @return \Drupal\Component\Plugin\Derivative\DeriverInterface|null
   *   A DerivativeInterface or NULL if none exists for the plugin.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidDeriverException
   *   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) {
    if (!isset($this->derivers[$base_plugin_id])) {
      $this->derivers[$base_plugin_id] = FALSE;
      $class = $this->getDeriverClass($base_definition);
      if ($class) {
        $this->derivers[$base_plugin_id] = new $class($base_plugin_id);
      }
    }
    return $this->derivers[$base_plugin_id] ?: NULL;
  }
  /**
   * 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);
  }
}