From 091483c1f1ab020f42df9f892a7c2fda596dd263 Mon Sep 17 00:00:00 2001 From: Alex Pott <alex.a.pott@googlemail.com> Date: Fri, 3 May 2024 09:01:25 +0100 Subject: [PATCH] Issue #3420983 by sorlov, godotislate, alexpott, quietone, kim.pepper, larowlan, mstrelan: Convert Layout plugin discovery to attributes --- .../Drupal/Core/Layout/Attribute/Layout.php | 111 ++++++++++++++++++ .../Drupal/Core/Layout/LayoutDefinition.php | 2 +- .../Core/Layout/LayoutPluginManager.php | 12 +- .../Plugin/Layout/TestLayoutContentFooter.php | 33 +++--- .../Plugin/Layout/TestLayoutMainFooter.php | 43 +++---- .../layout_builder/layout_builder.api.php | 2 +- .../src/Plugin/Layout/BlankLayout.php | 10 +- .../Plugin/Layout/LayoutBuilderTestPlugin.php | 21 ++-- .../src/Plugin/Layout/LayoutWithoutLabel.php | 21 ++-- .../Plugin/Layout/TestContextAwareLayout.php | 28 +++-- .../Layout/LayoutTestDependenciesPlugin.php | 25 ++-- .../src/Plugin/Layout/LayoutTestPlugin.php | 27 +++-- .../Core/Layout/LayoutPluginManagerTest.php | 97 +++++++++++++-- 13 files changed, 319 insertions(+), 113 deletions(-) create mode 100644 core/lib/Drupal/Core/Layout/Attribute/Layout.php diff --git a/core/lib/Drupal/Core/Layout/Attribute/Layout.php b/core/lib/Drupal/Core/Layout/Attribute/Layout.php new file mode 100644 index 000000000000..11bf8a206cf6 --- /dev/null +++ b/core/lib/Drupal/Core/Layout/Attribute/Layout.php @@ -0,0 +1,111 @@ +<?php + +namespace Drupal\Core\Layout\Attribute; + +use Drupal\Component\Plugin\Attribute\Plugin; +use Drupal\Core\StringTranslation\TranslatableMarkup; +use Drupal\Core\Layout\LayoutDefault; +use Drupal\Core\Layout\LayoutDefinition; + +/** + * Defines a Layout attribute object. + * + * Layouts are used to define a list of regions and then output render arrays + * in each of the regions, usually using a template. + * + * Plugin Namespace: Plugin\Layout + * + * @see \Drupal\Core\Layout\LayoutInterface + * @see \Drupal\Core\Layout\LayoutDefault + * @see \Drupal\Core\Layout\LayoutPluginManager + * @see plugin_api + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +class Layout extends Plugin { + + /** + * Any additional properties and values. + * + * @see \Drupal\Core\Layout\LayoutDefinition::$additional + * + * @var array + */ + public readonly array $additional; + + /** + * Constructs a Layout attribute. + * + * @param string $id + * The plugin ID. + * @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $label + * (optional) The human-readable name. @todo Deprecate optional label in + * https://www.drupal.org/project/drupal/issues/3392572. + * @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $category + * (optional) The human-readable category. @todo Deprecate optional category + * in https://www.drupal.org/project/drupal/issues/3392572. + * @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $description + * (optional) The description for advanced layouts. + * @param string|null $template + * (optional) The template file to render the layout. + * @param string $theme_hook + * (optional) The template hook to render the layout. + * @param string|null $path + * (optional) Path (relative to the module or theme) to resources like icon or template. + * @param string|null $library + * (optional) The asset library. + * @param string|null $icon + * (optional) The path to the preview image (relative to the 'path' given). + * @param string[][]|null $icon_map + * (optional) The icon map. + * @param array $regions + * (optional) An associative array of regions in this layout. + * @param string|null $default_region + * (optional) The default region. + * @param class-string $class + * (optional) The layout plugin class. + * @param \Drupal\Core\Plugin\Context\ContextDefinitionInterface[] $context_definitions + * (optional) The context definition. + * @param array $config_dependencies + * (optional) The config dependencies. + * @param class-string|null $deriver + * (optional) The deriver class. + * @param mixed $additional + * (optional) Additional properties passed in that can be used by a deriver. + */ + public function __construct( + public readonly string $id, + public readonly ?TranslatableMarkup $label = NULL, + public readonly ?TranslatableMarkup $category = NULL, + public readonly ?TranslatableMarkup $description = NULL, + public readonly ?string $template = NULL, + public readonly string $theme_hook = 'layout', + public readonly ?string $path = NULL, + public readonly ?string $library = NULL, + public readonly ?string $icon = NULL, + public readonly ?array $icon_map = NULL, + public readonly array $regions = [], + public readonly ?string $default_region = NULL, + public string $class = LayoutDefault::class, + public readonly array $context_definitions = [], + public readonly array $config_dependencies = [], + public readonly ?string $deriver = NULL, + ...$additional, + ) { + // Layout definitions support arbitrary properties being passed in, which + // are stored in the 'additional' property in LayoutDefinition. The variadic + // 'additional' parameter here saves arbitrary parameters passed into the + // 'additional' property in this attribute class. The 'additional' property + // gets passed to the LayoutDefinition constructor in ::get(). + // @see \Drupal\Core\Layout\LayoutDefinition::$additional + // @see \Drupal\Core\Layout\LayoutDefinition::get() + $this->additional = $additional; + } + + /** + * {@inheritdoc} + */ + public function get(): LayoutDefinition { + return new LayoutDefinition(parent::get()); + } + +} diff --git a/core/lib/Drupal/Core/Layout/LayoutDefinition.php b/core/lib/Drupal/Core/Layout/LayoutDefinition.php index 9936fcaa0d9d..11cbf5143662 100644 --- a/core/lib/Drupal/Core/Layout/LayoutDefinition.php +++ b/core/lib/Drupal/Core/Layout/LayoutDefinition.php @@ -132,7 +132,7 @@ class LayoutDefinition extends PluginDefinition implements PluginDefinitionInter * LayoutDefinition constructor. * * @param array $definition - * An array of values from the annotation. + * An array of values from the attribute. */ public function __construct(array $definition) { // If there are context definitions in the plugin definition, they should diff --git a/core/lib/Drupal/Core/Layout/LayoutPluginManager.php b/core/lib/Drupal/Core/Layout/LayoutPluginManager.php index 2265194bf30b..5e8f2f3b2ae6 100644 --- a/core/lib/Drupal/Core/Layout/LayoutPluginManager.php +++ b/core/lib/Drupal/Core/Layout/LayoutPluginManager.php @@ -2,16 +2,16 @@ namespace Drupal\Core\Layout; -use Drupal\Component\Annotation\Plugin\Discovery\AnnotationBridgeDecorator; +use Drupal\Component\Plugin\Discovery\AttributeBridgeDecorator; use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Extension\ThemeHandlerInterface; use Drupal\Core\Plugin\DefaultPluginManager; -use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery; +use Drupal\Core\Plugin\Discovery\AttributeDiscoveryWithAnnotations; use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator; use Drupal\Core\Plugin\Discovery\YamlDiscoveryDecorator; -use Drupal\Core\Layout\Annotation\Layout; +use Drupal\Core\Layout\Attribute\Layout; use Drupal\Core\Plugin\FilteredPluginManagerTrait; use Drupal\Core\StringTranslation\TranslatableMarkup; @@ -43,7 +43,7 @@ class LayoutPluginManager extends DefaultPluginManager implements LayoutPluginMa * The theme handler to invoke the alter hook with. */ public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler) { - parent::__construct('Plugin/Layout', $namespaces, $module_handler, LayoutInterface::class, Layout::class); + parent::__construct('Plugin/Layout', $namespaces, $module_handler, LayoutInterface::class, Layout::class, 'Drupal\Core\Layout\Annotation\Layout'); $this->themeHandler = $theme_handler; $type = $this->getType(); @@ -70,13 +70,13 @@ protected function providerExists($provider) { */ protected function getDiscovery() { if (!$this->discovery) { - $discovery = new AnnotatedClassDiscovery($this->subdir, $this->namespaces, $this->pluginDefinitionAnnotationName, $this->additionalAnnotationNamespaces); + $discovery = new AttributeDiscoveryWithAnnotations($this->subdir, $this->namespaces, $this->pluginDefinitionAttributeName, $this->pluginDefinitionAnnotationName, $this->additionalAnnotationNamespaces); $discovery = new YamlDiscoveryDecorator($discovery, 'layouts', $this->moduleHandler->getModuleDirectories() + $this->themeHandler->getThemeDirectories()); $discovery ->addTranslatableProperty('label') ->addTranslatableProperty('description') ->addTranslatableProperty('category'); - $discovery = new AnnotationBridgeDecorator($discovery, $this->pluginDefinitionAnnotationName); + $discovery = new AttributeBridgeDecorator($discovery, $this->pluginDefinitionAttributeName); $discovery = new ContainerDerivativeDiscoveryDecorator($discovery); $this->discovery = $discovery; } diff --git a/core/modules/field_layout/tests/modules/field_layout_test/src/Plugin/Layout/TestLayoutContentFooter.php b/core/modules/field_layout/tests/modules/field_layout_test/src/Plugin/Layout/TestLayoutContentFooter.php index c283c82d7875..75d7e384f5f9 100644 --- a/core/modules/field_layout/tests/modules/field_layout_test/src/Plugin/Layout/TestLayoutContentFooter.php +++ b/core/modules/field_layout/tests/modules/field_layout_test/src/Plugin/Layout/TestLayoutContentFooter.php @@ -2,26 +2,27 @@ namespace Drupal\field_layout_test\Plugin\Layout; +use Drupal\Core\Layout\Attribute\Layout; use Drupal\Core\Layout\LayoutDefault; +use Drupal\Core\StringTranslation\TranslatableMarkup; /** - * Provides an annotated layout plugin for field_layout tests. - * - * @Layout( - * id = "test_layout_content_and_footer", - * label = @Translation("Test plugin: Content and Footer"), - * category = @Translation("Layout test"), - * description = @Translation("Test layout"), - * regions = { - * "content" = { - * "label" = @Translation("Content Region") - * }, - * "footer" = { - * "label" = @Translation("Footer Region") - * } - * }, - * ) + * Provides a Layout plugin for field_layout tests. */ +#[Layout( + id: 'test_layout_content_and_footer', + label: new TranslatableMarkup('Test plugin: Content and Footer'), + category: new TranslatableMarkup('Layout test'), + description: new TranslatableMarkup('Test layout'), + regions: [ + "content" => [ + "label" => new TranslatableMarkup("Content Region"), + ], + "footer" => [ + "label" => new TranslatableMarkup("Footer Region"), + ], + ], +)] class TestLayoutContentFooter extends LayoutDefault { } diff --git a/core/modules/field_layout/tests/modules/field_layout_test/src/Plugin/Layout/TestLayoutMainFooter.php b/core/modules/field_layout/tests/modules/field_layout_test/src/Plugin/Layout/TestLayoutMainFooter.php index b01ec4518751..9a0df30325f4 100644 --- a/core/modules/field_layout/tests/modules/field_layout_test/src/Plugin/Layout/TestLayoutMainFooter.php +++ b/core/modules/field_layout/tests/modules/field_layout_test/src/Plugin/Layout/TestLayoutMainFooter.php @@ -2,31 +2,32 @@ namespace Drupal\field_layout_test\Plugin\Layout; +use Drupal\Core\Layout\Attribute\Layout; use Drupal\Core\Layout\LayoutDefault; +use Drupal\Core\StringTranslation\TranslatableMarkup; /** - * Provides an annotated layout plugin for field_layout tests. - * - * @Layout( - * id = "test_layout_main_and_footer", - * label = @Translation("Test plugin: Main and Footer"), - * category = @Translation("Layout test"), - * description = @Translation("Test layout"), - * regions = { - * "main" = { - * "label" = @Translation("Main Region") - * }, - * "footer" = { - * "label" = @Translation("Footer Region") - * } - * }, - * config_dependencies = { - * "module" = { - * "layout_discovery", - * }, - * }, - * ) + * Provides an attributed layout plugin for field_layout tests. */ +#[Layout( + id: 'test_layout_main_and_footer', + label: new TranslatableMarkup('Test plugin: Main and Footer'), + category: new TranslatableMarkup('Layout test'), + description: new TranslatableMarkup('Test layout'), + regions: [ + "main" => [ + "label" => new TranslatableMarkup("Main Region"), + ], + "footer" => [ + "label" => new TranslatableMarkup("Footer Region"), + ], + ], + config_dependencies: [ + "module" => [ + "layout_discovery", + ], + ], +)] class TestLayoutMainFooter extends LayoutDefault { /** diff --git a/core/modules/layout_builder/layout_builder.api.php b/core/modules/layout_builder/layout_builder.api.php index 66afc1fb175a..d7e84c6a26b4 100644 --- a/core/modules/layout_builder/layout_builder.api.php +++ b/core/modules/layout_builder/layout_builder.api.php @@ -15,7 +15,7 @@ * * By default, the Layout Builder access check requires the 'configure any * layout' permission. Individual section storage plugins may override this by - * setting the 'handles_permission_check' annotation key to TRUE. Any section + * setting the 'handles_permission_check' attribute key to TRUE. Any section * storage plugin that uses 'handles_permission_check' must provide its own * complete routing access checking to avoid any access bypasses. * diff --git a/core/modules/layout_builder/src/Plugin/Layout/BlankLayout.php b/core/modules/layout_builder/src/Plugin/Layout/BlankLayout.php index a3f71b0ef9c6..7d5b83e3c8a6 100644 --- a/core/modules/layout_builder/src/Plugin/Layout/BlankLayout.php +++ b/core/modules/layout_builder/src/Plugin/Layout/BlankLayout.php @@ -2,7 +2,9 @@ namespace Drupal\layout_builder\Plugin\Layout; +use Drupal\Core\Layout\Attribute\Layout; use Drupal\Core\Layout\LayoutDefault; +use Drupal\Core\StringTranslation\TranslatableMarkup; /** * Provides a layout plugin that produces no output. @@ -13,11 +15,11 @@ * * @internal * This layout plugin is intended for internal use by Layout Builder only. - * - * @Layout( - * id = "layout_builder_blank", - * ) */ +#[Layout( + id: 'layout_builder_blank', + label: new TranslatableMarkup('Blank'), +)] class BlankLayout extends LayoutDefault { /** diff --git a/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Layout/LayoutBuilderTestPlugin.php b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Layout/LayoutBuilderTestPlugin.php index 26aafb0d8ed7..c0b16b56b79e 100644 --- a/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Layout/LayoutBuilderTestPlugin.php +++ b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Layout/LayoutBuilderTestPlugin.php @@ -2,19 +2,22 @@ namespace Drupal\layout_builder_test\Plugin\Layout; +use Drupal\Core\Layout\Attribute\Layout; use Drupal\Core\Layout\LayoutDefault; +use Drupal\Core\StringTranslation\TranslatableMarkup; /** - * @Layout( - * id = "layout_builder_test_plugin", - * label = @Translation("Layout Builder Test Plugin"), - * regions = { - * "main" = { - * "label" = @Translation("Main Region") - * } - * }, - * ) + * The Layout Builder Test Plugin. */ +#[Layout( + id: 'layout_builder_test_plugin', + label: new TranslatableMarkup('Layout Builder Test Plugin'), + regions: [ + "main" => [ + "label" => new TranslatableMarkup("Main Region"), + ], + ], +)] class LayoutBuilderTestPlugin extends LayoutDefault { /** diff --git a/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Layout/LayoutWithoutLabel.php b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Layout/LayoutWithoutLabel.php index 9b32bbb1b90e..6298ca51941c 100644 --- a/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Layout/LayoutWithoutLabel.php +++ b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Layout/LayoutWithoutLabel.php @@ -3,21 +3,22 @@ namespace Drupal\layout_builder_test\Plugin\Layout; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Layout\Attribute\Layout; use Drupal\Core\Layout\LayoutDefault; +use Drupal\Core\StringTranslation\TranslatableMarkup; /** * Layout plugin without a label configuration. - * - * @Layout( - * id = "layout_without_label", - * label = @Translation("Layout Without Label"), - * regions = { - * "main" = { - * "label" = @Translation("Main Region") - * } - * }, - * ) */ +#[Layout( + id: 'layout_without_label', + label: new TranslatableMarkup('Layout Without Label'), + regions: [ + "main" => [ + "label" => new TranslatableMarkup("Main Region"), + ], + ], +)] class LayoutWithoutLabel extends LayoutDefault { /** diff --git a/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Layout/TestContextAwareLayout.php b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Layout/TestContextAwareLayout.php index b53e954c8703..21becc380247 100644 --- a/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Layout/TestContextAwareLayout.php +++ b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Layout/TestContextAwareLayout.php @@ -2,22 +2,26 @@ namespace Drupal\layout_builder_test\Plugin\Layout; +use Drupal\Core\Layout\Attribute\Layout; use Drupal\Core\Layout\LayoutDefault; +use Drupal\Core\Plugin\Context\EntityContextDefinition; +use Drupal\Core\StringTranslation\TranslatableMarkup; /** - * @Layout( - * id = "layout_builder_test_context_aware", - * label = @Translation("Layout Builder Test: Context Aware"), - * regions = { - * "main" = { - * "label" = @Translation("Main Region") - * } - * }, - * context_definitions = { - * "user" = @ContextDefinition("entity:user") - * } - * ) + * The TestContextAwareLayout Class. */ +#[Layout( + id: 'layout_builder_test_context_aware', + label: new TranslatableMarkup('Layout Builder Test: Context Aware'), + regions: [ + "main" => [ + "label" => new TranslatableMarkup("Main Region"), + ], + ], + context_definitions: [ + "user" => new EntityContextDefinition("entity:user"), + ], +)] class TestContextAwareLayout extends LayoutDefault { /** diff --git a/core/modules/system/tests/modules/layout_test/src/Plugin/Layout/LayoutTestDependenciesPlugin.php b/core/modules/system/tests/modules/layout_test/src/Plugin/Layout/LayoutTestDependenciesPlugin.php index 6ad62101dd96..c82d18f46dcb 100644 --- a/core/modules/system/tests/modules/layout_test/src/Plugin/Layout/LayoutTestDependenciesPlugin.php +++ b/core/modules/system/tests/modules/layout_test/src/Plugin/Layout/LayoutTestDependenciesPlugin.php @@ -3,23 +3,24 @@ namespace Drupal\layout_test\Plugin\Layout; use Drupal\Component\Plugin\DependentPluginInterface; +use Drupal\Core\Layout\Attribute\Layout; use Drupal\Core\Layout\LayoutDefault; +use Drupal\Core\StringTranslation\TranslatableMarkup; /** * Provides a plugin that contains config dependencies. - * - * @Layout( - * id = "layout_test_dependencies_plugin", - * label = @Translation("Layout plugin (with dependencies)"), - * category = @Translation("Layout test"), - * description = @Translation("Test layout"), - * regions = { - * "main" = { - * "label" = @Translation("Main Region") - * } - * } - * ) */ +#[Layout( + id: 'layout_test_dependencies_plugin', + label: new TranslatableMarkup('Layout plugin (with dependencies)'), + category: new TranslatableMarkup('Layout test'), + description: new TranslatableMarkup('Test layout'), + regions: [ + "main" => [ + "label" => new TranslatableMarkup("Main Region"), + ], + ], +)] class LayoutTestDependenciesPlugin extends LayoutDefault implements DependentPluginInterface { /** diff --git a/core/modules/system/tests/modules/layout_test/src/Plugin/Layout/LayoutTestPlugin.php b/core/modules/system/tests/modules/layout_test/src/Plugin/Layout/LayoutTestPlugin.php index 4598dccbf40a..7eb3450ec501 100644 --- a/core/modules/system/tests/modules/layout_test/src/Plugin/Layout/LayoutTestPlugin.php +++ b/core/modules/system/tests/modules/layout_test/src/Plugin/Layout/LayoutTestPlugin.php @@ -3,25 +3,26 @@ namespace Drupal\layout_test\Plugin\Layout; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Layout\Attribute\Layout; use Drupal\Core\Layout\LayoutDefault; use Drupal\Core\Plugin\PluginFormInterface; +use Drupal\Core\StringTranslation\TranslatableMarkup; /** * The plugin that handles the default layout template. - * - * @Layout( - * id = "layout_test_plugin", - * label = @Translation("Layout plugin (with settings)"), - * category = @Translation("Layout test"), - * description = @Translation("Test layout"), - * template = "templates/layout-test-plugin", - * regions = { - * "main" = { - * "label" = @Translation("Main Region") - * } - * } - * ) */ +#[Layout( + id: 'layout_test_plugin', + label: new TranslatableMarkup('Layout plugin (with settings)'), + category: new TranslatableMarkup('Layout test'), + description: new TranslatableMarkup('Test layout'), + template: "templates/layout-test-plugin", + regions: [ + "main" => [ + "label" => new TranslatableMarkup("Main Region"), + ], + ], +)] class LayoutTestPlugin extends LayoutDefault implements PluginFormInterface { /** diff --git a/core/tests/Drupal/Tests/Core/Layout/LayoutPluginManagerTest.php b/core/tests/Drupal/Tests/Core/Layout/LayoutPluginManagerTest.php index 7ce72081d8e3..7610bf16bf58 100644 --- a/core/tests/Drupal/Tests/Core/Layout/LayoutPluginManagerTest.php +++ b/core/tests/Drupal/Tests/Core/Layout/LayoutPluginManagerTest.php @@ -4,6 +4,7 @@ namespace Drupal\Tests\Core\Layout; +use Composer\Autoload\ClassLoader; use Drupal\Component\Plugin\Derivative\DeriverBase; use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException; use Drupal\Core\Cache\CacheBackendInterface; @@ -20,6 +21,8 @@ use org\bovigo\vfs\vfsStream; use Prophecy\Argument; +// cspell:ignore lorem, ipsum, consectetur, adipiscing + /** * @coversDefaultClass \Drupal\Core\Layout\LayoutPluginManager * @group Layout @@ -91,6 +94,9 @@ protected function setUp(): void { $this->cacheBackend = $this->prophesize(CacheBackendInterface::class); $namespaces = new \ArrayObject(['Drupal\Core' => vfsStream::url('root/core/lib/Drupal/Core')]); + $class_loader = new ClassLoader(); + $class_loader->addPsr4("Drupal\\Core\\", vfsStream::url("root/core/lib/Drupal/Core")); + $class_loader->register(TRUE); $this->layoutPluginManager = new LayoutPluginManager($namespaces, $this->cacheBackend->reveal(), $this->moduleHandler->reveal(), $this->themeHandler->reveal()); } @@ -103,6 +109,7 @@ public function testGetDefinitions() { 'module_a_provided_layout', 'theme_a_provided_layout', 'plugin_provided_layout', + 'plugin_provided_by_annotation_layout', ]; $layout_definitions = $this->layoutPluginManager->getDefinitions(); @@ -172,6 +179,8 @@ public function testGetDefinition() { $this->assertEquals($expected_regions, $regions); $this->assertInstanceOf(TranslatableMarkup::class, $regions['top']['label']); $this->assertInstanceOf(TranslatableMarkup::class, $regions['bottom']['label']); + // Check that arbitrary property value gets set correctly. + $this->assertSame('ipsum', $layout_definition->get('lorem')); $core_path = '/core/lib/Drupal/Core'; $layout_definition = $this->layoutPluginManager->getDefinition('plugin_provided_layout'); @@ -198,6 +207,37 @@ public function testGetDefinition() { $regions = $layout_definition->getRegions(); $this->assertEquals($expected_regions, $regions); $this->assertInstanceOf(TranslatableMarkup::class, $regions['main']['label']); + // Check that arbitrary property value gets set correctly. + $this->assertSame('adipiscing', $layout_definition->get('consectetur')); + + $layout_definition = $this->layoutPluginManager->getDefinition('plugin_provided_by_annotation_layout'); + $this->assertSame('plugin_provided_by_annotation_layout', $layout_definition->id()); + $this->assertEquals('Layout by annotation plugin', $layout_definition->getLabel()); + $this->assertEquals('Columns: 2', $layout_definition->getCategory()); + $this->assertEquals('Test layout provided by annotated plugin', $layout_definition->getDescription()); + $this->assertInstanceOf(TranslatableMarkup::class, $layout_definition->getLabel()); + $this->assertInstanceOf(TranslatableMarkup::class, $layout_definition->getCategory()); + $this->assertInstanceOf(TranslatableMarkup::class, $layout_definition->getDescription()); + $this->assertSame('plugin-provided-annotation-layout', $layout_definition->getTemplate()); + $this->assertSame($core_path, $layout_definition->getPath()); + $this->assertNull($layout_definition->getLibrary()); + $this->assertSame('plugin_provided_annotation_layout', $layout_definition->getThemeHook()); + $this->assertSame("$core_path/templates", $layout_definition->getTemplatePath()); + $this->assertSame('core', $layout_definition->getProvider()); + $this->assertSame('left', $layout_definition->getDefaultRegion()); + $this->assertSame('Drupal\Core\Plugin\Layout\TestAnnotationLayout', $layout_definition->getClass()); + $expected_regions = [ + 'left' => [ + 'label' => new TranslatableMarkup('Left Region', [], ['context' => 'layout_region']), + ], + 'right' => [ + 'label' => new TranslatableMarkup('Right Region', [], ['context' => 'layout_region']), + ], + ]; + $regions = $layout_definition->getRegions(); + $this->assertEquals($expected_regions, $regions); + $this->assertInstanceOf(TranslatableMarkup::class, $regions['left']['label']); + $this->assertInstanceOf(TranslatableMarkup::class, $regions['right']['label']); } /** @@ -243,6 +283,12 @@ public function testGetThemeImplementations() { 'template' => 'plugin-provided-layout', 'path' => "$core_path/templates", ], + 'plugin_provided_annotation_layout' => [ + 'render element' => 'content', + 'base hook' => 'layout', + 'template' => 'plugin-provided-annotation-layout', + 'path' => "$core_path/templates", + ], ]; $theme_implementations = $this->layoutPluginManager->getThemeImplementations(); $this->assertEquals($expected, $theme_implementations); @@ -264,10 +310,12 @@ public function testGetCategories() { * @covers ::getSortedDefinitions */ public function testGetSortedDefinitions() { + // Sorted by category first, then label. $expected = [ 'module_a_provided_layout', 'plugin_provided_layout', 'theme_a_provided_layout', + 'plugin_provided_by_annotation_layout', ]; $layout_definitions = $this->layoutPluginManager->getSortedDefinitions(); @@ -286,6 +334,7 @@ public function testGetGroupedDefinitions() { ], 'Columns: 2' => [ 'theme_a_provided_layout', + 'plugin_provided_by_annotation_layout', ], ]; @@ -315,7 +364,9 @@ protected function setUpFilesystem() { label: Top region bottom: label: Bottom region + lorem: ipsum module_a_derived_layout: + label: 'Invalid provider derived layout' deriver: \Drupal\Tests\Core\Layout\LayoutDeriver invalid_provider: true EOS; @@ -338,23 +389,52 @@ class: '\Drupal\Core\Layout\LayoutDefault' $plugin_provided_layout = <<<'EOS' <?php namespace Drupal\Core\Plugin\Layout; +use Drupal\Core\Layout\Attribute\Layout; +use Drupal\Core\Layout\LayoutDefault; +use Drupal\Core\StringTranslation\TranslatableMarkup; +/** + * The TestLayout Class. + */ +#[Layout( + id: 'plugin_provided_layout', + label: new TranslatableMarkup('Layout plugin'), + category: new TranslatableMarkup('Columns: 1'), + description: new TranslatableMarkup('Test layout'), + path: "core/lib/Drupal/Core", + template: "templates/plugin-provided-layout", + regions: [ + "main" => [ + "label" => new TranslatableMarkup("Main Region", [], ["context" => "layout_region"]), + ], + ], + consectetur: 'adipiscing', +)] +class TestLayout extends LayoutDefault {} +EOS; + $plugin_provided_by_annotation_layout = <<<'EOS' +<?php +namespace Drupal\Core\Plugin\Layout; use Drupal\Core\Layout\LayoutDefault; /** * @Layout( - * id = "plugin_provided_layout", - * label = @Translation("Layout plugin"), - * category = @Translation("Columns: 1"), - * description = @Translation("Test layout"), + * id = "plugin_provided_by_annotation_layout", + * label = @Translation("Layout by annotation plugin"), + * category = @Translation("Columns: 2"), + * description = @Translation("Test layout provided by annotated plugin"), * path = "core/lib/Drupal/Core", - * template = "templates/plugin-provided-layout", + * template = "templates/plugin-provided-annotation-layout", + * default_region = "left", * regions = { - * "main" = { - * "label" = @Translation("Main Region", context = "layout_region") + * "left" = { + * "label" = @Translation("Left Region", context = "layout_region") + * }, + * "right" = { + * "label" = @Translation("Right Region", context = "layout_region") * } * } * ) */ -class TestLayout extends LayoutDefault {} +class TestAnnotationLayout extends LayoutDefault {} EOS; vfsStream::setup('root'); vfsStream::create([ @@ -379,6 +459,7 @@ class TestLayout extends LayoutDefault {} 'Plugin' => [ 'Layout' => [ 'TestLayout.php' => $plugin_provided_layout, + 'TestAnnotationLayout.php' => $plugin_provided_by_annotation_layout, ], ], ], -- GitLab