DefaultPluginManager.php 10.2 KB
Newer Older
1 2 3 4
<?php

/**
 * @file
5
 * Contains \Drupal\Core\Plugin\DefaultPluginManager.
6 7 8 9 10
 */

namespace Drupal\Core\Plugin;

use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface;
11
use Drupal\Component\Plugin\Discovery\DiscoveryCachedTrait;
12
use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
13 14 15
use Drupal\Component\Plugin\PluginManagerBase;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Component\Utility\NestedArray;
16
use Drupal\Core\Cache\Cache;
17 18 19 20 21 22 23
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
use Drupal\Core\Plugin\Factory\ContainerFactory;

/**
 * Base class for plugin managers.
24 25
 *
 * @ingroup plugin_api
26 27 28
 */
class DefaultPluginManager extends PluginManagerBase implements PluginManagerInterface, CachedDiscoveryInterface {

29
  use DiscoveryCachedTrait;
30 31 32 33 34 35 36 37 38

  /**
   * Cache backend instance.
   *
   * @var \Drupal\Core\Cache\CacheBackendInterface
   */
  protected $cacheBackend;

  /**
39
   * The cache key.
40 41 42 43 44
   *
   * @var string
   */
  protected $cacheKey;

45 46 47 48 49 50 51
  /**
   * An array of cache tags to use for the cached definitions.
   *
   * @var array
   */
  protected $cacheTags = array();

52 53 54 55 56 57 58 59
  /**
   * Name of the alter hook if one should be invoked.
   *
   * @var string
   */
  protected $alterHook;

  /**
60 61
   * The subdirectory within a namespace to look for plugins, or FALSE if the
   * plugins are in the top level of the namespace.
62
   *
63
   * @var string|bool
64 65 66 67 68 69 70 71 72 73
   */
  protected $subdir;

  /**
   * The module handler to invoke the alter hook.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

74 75 76 77 78 79 80 81 82
  /**
   * A set of defaults to be referenced by $this->processDefinition() if
   * additional processing of plugins is necessary or helpful for development
   * purposes.
   *
   * @var array
   */
  protected $defaults = array();

83 84 85 86 87 88 89
  /**
   * Flag whether persistent caches should be used.
   *
   * @var bool
   */
  protected $useCaches = TRUE;

90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
  /**
   * The name of the annotation that contains the plugin definition.
   *
   * @var string
   */
  protected $pluginDefinitionAnnotationName;

  /**
   * The interface each plugin should implement.
   *
   * @var string|null
   */
  protected $pluginInterface;

  /**
   * An object that implements \Traversable which contains the root paths
   * keyed by the corresponding namespace to look for plugin implementations.
   *
   * @var \Traversable
   */
  protected $namespaces;

112 113 114
  /**
   * Creates the discovery object.
   *
115 116
   * @param string|bool $subdir
   *   The plugin's subdirectory, for example Plugin/views/filter.
117 118
   * @param \Traversable $namespaces
   *   An object that implements \Traversable which contains the root paths
119
   *   keyed by the corresponding namespace to look for plugin implementations.
120 121
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler.
122 123
   * @param string|null $plugin_interface
   *   (optional) The interface each plugin should implement.
124 125 126 127
   * @param string $plugin_definition_annotation_name
   *   (optional) The name of the annotation that contains the plugin definition.
   *   Defaults to 'Drupal\Component\Annotation\Plugin'.
   */
128
  public function __construct($subdir, \Traversable $namespaces, ModuleHandlerInterface $module_handler, $plugin_interface = NULL, $plugin_definition_annotation_name = 'Drupal\Component\Annotation\Plugin') {
129
    $this->subdir = $subdir;
130 131 132
    $this->namespaces = $namespaces;
    $this->pluginDefinitionAnnotationName = $plugin_definition_annotation_name;
    $this->pluginInterface = $plugin_interface;
133
    $this->moduleHandler = $module_handler;
134 135 136 137 138 139 140 141 142 143
  }

  /**
   * Initialize the cache backend.
   *
   * Plugin definitions are cached using the provided cache backend. The
   * interface language is added as a suffix to the cache key.
   *
   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
   *   Cache backend instance to use.
144
   * @param string $cache_key
145 146
   *   Cache key prefix to use, the language code will be appended
   *   automatically.
147
   * @param array $cache_tags
148 149 150 151 152 153 154
   *   (optional) When providing a list of cache tags, the cached plugin
   *   definitions are tagged with the provided cache tags. These cache tags can
   *   then be used to clear the corresponding cached plugin definitions. Note
   *   that this should be used with care! For clearing all cached plugin
   *   definitions of a plugin manager, call that plugin manager's
   *   clearCachedDefinitions() method. Only use cache tags when cached plugin
   *   definitions should be cleared along with other, related cache entries.
155
   */
156
  public function setCacheBackend(CacheBackendInterface $cache_backend, $cache_key, array $cache_tags = array()) {
157
    Cache::validateTags($cache_tags);
158
    $this->cacheBackend = $cache_backend;
159
    $this->cacheKey = $cache_key;
160
    $this->cacheTags = $cache_tags;
161 162 163 164 165 166
  }

  /**
   * Initializes the alter hook.
   *
   * @param string $alter_hook
167 168
   *   Name of the alter hook; for example, to invoke
   *   hook_mymodule_data_alter() pass in "mymodule_data".
169
   */
170
  protected function alterInfo($alter_hook) {
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
    $this->alterHook = $alter_hook;
  }

  /**
   * {@inheritdoc}
   */
  public function getDefinitions() {
    $definitions = $this->getCachedDefinitions();
    if (!isset($definitions)) {
      $definitions = $this->findDefinitions();
      $this->setCachedDefinitions($definitions);
    }
    return $definitions;
  }

  /**
   * {@inheritdoc}
   */
  public function clearCachedDefinitions() {
    if ($this->cacheBackend) {
191 192
      if ($this->cacheTags) {
        // Use the cache tags to clear the cache.
193
        Cache::invalidateTags($this->cacheTags);
194
      }
195 196 197
      else {
        $this->cacheBackend->delete($this->cacheKey);
      }
198 199 200 201 202 203 204
    }
    $this->definitions = NULL;
  }

  /**
   * Returns the cached plugin definitions of the decorated discovery class.
   *
205
   * @return array|null
206 207 208 209 210 211
   *   On success this will return an array of plugin definitions. On failure
   *   this should return NULL, indicating to other methods that this has not
   *   yet been defined. Success with no values should return as an empty array
   *   and would actually be returned by the getDefinitions() method.
   */
  protected function getCachedDefinitions() {
212
    if (!isset($this->definitions) && $cache = $this->cacheGet($this->cacheKey)) {
213 214 215 216 217 218 219 220 221 222 223 224
      $this->definitions = $cache->data;
    }
    return $this->definitions;
  }

  /**
   * Sets a cache of plugin definitions for the decorated discovery class.
   *
   * @param array $definitions
   *   List of definitions to store in cache.
   */
  protected function setCachedDefinitions($definitions) {
225
    $this->cacheSet($this->cacheKey, $definitions, Cache::PERMANENT, $this->cacheTags);
226 227 228
    $this->definitions = $definitions;
  }

229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
  /**
   * {@inheritdoc}
   */
  public function useCaches($use_caches = FALSE) {
    $this->useCaches = $use_caches;
    if (!$use_caches) {
      $this->definitions = NULL;
    }
  }

  /**
   * Fetches from the cache backend, respecting the use caches flag.
   *
   * @see \Drupal\Core\Cache\CacheBackendInterface::get()
   */
  protected function cacheGet($cid) {
    if ($this->useCaches && $this->cacheBackend) {
      return $this->cacheBackend->get($cid);
    }
    return FALSE;
  }

  /**
   * Stores data in the persistent cache, respecting the use caches flag.
   *
   * @see \Drupal\Core\Cache\CacheBackendInterface::set()
   */
  protected function cacheSet($cid, $data, $expire = Cache::PERMANENT, array $tags = array()) {
    if ($this->cacheBackend && $this->useCaches) {
      $this->cacheBackend->set($cid, $data, $expire, $tags);
    }
  }

262 263 264 265 266 267 268 269 270 271 272 273 274 275

  /**
   * Performs extra processing on plugin definitions.
   *
   * By default we add defaults for the type to the definition. If a type has
   * additional processing logic they can do that by replacing or extending the
   * method.
   */
  public function processDefinition(&$definition, $plugin_id) {
    if (!empty($this->defaults) && is_array($this->defaults)) {
      $definition = NestedArray::mergeDeep($this->defaults, $definition);
    }
  }

276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
  /**
   * {@inheritdoc}
   */
  protected function getDiscovery() {
    if (!$this->discovery) {
      $discovery = new AnnotatedClassDiscovery($this->subdir, $this->namespaces, $this->pluginDefinitionAnnotationName);
      $this->discovery = new ContainerDerivativeDiscoveryDecorator($discovery);
    }
    return $this->discovery;
  }

  /**
   * {@inheritdoc}
   */
  protected function getFactory() {
    if (!$this->factory) {
      $this->factory = new ContainerFactory($this, $this->pluginInterface);
    }
    return $this->factory;
  }

297 298 299 300 301 302 303
  /**
   * Finds plugin definitions.
   *
   * @return array
   *   List of definitions to store in cache.
   */
  protected function findDefinitions() {
304
    $definitions = $this->getDiscovery()->getDefinitions();
305 306 307
    foreach ($definitions as $plugin_id => &$definition) {
      $this->processDefinition($definition, $plugin_id);
    }
308
    $this->alterDefinitions($definitions);
309 310 311
    // If this plugin was provided by a module that does not exist, remove the
    // plugin definition.
    foreach ($definitions as $plugin_id => $plugin_definition) {
312 313 314 315 316
      // If the plugin definition is an object, attempt to convert it to an
      // array, if that is not possible, skip further processing.
      if (is_object($plugin_definition) && !($plugin_definition = (array) $plugin_definition)) {
        continue;
      }
317
      if (isset($plugin_definition['provider']) && !in_array($plugin_definition['provider'], array('core', 'component')) && !$this->providerExists($plugin_definition['provider'])) {
318 319 320
        unset($definitions[$plugin_id]);
      }
    }
321 322 323
    return $definitions;
  }

324 325 326 327 328 329 330 331 332 333 334 335
  /**
   * Invokes the hook to alter the definitions if the alter hook is set.
   *
   * @param $definitions
   *   The discovered plugin defintions.
   */
  protected function alterDefinitions(&$definitions) {
    if ($this->alterHook) {
      $this->moduleHandler->alter($this->alterHook, $definitions);
    }
  }

336 337 338 339 340 341 342 343 344 345
  /**
   * Determines if the provider of a definition exists.
   *
   * @return boolean
   *   TRUE if provider exists, FALSE otherwise.
   */
  protected function providerExists($provider) {
    return $this->moduleHandler->moduleExists($provider);
  }

346
}