Chris@0: processDefinition() if Chris@0: * additional processing of plugins is necessary or helpful for development Chris@0: * purposes. Chris@0: * Chris@0: * @var array Chris@0: */ Chris@0: protected $defaults = []; Chris@0: Chris@0: /** Chris@0: * The name of the annotation that contains the plugin definition. Chris@0: * Chris@0: * @var string Chris@0: */ Chris@0: protected $pluginDefinitionAnnotationName; Chris@0: Chris@0: /** Chris@0: * The interface each plugin should implement. Chris@0: * Chris@0: * @var string|null Chris@0: */ Chris@0: protected $pluginInterface; Chris@0: Chris@0: /** Chris@0: * An object that implements \Traversable which contains the root paths Chris@0: * keyed by the corresponding namespace to look for plugin implementations. Chris@0: * Chris@0: * @var \Traversable Chris@0: */ Chris@0: protected $namespaces; Chris@0: Chris@0: /** Chris@0: * Additional namespaces the annotation discovery mechanism should scan for Chris@0: * annotation definitions. Chris@0: * Chris@0: * @var string[] Chris@0: */ Chris@0: protected $additionalAnnotationNamespaces = []; Chris@0: Chris@0: /** Chris@0: * Creates the discovery object. Chris@0: * Chris@0: * @param string|bool $subdir Chris@0: * The plugin's subdirectory, for example Plugin/views/filter. Chris@0: * @param \Traversable $namespaces Chris@0: * An object that implements \Traversable which contains the root paths Chris@0: * keyed by the corresponding namespace to look for plugin implementations. Chris@0: * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler Chris@0: * The module handler. Chris@0: * @param string|null $plugin_interface Chris@0: * (optional) The interface each plugin should implement. Chris@0: * @param string $plugin_definition_annotation_name Chris@0: * (optional) The name of the annotation that contains the plugin definition. Chris@0: * Defaults to 'Drupal\Component\Annotation\Plugin'. Chris@0: * @param string[] $additional_annotation_namespaces Chris@0: * (optional) Additional namespaces to scan for annotation definitions. Chris@0: */ Chris@0: 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: $this->subdir = $subdir; Chris@0: $this->namespaces = $namespaces; Chris@0: $this->pluginDefinitionAnnotationName = $plugin_definition_annotation_name; Chris@0: $this->pluginInterface = $plugin_interface; Chris@0: $this->moduleHandler = $module_handler; Chris@0: $this->additionalAnnotationNamespaces = $additional_annotation_namespaces; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Initialize the cache backend. Chris@0: * Chris@0: * Plugin definitions are cached using the provided cache backend. Chris@0: * Chris@0: * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend Chris@0: * Cache backend instance to use. Chris@0: * @param string $cache_key Chris@0: * Cache key prefix to use. Chris@0: * @param array $cache_tags Chris@0: * (optional) When providing a list of cache tags, the cached plugin Chris@0: * definitions are tagged with the provided cache tags. These cache tags can Chris@0: * then be used to clear the corresponding cached plugin definitions. Note Chris@0: * that this should be used with care! For clearing all cached plugin Chris@0: * definitions of a plugin manager, call that plugin manager's Chris@0: * clearCachedDefinitions() method. Only use cache tags when cached plugin Chris@0: * definitions should be cleared along with other, related cache entries. Chris@0: */ Chris@0: public function setCacheBackend(CacheBackendInterface $cache_backend, $cache_key, array $cache_tags = []) { Chris@14: assert(Inspector::assertAllStrings($cache_tags), 'Cache Tags must be strings.'); Chris@0: $this->cacheBackend = $cache_backend; Chris@0: $this->cacheKey = $cache_key; Chris@0: $this->cacheTags = $cache_tags; Chris@0: } Chris@0: Chris@0: /** Chris@14: * Sets the alter hook name. Chris@0: * Chris@0: * @param string $alter_hook Chris@0: * Name of the alter hook; for example, to invoke Chris@0: * hook_mymodule_data_alter() pass in "mymodule_data". Chris@0: */ Chris@0: protected function alterInfo($alter_hook) { Chris@0: $this->alterHook = $alter_hook; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getDefinitions() { Chris@0: $definitions = $this->getCachedDefinitions(); Chris@0: if (!isset($definitions)) { Chris@0: $definitions = $this->findDefinitions(); Chris@0: $this->setCachedDefinitions($definitions); Chris@0: } Chris@0: return $definitions; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function clearCachedDefinitions() { Chris@0: if ($this->cacheBackend) { Chris@0: if ($this->cacheTags) { Chris@0: // Use the cache tags to clear the cache. Chris@0: Cache::invalidateTags($this->cacheTags); Chris@0: } Chris@0: else { Chris@0: $this->cacheBackend->delete($this->cacheKey); Chris@0: } Chris@0: } Chris@0: $this->definitions = NULL; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the cached plugin definitions of the decorated discovery class. Chris@0: * Chris@0: * @return array|null Chris@0: * On success this will return an array of plugin definitions. On failure Chris@0: * this should return NULL, indicating to other methods that this has not Chris@0: * yet been defined. Success with no values should return as an empty array Chris@0: * and would actually be returned by the getDefinitions() method. Chris@0: */ Chris@0: protected function getCachedDefinitions() { Chris@0: if (!isset($this->definitions) && $cache = $this->cacheGet($this->cacheKey)) { Chris@0: $this->definitions = $cache->data; Chris@0: } Chris@0: return $this->definitions; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets a cache of plugin definitions for the decorated discovery class. Chris@0: * Chris@0: * @param array $definitions Chris@0: * List of definitions to store in cache. Chris@0: */ Chris@0: protected function setCachedDefinitions($definitions) { Chris@0: $this->cacheSet($this->cacheKey, $definitions, Cache::PERMANENT, $this->cacheTags); Chris@0: $this->definitions = $definitions; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function useCaches($use_caches = FALSE) { Chris@0: $this->useCaches = $use_caches; Chris@0: if (!$use_caches) { Chris@0: $this->definitions = NULL; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Performs extra processing on plugin definitions. Chris@0: * Chris@0: * By default we add defaults for the type to the definition. If a type has Chris@0: * additional processing logic they can do that by replacing or extending the Chris@0: * method. Chris@0: */ Chris@0: public function processDefinition(&$definition, $plugin_id) { Chris@0: // Only array-based definitions can have defaults merged in. Chris@0: if (is_array($definition) && !empty($this->defaults) && is_array($this->defaults)) { Chris@0: $definition = NestedArray::mergeDeep($this->defaults, $definition); Chris@0: } Chris@0: Chris@0: // Keep class definitions standard with no leading slash. Chris@0: if ($definition instanceof PluginDefinitionInterface) { Chris@0: $definition->setClass(ltrim($definition->getClass(), '\\')); Chris@0: } Chris@0: elseif (is_array($definition) && isset($definition['class'])) { Chris@0: $definition['class'] = ltrim($definition['class'], '\\'); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: protected function getDiscovery() { Chris@0: if (!$this->discovery) { Chris@0: $discovery = new AnnotatedClassDiscovery($this->subdir, $this->namespaces, $this->pluginDefinitionAnnotationName, $this->additionalAnnotationNamespaces); Chris@0: $this->discovery = new ContainerDerivativeDiscoveryDecorator($discovery); Chris@0: } Chris@0: return $this->discovery; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: protected function getFactory() { Chris@0: if (!$this->factory) { Chris@0: $this->factory = new ContainerFactory($this, $this->pluginInterface); Chris@0: } Chris@0: return $this->factory; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Finds plugin definitions. Chris@0: * Chris@0: * @return array Chris@0: * List of definitions to store in cache. Chris@0: */ Chris@0: protected function findDefinitions() { Chris@0: $definitions = $this->getDiscovery()->getDefinitions(); Chris@0: foreach ($definitions as $plugin_id => &$definition) { Chris@0: $this->processDefinition($definition, $plugin_id); Chris@0: } Chris@0: $this->alterDefinitions($definitions); Chris@18: $this->fixContextAwareDefinitions($definitions); Chris@0: // If this plugin was provided by a module that does not exist, remove the Chris@0: // plugin definition. Chris@0: foreach ($definitions as $plugin_id => $plugin_definition) { Chris@0: $provider = $this->extractProviderFromDefinition($plugin_definition); Chris@0: if ($provider && !in_array($provider, ['core', 'component']) && !$this->providerExists($provider)) { Chris@0: unset($definitions[$plugin_id]); Chris@0: } Chris@0: } Chris@0: return $definitions; Chris@0: } Chris@0: Chris@0: /** Chris@18: * Fix the definitions of context-aware plugins. Chris@18: * Chris@18: * @param array $definitions Chris@18: * The array of plugin definitions. Chris@18: * Chris@18: * @todo Remove before Drupal 9.0.0. Chris@18: */ Chris@18: private function fixContextAwareDefinitions(array &$definitions) { Chris@18: foreach ($definitions as $name => &$definition) { Chris@18: if (is_array($definition) && (!empty($definition['context']) || !empty($definition['context_definitions']))) { Chris@18: // Ensure the new definition key is available. Chris@18: if (!isset($definition['context_definitions'])) { Chris@18: $definition['context_definitions'] = []; Chris@18: } Chris@18: Chris@18: // If a context definition is defined with the old key, add it to the Chris@18: // new key and trigger a deprecation error. Chris@18: if (!empty($definition['context'])) { Chris@18: $definition['context_definitions'] += $definition['context']; Chris@18: @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: } Chris@18: Chris@18: // Copy the context definitions from the new key to the old key for Chris@18: // backwards compatibility. Chris@18: if (isset($definition['context'])) { Chris@18: $definition['context'] = $definition['context_definitions']; Chris@18: } Chris@18: } Chris@18: } Chris@18: } Chris@18: Chris@18: /** Chris@0: * Extracts the provider from a plugin definition. Chris@0: * Chris@0: * @param mixed $plugin_definition Chris@0: * The plugin definition. Usually either an array or an instance of Chris@0: * \Drupal\Component\Plugin\Definition\PluginDefinitionInterface Chris@0: * Chris@0: * @return string|null Chris@0: * The provider string, if it exists. NULL otherwise. Chris@0: */ Chris@0: protected function extractProviderFromDefinition($plugin_definition) { Chris@0: if ($plugin_definition instanceof PluginDefinitionInterface) { Chris@0: return $plugin_definition->getProvider(); Chris@0: } Chris@0: Chris@0: // Attempt to convert the plugin definition to an array. Chris@0: if (is_object($plugin_definition)) { Chris@0: $plugin_definition = (array) $plugin_definition; Chris@0: } Chris@0: Chris@0: if (isset($plugin_definition['provider'])) { Chris@0: return $plugin_definition['provider']; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Invokes the hook to alter the definitions if the alter hook is set. Chris@0: * Chris@0: * @param $definitions Chris@0: * The discovered plugin definitions. Chris@0: */ Chris@0: protected function alterDefinitions(&$definitions) { Chris@0: if ($this->alterHook) { Chris@0: $this->moduleHandler->alter($this->alterHook, $definitions); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Determines if the provider of a definition exists. Chris@0: * Chris@0: * @return bool Chris@0: * TRUE if provider exists, FALSE otherwise. Chris@0: */ Chris@0: protected function providerExists($provider) { Chris@0: return $this->moduleHandler->moduleExists($provider); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getCacheContexts() { Chris@0: return []; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getCacheTags() { Chris@0: return $this->cacheTags; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getCacheMaxAge() { Chris@0: return Cache::PERMANENT; Chris@0: } Chris@0: Chris@0: }