DerivativeDiscoveryDecorator.php 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. <?php
  2. namespace Drupal\Component\Plugin\Discovery;
  3. use Drupal\Component\Plugin\Definition\DerivablePluginDefinitionInterface;
  4. use Drupal\Component\Plugin\Exception\InvalidDeriverException;
  5. /**
  6. * Base class providing the tools for a plugin discovery to be derivative aware.
  7. *
  8. * Provides a decorator that allows the use of plugin derivatives for normal
  9. * implementations DiscoveryInterface.
  10. */
  11. class DerivativeDiscoveryDecorator implements DiscoveryInterface {
  12. use DiscoveryTrait;
  13. /**
  14. * Plugin derivers.
  15. *
  16. * @var \Drupal\Component\Plugin\Derivative\DeriverInterface[]
  17. * Keys are base plugin IDs.
  18. */
  19. protected $derivers = [];
  20. /**
  21. * The decorated plugin discovery.
  22. *
  23. * @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface
  24. */
  25. protected $decorated;
  26. /**
  27. * Creates a new instance.
  28. *
  29. * @param \Drupal\Component\Plugin\Discovery\DiscoveryInterface $decorated
  30. * The parent object implementing DiscoveryInterface that is being
  31. * decorated.
  32. */
  33. public function __construct(DiscoveryInterface $decorated) {
  34. $this->decorated = $decorated;
  35. }
  36. /**
  37. * {@inheritdoc}
  38. *
  39. * @throws \Drupal\Component\Plugin\Exception\InvalidDeriverException
  40. * Thrown if the 'deriver' class specified in the plugin definition
  41. * does not implement \Drupal\Component\Plugin\Derivative\DeriverInterface.
  42. */
  43. public function getDefinition($plugin_id, $exception_on_invalid = TRUE) {
  44. // This check is only for derivative plugins that have explicitly provided
  45. // an ID. This is not common, and can be expected to fail. Therefore, opt
  46. // out of the thrown exception, which will be handled when checking the
  47. // $base_plugin_id.
  48. $plugin_definition = $this->decorated->getDefinition($plugin_id, FALSE);
  49. list($base_plugin_id, $derivative_id) = $this->decodePluginId($plugin_id);
  50. $base_plugin_definition = $this->decorated->getDefinition($base_plugin_id, $exception_on_invalid);
  51. if ($base_plugin_definition) {
  52. $deriver = $this->getDeriver($base_plugin_id, $base_plugin_definition);
  53. if ($deriver) {
  54. $derivative_plugin_definition = $deriver->getDerivativeDefinition($derivative_id, $base_plugin_definition);
  55. // If a plugin defined itself as a derivative, merge in possible
  56. // defaults from the derivative.
  57. if ($derivative_id && isset($plugin_definition)) {
  58. $plugin_definition = $this->mergeDerivativeDefinition($plugin_definition, $derivative_plugin_definition);
  59. }
  60. else {
  61. $plugin_definition = $derivative_plugin_definition;
  62. }
  63. }
  64. }
  65. return $plugin_definition;
  66. }
  67. /**
  68. * {@inheritdoc}
  69. *
  70. * @throws \Drupal\Component\Plugin\Exception\InvalidDeriverException
  71. * Thrown if the 'deriver' class specified in the plugin definition
  72. * does not implement \Drupal\Component\Plugin\Derivative\DeriverInterface.
  73. */
  74. public function getDefinitions() {
  75. $plugin_definitions = $this->decorated->getDefinitions();
  76. return $this->getDerivatives($plugin_definitions);
  77. }
  78. /**
  79. * Adds derivatives to a list of plugin definitions.
  80. *
  81. * This should be called by the class extending this in
  82. * DiscoveryInterface::getDefinitions().
  83. */
  84. protected function getDerivatives(array $base_plugin_definitions) {
  85. $plugin_definitions = [];
  86. foreach ($base_plugin_definitions as $base_plugin_id => $plugin_definition) {
  87. $deriver = $this->getDeriver($base_plugin_id, $plugin_definition);
  88. if ($deriver) {
  89. $derivative_definitions = $deriver->getDerivativeDefinitions($plugin_definition);
  90. foreach ($derivative_definitions as $derivative_id => $derivative_definition) {
  91. $plugin_id = $this->encodePluginId($base_plugin_id, $derivative_id);
  92. // Use this definition as defaults if a plugin already defined
  93. // itself as this derivative.
  94. if ($derivative_id && isset($base_plugin_definitions[$plugin_id])) {
  95. $derivative_definition = $this->mergeDerivativeDefinition($base_plugin_definitions[$plugin_id], $derivative_definition);
  96. }
  97. $plugin_definitions[$plugin_id] = $derivative_definition;
  98. }
  99. }
  100. // If a plugin already defined itself as a derivative it might already
  101. // be merged into the definitions.
  102. elseif (!isset($plugin_definitions[$base_plugin_id])) {
  103. $plugin_definitions[$base_plugin_id] = $plugin_definition;
  104. }
  105. }
  106. return $plugin_definitions;
  107. }
  108. /**
  109. * Decodes derivative id and plugin id from a string.
  110. *
  111. * @param string $plugin_id
  112. * Plugin identifier that may point to a derivative plugin.
  113. *
  114. * @return array
  115. * An array with the base plugin id as the first index and the derivative id
  116. * as the second. If there is no derivative id it will be null.
  117. */
  118. protected function decodePluginId($plugin_id) {
  119. // Try and split the passed plugin definition into a plugin and a
  120. // derivative id. We don't need to check for !== FALSE because a leading
  121. // colon would break the derivative system and doesn't makes sense.
  122. if (strpos($plugin_id, ':')) {
  123. return explode(':', $plugin_id, 2);
  124. }
  125. return [$plugin_id, NULL];
  126. }
  127. /**
  128. * Encodes plugin and derivative id's into a string.
  129. *
  130. * @param string $base_plugin_id
  131. * The base plugin identifier.
  132. * @param string $derivative_id
  133. * The derivative identifier.
  134. *
  135. * @return string
  136. * A uniquely encoded combination of the $base_plugin_id and $derivative_id.
  137. */
  138. protected function encodePluginId($base_plugin_id, $derivative_id) {
  139. if ($derivative_id) {
  140. return "$base_plugin_id:$derivative_id";
  141. }
  142. // By returning the unmerged plugin_id, we are able to support derivative
  143. // plugins that support fetching the base definitions.
  144. return $base_plugin_id;
  145. }
  146. /**
  147. * Gets a deriver for a base plugin.
  148. *
  149. * @param string $base_plugin_id
  150. * The base plugin id of the plugin.
  151. * @param mixed $base_definition
  152. * The base plugin definition to build derivatives.
  153. *
  154. * @return \Drupal\Component\Plugin\Derivative\DeriverInterface|null
  155. * A DerivativeInterface or NULL if none exists for the plugin.
  156. *
  157. * @throws \Drupal\Component\Plugin\Exception\InvalidDeriverException
  158. * Thrown if the 'deriver' class specified in the plugin definition
  159. * does not implement \Drupal\Component\Plugin\Derivative\DeriverInterface.
  160. */
  161. protected function getDeriver($base_plugin_id, $base_definition) {
  162. if (!isset($this->derivers[$base_plugin_id])) {
  163. $this->derivers[$base_plugin_id] = FALSE;
  164. $class = $this->getDeriverClass($base_definition);
  165. if ($class) {
  166. $this->derivers[$base_plugin_id] = new $class($base_plugin_id);
  167. }
  168. }
  169. return $this->derivers[$base_plugin_id] ?: NULL;
  170. }
  171. /**
  172. * Gets the deriver class name from the base plugin definition.
  173. *
  174. * @param array $base_definition
  175. * The base plugin definition to build derivatives.
  176. *
  177. * @return string|null
  178. * The name of a class implementing
  179. * \Drupal\Component\Plugin\Derivative\DeriverInterface.
  180. *
  181. * @throws \Drupal\Component\Plugin\Exception\InvalidDeriverException
  182. * Thrown if the 'deriver' class specified in the plugin definition
  183. * does not implement
  184. * \Drupal\Component\Plugin\Derivative\DerivativeInterface.
  185. */
  186. protected function getDeriverClass($base_definition) {
  187. $class = NULL;
  188. $id = NULL;
  189. if ($base_definition instanceof DerivablePluginDefinitionInterface) {
  190. $class = $base_definition->getDeriver();
  191. $id = $base_definition->id();
  192. }
  193. if ((is_array($base_definition) || ($base_definition = (array) $base_definition)) && (isset($base_definition['deriver']))) {
  194. $class = $base_definition['deriver'];
  195. $id = $base_definition['id'];
  196. }
  197. if ($class) {
  198. if (!class_exists($class)) {
  199. throw new InvalidDeriverException(sprintf('Plugin (%s) deriver "%s" does not exist.', $id, $class));
  200. }
  201. if (!is_subclass_of($class, '\Drupal\Component\Plugin\Derivative\DeriverInterface')) {
  202. throw new InvalidDeriverException(sprintf('Plugin (%s) deriver "%s" must implement \Drupal\Component\Plugin\Derivative\DeriverInterface.', $id, $class));
  203. }
  204. }
  205. return $class;
  206. }
  207. /**
  208. * Merges a base and derivative definition, taking into account empty values.
  209. *
  210. * @param array $base_plugin_definition
  211. * The base plugin definition.
  212. * @param array $derivative_definition
  213. * The derivative plugin definition.
  214. *
  215. * @return array
  216. * The merged definition.
  217. */
  218. protected function mergeDerivativeDefinition($base_plugin_definition, $derivative_definition) {
  219. // Use this definition as defaults if a plugin already defined itself as
  220. // this derivative, but filter out empty values first.
  221. $filtered_base = array_filter($base_plugin_definition);
  222. $derivative_definition = $filtered_base + ($derivative_definition ?: []);
  223. // Add back any empty keys that the derivative didn't have.
  224. return $derivative_definition + $base_plugin_definition;
  225. }
  226. /**
  227. * Passes through all unknown calls onto the decorated object.
  228. */
  229. public function __call($method, $args) {
  230. return call_user_func_array([$this->decorated, $method], $args);
  231. }
  232. }