/core/lib/Drupal/Component/Plugin/Discovery/DerivativeDiscoveryDecorator.php

https://gitlab.com/reasonat/test8 · PHP · 244 lines · 95 code · 22 blank · 127 comment · 18 complexity · ea0b34c09ab2334d73dd9aef5b1096b5 MD5 · raw file

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