annotate core/lib/Drupal/Core/Plugin/DefaultPluginManager.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\Core\Plugin;
Chris@0 4
Chris@14 5 use Drupal\Component\Assertion\Inspector;
Chris@0 6 use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
Chris@0 7 use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface;
Chris@0 8 use Drupal\Core\Cache\CacheableDependencyInterface;
Chris@0 9 use Drupal\Core\Cache\CacheBackendInterface;
Chris@0 10 use Drupal\Core\Cache\UseCacheBackendTrait;
Chris@0 11 use Drupal\Component\Plugin\Discovery\DiscoveryCachedTrait;
Chris@0 12 use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
Chris@0 13 use Drupal\Component\Plugin\PluginManagerBase;
Chris@0 14 use Drupal\Component\Plugin\PluginManagerInterface;
Chris@0 15 use Drupal\Component\Utility\NestedArray;
Chris@0 16 use Drupal\Core\Cache\Cache;
Chris@0 17 use Drupal\Core\Extension\ModuleHandlerInterface;
Chris@0 18 use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
Chris@0 19 use Drupal\Core\Plugin\Factory\ContainerFactory;
Chris@0 20
Chris@0 21 /**
Chris@0 22 * Base class for plugin managers.
Chris@0 23 *
Chris@0 24 * @ingroup plugin_api
Chris@0 25 */
Chris@0 26 class DefaultPluginManager extends PluginManagerBase implements PluginManagerInterface, CachedDiscoveryInterface, CacheableDependencyInterface {
Chris@0 27
Chris@0 28 use DiscoveryCachedTrait;
Chris@0 29 use UseCacheBackendTrait;
Chris@0 30
Chris@0 31 /**
Chris@0 32 * The cache key.
Chris@0 33 *
Chris@0 34 * @var string
Chris@0 35 */
Chris@0 36 protected $cacheKey;
Chris@0 37
Chris@0 38 /**
Chris@0 39 * An array of cache tags to use for the cached definitions.
Chris@0 40 *
Chris@0 41 * @var array
Chris@0 42 */
Chris@0 43 protected $cacheTags = [];
Chris@0 44
Chris@0 45 /**
Chris@0 46 * Name of the alter hook if one should be invoked.
Chris@0 47 *
Chris@0 48 * @var string
Chris@0 49 */
Chris@0 50 protected $alterHook;
Chris@0 51
Chris@0 52 /**
Chris@0 53 * The subdirectory within a namespace to look for plugins, or FALSE if the
Chris@0 54 * plugins are in the top level of the namespace.
Chris@0 55 *
Chris@0 56 * @var string|bool
Chris@0 57 */
Chris@0 58 protected $subdir;
Chris@0 59
Chris@0 60 /**
Chris@0 61 * The module handler to invoke the alter hook.
Chris@0 62 *
Chris@0 63 * @var \Drupal\Core\Extension\ModuleHandlerInterface
Chris@0 64 */
Chris@0 65 protected $moduleHandler;
Chris@0 66
Chris@0 67 /**
Chris@0 68 * A set of defaults to be referenced by $this->processDefinition() if
Chris@0 69 * additional processing of plugins is necessary or helpful for development
Chris@0 70 * purposes.
Chris@0 71 *
Chris@0 72 * @var array
Chris@0 73 */
Chris@0 74 protected $defaults = [];
Chris@0 75
Chris@0 76 /**
Chris@0 77 * The name of the annotation that contains the plugin definition.
Chris@0 78 *
Chris@0 79 * @var string
Chris@0 80 */
Chris@0 81 protected $pluginDefinitionAnnotationName;
Chris@0 82
Chris@0 83 /**
Chris@0 84 * The interface each plugin should implement.
Chris@0 85 *
Chris@0 86 * @var string|null
Chris@0 87 */
Chris@0 88 protected $pluginInterface;
Chris@0 89
Chris@0 90 /**
Chris@0 91 * An object that implements \Traversable which contains the root paths
Chris@0 92 * keyed by the corresponding namespace to look for plugin implementations.
Chris@0 93 *
Chris@0 94 * @var \Traversable
Chris@0 95 */
Chris@0 96 protected $namespaces;
Chris@0 97
Chris@0 98 /**
Chris@0 99 * Additional namespaces the annotation discovery mechanism should scan for
Chris@0 100 * annotation definitions.
Chris@0 101 *
Chris@0 102 * @var string[]
Chris@0 103 */
Chris@0 104 protected $additionalAnnotationNamespaces = [];
Chris@0 105
Chris@0 106 /**
Chris@0 107 * Creates the discovery object.
Chris@0 108 *
Chris@0 109 * @param string|bool $subdir
Chris@0 110 * The plugin's subdirectory, for example Plugin/views/filter.
Chris@0 111 * @param \Traversable $namespaces
Chris@0 112 * An object that implements \Traversable which contains the root paths
Chris@0 113 * keyed by the corresponding namespace to look for plugin implementations.
Chris@0 114 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
Chris@0 115 * The module handler.
Chris@0 116 * @param string|null $plugin_interface
Chris@0 117 * (optional) The interface each plugin should implement.
Chris@0 118 * @param string $plugin_definition_annotation_name
Chris@0 119 * (optional) The name of the annotation that contains the plugin definition.
Chris@0 120 * Defaults to 'Drupal\Component\Annotation\Plugin'.
Chris@0 121 * @param string[] $additional_annotation_namespaces
Chris@0 122 * (optional) Additional namespaces to scan for annotation definitions.
Chris@0 123 */
Chris@0 124 public function __construct($subdir, \Traversable $namespaces, ModuleHandlerInterface $module_handler, $plugin_interface = NULL, $plugin_definition_annotation_name = 'Drupal\Component\Annotation\Plugin', array $additional_annotation_namespaces = []) {
Chris@0 125 $this->subdir = $subdir;
Chris@0 126 $this->namespaces = $namespaces;
Chris@0 127 $this->pluginDefinitionAnnotationName = $plugin_definition_annotation_name;
Chris@0 128 $this->pluginInterface = $plugin_interface;
Chris@0 129 $this->moduleHandler = $module_handler;
Chris@0 130 $this->additionalAnnotationNamespaces = $additional_annotation_namespaces;
Chris@0 131 }
Chris@0 132
Chris@0 133 /**
Chris@0 134 * Initialize the cache backend.
Chris@0 135 *
Chris@0 136 * Plugin definitions are cached using the provided cache backend.
Chris@0 137 *
Chris@0 138 * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
Chris@0 139 * Cache backend instance to use.
Chris@0 140 * @param string $cache_key
Chris@0 141 * Cache key prefix to use.
Chris@0 142 * @param array $cache_tags
Chris@0 143 * (optional) When providing a list of cache tags, the cached plugin
Chris@0 144 * definitions are tagged with the provided cache tags. These cache tags can
Chris@0 145 * then be used to clear the corresponding cached plugin definitions. Note
Chris@0 146 * that this should be used with care! For clearing all cached plugin
Chris@0 147 * definitions of a plugin manager, call that plugin manager's
Chris@0 148 * clearCachedDefinitions() method. Only use cache tags when cached plugin
Chris@0 149 * definitions should be cleared along with other, related cache entries.
Chris@0 150 */
Chris@0 151 public function setCacheBackend(CacheBackendInterface $cache_backend, $cache_key, array $cache_tags = []) {
Chris@14 152 assert(Inspector::assertAllStrings($cache_tags), 'Cache Tags must be strings.');
Chris@0 153 $this->cacheBackend = $cache_backend;
Chris@0 154 $this->cacheKey = $cache_key;
Chris@0 155 $this->cacheTags = $cache_tags;
Chris@0 156 }
Chris@0 157
Chris@0 158 /**
Chris@14 159 * Sets the alter hook name.
Chris@0 160 *
Chris@0 161 * @param string $alter_hook
Chris@0 162 * Name of the alter hook; for example, to invoke
Chris@0 163 * hook_mymodule_data_alter() pass in "mymodule_data".
Chris@0 164 */
Chris@0 165 protected function alterInfo($alter_hook) {
Chris@0 166 $this->alterHook = $alter_hook;
Chris@0 167 }
Chris@0 168
Chris@0 169 /**
Chris@0 170 * {@inheritdoc}
Chris@0 171 */
Chris@0 172 public function getDefinitions() {
Chris@0 173 $definitions = $this->getCachedDefinitions();
Chris@0 174 if (!isset($definitions)) {
Chris@0 175 $definitions = $this->findDefinitions();
Chris@0 176 $this->setCachedDefinitions($definitions);
Chris@0 177 }
Chris@0 178 return $definitions;
Chris@0 179 }
Chris@0 180
Chris@0 181 /**
Chris@0 182 * {@inheritdoc}
Chris@0 183 */
Chris@0 184 public function clearCachedDefinitions() {
Chris@0 185 if ($this->cacheBackend) {
Chris@0 186 if ($this->cacheTags) {
Chris@0 187 // Use the cache tags to clear the cache.
Chris@0 188 Cache::invalidateTags($this->cacheTags);
Chris@0 189 }
Chris@0 190 else {
Chris@0 191 $this->cacheBackend->delete($this->cacheKey);
Chris@0 192 }
Chris@0 193 }
Chris@0 194 $this->definitions = NULL;
Chris@0 195 }
Chris@0 196
Chris@0 197 /**
Chris@0 198 * Returns the cached plugin definitions of the decorated discovery class.
Chris@0 199 *
Chris@0 200 * @return array|null
Chris@0 201 * On success this will return an array of plugin definitions. On failure
Chris@0 202 * this should return NULL, indicating to other methods that this has not
Chris@0 203 * yet been defined. Success with no values should return as an empty array
Chris@0 204 * and would actually be returned by the getDefinitions() method.
Chris@0 205 */
Chris@0 206 protected function getCachedDefinitions() {
Chris@0 207 if (!isset($this->definitions) && $cache = $this->cacheGet($this->cacheKey)) {
Chris@0 208 $this->definitions = $cache->data;
Chris@0 209 }
Chris@0 210 return $this->definitions;
Chris@0 211 }
Chris@0 212
Chris@0 213 /**
Chris@0 214 * Sets a cache of plugin definitions for the decorated discovery class.
Chris@0 215 *
Chris@0 216 * @param array $definitions
Chris@0 217 * List of definitions to store in cache.
Chris@0 218 */
Chris@0 219 protected function setCachedDefinitions($definitions) {
Chris@0 220 $this->cacheSet($this->cacheKey, $definitions, Cache::PERMANENT, $this->cacheTags);
Chris@0 221 $this->definitions = $definitions;
Chris@0 222 }
Chris@0 223
Chris@0 224 /**
Chris@0 225 * {@inheritdoc}
Chris@0 226 */
Chris@0 227 public function useCaches($use_caches = FALSE) {
Chris@0 228 $this->useCaches = $use_caches;
Chris@0 229 if (!$use_caches) {
Chris@0 230 $this->definitions = NULL;
Chris@0 231 }
Chris@0 232 }
Chris@0 233
Chris@0 234 /**
Chris@0 235 * Performs extra processing on plugin definitions.
Chris@0 236 *
Chris@0 237 * By default we add defaults for the type to the definition. If a type has
Chris@0 238 * additional processing logic they can do that by replacing or extending the
Chris@0 239 * method.
Chris@0 240 */
Chris@0 241 public function processDefinition(&$definition, $plugin_id) {
Chris@0 242 // Only array-based definitions can have defaults merged in.
Chris@0 243 if (is_array($definition) && !empty($this->defaults) && is_array($this->defaults)) {
Chris@0 244 $definition = NestedArray::mergeDeep($this->defaults, $definition);
Chris@0 245 }
Chris@0 246
Chris@0 247 // Keep class definitions standard with no leading slash.
Chris@0 248 if ($definition instanceof PluginDefinitionInterface) {
Chris@0 249 $definition->setClass(ltrim($definition->getClass(), '\\'));
Chris@0 250 }
Chris@0 251 elseif (is_array($definition) && isset($definition['class'])) {
Chris@0 252 $definition['class'] = ltrim($definition['class'], '\\');
Chris@0 253 }
Chris@0 254 }
Chris@0 255
Chris@0 256 /**
Chris@0 257 * {@inheritdoc}
Chris@0 258 */
Chris@0 259 protected function getDiscovery() {
Chris@0 260 if (!$this->discovery) {
Chris@0 261 $discovery = new AnnotatedClassDiscovery($this->subdir, $this->namespaces, $this->pluginDefinitionAnnotationName, $this->additionalAnnotationNamespaces);
Chris@0 262 $this->discovery = new ContainerDerivativeDiscoveryDecorator($discovery);
Chris@0 263 }
Chris@0 264 return $this->discovery;
Chris@0 265 }
Chris@0 266
Chris@0 267 /**
Chris@0 268 * {@inheritdoc}
Chris@0 269 */
Chris@0 270 protected function getFactory() {
Chris@0 271 if (!$this->factory) {
Chris@0 272 $this->factory = new ContainerFactory($this, $this->pluginInterface);
Chris@0 273 }
Chris@0 274 return $this->factory;
Chris@0 275 }
Chris@0 276
Chris@0 277 /**
Chris@0 278 * Finds plugin definitions.
Chris@0 279 *
Chris@0 280 * @return array
Chris@0 281 * List of definitions to store in cache.
Chris@0 282 */
Chris@0 283 protected function findDefinitions() {
Chris@0 284 $definitions = $this->getDiscovery()->getDefinitions();
Chris@0 285 foreach ($definitions as $plugin_id => &$definition) {
Chris@0 286 $this->processDefinition($definition, $plugin_id);
Chris@0 287 }
Chris@0 288 $this->alterDefinitions($definitions);
Chris@18 289 $this->fixContextAwareDefinitions($definitions);
Chris@0 290 // If this plugin was provided by a module that does not exist, remove the
Chris@0 291 // plugin definition.
Chris@0 292 foreach ($definitions as $plugin_id => $plugin_definition) {
Chris@0 293 $provider = $this->extractProviderFromDefinition($plugin_definition);
Chris@0 294 if ($provider && !in_array($provider, ['core', 'component']) && !$this->providerExists($provider)) {
Chris@0 295 unset($definitions[$plugin_id]);
Chris@0 296 }
Chris@0 297 }
Chris@0 298 return $definitions;
Chris@0 299 }
Chris@0 300
Chris@0 301 /**
Chris@18 302 * Fix the definitions of context-aware plugins.
Chris@18 303 *
Chris@18 304 * @param array $definitions
Chris@18 305 * The array of plugin definitions.
Chris@18 306 *
Chris@18 307 * @todo Remove before Drupal 9.0.0.
Chris@18 308 */
Chris@18 309 private function fixContextAwareDefinitions(array &$definitions) {
Chris@18 310 foreach ($definitions as $name => &$definition) {
Chris@18 311 if (is_array($definition) && (!empty($definition['context']) || !empty($definition['context_definitions']))) {
Chris@18 312 // Ensure the new definition key is available.
Chris@18 313 if (!isset($definition['context_definitions'])) {
Chris@18 314 $definition['context_definitions'] = [];
Chris@18 315 }
Chris@18 316
Chris@18 317 // If a context definition is defined with the old key, add it to the
Chris@18 318 // new key and trigger a deprecation error.
Chris@18 319 if (!empty($definition['context'])) {
Chris@18 320 $definition['context_definitions'] += $definition['context'];
Chris@18 321 @trigger_error('Providing context definitions via the "context" key is deprecated in Drupal 8.7.x and will be removed before Drupal 9.0.0. Use the "context_definitions" key instead.', E_USER_DEPRECATED);
Chris@18 322 }
Chris@18 323
Chris@18 324 // Copy the context definitions from the new key to the old key for
Chris@18 325 // backwards compatibility.
Chris@18 326 if (isset($definition['context'])) {
Chris@18 327 $definition['context'] = $definition['context_definitions'];
Chris@18 328 }
Chris@18 329 }
Chris@18 330 }
Chris@18 331 }
Chris@18 332
Chris@18 333 /**
Chris@0 334 * Extracts the provider from a plugin definition.
Chris@0 335 *
Chris@0 336 * @param mixed $plugin_definition
Chris@0 337 * The plugin definition. Usually either an array or an instance of
Chris@0 338 * \Drupal\Component\Plugin\Definition\PluginDefinitionInterface
Chris@0 339 *
Chris@0 340 * @return string|null
Chris@0 341 * The provider string, if it exists. NULL otherwise.
Chris@0 342 */
Chris@0 343 protected function extractProviderFromDefinition($plugin_definition) {
Chris@0 344 if ($plugin_definition instanceof PluginDefinitionInterface) {
Chris@0 345 return $plugin_definition->getProvider();
Chris@0 346 }
Chris@0 347
Chris@0 348 // Attempt to convert the plugin definition to an array.
Chris@0 349 if (is_object($plugin_definition)) {
Chris@0 350 $plugin_definition = (array) $plugin_definition;
Chris@0 351 }
Chris@0 352
Chris@0 353 if (isset($plugin_definition['provider'])) {
Chris@0 354 return $plugin_definition['provider'];
Chris@0 355 }
Chris@0 356 }
Chris@0 357
Chris@0 358 /**
Chris@0 359 * Invokes the hook to alter the definitions if the alter hook is set.
Chris@0 360 *
Chris@0 361 * @param $definitions
Chris@0 362 * The discovered plugin definitions.
Chris@0 363 */
Chris@0 364 protected function alterDefinitions(&$definitions) {
Chris@0 365 if ($this->alterHook) {
Chris@0 366 $this->moduleHandler->alter($this->alterHook, $definitions);
Chris@0 367 }
Chris@0 368 }
Chris@0 369
Chris@0 370 /**
Chris@0 371 * Determines if the provider of a definition exists.
Chris@0 372 *
Chris@0 373 * @return bool
Chris@0 374 * TRUE if provider exists, FALSE otherwise.
Chris@0 375 */
Chris@0 376 protected function providerExists($provider) {
Chris@0 377 return $this->moduleHandler->moduleExists($provider);
Chris@0 378 }
Chris@0 379
Chris@0 380 /**
Chris@0 381 * {@inheritdoc}
Chris@0 382 */
Chris@0 383 public function getCacheContexts() {
Chris@0 384 return [];
Chris@0 385 }
Chris@0 386
Chris@0 387 /**
Chris@0 388 * {@inheritdoc}
Chris@0 389 */
Chris@0 390 public function getCacheTags() {
Chris@0 391 return $this->cacheTags;
Chris@0 392 }
Chris@0 393
Chris@0 394 /**
Chris@0 395 * {@inheritdoc}
Chris@0 396 */
Chris@0 397 public function getCacheMaxAge() {
Chris@0 398 return Cache::PERMANENT;
Chris@0 399 }
Chris@0 400
Chris@0 401 }