Commit 7f3c710f authored by effulgentsia's avatar effulgentsia

Issue #2821189 by tim.plunkett, tstoeckler: Allow object-based plugin...

Issue #2821189 by tim.plunkett, tstoeckler: Allow object-based plugin definitions to be processed in DerivativeDiscoveryDecorator
parent 09769ab5
<?php <?php
namespace Drupal\Core\Layout; namespace Drupal\Component\Plugin\Definition;
use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
/** /**
* Provides an interface for a derivable plugin definition. * Provides an interface for a derivable plugin definition.
* *
* @see \Drupal\Component\Plugin\Derivative\DeriverInterface * @see \Drupal\Component\Plugin\Derivative\DeriverInterface
* @see \Drupal\Core\Layout\ObjectDefinitionContainerDerivativeDiscoveryDecorator
*
* @internal
* The layout system is currently experimental and should only be leveraged by
* experimental modules and development releases of contributed modules.
* See https://www.drupal.org/core/experimental for more information.
*
* @todo Move into \Drupal\Component\Plugin\Definition in
* https://www.drupal.org/node/2821189.
*/ */
interface DerivablePluginDefinitionInterface extends PluginDefinitionInterface { interface DerivablePluginDefinitionInterface extends PluginDefinitionInterface {
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
namespace Drupal\Component\Plugin\Discovery; namespace Drupal\Component\Plugin\Discovery;
use Drupal\Component\Plugin\Definition\DerivablePluginDefinitionInterface;
use Drupal\Component\Plugin\Exception\InvalidDeriverException; use Drupal\Component\Plugin\Exception\InvalidDeriverException;
/** /**
...@@ -203,12 +204,21 @@ protected function getDeriver($base_plugin_id, $base_definition) { ...@@ -203,12 +204,21 @@ protected function getDeriver($base_plugin_id, $base_definition) {
*/ */
protected function getDeriverClass($base_definition) { protected function getDeriverClass($base_definition) {
$class = NULL; $class = NULL;
if ((is_array($base_definition) || ($base_definition = (array) $base_definition)) && (isset($base_definition['deriver']) && $class = $base_definition['deriver'])) { $id = NULL;
if ($base_definition instanceof DerivablePluginDefinitionInterface) {
$class = $base_definition->getDeriver();
$id = $base_definition->id();
}
if ((is_array($base_definition) || ($base_definition = (array) $base_definition)) && (isset($base_definition['deriver']))) {
$class = $base_definition['deriver'];
$id = $base_definition['id'];
}
if ($class) {
if (!class_exists($class)) { if (!class_exists($class)) {
throw new InvalidDeriverException(sprintf('Plugin (%s) deriver "%s" does not exist.', $base_definition['id'], $class)); throw new InvalidDeriverException(sprintf('Plugin (%s) deriver "%s" does not exist.', $id, $class));
} }
if (!is_subclass_of($class, '\Drupal\Component\Plugin\Derivative\DeriverInterface')) { if (!is_subclass_of($class, '\Drupal\Component\Plugin\Derivative\DeriverInterface')) {
throw new InvalidDeriverException(sprintf('Plugin (%s) deriver "%s" must implement \Drupal\Component\Plugin\Derivative\DeriverInterface.', $base_definition['id'], $class)); throw new InvalidDeriverException(sprintf('Plugin (%s) deriver "%s" must implement \Drupal\Component\Plugin\Derivative\DeriverInterface.', $id, $class));
} }
} }
return $class; return $class;
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
use Drupal\Component\Plugin\Definition\DependentPluginDefinitionInterface; use Drupal\Component\Plugin\Definition\DependentPluginDefinitionInterface;
use Drupal\Component\Plugin\Definition\DependentPluginDefinitionTrait; use Drupal\Component\Plugin\Definition\DependentPluginDefinitionTrait;
use Drupal\Component\Plugin\Definition\DerivablePluginDefinitionInterface;
use Drupal\Component\Plugin\Definition\PluginDefinitionInterface; use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
use Drupal\Component\Plugin\Definition\PluginDefinition; use Drupal\Component\Plugin\Definition\PluginDefinition;
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
namespace Drupal\Core\Layout; namespace Drupal\Core\Layout;
use Drupal\Component\Annotation\Plugin\Discovery\AnnotationBridgeDecorator; use Drupal\Component\Annotation\Plugin\Discovery\AnnotationBridgeDecorator;
use Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator;
use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException; use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Extension\ModuleHandlerInterface;
...@@ -65,7 +66,7 @@ protected function getDiscovery() { ...@@ -65,7 +66,7 @@ protected function getDiscovery() {
$discovery = new AnnotatedClassDiscovery($this->subdir, $this->namespaces, $this->pluginDefinitionAnnotationName, $this->additionalAnnotationNamespaces); $discovery = new AnnotatedClassDiscovery($this->subdir, $this->namespaces, $this->pluginDefinitionAnnotationName, $this->additionalAnnotationNamespaces);
$discovery = new YamlDiscoveryDecorator($discovery, 'layouts', $this->moduleHandler->getModuleDirectories() + $this->themeHandler->getThemeDirectories()); $discovery = new YamlDiscoveryDecorator($discovery, 'layouts', $this->moduleHandler->getModuleDirectories() + $this->themeHandler->getThemeDirectories());
$discovery = new AnnotationBridgeDecorator($discovery, $this->pluginDefinitionAnnotationName); $discovery = new AnnotationBridgeDecorator($discovery, $this->pluginDefinitionAnnotationName);
$discovery = new ObjectDefinitionContainerDerivativeDiscoveryDecorator($discovery); $discovery = new DerivativeDiscoveryDecorator($discovery);
$this->discovery = $discovery; $this->discovery = $discovery;
} }
return $this->discovery; return $this->discovery;
......
<?php
namespace Drupal\Core\Layout;
use Drupal\Component\Plugin\Exception\InvalidDeriverException;
use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
/**
* Allows object-based definitions to use derivatives.
*
* @internal
* The layout system is currently experimental and should only be leveraged by
* experimental modules and development releases of contributed modules.
* See https://www.drupal.org/core/experimental for more information.
*
* @todo In https://www.drupal.org/node/2821189 merge into
* \Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator.
*/
class ObjectDefinitionContainerDerivativeDiscoveryDecorator extends ContainerDerivativeDiscoveryDecorator {
/**
* {@inheritdoc}
*/
protected function getDeriverClass($base_definition) {
$class = NULL;
if ($base_definition instanceof DerivablePluginDefinitionInterface && $class = $base_definition->getDeriver()) {
if (!class_exists($class)) {
throw new InvalidDeriverException(sprintf('Plugin (%s) deriver "%s" does not exist.', $base_definition['id'], $class));
}
if (!is_subclass_of($class, '\Drupal\Component\Plugin\Derivative\DeriverInterface')) {
throw new InvalidDeriverException(sprintf('Plugin (%s) deriver "%s" must implement \Drupal\Component\Plugin\Derivative\DeriverInterface.', $base_definition['id'], $class));
}
}
return $class;
}
}
...@@ -35,6 +35,7 @@ public function __construct() { ...@@ -35,6 +35,7 @@ public function __construct() {
// A simple plugin: the user login block. // A simple plugin: the user login block.
$this->discovery->setDefinition('user_login', array( $this->discovery->setDefinition('user_login', array(
'id' => 'user_login',
'label' => t('User login'), 'label' => t('User login'),
'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserLoginBlock', 'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserLoginBlock',
)); ));
...@@ -46,11 +47,13 @@ public function __construct() { ...@@ -46,11 +47,13 @@ public function __construct() {
// MockMenuBlockDeriver class ensures that only derivatives, and not the // MockMenuBlockDeriver class ensures that only derivatives, and not the
// base plugin, are available to the system. // base plugin, are available to the system.
$this->discovery->setDefinition('menu', array( $this->discovery->setDefinition('menu', array(
'id' => 'menu',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockMenuBlock', 'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockMenuBlock',
'deriver' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockMenuBlockDeriver', 'deriver' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockMenuBlockDeriver',
)); ));
// A plugin defining itself as a derivative. // A plugin defining itself as a derivative.
$this->discovery->setDefinition('menu:foo', array( $this->discovery->setDefinition('menu:foo', array(
'id' => 'menu',
'label' => t('Base label'), 'label' => t('Base label'),
'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockMenuBlock', 'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockMenuBlock',
)); ));
...@@ -62,6 +65,7 @@ public function __construct() { ...@@ -62,6 +65,7 @@ public function __construct() {
// MockLayoutBlockDeriver class ensures that both the base plugin and the // MockLayoutBlockDeriver class ensures that both the base plugin and the
// derivatives are available to the system. // derivatives are available to the system.
$this->discovery->setDefinition('layout', array( $this->discovery->setDefinition('layout', array(
'id' => 'layout',
'label' => t('Layout'), 'label' => t('Layout'),
'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockLayoutBlock', 'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockLayoutBlock',
'deriver' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockLayoutBlockDeriver', 'deriver' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockLayoutBlockDeriver',
...@@ -70,6 +74,7 @@ public function __construct() { ...@@ -70,6 +74,7 @@ public function __construct() {
// A block plugin that requires context to function. This block requires a // A block plugin that requires context to function. This block requires a
// user object in order to return the user name from the getTitle() method. // user object in order to return the user name from the getTitle() method.
$this->discovery->setDefinition('user_name', array( $this->discovery->setDefinition('user_name', array(
'id' => 'user_name',
'label' => t('User name'), 'label' => t('User name'),
'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserNameBlock', 'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserNameBlock',
'context' => array( 'context' => array(
...@@ -79,6 +84,7 @@ public function __construct() { ...@@ -79,6 +84,7 @@ public function __construct() {
// An optional context version of the previous block plugin. // An optional context version of the previous block plugin.
$this->discovery->setDefinition('user_name_optional', array( $this->discovery->setDefinition('user_name_optional', array(
'id' => 'user_name_optional',
'label' => t('User name optional'), 'label' => t('User name optional'),
'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserNameBlock', 'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserNameBlock',
'context' => array( 'context' => array(
...@@ -88,12 +94,14 @@ public function __construct() { ...@@ -88,12 +94,14 @@ public function __construct() {
// A block plugin that requires a typed data string context to function. // A block plugin that requires a typed data string context to function.
$this->discovery->setDefinition('string_context', array( $this->discovery->setDefinition('string_context', array(
'id' => 'string_context',
'label' => t('String typed data'), 'label' => t('String typed data'),
'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\TypedDataStringBlock', 'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\TypedDataStringBlock',
)); ));
// A complex context plugin that requires both a user and node for context. // A complex context plugin that requires both a user and node for context.
$this->discovery->setDefinition('complex_context', array( $this->discovery->setDefinition('complex_context', array(
'id' => 'complex_context',
'label' => t('Complex context'), 'label' => t('Complex context'),
'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockComplexContextBlock', 'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockComplexContextBlock',
'context' => array( 'context' => array(
......
...@@ -58,31 +58,38 @@ protected function setUp() { ...@@ -58,31 +58,38 @@ protected function setUp() {
); );
$this->mockBlockExpectedDefinitions = array( $this->mockBlockExpectedDefinitions = array(
'user_login' => array( 'user_login' => array(
'id' => 'user_login',
'label' => 'User login', 'label' => 'User login',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserLoginBlock', 'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserLoginBlock',
), ),
'menu:main_menu' => array( 'menu:main_menu' => array(
'id' => 'menu',
'label' => 'Main menu', 'label' => 'Main menu',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockMenuBlock', 'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockMenuBlock',
), ),
'menu:navigation' => array( 'menu:navigation' => array(
'id' => 'menu',
'label' => 'Navigation', 'label' => 'Navigation',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockMenuBlock', 'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockMenuBlock',
), ),
'menu:foo' => array( 'menu:foo' => array(
'id' => 'menu',
'label' => 'Base label', 'label' => 'Base label',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockMenuBlock', 'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockMenuBlock',
'setting' => 'default', 'setting' => 'default',
), ),
'layout' => array( 'layout' => array(
'id' => 'layout',
'label' => 'Layout', 'label' => 'Layout',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockLayoutBlock', 'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockLayoutBlock',
), ),
'layout:foo' => array( 'layout:foo' => array(
'id' => 'layout',
'label' => 'Layout Foo', 'label' => 'Layout Foo',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockLayoutBlock', 'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockLayoutBlock',
), ),
'user_name' => array( 'user_name' => array(
'id' => 'user_name',
'label' => 'User name', 'label' => 'User name',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserNameBlock', 'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserNameBlock',
'context' => array( 'context' => array(
...@@ -90,6 +97,7 @@ protected function setUp() { ...@@ -90,6 +97,7 @@ protected function setUp() {
), ),
), ),
'user_name_optional' => array( 'user_name_optional' => array(
'id' => 'user_name_optional',
'label' => 'User name optional', 'label' => 'User name optional',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserNameBlock', 'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserNameBlock',
'context' => array( 'context' => array(
...@@ -97,10 +105,12 @@ protected function setUp() { ...@@ -97,10 +105,12 @@ protected function setUp() {
), ),
), ),
'string_context' => array( 'string_context' => array(
'id' => 'string_context',
'label' => 'String typed data', 'label' => 'String typed data',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\TypedDataStringBlock', 'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\TypedDataStringBlock',
), ),
'complex_context' => array( 'complex_context' => array(
'id' => 'complex_context',
'label' => 'Complex context', 'label' => 'Complex context',
'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockComplexContextBlock', 'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockComplexContextBlock',
'context' => array( 'context' => array(
......
...@@ -2,12 +2,16 @@ ...@@ -2,12 +2,16 @@
namespace Drupal\Tests\Core\Plugin\Discovery; namespace Drupal\Tests\Core\Plugin\Discovery;
use Drupal\Component\Plugin\Definition\DerivablePluginDefinitionInterface;
use Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator; use Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator;
use Drupal\Component\Plugin\Exception\InvalidDeriverException;
use Drupal\Tests\UnitTestCase; use Drupal\Tests\UnitTestCase;
/** /**
* Unit tests for the derivative discovery decorator. * Unit tests for the derivative discovery decorator.
* *
* @coversDefaultClass \Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator
*
* @group Plugin * @group Plugin
*/ */
class DerivativeDiscoveryDecoratorTest extends UnitTestCase { class DerivativeDiscoveryDecoratorTest extends UnitTestCase {
...@@ -82,6 +86,50 @@ public function testGetDerivativeFetcherWithAnnotationObjects() { ...@@ -82,6 +86,50 @@ public function testGetDerivativeFetcherWithAnnotationObjects() {
$this->assertEquals('\Drupal\Tests\Core\Plugin\Discovery\TestDerivativeDiscoveryWithObject', $definitions['non_container_aware_discovery:test_discovery_1']->deriver); $this->assertEquals('\Drupal\Tests\Core\Plugin\Discovery\TestDerivativeDiscoveryWithObject', $definitions['non_container_aware_discovery:test_discovery_1']->deriver);
} }
/**
* Tests getDeriverClass with classed objects instead of arrays.
*
* @covers ::getDeriverClass
*/
public function testGetDeriverClassWithClassedDefinitions() {
$definitions = array();
$definition = $this->prophesize(DerivablePluginDefinitionInterface::class);
$definition->id()->willReturn('non_container_aware_discovery');
$definition->getDeriver()->willReturn(TestDerivativeDiscoveryWithObject::class);
$definitions['non_container_aware_discovery'] = $definition->reveal();
$this->discoveryMain->expects($this->any())
->method('getDefinitions')
->will($this->returnValue($definitions));
$discovery = new DerivativeDiscoveryDecorator($this->discoveryMain);
$definitions = $discovery->getDefinitions();
// Ensure that both test derivatives got added.
$this->assertContainsOnlyInstancesOf(DerivablePluginDefinitionInterface::class, $definitions);
$this->assertEquals(['non_container_aware_discovery:test_discovery_0', 'non_container_aware_discovery:test_discovery_1'], array_keys($definitions));
}
/**
* @covers ::getDeriverClass
*/
public function testGetDeriverClassWithInvalidClassedDefinitions() {
$definition = $this->prophesize(DerivablePluginDefinitionInterface::class);
$definition->id()->willReturn('non_existent_discovery');
$definition->getDeriver()->willReturn('\Drupal\system\Tests\Plugin\NonExistentDeriver');
$definitions['non_existent_discovery'] = $definition->reveal();
$this->discoveryMain->expects($this->any())
->method('getDefinitions')
->willReturn($definitions);
$discovery = new DerivativeDiscoveryDecorator($this->discoveryMain);
$this->setExpectedException(InvalidDeriverException::class, 'Plugin (non_existent_discovery) deriver "\Drupal\system\Tests\Plugin\NonExistentDeriver" does not exist.');
$discovery->getDefinitions();
}
/** /**
* Tests the getDerivativeFetcher method with a non-existent class. * Tests the getDerivativeFetcher method with a non-existent class.
* *
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment