DefaultPluginManager.php 9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
<?php

/**
 * @file
 * Contains \Drupal\Core\Plugin\DefaultPluginManager
 */

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
  /**
   * Creates the discovery object.
   *
93 94
   * @param string|bool $subdir
   *   The plugin's subdirectory, for example Plugin/views/filter.
95 96
   * @param \Traversable $namespaces
   *   An object that implements \Traversable which contains the root paths
97
   *   keyed by the corresponding namespace to look for plugin implementations.
98 99
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler.
100 101
   * @param string|null $plugin_interface
   *   (optional) The interface each plugin should implement.
102 103 104 105
   * @param string $plugin_definition_annotation_name
   *   (optional) The name of the annotation that contains the plugin definition.
   *   Defaults to 'Drupal\Component\Annotation\Plugin'.
   */
106
  public function __construct($subdir, \Traversable $namespaces, ModuleHandlerInterface $module_handler, $plugin_interface = NULL, $plugin_definition_annotation_name = 'Drupal\Component\Annotation\Plugin') {
107
    $this->subdir = $subdir;
108
    $this->discovery = new AnnotatedClassDiscovery($subdir, $namespaces, $plugin_definition_annotation_name);
109
    $this->discovery = new ContainerDerivativeDiscoveryDecorator($this->discovery);
110
    $this->factory = new ContainerFactory($this, $plugin_interface);
111
    $this->moduleHandler = $module_handler;
112 113 114 115 116 117 118 119 120 121
  }

  /**
   * 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.
122
   * @param string $cache_key
123 124
   *   Cache key prefix to use, the language code will be appended
   *   automatically.
125
   * @param array $cache_tags
126 127 128 129 130 131 132
   *   (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.
133
   */
134
  public function setCacheBackend(CacheBackendInterface $cache_backend, $cache_key, array $cache_tags = array()) {
135
    Cache::validateTags($cache_tags);
136
    $this->cacheBackend = $cache_backend;
137
    $this->cacheKey = $cache_key;
138
    $this->cacheTags = $cache_tags;
139 140 141 142 143 144
  }

  /**
   * Initializes the alter hook.
   *
   * @param string $alter_hook
145 146
   *   Name of the alter hook; for example, to invoke
   *   hook_mymodule_data_alter() pass in "mymodule_data".
147
   */
148
  protected function alterInfo($alter_hook) {
149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
    $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) {
169 170
      if ($this->cacheTags) {
        // Use the cache tags to clear the cache.
171
        Cache::invalidateTags($this->cacheTags);
172
      }
173 174 175
      else {
        $this->cacheBackend->delete($this->cacheKey);
      }
176 177 178 179 180 181 182
    }
    $this->definitions = NULL;
  }

  /**
   * Returns the cached plugin definitions of the decorated discovery class.
   *
183
   * @return array|null
184 185 186 187 188 189
   *   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() {
190
    if (!isset($this->definitions) && $cache = $this->cacheGet($this->cacheKey)) {
191 192 193 194 195 196 197 198 199 200 201 202
      $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) {
203
    $this->cacheSet($this->cacheKey, $definitions, Cache::PERMANENT, $this->cacheTags);
204 205 206
    $this->definitions = $definitions;
  }

207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
  /**
   * {@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);
    }
  }

240 241 242 243 244 245 246 247 248 249 250 251 252 253

  /**
   * 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);
    }
  }

254 255 256 257 258 259 260 261 262 263 264
  /**
   * Finds plugin definitions.
   *
   * @return array
   *   List of definitions to store in cache.
   */
  protected function findDefinitions() {
    $definitions = $this->discovery->getDefinitions();
    foreach ($definitions as $plugin_id => &$definition) {
      $this->processDefinition($definition, $plugin_id);
    }
265
    $this->alterDefinitions($definitions);
266 267 268
    // If this plugin was provided by a module that does not exist, remove the
    // plugin definition.
    foreach ($definitions as $plugin_id => $plugin_definition) {
269 270 271 272 273
      // 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;
      }
274
      if (isset($plugin_definition['provider']) && !in_array($plugin_definition['provider'], array('core', 'component')) && !$this->moduleHandler->moduleExists($plugin_definition['provider'])) {
275 276 277
        unset($definitions[$plugin_id]);
      }
    }
278 279 280
    return $definitions;
  }

281 282 283 284 285 286 287 288 289 290 291 292
  /**
   * 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);
    }
  }

293
}