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