3 namespace Drupal\Component\Plugin\Discovery;
5 use Drupal\Component\Plugin\Definition\DerivablePluginDefinitionInterface;
6 use Drupal\Component\Plugin\Exception\InvalidDeriverException;
9 * Base class providing the tools for a plugin discovery to be derivative aware.
11 * Provides a decorator that allows the use of plugin derivatives for normal
12 * implementations DiscoveryInterface.
14 class DerivativeDiscoveryDecorator implements DiscoveryInterface {
21 * @var \Drupal\Component\Plugin\Derivative\DeriverInterface[]
22 * Keys are base plugin IDs.
24 protected $derivers = [];
27 * The decorated plugin discovery.
29 * @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface
34 * Creates a new instance.
36 * @param \Drupal\Component\Plugin\Discovery\DiscoveryInterface $decorated
37 * The parent object implementing DiscoveryInterface that is being
40 public function __construct(DiscoveryInterface $decorated) {
41 $this->decorated = $decorated;
47 * @throws \Drupal\Component\Plugin\Exception\InvalidDeriverException
48 * Thrown if the 'deriver' class specified in the plugin definition
49 * does not implement \Drupal\Component\Plugin\Derivative\DeriverInterface.
51 public function getDefinition($plugin_id, $exception_on_invalid = TRUE) {
52 // This check is only for derivative plugins that have explicitly provided
53 // an ID. This is not common, and can be expected to fail. Therefore, opt
54 // out of the thrown exception, which will be handled when checking the
56 $plugin_definition = $this->decorated->getDefinition($plugin_id, FALSE);
58 list($base_plugin_id, $derivative_id) = $this->decodePluginId($plugin_id);
59 $base_plugin_definition = $this->decorated->getDefinition($base_plugin_id, $exception_on_invalid);
60 if ($base_plugin_definition) {
61 $deriver = $this->getDeriver($base_plugin_id, $base_plugin_definition);
63 $derivative_plugin_definition = $deriver->getDerivativeDefinition($derivative_id, $base_plugin_definition);
64 // If a plugin defined itself as a derivative, merge in possible
65 // defaults from the derivative.
66 if ($derivative_id && isset($plugin_definition)) {
67 $plugin_definition = $this->mergeDerivativeDefinition($plugin_definition, $derivative_plugin_definition);
70 $plugin_definition = $derivative_plugin_definition;
75 return $plugin_definition;
81 * @throws \Drupal\Component\Plugin\Exception\InvalidDeriverException
82 * Thrown if the 'deriver' class specified in the plugin definition
83 * does not implement \Drupal\Component\Plugin\Derivative\DeriverInterface.
85 public function getDefinitions() {
86 $plugin_definitions = $this->decorated->getDefinitions();
87 return $this->getDerivatives($plugin_definitions);
91 * Adds derivatives to a list of plugin definitions.
93 * This should be called by the class extending this in
94 * DiscoveryInterface::getDefinitions().
96 protected function getDerivatives(array $base_plugin_definitions) {
97 $plugin_definitions = [];
98 foreach ($base_plugin_definitions as $base_plugin_id => $plugin_definition) {
99 $deriver = $this->getDeriver($base_plugin_id, $plugin_definition);
101 $derivative_definitions = $deriver->getDerivativeDefinitions($plugin_definition);
102 foreach ($derivative_definitions as $derivative_id => $derivative_definition) {
103 $plugin_id = $this->encodePluginId($base_plugin_id, $derivative_id);
104 // Use this definition as defaults if a plugin already defined
105 // itself as this derivative.
106 if ($derivative_id && isset($base_plugin_definitions[$plugin_id])) {
107 $derivative_definition = $this->mergeDerivativeDefinition($base_plugin_definitions[$plugin_id], $derivative_definition);
109 $plugin_definitions[$plugin_id] = $derivative_definition;
112 // If a plugin already defined itself as a derivative it might already
113 // be merged into the definitions.
114 elseif (!isset($plugin_definitions[$base_plugin_id])) {
115 $plugin_definitions[$base_plugin_id] = $plugin_definition;
119 return $plugin_definitions;
123 * Decodes derivative id and plugin id from a string.
125 * @param string $plugin_id
126 * Plugin identifier that may point to a derivative plugin.
129 * An array with the base plugin id as the first index and the derivative id
130 * as the second. If there is no derivative id it will be null.
132 protected function decodePluginId($plugin_id) {
133 // Try and split the passed plugin definition into a plugin and a
134 // derivative id. We don't need to check for !== FALSE because a leading
135 // colon would break the derivative system and doesn't makes sense.
136 if (strpos($plugin_id, ':')) {
137 return explode(':', $plugin_id, 2);
140 return [$plugin_id, NULL];
144 * Encodes plugin and derivative id's into a string.
146 * @param string $base_plugin_id
147 * The base plugin identifier.
148 * @param string $derivative_id
149 * The derivative identifier.
152 * A uniquely encoded combination of the $base_plugin_id and $derivative_id.
154 protected function encodePluginId($base_plugin_id, $derivative_id) {
155 if ($derivative_id) {
156 return "$base_plugin_id:$derivative_id";
159 // By returning the unmerged plugin_id, we are able to support derivative
160 // plugins that support fetching the base definitions.
161 return $base_plugin_id;
165 * Gets a deriver for a base plugin.
167 * @param string $base_plugin_id
168 * The base plugin id of the plugin.
169 * @param mixed $base_definition
170 * The base plugin definition to build derivatives.
172 * @return \Drupal\Component\Plugin\Derivative\DeriverInterface|null
173 * A DerivativeInterface or NULL if none exists for the plugin.
175 * @throws \Drupal\Component\Plugin\Exception\InvalidDeriverException
176 * Thrown if the 'deriver' class specified in the plugin definition
177 * does not implement \Drupal\Component\Plugin\Derivative\DeriverInterface.
179 protected function getDeriver($base_plugin_id, $base_definition) {
180 if (!isset($this->derivers[$base_plugin_id])) {
181 $this->derivers[$base_plugin_id] = FALSE;
182 $class = $this->getDeriverClass($base_definition);
184 $this->derivers[$base_plugin_id] = new $class($base_plugin_id);
187 return $this->derivers[$base_plugin_id] ?: NULL;
191 * Gets the deriver class name from the base plugin definition.
193 * @param array $base_definition
194 * The base plugin definition to build derivatives.
196 * @return string|null
197 * The name of a class implementing
198 * \Drupal\Component\Plugin\Derivative\DeriverInterface.
200 * @throws \Drupal\Component\Plugin\Exception\InvalidDeriverException
201 * Thrown if the 'deriver' class specified in the plugin definition
203 * \Drupal\Component\Plugin\Derivative\DerivativeInterface.
205 protected function getDeriverClass($base_definition) {
208 if ($base_definition instanceof DerivablePluginDefinitionInterface) {
209 $class = $base_definition->getDeriver();
210 $id = $base_definition->id();
212 if ((is_array($base_definition) || ($base_definition = (array) $base_definition)) && (isset($base_definition['deriver']))) {
213 $class = $base_definition['deriver'];
214 $id = $base_definition['id'];
217 if (!class_exists($class)) {
218 throw new InvalidDeriverException(sprintf('Plugin (%s) deriver "%s" does not exist.', $id, $class));
220 if (!is_subclass_of($class, '\Drupal\Component\Plugin\Derivative\DeriverInterface')) {
221 throw new InvalidDeriverException(sprintf('Plugin (%s) deriver "%s" must implement \Drupal\Component\Plugin\Derivative\DeriverInterface.', $id, $class));
228 * Merges a base and derivative definition, taking into account empty values.
230 * @param array $base_plugin_definition
231 * The base plugin definition.
232 * @param array $derivative_definition
233 * The derivative plugin definition.
236 * The merged definition.
238 protected function mergeDerivativeDefinition($base_plugin_definition, $derivative_definition) {
239 // Use this definition as defaults if a plugin already defined itself as
240 // this derivative, but filter out empty values first.
241 $filtered_base = array_filter($base_plugin_definition);
242 $derivative_definition = $filtered_base + ($derivative_definition ?: []);
243 // Add back any empty keys that the derivative didn't have.
244 return $derivative_definition + $base_plugin_definition;
248 * Passes through all unknown calls onto the decorated object.
250 public function __call($method, $args) {
251 return call_user_func_array([$this->decorated, $method], $args);