Commit 1eb9af15 authored by catch's avatar catch

Issue #1930020 by dawehner, Berdir, olli: Inject a class into the Core...

Issue #1930020 by dawehner, Berdir, olli: Inject a class into the Core annotated discovery implementation so that namespaces can be updated.
parent 8b0ea750
......@@ -143,12 +143,17 @@ services:
calls:
- [addSubscriber, ['@http_client_simpletest_subscriber']]
- [setUserAgent, ['Drupal (+http://drupal.org/)']]
container.namespaces:
class: ArrayObject
arguments: [ '%container.namespaces%' ]
tags:
- { name: persist }
plugin.manager.entity:
class: Drupal\Core\Entity\EntityManager
arguments: ['%container.namespaces%']
arguments: ['@container.namespaces']
plugin.manager.archiver:
class: Drupal\Core\Archiver\ArchiverManager
arguments: ['%container.namespaces%']
arguments: ['@container.namespaces']
request:
class: Symfony\Component\HttpFoundation\Request
event_dispatcher:
......@@ -173,7 +178,7 @@ services:
- [setValidationConstraintManager, ['@validation.constraint']]
validation.constraint:
class: Drupal\Core\Validation\ConstraintManager
arguments: ['%container.namespaces%']
arguments: ['@container.namespaces']
lock:
class: Drupal\Core\Lock\DatabaseLockBackend
user.tempstore:
......@@ -378,7 +383,7 @@ services:
arguments: ['@database', '@request']
plugin.manager.condition:
class: Drupal\Core\Condition\ConditionManager
arguments: ['%container.namespaces%']
arguments: ['@container.namespaces']
kernel_destruct_subscriber:
class: Drupal\Core\EventSubscriber\KernelDestructionSubscriber
tags:
......@@ -391,7 +396,7 @@ services:
- { name: event_subscriber }
image.toolkit.manager:
class: Drupal\system\Plugin\ImageToolkitManager
arguments: ['%container.namespaces%']
arguments: ['@container.namespaces']
image.toolkit:
class: Drupal\system\Plugin\ImageToolkitInterface
factory_method: getDefaultToolkit
......
......@@ -20,10 +20,11 @@ class ArchiverManager extends PluginManagerBase {
/**
* Constructs a ArchiverManager object.
*
* @param array $namespaces
* An array of paths keyed by its corresponding namespaces.
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations,
*/
public function __construct(array $namespaces) {
public function __construct(\Traversable $namespaces) {
$this->discovery = new AnnotatedClassDiscovery('Core', 'Archiver', $namespaces);
$this->discovery = new AlterDecorator($this->discovery, 'archiver_info');
$this->discovery = new CacheDecorator($this->discovery, 'archiver_info');
......
......@@ -24,10 +24,11 @@ class ConditionManager extends PluginManagerBase implements ExecutableManagerInt
/**
* Constructs aa ConditionManager object.
*
* @param array $namespaces
* An array of paths keyed by it's corresponding namespaces.
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations,
*/
public function __construct(array $namespaces) {
public function __construct(\Traversable $namespaces) {
$this->discovery = new AnnotatedClassDiscovery('Core', 'Condition', $namespaces);
$this->discovery = new DerivativeDiscoveryDecorator($this->discovery);
$this->discovery = new AlterDecorator($this->discovery, 'condition_info');
......
......@@ -342,6 +342,14 @@ protected function initializeContainer() {
if (!isset($this->container)) {
$this->container = $this->buildContainer();
$this->persistServices($persist);
// The namespaces are marked as persistent, so objects like the annotated
// class discovery still has the right object. We may have updated the
// list of modules, so set it.
if ($this->container->initialized('container.namespaces')) {
$this->container->get('container.namespaces')->exchangeArray($this->container->getParameter('container.namespaces'));
}
if ($this->allowDumping) {
$this->containerNeedsDumping = TRUE;
}
......
......@@ -41,10 +41,11 @@ class EntityManager extends PluginManagerBase {
/**
* Constructs a new Entity plugin manager.
*
* @param array $namespaces
* An array of paths keyed by it's corresponding namespaces.
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations,
*/
public function __construct(array $namespaces) {
public function __construct(\Traversable $namespaces) {
// Allow the plugin definition to be altered by hook_entity_info_alter().
$annotation_namespaces = array(
'Drupal\Core\Entity\Annotation' => DRUPAL_ROOT . '/core/lib',
......
......@@ -14,6 +14,27 @@
*/
class AnnotatedClassDiscovery extends ComponentAnnotatedClassDiscovery {
/**
* The module name that defines the plugin type.
*
* @var string
*/
protected $owner;
/**
* The plugin type, for example filter.
*
* @var string
*/
protected $type;
/**
* An object containing the namespaces to look for plugin implementations.
*
* @var \Traversable
*/
protected $rootNamespacesIterator;
/**
* Constructs an AnnotatedClassDiscovery object.
*
......@@ -21,34 +42,39 @@ class AnnotatedClassDiscovery extends ComponentAnnotatedClassDiscovery {
* The module name that defines the plugin type.
* @param string $type
* The plugin type, for example filter.
* @param array $root_namespaces
* (optional) An array of root paths keyed by the corresponding namespace to
* look for plugin implementations. '\Plugin\$owner\$type' will be appended
* to each namespace. Defaults to an empty array.
* @param \Traversable $root_namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations,
* \Plugin\$owner\$type will be appended to each namespace.
* @param array $annotation_namespaces
* (optional) The namespaces of classes that can be used as annotations.
* Defaults to an empty array.
* @param string $plugin_definition_annotation_name
* (optional) The name of the annotation that contains the plugin definition.
* Defaults to 'Drupal\Component\Annotation\Plugin'.
*
* @todo Figure out how to make the following comment FALSE.
* Drupal modules can be enabled (and therefore, namespaces registered)
* during the lifetime of a plugin manager. Passing $root_namespaces into
* the constructor means plugins in the new namespaces will not be available
* until the next request. Additionally when a namespace is unregistered,
* plugins will not be removed until the next request.
*/
function __construct($owner, $type, array $root_namespaces = array(), $annotation_namespaces = array(), $plugin_definition_annotation_name = 'Drupal\Component\Annotation\Plugin') {
function __construct($owner, $type, \Traversable $root_namespaces, $annotation_namespaces = array(), $plugin_definition_annotation_name = 'Drupal\Component\Annotation\Plugin') {
$this->owner = $owner;
$this->type = $type;
$this->rootNamespacesIterator = $root_namespaces;
$annotation_namespaces += array(
'Drupal\Component\Annotation' => DRUPAL_ROOT . '/core/lib',
'Drupal\Core\Annotation' => DRUPAL_ROOT . '/core/lib',
);
$plugin_namespaces = array();
foreach ($root_namespaces as $namespace => $dir) {
$plugin_namespaces["$namespace\\Plugin\\{$owner}\\{$type}"] = array($dir);
}
parent::__construct($plugin_namespaces, $annotation_namespaces, $plugin_definition_annotation_name);
}
/**
* {@inheritdoc}
*/
protected function getPluginNamespaces() {
$plugin_namespaces = array();
foreach ($this->rootNamespacesIterator as $namespace => $dir) {
$plugin_namespaces["$namespace\\Plugin\\{$this->owner}\\{$this->type}"] = array($dir);
}
return $plugin_namespaces;
}
}
......@@ -39,10 +39,11 @@ class ConstraintManager extends PluginManagerBase {
/**
* Overrides \Drupal\Component\Plugin\PluginManagerBase::__construct().
*
* @param array $namespaces
* An array of paths keyed by it's corresponding namespaces.
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations,
*/
public function __construct(array $namespaces) {
public function __construct(\Traversable $namespaces) {
$this->discovery = new AnnotatedClassDiscovery('Validation', 'Constraint', $namespaces);
$this->discovery = new StaticDiscoveryDecorator($this->discovery, array($this, 'registerDefinitions'));
$this->discovery = new DerivativeDiscoveryDecorator($this->discovery);
......
services:
plugin.manager.aggregator.fetcher:
class: Drupal\aggregator\Plugin\AggregatorPluginManager
arguments: [fetcher, '%container.namespaces%']
arguments: [fetcher, '@container.namespaces']
plugin.manager.aggregator.parser:
class: Drupal\aggregator\Plugin\AggregatorPluginManager
arguments: [parser, '%container.namespaces%']
arguments: [parser, '@container.namespaces']
plugin.manager.aggregator.processor:
class: Drupal\aggregator\Plugin\AggregatorPluginManager
arguments: [processor, '%container.namespaces%']
arguments: [processor, '@container.namespaces']
......@@ -22,10 +22,11 @@ class AggregatorPluginManager extends PluginManagerBase {
*
* @param string $type
* The plugin type, for example fetcher.
* @param array $namespaces
* An array of paths keyed by it's corresponding namespaces.
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations,
*/
public function __construct($type, array $namespaces) {
public function __construct($type, \Traversable $namespaces) {
$this->discovery = new AnnotatedClassDiscovery('aggregator', $type, $namespaces);
$this->discovery = new CacheDecorator($this->discovery, "aggregator_$type:" . language(LANGUAGE_TYPE_INTERFACE)->langcode);
$this->factory = new DefaultFactory($this->discovery);
......
services:
plugin.manager.block:
class: Drupal\block\Plugin\Type\BlockManager
arguments: ['%container.namespaces%']
arguments: ['@container.namespaces']
cache.block:
class: Drupal\Core\Cache\CacheBackendInterface
tags:
......
......@@ -27,10 +27,11 @@ class BlockManager extends PluginManagerBase {
/**
* Constructs a new \Drupal\block\Plugin\Type\BlockManager object.
*
* @param array $namespaces
* An array of paths keyed by it's corresponding namespaces.
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations,
*/
public function __construct(array $namespaces) {
public function __construct(\Traversable $namespaces) {
$this->discovery = new AnnotatedClassDiscovery('block', 'block', $namespaces);
$this->discovery = new DerivativeDiscoveryDecorator($this->discovery);
$this->discovery = new AlterDecorator($this->discovery, 'block');
......
services:
plugin.manager.ckeditor.plugin:
class: Drupal\ckeditor\CKEditorPluginManager
arguments: ['%container.namespaces%']
arguments: ['@container.namespaces']
......@@ -24,10 +24,11 @@ class CKEditorPluginManager extends PluginManagerBase {
/**
* Overrides \Drupal\Component\Plugin\PluginManagerBase::__construct().
*
* @param array $namespaces
* An array of paths keyed by it's corresponding namespaces.
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations,
*/
public function __construct(array $namespaces) {
public function __construct(\Traversable $namespaces) {
$this->discovery = new AnnotatedClassDiscovery('ckeditor', 'plugin', $namespaces);
$this->discovery = new AlterDecorator($this->discovery, 'ckeditor_plugin_info');
$this->discovery = new CacheDecorator($this->discovery, 'ckeditor_plugin');
......
......@@ -63,7 +63,7 @@ function setUp() {
* Tests the enabling of plugins.
*/
function testEnabledPlugins() {
$this->manager = new CKEditorPluginManager($this->container->getParameter('container.namespaces'));
$this->manager = $this->container->get('plugin.manager.ckeditor.plugin');
$editor = entity_load('editor', 'filtered_html');
// Case 1: no CKEditor plugins.
......@@ -77,7 +77,6 @@ function testEnabledPlugins() {
// variations of it, to cover all possible ways a plugin can be enabled) and
// clear the editor manager's cache so it is picked up.
$this->enableModules(array('ckeditor_test'));
$this->manager = new CKEditorPluginManager($this->container->getParameter('container.namespaces'));
$this->manager->clearCachedDefinitions();
// Case 2: CKEditor plugins are available.
......
......@@ -64,7 +64,7 @@ function setUp() {
$editor->save();
// Create "CKEditor" text editor plugin instance.
$manager = new EditorManager($this->container->getParameter('container.namespaces'));
$manager = $this->container->get('plugin.manager.editor');
$this->ckeditor = $manager->createInstance('ckeditor');
}
......
services:
plugin.manager.edit.editor:
class: Drupal\edit\Plugin\EditorManager
arguments: ['%container.namespaces%']
arguments: ['@container.namespaces']
access_check.edit.entity_field:
class: Drupal\edit\Access\EditEntityFieldAccessCheck
tags:
......
......@@ -24,10 +24,11 @@ class EditorManager extends PluginManagerBase {
/**
* Overrides \Drupal\Component\Plugin\PluginManagerBase::__construct().
*
* @param array $namespaces
* An array of paths keyed by it's corresponding namespaces.
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations,
*/
public function __construct(array $namespaces) {
public function __construct(\Traversable $namespaces) {
$this->discovery = new AnnotatedClassDiscovery('edit', 'editor', $namespaces);
$this->discovery = new ProcessDecorator($this->discovery, array($this, 'processDefinition'));
$this->discovery = new AlterDecorator($this->discovery, 'edit_editor');
......
......@@ -40,7 +40,7 @@ public static function getInfo() {
function setUp() {
parent::setUp();
$this->editorManager = new EditorManager($this->container->getParameter('container.namespaces'));
$this->editorManager = $this->container->get('plugin.manager.edit.editor');
$this->editorSelector = new EditorSelector($this->editorManager);
}
......@@ -109,8 +109,6 @@ function testTextWysiwyg() {
// Enable edit_test module so that the 'wysiwyg' Create.js PropertyEditor
// widget becomes available.
$this->enableModules(array('edit_test'));
$this->editorManager = new EditorManager($this->container->getParameter('container.namespaces'));
$this->editorSelector = new EditorSelector($this->editorManager);
$field_name = 'field_textarea';
$this->createFieldWithInstance(
......
......@@ -56,7 +56,7 @@ public static function getInfo() {
function setUp() {
parent::setUp();
$this->editorManager = new EditorManager($this->container->getParameter('container.namespaces'));
$this->editorManager = $this->container->get('plugin.manager.edit.editor');
$this->accessChecker = new MockEditEntityFieldAccessCheck();
$this->editorSelector = new EditorSelector($this->editorManager);
$this->metadataGenerator = new MetadataGenerator($this->accessChecker, $this->editorSelector, $this->editorManager);
......@@ -131,9 +131,6 @@ function testEditorWithCustomMetadata() {
// Enable edit_test module so that the WYSIWYG Create.js PropertyEditor
// widget becomes available.
$this->enableModules(array('edit_test'));
$this->editorManager = new EditorManager($this->container->getParameter('container.namespaces'));
$this->editorSelector = new EditorSelector($this->editorManager);
$this->metadataGenerator = new MetadataGenerator($this->accessChecker, $this->editorSelector, $this->editorManager);
// Create a rich text field.
$field_name = 'field_rich';
......
services:
plugin.manager.editor:
class: Drupal\editor\Plugin\EditorManager
arguments: ['%container.namespaces%']
arguments: ['@container.namespaces']
......@@ -22,10 +22,11 @@ class EditorManager extends PluginManagerBase {
/**
* Overrides \Drupal\Component\Plugin\PluginManagerBase::__construct().
*
* @param array $namespaces
* An array of paths keyed by it's corresponding namespaces.
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations,
*/
public function __construct(array $namespaces) {
public function __construct(\Traversable $namespaces) {
$this->discovery = new AnnotatedClassDiscovery('editor', 'editor', $namespaces);
$this->discovery = new AlterDecorator($this->discovery, 'editor_info');
$this->discovery = new CacheDecorator($this->discovery, 'editor');
......
......@@ -122,7 +122,7 @@ protected function getSelectedEditor($items, $field_name, $view_mode = 'default'
* format compatibility.
*/
function testEditorSelection() {
$this->editorManager = new EditorManager($this->container->getParameter('container.namespaces'));
$this->editorManager = new EditorManager($this->container->get('container.namespaces'));
$this->editorSelector = new EditorSelector($this->editorManager);
// Pretend there is an entity with these items for the field.
......@@ -146,7 +146,7 @@ function testEditorSelection() {
* Tests (custom) metadata when the "Editor" Create.js editor is used.
*/
function testMetadata() {
$this->editorManager = new EditorManager($this->container->getParameter('container.namespaces'));
$this->editorManager = new EditorManager($this->container->get('container.namespaces'));
$this->accessChecker = new MockEditEntityFieldAccessCheck();
$this->editorSelector = new EditorSelector($this->editorManager);
$this->metadataGenerator = new MetadataGenerator($this->accessChecker, $this->editorSelector, $this->editorManager);
......
......@@ -66,7 +66,7 @@ function setUp() {
* Tests the configurable text editor manager.
*/
function testManager() {
$this->editorManager = new EditorManager($this->container->getParameter('container.namespaces'));
$this->editorManager = new EditorManager($this->container->get('container.namespaces'));
// Case 1: no text editor available:
// - listOptions() should return an empty list of options
......@@ -79,7 +79,6 @@ function testManager() {
// Enable the Text Editor Test module, which has the Unicorn Editor and
// clear the editor manager's cache so it is picked up.
$this->enableModules(array('editor_test'));
$this->editorManager = new EditorManager($this->container->getParameter('container.namespaces'));
$this->editorManager->clearCachedDefinitions();
// Case 2: a text editor available.
......
services:
plugin.manager.entity_reference.selection:
class: Drupal\entity_reference\Plugin\Type\SelectionPluginManager
arguments: ['%container.namespaces%']
arguments: ['@container.namespaces']
entity_reference.autocomplete:
class: Drupal\entity_reference\EntityReferenceAutocomplete
arguments: ['@plugin.manager.entity']
......@@ -23,10 +23,11 @@ class SelectionPluginManager extends PluginManagerBase {
/**
* Constructs a SelectionPluginManager object.
*
* @param array $namespaces
* An array of paths keyed by it's corresponding namespaces.
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations,
*/
public function __construct($namespaces) {
public function __construct(\Traversable $namespaces) {
$this->baseDiscovery = new AlterDecorator(new AnnotatedClassDiscovery('entity_reference', 'selection', $namespaces), 'entity_reference_selection');
$this->discovery = new CacheDecorator($this->baseDiscovery, 'entity_reference_selection');
$this->factory = new ReflectionFactory($this);
......
services:
plugin.manager.field.widget:
class: Drupal\field\Plugin\Type\Widget\WidgetPluginManager
arguments: ['%container.namespaces%']
arguments: ['@container.namespaces']
plugin.manager.field.formatter:
class: Drupal\field\Plugin\Type\Formatter\FormatterPluginManager
arguments: ['%container.namespaces%']
arguments: ['@container.namespaces']
field.info:
class: Drupal\field\FieldInfo
arguments: ['@cache.field', '@config.factory', '@module_handler']
......
......@@ -30,10 +30,11 @@ class FormatterPluginManager extends PluginManagerBase {
/**
* Constructs a FormatterPluginManager object.
*
* @param array $namespaces
* An array of paths keyed by it's corresponding namespaces.
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations,
*/
public function __construct($namespaces) {
public function __construct(\Traversable $namespaces) {
$this->discovery = new AnnotatedClassDiscovery('field', 'formatter', $namespaces);
$this->discovery = new ProcessDecorator($this->discovery, array($this, 'processDefinition'));
$this->discovery = new AlterDecorator($this->discovery, 'field_formatter_info');
......
......@@ -31,10 +31,11 @@ class WidgetPluginManager extends PluginManagerBase {
/**
* Constructs a WidgetPluginManager object.
*
* @param array $namespaces
* An array of paths keyed by it's corresponding namespaces.
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations,
*/
public function __construct($namespaces) {
public function __construct(\Traversable $namespaces) {
$this->discovery = new AnnotatedClassDiscovery('field', 'widget', $namespaces);
$this->discovery = new ProcessDecorator($this->discovery, array($this, 'processDefinition'));
$this->discovery = new AlterDecorator($this->discovery, 'field_widget_info');
......
services:
plugin.manager.layout:
class: Drupal\layout\Plugin\Type\LayoutManager
arguments: ['%container.namespaces%']
arguments: ['@container.namespaces']
......@@ -25,10 +25,11 @@ class LayoutManager extends PluginManagerBase {
/**
* Overrides Drupal\Component\Plugin\PluginManagerBase::__construct().
*
* @param array $namespaces
* An array of paths keyed by it's corresponding namespaces.
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations,
*/
public function __construct($namespaces) {
public function __construct(\Traversable $namespaces) {
// Create layout plugin derivatives from declaratively defined layouts.
$this->discovery = new AnnotatedClassDiscovery('layout', 'layout', $namespaces);
$this->discovery = new DerivativeDiscoveryDecorator($this->discovery);
......
......@@ -35,7 +35,7 @@ protected function setUp() {
* Tests conditions.
*/
function testConditions() {
$manager = $this->container->get('plugin.manager.condition');
$manager = $this->container->get('plugin.manager.condition', $this->container->get('container.namespaces'));
// Get some nodes of various types to check against.
$page = entity_create('node', array('type' => 'page', 'title' => $this->randomName()));
......
......@@ -20,10 +20,11 @@ class ResourcePluginManager extends PluginManagerBase {
/**
* Overrides Drupal\Component\Plugin\PluginManagerBase::__construct().
*
* @param array $namespaces
* An array of paths keyed by it's corresponding namespaces.
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations,
*/
public function __construct($namespaces) {
public function __construct(\Traversable $namespaces) {
// Create resource plugin derivatives from declaratively defined resources.
$this->discovery = new DerivativeDiscoveryDecorator(new AnnotatedClassDiscovery('rest', 'resource', $namespaces));
$this->factory = new ReflectionFactory($this->discovery);
......
services:
plugin.manager.rest:
class: Drupal\rest\Plugin\Type\ResourcePluginManager
arguments: ['%container.namespaces%']
arguments: ['@container.namespaces']
rest.route_subscriber:
class: Drupal\rest\EventSubscriber\RouteSubscriber
tags:
......
......@@ -14,6 +14,7 @@ public static function getInfo() {
'name' => 'PHPUnit errors',
'description' => 'Test PHPUnit errors getting converted to Simpletest errors.',
'group' => 'Simpletest',
);
}
......
......@@ -18,8 +18,12 @@ class ImageToolkitManager extends PluginManagerBase {
/**
* Constructs the ImageToolkitManager object.
*
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations,
*/
public function __construct(array $namespaces) {
public function __construct(\Traversable $namespaces) {
$this->discovery = new AnnotatedClassDiscovery('system', 'imagetoolkit', $namespaces);
$this->factory = new DefaultFactory($this->discovery);
}
......
......@@ -24,10 +24,11 @@ class PluginUIManager extends PluginManagerBase {
/**
* Constructs a \Drupal\system\Plugin\Type\PluginUIManager object.
*
* @param array $namespaces
* An array of paths keyed by it's corresponding namespaces.
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations,
*/
public function __construct($namespaces) {
public function __construct(\Traversable $namespaces) {
$this->discovery = new AnnotatedClassDiscovery('system', 'plugin_ui', $namespaces);
$this->discovery = new DerivativeDiscoveryDecorator($this->discovery);
$this->discovery = new AlterDecorator($this->discovery, 'plugin_ui');
......