Further Drupal 8.6.4 changes. Some core files were not committed before a commit...
[yaffs-website] / web / core / lib / Drupal / Component / Plugin / Discovery / DerivativeDiscoveryDecorator.php
1 <?php
2
3 namespace Drupal\Component\Plugin\Discovery;
4
5 use Drupal\Component\Plugin\Definition\DerivablePluginDefinitionInterface;
6 use Drupal\Component\Plugin\Exception\InvalidDeriverException;
7
8 /**
9  * Base class providing the tools for a plugin discovery to be derivative aware.
10  *
11  * Provides a decorator that allows the use of plugin derivatives for normal
12  * implementations DiscoveryInterface.
13  */
14 class DerivativeDiscoveryDecorator implements DiscoveryInterface {
15
16   use DiscoveryTrait;
17
18   /**
19    * Plugin derivers.
20    *
21    * @var \Drupal\Component\Plugin\Derivative\DeriverInterface[]
22    *   Keys are base plugin IDs.
23    */
24   protected $derivers = [];
25
26   /**
27    * The decorated plugin discovery.
28    *
29    * @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface
30    */
31   protected $decorated;
32
33   /**
34    * Creates a new instance.
35    *
36    * @param \Drupal\Component\Plugin\Discovery\DiscoveryInterface $decorated
37    *   The parent object implementing DiscoveryInterface that is being
38    *   decorated.
39    */
40   public function __construct(DiscoveryInterface $decorated) {
41     $this->decorated = $decorated;
42   }
43
44   /**
45    * {@inheritdoc}
46    *
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.
50    */
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
55     // $base_plugin_id.
56     $plugin_definition = $this->decorated->getDefinition($plugin_id, FALSE);
57
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);
62       if ($deriver) {
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);
68         }
69         else {
70           $plugin_definition = $derivative_plugin_definition;
71         }
72       }
73     }
74
75     return $plugin_definition;
76   }
77
78   /**
79    * {@inheritdoc}
80    *
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.
84    */
85   public function getDefinitions() {
86     $plugin_definitions = $this->decorated->getDefinitions();
87     return $this->getDerivatives($plugin_definitions);
88   }
89
90   /**
91    * Adds derivatives to a list of plugin definitions.
92    *
93    * This should be called by the class extending this in
94    * DiscoveryInterface::getDefinitions().
95    */
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);
100       if ($deriver) {
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);
108           }
109           $plugin_definitions[$plugin_id] = $derivative_definition;
110         }
111       }
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;
116       }
117     }
118
119     return $plugin_definitions;
120   }
121
122   /**
123    * Decodes derivative id and plugin id from a string.
124    *
125    * @param string $plugin_id
126    *   Plugin identifier that may point to a derivative plugin.
127    *
128    * @return array
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.
131    */
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);
138     }
139
140     return [$plugin_id, NULL];
141   }
142
143   /**
144    * Encodes plugin and derivative id's into a string.
145    *
146    * @param string $base_plugin_id
147    *   The base plugin identifier.
148    * @param string $derivative_id
149    *   The derivative identifier.
150    *
151    * @return string
152    *   A uniquely encoded combination of the $base_plugin_id and $derivative_id.
153    */
154   protected function encodePluginId($base_plugin_id, $derivative_id) {
155     if ($derivative_id) {
156       return "$base_plugin_id:$derivative_id";
157     }
158
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;
162   }
163
164   /**
165    * Gets a deriver for a base plugin.
166    *
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.
171    *
172    * @return \Drupal\Component\Plugin\Derivative\DeriverInterface|null
173    *   A DerivativeInterface or NULL if none exists for the plugin.
174    *
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.
178    */
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);
183       if ($class) {
184         $this->derivers[$base_plugin_id] = new $class($base_plugin_id);
185       }
186     }
187     return $this->derivers[$base_plugin_id] ?: NULL;
188   }
189
190   /**
191    * Gets the deriver class name from the base plugin definition.
192    *
193    * @param array $base_definition
194    *   The base plugin definition to build derivatives.
195    *
196    * @return string|null
197    *   The name of a class implementing
198    *   \Drupal\Component\Plugin\Derivative\DeriverInterface.
199    *
200    * @throws \Drupal\Component\Plugin\Exception\InvalidDeriverException
201    *   Thrown if the 'deriver' class specified in the plugin definition
202    *   does not implement
203    *   \Drupal\Component\Plugin\Derivative\DerivativeInterface.
204    */
205   protected function getDeriverClass($base_definition) {
206     $class = NULL;
207     $id = NULL;
208     if ($base_definition instanceof DerivablePluginDefinitionInterface) {
209       $class = $base_definition->getDeriver();
210       $id = $base_definition->id();
211     }
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'];
215     }
216     if ($class) {
217       if (!class_exists($class)) {
218         throw new InvalidDeriverException(sprintf('Plugin (%s) deriver "%s" does not exist.', $id, $class));
219       }
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));
222       }
223     }
224     return $class;
225   }
226
227   /**
228    * Merges a base and derivative definition, taking into account empty values.
229    *
230    * @param array $base_plugin_definition
231    *   The base plugin definition.
232    * @param array $derivative_definition
233    *   The derivative plugin definition.
234    *
235    * @return array
236    *   The merged definition.
237    */
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;
245   }
246
247   /**
248    * Passes through all unknown calls onto the decorated object.
249    */
250   public function __call($method, $args) {
251     return call_user_func_array([$this->decorated, $method], $args);
252   }
253
254 }