diff --git a/core/lib/Drupal/Core/Plugin/Context/EntityContext.php b/core/lib/Drupal/Core/Plugin/Context/EntityContext.php index 131360df26..00ea81f759 100644 --- a/core/lib/Drupal/Core/Plugin/Context/EntityContext.php +++ b/core/lib/Drupal/Core/Plugin/Context/EntityContext.php @@ -2,6 +2,7 @@ namespace Drupal\Core\Plugin\Context; +use Drupal\Core\DependencyInjection\DependencySerializationTrait; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeInterface; @@ -10,6 +11,8 @@ */ class EntityContext extends Context { + use DependencySerializationTrait; + /** * Gets a context from an entity type ID. * diff --git a/core/modules/layout_builder/layout_builder.module b/core/modules/layout_builder/layout_builder.module index 5d7c60615c..0676365f70 100644 --- a/core/modules/layout_builder/layout_builder.module +++ b/core/modules/layout_builder/layout_builder.module @@ -202,3 +202,12 @@ function layout_builder_block_content_access(EntityInterface $entity, $operation } return AccessResult::forbidden(); } + +/** + * Implements hook_layout_builder_section_storage_alter(). + */ +function layout_builder_layout_builder_section_storage_alter(array &$definitions) { + /** @var \Drupal\layout_builder\SectionStorage\SectionStorageDefinition[] $definitions */ + $definitions['overrides']->getContextDefinition('entity') + ->addConstraint('EntityHasField', 'layout_builder__layout'); +} diff --git a/core/modules/layout_builder/src/Annotation/SectionStorage.php b/core/modules/layout_builder/src/Annotation/SectionStorage.php index 42f4a47fe6..73924be8ad 100644 --- a/core/modules/layout_builder/src/Annotation/SectionStorage.php +++ b/core/modules/layout_builder/src/Annotation/SectionStorage.php @@ -22,6 +22,30 @@ class SectionStorage extends Plugin { */ public $id; + /** + * The plugin weight, optional (defaults to 0). + * + * When an entity with layout is rendered, section storage plugins are + * checked, in order of their weight, to determine which one should be used + * to render the layout. + * + * @var int + */ + public $weight = 0; + + /** + * Any required context definitions, optional. + * + * When an entity with layout is rendered, all section storage plugins which + * match a particular set of contexts are checked, in order of their weight, + * to determine which plugin should be used to render the layout. + * + * @var array + * + * @see \Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay::getRuntimeSections() + */ + public $context = []; + /** * {@inheritdoc} */ diff --git a/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php index 2bdeb03870..d91d08575d 100644 --- a/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php +++ b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php @@ -9,6 +9,7 @@ use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; +use Drupal\layout_builder\LayoutEntityHelperTrait; use Drupal\layout_builder\Section; use Drupal\layout_builder\SectionComponent; use Drupal\layout_builder\SectionStorage\SectionStorageTrait; @@ -23,6 +24,7 @@ */ class LayoutBuilderEntityViewDisplay extends BaseEntityViewDisplay implements LayoutEntityDisplayInterface { + use LayoutEntityHelperTrait; use SectionStorageTrait; /** @@ -264,11 +266,15 @@ public function buildMultiple(array $entities) { * The sections. */ protected function getRuntimeSections(FieldableEntityInterface $entity) { - if ($this->isOverridable() && !$entity->get('layout_builder__layout')->isEmpty()) { - return $entity->get('layout_builder__layout')->getSections(); - } + $sections = NULL; - return $this->getSections(); + if ($this->isOverridable()) { + $sections = $this->getEntitySections($entity, $this->getMode()); + } + // If we don't have a section list yet (i.e., no section storage plugin + // was able to derive a section list from context, or this display is not + // overridable), use this display as the section list. + return $sections ?: $this->getSections(); } /** diff --git a/core/modules/layout_builder/src/EventSubscriber/SetInlineBlockDependency.php b/core/modules/layout_builder/src/EventSubscriber/SetInlineBlockDependency.php index edc05f83dd..4ea6259860 100644 --- a/core/modules/layout_builder/src/EventSubscriber/SetInlineBlockDependency.php +++ b/core/modules/layout_builder/src/EventSubscriber/SetInlineBlockDependency.php @@ -127,7 +127,8 @@ protected function getInlineBlockDependency(BlockContentInterface $block_content /** @var \Drupal\layout_builder\InlineBlockUsage $usage */ $layout_entity_storage = $this->entityTypeManager->getStorage($layout_entity_info->layout_entity_type); $layout_entity = $layout_entity_storage->load($layout_entity_info->layout_entity_id); - if ($this->isLayoutCompatibleEntity($layout_entity)) { + // @todo Pass 'default' until resolving https://www.drupal.org/node/3008924. + if ($this->isLayoutCompatibleEntity($layout_entity, 'default')) { if ($this->isBlockRevisionUsedInEntity($layout_entity, $block_content)) { return $layout_entity; } @@ -149,7 +150,8 @@ protected function getInlineBlockDependency(BlockContentInterface $block_content * layout entity. */ protected function isBlockRevisionUsedInEntity(EntityInterface $layout_entity, BlockContentInterface $block_content) { - $sections_blocks_revision_ids = $this->getInlineBlockRevisionIdsInSections($this->getEntitySections($layout_entity)); + // @todo Pass 'default' until resolving https://www.drupal.org/node/3008924. + $sections_blocks_revision_ids = $this->getInlineBlockRevisionIdsInSections($this->getEntitySections($layout_entity, 'default')); return in_array($block_content->getRevisionId(), $sections_blocks_revision_ids); } diff --git a/core/modules/layout_builder/src/InlineBlockEntityOperations.php b/core/modules/layout_builder/src/InlineBlockEntityOperations.php index 7e64b83cf1..3469fca493 100644 --- a/core/modules/layout_builder/src/InlineBlockEntityOperations.php +++ b/core/modules/layout_builder/src/InlineBlockEntityOperations.php @@ -6,7 +6,9 @@ use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Entity\RevisionableInterface; +use Drupal\layout_builder\Entity\LayoutEntityDisplayInterface; use Drupal\layout_builder\Plugin\Block\InlineBlock; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -85,10 +87,12 @@ protected function removeUnusedForEntityOnSave(EntityInterface $entity) { if ($entity->isNew() || !isset($entity->original) || $entity instanceof RevisionableInterface) { return; } - $sections = $this->getEntitySections($entity); + // @todo Use 'default' until resolving https://www.drupal.org/node/3008924. + $view_mode = 'default'; + $sections = $this->getEntitySections($entity, $view_mode); // If this is a layout override and there are no sections then it is a new // override. - if ($this->isEntityUsingFieldOverride($entity) && empty($sections)) { + if ($this->isEntityUsingFieldOverride($entity, $view_mode) && empty($sections)) { return; } @@ -108,8 +112,10 @@ protected function removeUnusedForEntityOnSave(EntityInterface $entity) { * The block content IDs that were removed. */ protected function getRemovedBlockIds(EntityInterface $entity) { - $original_sections = $this->getEntitySections($entity->original); - $current_sections = $this->getEntitySections($entity); + // @todo Use 'default' until resolving https://www.drupal.org/node/3008924. + $view_mode = 'default'; + $original_sections = $this->getEntitySections($entity->original, $view_mode); + $current_sections = $this->getEntitySections($entity, $view_mode); // Avoid un-needed conversion from revision IDs to block content IDs by // first determining if there are any revisions in the original that are not // also in the current sections. @@ -132,11 +138,28 @@ protected function getRemovedBlockIds(EntityInterface $entity) { * The parent entity. */ public function handleEntityDelete(EntityInterface $entity) { - if ($this->isLayoutCompatibleEntity($entity)) { + // @todo Pass 'default' until resolving https://www.drupal.org/node/3008924. + if ($this->isLayoutCompatibleEntity($entity, 'default')) { $this->usage->removeByLayoutEntity($entity); } } + /** + * {@inheritdoc} + */ + protected function isLayoutCompatibleEntity(EntityInterface $entity, $view_mode) { + // @todo Hardcode these checks to avoid https://www.drupal.org/node/3008943. + return $entity instanceof LayoutEntityDisplayInterface || $this->isEntityUsingFieldOverride($entity, $view_mode); + } + + /** + * {@inheritdoc} + */ + protected function isEntityUsingFieldOverride(EntityInterface $entity, $view_mode) { + // @todo Hardcode these checks to avoid https://www.drupal.org/node/3008943. + return $entity instanceof FieldableEntityInterface && $entity->hasField('layout_builder__layout'); + } + /** * Handles saving a parent entity. * @@ -144,15 +167,17 @@ public function handleEntityDelete(EntityInterface $entity) { * The parent entity. */ public function handlePreSave(EntityInterface $entity) { - if (!$this->isLayoutCompatibleEntity($entity)) { + // @todo Use 'default' until resolving https://www.drupal.org/node/3008924. + $view_mode = 'default'; + if (!$this->isLayoutCompatibleEntity($entity, $view_mode)) { return; } $duplicate_blocks = FALSE; - if ($sections = $this->getEntitySections($entity)) { - if ($this->isEntityUsingFieldOverride($entity)) { + if ($sections = $this->getEntitySections($entity, $view_mode)) { + if ($this->isEntityUsingFieldOverride($entity, $view_mode)) { if (!$entity->isNew() && isset($entity->original)) { - if (empty($this->getEntitySections($entity->original))) { + if (empty($this->getEntitySections($entity->original, $view_mode))) { // If there were no sections in the original entity then this is a // new override from a default and the blocks need to be duplicated. $duplicate_blocks = TRUE; diff --git a/core/modules/layout_builder/src/LayoutEntityHelperTrait.php b/core/modules/layout_builder/src/LayoutEntityHelperTrait.php index 9124027542..907b8018f3 100644 --- a/core/modules/layout_builder/src/LayoutEntityHelperTrait.php +++ b/core/modules/layout_builder/src/LayoutEntityHelperTrait.php @@ -4,8 +4,8 @@ use Drupal\Component\Plugin\DerivativeInspectionInterface; use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Entity\FieldableEntityInterface; -use Drupal\layout_builder\Entity\LayoutEntityDisplayInterface; +use Drupal\Core\Plugin\Context\Context; +use Drupal\Core\Plugin\Context\ContextDefinition; /** * Methods to help with entities using the layout builder. @@ -19,12 +19,14 @@ * * @param \Drupal\Core\Entity\EntityInterface $entity * The entity to check. + * @param string $view_mode + * The view mode. * * @return bool * TRUE if the entity can have a layout otherwise FALSE. */ - protected function isLayoutCompatibleEntity(EntityInterface $entity) { - return $entity instanceof LayoutEntityDisplayInterface || $this->isEntityUsingFieldOverride($entity); + protected function isLayoutCompatibleEntity(EntityInterface $entity, $view_mode) { + return (bool) $this->getSectionStorageFromEntity($entity, $view_mode); } /** @@ -47,27 +49,42 @@ protected function getInlineBlockRevisionIdsInSections(array $sections) { return $revision_ids; } + /** + * Gets the section storage given an entity and view mode. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * The entity. + * @param string $view_mode + * The view mode. + * + * @return \Drupal\layout_builder\SectionStorageInterface|null + * The section storage, if it exists. + */ + private function getSectionStorageFromEntity(EntityInterface $entity, $view_mode) { + /** @var \Drupal\layout_builder\SectionStorageInterface $storage */ + return \Drupal::service('plugin.manager.layout_builder.section_storage') + ->loadFromContext([ + 'view_mode' => new Context(ContextDefinition::create('string'), $view_mode), + // @todo Use EntityContext::fromEntity() after + // https://www.drupal.org/project/drupal/issues/3008431 is fixed. + 'entity' => new Context(ContextDefinition::create('entity'), $entity), + ]); + } + /** * Gets the sections for an entity if any. * - * @todo Replace this method with calls to the SectionStorageManagerInterface - * method for getting sections from an entity in - * https://www.drupal.org/node/2986403. - * * @param \Drupal\Core\Entity\EntityInterface $entity * The entity. + * @param string $view_mode + * The view mode. * - * @return \Drupal\layout_builder\Section[]|null + * @return \Drupal\layout_builder\Section[] * The entity layout sections if available. */ - protected function getEntitySections(EntityInterface $entity) { - if ($entity instanceof LayoutEntityDisplayInterface) { - return $entity->getSections(); - } - elseif ($this->isEntityUsingFieldOverride($entity)) { - return $entity->get('layout_builder__layout')->getSections(); - } - return NULL; + protected function getEntitySections(EntityInterface $entity, $view_mode) { + $storage = $this->getSectionStorageFromEntity($entity, $view_mode); + return $storage ? $storage->getSections() : []; } /** @@ -97,12 +114,15 @@ protected function getInlineBlockComponents(array $sections) { * * @param \Drupal\Core\Entity\EntityInterface $entity * The entity. + * @param string $view_mode + * The view mode. * * @return bool * TRUE if the entity is using a field for a layout override. */ - protected function isEntityUsingFieldOverride(EntityInterface $entity) { - return $entity instanceof FieldableEntityInterface && $entity->hasField('layout_builder__layout'); + protected function isEntityUsingFieldOverride(EntityInterface $entity, $view_mode) { + $storage = $this->getSectionStorageFromEntity($entity, $view_mode); + return $storage && $storage->getPluginId() === 'overrides'; } } diff --git a/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php b/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php index d35041d03d..24c230055c 100644 --- a/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/DefaultsSectionStorage.php @@ -15,8 +15,6 @@ use Drupal\field_ui\FieldUI; use Drupal\layout_builder\DefaultsSectionStorageInterface; use Drupal\layout_builder\Entity\LayoutBuilderSampleEntityGenerator; -use Drupal\layout_builder\Entity\LayoutEntityDisplayInterface; -use Drupal\layout_builder\SectionListInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Routing\RouteCollection; @@ -25,6 +23,9 @@ * * @SectionStorage( * id = "defaults", + * context = { + * "entity" = @ContextDefinition("entity:entity_view_display"), + * }, * ) * * @internal @@ -90,12 +91,8 @@ public static function create(ContainerInterface $container, array $configuratio /** * {@inheritdoc} */ - public function setSectionList(SectionListInterface $section_list) { - if (!$section_list instanceof LayoutEntityDisplayInterface) { - throw new \InvalidArgumentException('Defaults expect a display-based section list'); - } - - return parent::setSectionList($section_list); + protected function getSectionList() { + return $this->getContextValue('entity'); } /** @@ -237,34 +234,41 @@ protected function getEntityTypes() { /** * {@inheritdoc} */ - public function extractIdFromRoute($value, $definition, $name, array $defaults) { - if (is_string($value) && strpos($value, '.') !== FALSE) { - return $value; + public function getContextsFromRoute($value, $definition, $name, array $defaults) { + $contexts = []; + + if ($entity = $this->extractEntityFromRoute($value, $defaults)) { + $contexts['entity'] = EntityContext::fromEntity($entity); } + return $contexts; + } + /** + * {@inheritdoc} + */ + protected function extractEntityFromRoute($value, array $defaults) { // If a bundle is not provided but a value corresponding to the bundle key // is, use that for the bundle value. if (empty($defaults['bundle']) && isset($defaults['bundle_key']) && !empty($defaults[$defaults['bundle_key']])) { $defaults['bundle'] = $defaults[$defaults['bundle_key']]; } - if (!empty($defaults['entity_type_id']) && !empty($defaults['bundle']) && !empty($defaults['view_mode_name'])) { - return $defaults['entity_type_id'] . '.' . $defaults['bundle'] . '.' . $defaults['view_mode_name']; + if (is_string($value) && strpos($value, '.') !== FALSE) { + list($entity_type_id, $bundle, $view_mode) = explode('.', $value, 3); } - } - - /** - * {@inheritdoc} - */ - public function getSectionListFromId($id) { - if (strpos($id, '.') === FALSE) { - throw new \InvalidArgumentException(sprintf('The "%s" ID for the "%s" section storage type is invalid', $id, $this->getStorageType())); + elseif (!empty($defaults['entity_type_id']) && !empty($defaults['bundle']) && !empty($defaults['view_mode_name'])) { + $entity_type_id = $defaults['entity_type_id']; + $bundle = $defaults['bundle']; + $view_mode = $defaults['view_mode_name']; + $value = "$entity_type_id.$bundle.$view_mode"; + } + else { + return NULL; } $storage = $this->entityTypeManager->getStorage('entity_view_display'); // If the display does not exist, create a new one. - if (!$display = $storage->load($id)) { - list($entity_type_id, $bundle, $view_mode) = explode('.', $id, 3); + if (!$display = $storage->load($value)) { $display = $storage->create([ 'targetEntityType' => $entity_type_id, 'bundle' => $bundle, diff --git a/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php index c623c5ede3..4eec93230b 100644 --- a/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/OverridesSectionStorage.php @@ -7,14 +7,12 @@ use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\FieldableEntityInterface; -use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Plugin\Context\EntityContext; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Url; use Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay; use Drupal\layout_builder\OverridesSectionStorageInterface; -use Drupal\layout_builder\SectionListInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Routing\RouteCollection; @@ -23,6 +21,10 @@ * * @SectionStorage( * id = "overrides", + * context = { + * "entity" = @ContextDefinition("entity"), + * "view_mode" = @ContextDefinition("string", required = FALSE), + * } * ) * * @internal @@ -79,12 +81,8 @@ public static function create(ContainerInterface $container, array $configuratio /** * {@inheritdoc} */ - public function setSectionList(SectionListInterface $section_list) { - if (!$section_list instanceof FieldItemListInterface) { - throw new \InvalidArgumentException('Overrides expect a field-based section list'); - } - - return parent::setSectionList($section_list); + protected function getSectionList() { + return $this->getEntity()->get('layout_builder__layout'); } /** @@ -94,7 +92,7 @@ public function setSectionList(SectionListInterface $section_list) { * The entity storing the overrides. */ protected function getEntity() { - return $this->getSectionList()->getEntity(); + return $this->getContextValue('entity'); } /** @@ -108,30 +106,34 @@ public function getStorageId() { /** * {@inheritdoc} */ - public function extractIdFromRoute($value, $definition, $name, array $defaults) { + public function getContextsFromRoute($value, $definition, $name, array $defaults) { + $contexts = []; + + if ($entity = $this->extractEntityFromRoute($value, $defaults)) { + $contexts['entity'] = EntityContext::fromEntity($entity); + } + return $contexts; + } + + /** + * {@inheritdoc} + */ + protected function extractEntityFromRoute($value, array $defaults) { if (strpos($value, '.') !== FALSE) { - return $value; + list($entity_type_id, $entity_id) = explode('.', $value, 2); } - - if (isset($defaults['entity_type_id']) && !empty($defaults[$defaults['entity_type_id']])) { + elseif (isset($defaults['entity_type_id']) && !empty($defaults[$defaults['entity_type_id']])) { $entity_type_id = $defaults['entity_type_id']; $entity_id = $defaults[$entity_type_id]; - return $entity_type_id . '.' . $entity_id; } - } + else { + return NULL; + } - /** - * {@inheritdoc} - */ - public function getSectionListFromId($id) { - if (strpos($id, '.') !== FALSE) { - list($entity_type_id, $entity_id) = explode('.', $id, 2); - $entity = $this->entityTypeManager->getStorage($entity_type_id)->load($entity_id); - if ($entity instanceof FieldableEntityInterface && $entity->hasField('layout_builder__layout')) { - return $entity->get('layout_builder__layout'); - } + $entity = $this->entityTypeManager->getStorage($entity_type_id)->load($entity_id); + if ($entity instanceof FieldableEntityInterface && $entity->hasField('layout_builder__layout')) { + return $entity; } - throw new \InvalidArgumentException(sprintf('The "%s" ID for the "%s" section storage type is invalid', $id, $this->getStorageType())); } /** diff --git a/core/modules/layout_builder/src/Plugin/SectionStorage/SectionStorageBase.php b/core/modules/layout_builder/src/Plugin/SectionStorage/SectionStorageBase.php index c419060c45..8e8178355b 100644 --- a/core/modules/layout_builder/src/Plugin/SectionStorage/SectionStorageBase.php +++ b/core/modules/layout_builder/src/Plugin/SectionStorage/SectionStorageBase.php @@ -2,10 +2,9 @@ namespace Drupal\layout_builder\Plugin\SectionStorage; -use Drupal\Core\Plugin\PluginBase; +use Drupal\Core\Plugin\ContextAwarePluginBase; use Drupal\layout_builder\Routing\LayoutBuilderRoutesTrait; use Drupal\layout_builder\Section; -use Drupal\layout_builder\SectionListInterface; use Drupal\layout_builder\SectionStorageInterface; /** @@ -16,40 +15,17 @@ * experimental modules and development releases of contributed modules. * See https://www.drupal.org/core/experimental for more information. */ -abstract class SectionStorageBase extends PluginBase implements SectionStorageInterface { +abstract class SectionStorageBase extends ContextAwarePluginBase implements SectionStorageInterface { use LayoutBuilderRoutesTrait; - /** - * The section storage instance. - * - * @var \Drupal\layout_builder\SectionListInterface|null - */ - protected $sectionList; - - /** - * {@inheritdoc} - */ - public function setSectionList(SectionListInterface $section_list) { - $this->sectionList = $section_list; - return $this; - } - /** * Gets the section list. * * @return \Drupal\layout_builder\SectionListInterface * The section list. - * - * @throws \RuntimeException - * Thrown if ::setSectionList() is not called first. */ - protected function getSectionList() { - if (!$this->sectionList) { - throw new \RuntimeException(sprintf('%s::setSectionList() must be called first', static::class)); - } - return $this->sectionList; - } + abstract protected function getSectionList(); /** * {@inheritdoc} @@ -103,4 +79,22 @@ public function removeSection($delta) { return $this; } + /** + * {@inheritdoc} + * + * @todo Remove after https://www.drupal.org/project/drupal/issues/2982626. + */ + public function getContextDefinition($name) { + return $this->getPluginDefinition()->getContextDefinition($name); + } + + /** + * {@inheritdoc} + * + * @todo Remove after https://www.drupal.org/project/drupal/issues/2982626. + */ + public function getContextDefinitions() { + return $this->getPluginDefinition()->getContextDefinitions(); + } + } diff --git a/core/modules/layout_builder/src/SectionStorage/SectionStorageDefinition.php b/core/modules/layout_builder/src/SectionStorage/SectionStorageDefinition.php index 61b975a471..4c3bac2c49 100644 --- a/core/modules/layout_builder/src/SectionStorage/SectionStorageDefinition.php +++ b/core/modules/layout_builder/src/SectionStorage/SectionStorageDefinition.php @@ -2,6 +2,8 @@ namespace Drupal\layout_builder\SectionStorage; +use Drupal\Component\Plugin\Definition\ContextAwarePluginDefinitionInterface; +use Drupal\Component\Plugin\Definition\ContextAwarePluginDefinitionTrait; use Drupal\Component\Plugin\Definition\PluginDefinition; /** @@ -12,7 +14,9 @@ * experimental modules and development releases of contributed modules. * See https://www.drupal.org/core/experimental for more information. */ -class SectionStorageDefinition extends PluginDefinition { +class SectionStorageDefinition extends PluginDefinition implements ContextAwarePluginDefinitionInterface { + + use ContextAwarePluginDefinitionTrait; /** * Any additional properties and values. @@ -28,6 +32,16 @@ class SectionStorageDefinition extends PluginDefinition { * An array of values from the annotation. */ public function __construct(array $definition = []) { + // If there are context definitions in the plugin definition, they should + // be added to this object using ::addContextDefinition() so that they can + // be manipulated using other ContextAwarePluginDefinitionInterface methods. + if (isset($definition['context'])) { + foreach ($definition['context'] as $name => $context_definition) { + $this->addContextDefinition($name, $context_definition); + } + unset($definition['context']); + } + foreach ($definition as $property => $value) { $this->set($property, $value); } diff --git a/core/modules/layout_builder/src/SectionStorage/SectionStorageManager.php b/core/modules/layout_builder/src/SectionStorage/SectionStorageManager.php index 18147cd1d8..933ebd29b6 100644 --- a/core/modules/layout_builder/src/SectionStorage/SectionStorageManager.php +++ b/core/modules/layout_builder/src/SectionStorage/SectionStorageManager.php @@ -2,8 +2,10 @@ namespace Drupal\layout_builder\SectionStorage; +use Drupal\Component\Utility\SortArray; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Plugin\Context\ContextAwarePluginManagerTrait; use Drupal\Core\Plugin\DefaultPluginManager; use Drupal\layout_builder\Annotation\SectionStorage; use Drupal\layout_builder\SectionStorageInterface; @@ -18,6 +20,8 @@ */ class SectionStorageManager extends DefaultPluginManager implements SectionStorageManagerInterface { + use ContextAwarePluginManagerTrait; + /** * Constructs a new SectionStorageManager object. * @@ -36,6 +40,25 @@ public function __construct(\Traversable $namespaces, CacheBackendInterface $cac $this->setCacheBackend($cache_backend, 'layout_builder_section_storage_plugins'); } + /** + * {@inheritdoc} + */ + protected function findDefinitions() { + $definitions = parent::findDefinitions(); + + // Sort the definitions before they are cached. + uasort($definitions, function (SectionStorageDefinition $a, SectionStorageDefinition $b) { + $a = [ + 'weight' => $a->get('weight'), + ]; + $b = [ + 'weight' => $b->get('weight'), + ]; + return SortArray::sortByWeightElement($a, $b); + }); + return $definitions; + } + /** * {@inheritdoc} */ @@ -46,10 +69,23 @@ public function loadEmpty($id) { /** * {@inheritdoc} */ - public function loadFromStorageId($type, $id) { - /** @var \Drupal\layout_builder\SectionStorageInterface $plugin */ - $plugin = $this->createInstance($type); - return $plugin->setSectionList($plugin->getSectionListFromId($id)); + public function loadFromContext(array $contexts) { + $storage_types = array_keys($this->getDefinitionsForContexts($contexts)); + + foreach ($storage_types as $type) { + /** @var \Drupal\layout_builder\SectionStorageInterface $plugin */ + $plugin = $this->createInstance($type); + + foreach ($contexts as $name => $context) { + $plugin->setContext($name, $context); + } + // Now that all contexts are set, the storage has the final say on whether + // it should be used or not. + if ($plugin->access('load')) { + return $plugin; + } + } + return NULL; } /** @@ -58,14 +94,18 @@ public function loadFromStorageId($type, $id) { public function loadFromRoute($type, $value, $definition, $name, array $defaults) { /** @var \Drupal\layout_builder\SectionStorageInterface $plugin */ $plugin = $this->createInstance($type); - if ($id = $plugin->extractIdFromRoute($value, $definition, $name, $defaults)) { - try { - return $plugin->setSectionList($plugin->getSectionListFromId($id)); - } - catch (\InvalidArgumentException $e) { - // Intentionally empty. - } + + try { + $contexts = $plugin->getContextsFromRoute($value, $definition, $name, $defaults); } + catch (\InvalidArgumentException $e) { + $contexts = []; + } + + foreach ($contexts as $name => $context) { + $plugin->setContext($name, $context); + } + return $contexts ? $plugin : NULL; } } diff --git a/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php b/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php index 3b269fcbba..b7627e9a4a 100644 --- a/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php +++ b/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php @@ -2,7 +2,7 @@ namespace Drupal\layout_builder\SectionStorage; -use Drupal\Component\Plugin\Discovery\DiscoveryInterface; +use Drupal\Core\Plugin\Context\ContextAwarePluginManagerInterface; /** * Provides the interface for a plugin manager of section storage types. @@ -12,7 +12,7 @@ * experimental modules and development releases of contributed modules. * See https://www.drupal.org/core/experimental for more information. */ -interface SectionStorageManagerInterface extends DiscoveryInterface { +interface SectionStorageManagerInterface extends ContextAwarePluginManagerInterface { /** * Loads a section storage with no associated section list. @@ -26,20 +26,15 @@ public function loadEmpty($id); /** - * Loads a section storage populated with an existing section list. + * Loads a section storage populated with a section list derived from context. * - * @param string $type - * The section storage type. - * @param string $id - * The section list ID. + * @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts + * The contexts which should be used to determine which storage to load. * - * @return \Drupal\layout_builder\SectionStorageInterface - * The section storage. - * - * @throws \InvalidArgumentException - * Thrown if the ID is invalid. + * @return \Drupal\layout_builder\SectionStorageInterface|null + * The section storage if one matched all contexts, or NULL otherwise. */ - public function loadFromStorageId($type, $id); + public function loadFromContext(array $contexts); /** * Loads a section storage populated with a section list derived from a route. diff --git a/core/modules/layout_builder/src/SectionStorageInterface.php b/core/modules/layout_builder/src/SectionStorageInterface.php index 90ce9072fd..a77c838620 100644 --- a/core/modules/layout_builder/src/SectionStorageInterface.php +++ b/core/modules/layout_builder/src/SectionStorageInterface.php @@ -2,8 +2,8 @@ namespace Drupal\layout_builder; -use Drupal\Component\Plugin\PluginInspectionInterface; use Drupal\Core\Access\AccessibleInterface; +use Drupal\Core\Plugin\ContextAwarePluginInterface; use Symfony\Component\Routing\RouteCollection; /** @@ -14,7 +14,7 @@ * experimental modules and development releases of contributed modules. * See https://www.drupal.org/core/experimental for more information. */ -interface SectionStorageInterface extends SectionListInterface, PluginInspectionInterface, AccessibleInterface { +interface SectionStorageInterface extends SectionListInterface, ContextAwarePluginInterface, AccessibleInterface { /** * Returns an identifier for this storage. @@ -34,36 +34,6 @@ public function getStorageId(); */ public function getStorageType(); - /** - * Sets the section list on the storage. - * - * @param \Drupal\layout_builder\SectionListInterface $section_list - * The section list. - * - * @return $this - * - * @internal - * This should only be called during section storage instantiation. - */ - public function setSectionList(SectionListInterface $section_list); - - /** - * Derives the section list from the storage ID. - * - * @param string $id - * The storage ID, see ::getStorageId(). - * - * @return \Drupal\layout_builder\SectionListInterface - * The section list. - * - * @throws \InvalidArgumentException - * Thrown if the ID is invalid. - * - * @internal - * This should only be called during section storage instantiation. - */ - public function getSectionListFromId($id); - /** * Provides the routes needed for Layout Builder UI. * @@ -99,7 +69,7 @@ public function getRedirectUrl(); public function getLayoutBuilderUrl($rel = 'view'); /** - * Configures the plugin based on route values. + * Derives the required plugin contexts from route values. * * @param mixed $value * The raw value. @@ -110,21 +80,10 @@ public function getLayoutBuilderUrl($rel = 'view'); * @param array $defaults * The route defaults array. * - * @return string|null - * The section storage ID if it could be extracted, NULL otherwise. - * - * @internal - * This should only be called during section storage instantiation. + * @return \Drupal\Component\Plugin\Context\ContextInterface[] + * The required plugin contexts. */ - public function extractIdFromRoute($value, $definition, $name, array $defaults); - - /** - * Provides any available contexts for the object using the sections. - * - * @return \Drupal\Core\Plugin\Context\ContextInterface[] - * The array of context objects. - */ - public function getContexts(); + public function getContextsFromRoute($value, $definition, $name, array $defaults); /** * Gets the label for the object using the sections. diff --git a/core/modules/layout_builder/tests/modules/layout_builder_test/layout_builder_test.module b/core/modules/layout_builder/tests/modules/layout_builder_test/layout_builder_test.module index e7d310a6cb..8e9991f8cd 100644 --- a/core/modules/layout_builder/tests/modules/layout_builder_test/layout_builder_test.module +++ b/core/modules/layout_builder/tests/modules/layout_builder_test/layout_builder_test.module @@ -7,6 +7,7 @@ use Drupal\Core\Entity\Display\EntityViewDisplayInterface; use Drupal\Core\Entity\EntityInterface; +use Drupal\layout_builder_test\Plugin\SectionStorage\TestOverridesSectionStorage; /** * Implements hook_plugin_filter_TYPE__CONSUMER_alter(). @@ -58,3 +59,15 @@ function layout_builder_test_node_view(array &$build, EntityInterface $entity, E ]; } } + +/** + * Implements hook_layout_builder_section_storage_alter(). + */ +function layout_builder_test_layout_builder_section_storage_alter(array &$storages) { + /** @var \Drupal\layout_builder\SectionStorage\SectionStorageDefinition[] $storages */ + $storages['overrides']->setClass(TestOverridesSectionStorage::class); + $storages['overrides']->set('weight', -10); + + $storages['overrides_heavy'] = clone $storages['overrides']; + $storages['overrides_heavy']->set('weight', -8); +} diff --git a/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/SectionStorage/TestOverridesSectionStorage.php b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/SectionStorage/TestOverridesSectionStorage.php new file mode 100644 index 0000000000..d4fd459343 --- /dev/null +++ b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/SectionStorage/TestOverridesSectionStorage.php @@ -0,0 +1,23 @@ +set('layout_builder_test_storage', [ + $this->getPluginDefinition()->get('weight'), + $this->getContextValue('view_mode'), + ]); + return parent::getSectionList(); + } + +} diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php index 1e4f240869..3c2301f62b 100644 --- a/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php +++ b/core/modules/layout_builder/tests/src/Functional/LayoutBuilderTest.php @@ -526,4 +526,31 @@ public function testBlockPlaceholder() { $assert_session->pageTextContains($block_content); } + /** + * Tests that section loading is delegated to plugins during rendering. + */ + public function testRenderByContextualPluginDelegate() { + $state_key = 'layout_builder_test_storage'; + /** @var \Drupal\Core\State\StateInterface $state */ + $state = $this->container->get('state'); + + $this->drupalGet('node/1'); + $this->assertEmpty($state->get($state_key)); + + entity_get_display('node', 'bundle_with_section_field', 'full') + ->enableLayoutBuilder() + ->setOverridable() + ->save(); + + $this->getSession()->reload(); + $state->resetCache(); + // During layout rendering, the storage plugin used for testing will set the + // state key to an array containing the plugin weight and view mode, which + // proves that the plugin matched the appropriate contexts and was actually + // used to render the layout. + list ($weight, $view_mode) = $state->get($state_key); + $this->assertSame(-10, $weight); + $this->assertSame('full', $view_mode); + } + } diff --git a/core/modules/layout_builder/tests/src/Unit/DefaultsSectionStorageTest.php b/core/modules/layout_builder/tests/src/Unit/DefaultsSectionStorageTest.php index d30917a446..726d62dcab 100644 --- a/core/modules/layout_builder/tests/src/Unit/DefaultsSectionStorageTest.php +++ b/core/modules/layout_builder/tests/src/Unit/DefaultsSectionStorageTest.php @@ -8,6 +8,7 @@ use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\FieldableEntityInterface; +use Drupal\Core\Plugin\Context\ContextInterface; use Drupal\layout_builder\Entity\LayoutBuilderSampleEntityGenerator; use Drupal\layout_builder\Entity\LayoutEntityDisplayInterface; use Drupal\layout_builder\Plugin\SectionStorage\DefaultsSectionStorage; @@ -61,8 +62,12 @@ protected function setUp() { public function testThirdPartySettings() { // Set an initial value on the section list. $section_list = $this->prophesize(LayoutEntityDisplayInterface::class); + + $context = $this->prophesize(ContextInterface::class); + $context->getContextValue()->willReturn($section_list->reveal()); + $this->plugin->setContext('entity', $context->reveal()); + $section_list->getThirdPartySetting('the_module', 'the_key', NULL)->willReturn('value 1'); - $this->plugin->setSectionList($section_list->reveal()); // The plugin returns the initial value. $this->assertSame('value 1', $this->plugin->getThirdPartySetting('the_module', 'the_key')); @@ -79,58 +84,11 @@ public function testThirdPartySettings() { } /** - * @covers ::extractIdFromRoute + * @covers ::extractEntityFromRoute * - * @dataProvider providerTestExtractIdFromRoute + * @dataProvider providerTestExtractEntityFromRoute */ - public function testExtractIdFromRoute($expected, $value, array $defaults) { - $result = $this->plugin->extractIdFromRoute($value, [], 'the_parameter_name', $defaults); - $this->assertSame($expected, $result); - } - - /** - * Provides data for ::testExtractIdFromRoute(). - */ - public function providerTestExtractIdFromRoute() { - $data = []; - $data['with value'] = [ - 'foo.bar.baz', - 'foo.bar.baz', - [], - ]; - $data['empty value, without bundle'] = [ - 'my_entity_type.bundle_name.default', - '', - [ - 'entity_type_id' => 'my_entity_type', - 'view_mode_name' => 'default', - 'bundle_key' => 'my_bundle', - 'my_bundle' => 'bundle_name', - ], - ]; - $data['empty value, with bundle'] = [ - 'my_entity_type.bundle_name.default', - '', - [ - 'entity_type_id' => 'my_entity_type', - 'view_mode_name' => 'default', - 'bundle' => 'bundle_name', - ], - ]; - $data['without value, empty defaults'] = [ - NULL, - '', - [], - ]; - return $data; - } - - /** - * @covers ::getSectionListFromId - * - * @dataProvider providerTestGetSectionListFromId - */ - public function testGetSectionListFromId($success, $expected_entity_id, $value) { + public function testExtractEntityFromRoute($success, $expected_entity_id, $value, array $defaults = []) { if ($expected_entity_id) { $entity_storage = $this->prophesize(EntityStorageInterface::class); $entity_storage->load($expected_entity_id)->willReturn('the_return_value'); @@ -143,26 +101,48 @@ public function testGetSectionListFromId($success, $expected_entity_id, $value) $this->entityTypeManager->getStorage('entity_view_display')->shouldNotBeCalled(); } - if (!$success) { - $this->setExpectedException(\InvalidArgumentException::class); - } - - $result = $this->plugin->getSectionListFromId($value); + $method = new \ReflectionMethod($this->plugin, 'extractEntityFromRoute'); + $method->setAccessible(TRUE); + $result = $method->invoke($this->plugin, $value, $defaults); if ($success) { $this->assertEquals('the_return_value', $result); } + else { + $this->assertNull($result); + } } /** - * Provides data for ::testGetSectionListFromId(). + * Provides data for ::testExtractEntityFromRoute(). */ - public function providerTestGetSectionListFromId() { + public function providerTestExtractEntityFromRoute() { $data = []; $data['with value'] = [ TRUE, 'foo.bar.baz', 'foo.bar.baz', ]; + $data['empty value, without bundle'] = [ + TRUE, + 'my_entity_type.bundle_name.default', + '', + [ + 'entity_type_id' => 'my_entity_type', + 'view_mode_name' => 'default', + 'bundle_key' => 'my_bundle', + 'my_bundle' => 'bundle_name', + ], + ]; + $data['empty value, with bundle'] = [ + TRUE, + 'my_entity_type.bundle_name.default', + '', + [ + 'entity_type_id' => 'my_entity_type', + 'view_mode_name' => 'default', + 'bundle' => 'bundle_name', + ], + ]; $data['without value, empty defaults'] = [ FALSE, NULL, @@ -172,9 +152,9 @@ public function providerTestGetSectionListFromId() { } /** - * @covers ::getSectionListFromId + * @covers ::extractEntityFromRoute */ - public function testGetSectionListFromIdCreate() { + public function testExtractEntityFromRouteCreate() { $expected = 'the_return_value'; $value = 'foo.bar.baz'; $expected_create_values = [ @@ -190,7 +170,9 @@ public function testGetSectionListFromIdCreate() { $this->entityTypeManager->getDefinition('entity_view_display')->willReturn(new EntityType(['id' => 'entity_view_display'])); $this->entityTypeManager->getStorage('entity_view_display')->willReturn($entity_storage->reveal()); - $result = $this->plugin->getSectionListFromId($value); + $method = new \ReflectionMethod($this->plugin, 'extractEntityFromRoute'); + $method->setAccessible(TRUE); + $result = $method->invoke($this->plugin, $value, []); $this->assertSame($expected, $result); } diff --git a/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php b/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php index 110ec2021e..d3574be6dd 100644 --- a/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php +++ b/core/modules/layout_builder/tests/src/Unit/OverridesSectionStorageTest.php @@ -60,87 +60,42 @@ protected function setUp() { } /** - * @covers ::extractIdFromRoute + * @covers ::extractEntityFromRoute * - * @dataProvider providerTestExtractIdFromRoute + * @dataProvider providerTestExtractEntityFromRoute */ - public function testExtractIdFromRoute($expected, $value, array $defaults) { - $result = $this->plugin->extractIdFromRoute($value, [], 'the_parameter_name', $defaults); - $this->assertSame($expected, $result); - } - - /** - * Provides data for ::testExtractIdFromRoute(). - */ - public function providerTestExtractIdFromRoute() { - $data = []; - $data['with value, with layout'] = [ - 'my_entity_type.entity_with_layout', - 'my_entity_type.entity_with_layout', - [], - ]; - $data['with value, without layout'] = [ - NULL, - 'my_entity_type', - [], - ]; - $data['empty value, populated defaults'] = [ - 'my_entity_type.entity_with_layout', - '', - [ - 'entity_type_id' => 'my_entity_type', - 'my_entity_type' => 'entity_with_layout', - ], - ]; - $data['empty value, empty defaults'] = [ - NULL, - '', - [], - ]; - return $data; - } - - /** - * @covers ::getSectionListFromId - * - * @dataProvider providerTestGetSectionListFromId - */ - public function testGetSectionListFromId($success, $expected_entity_type_id, $id) { - $defaults['the_parameter_name'] = $id; - + public function testExtractEntityFromRoute($success, $expected_entity_type_id, $value, array $defaults = []) { if ($expected_entity_type_id) { $entity_storage = $this->prophesize(EntityStorageInterface::class); $entity_without_layout = $this->prophesize(FieldableEntityInterface::class); $entity_without_layout->hasField('layout_builder__layout')->willReturn(FALSE); - $entity_without_layout->get('layout_builder__layout')->shouldNotBeCalled(); $entity_storage->load('entity_without_layout')->willReturn($entity_without_layout->reveal()); $entity_with_layout = $this->prophesize(FieldableEntityInterface::class); $entity_with_layout->hasField('layout_builder__layout')->willReturn(TRUE); - $entity_with_layout->get('layout_builder__layout')->willReturn('the_return_value'); $entity_storage->load('entity_with_layout')->willReturn($entity_with_layout->reveal()); - $this->entityTypeManager->getStorage($expected_entity_type_id)->willReturn($entity_storage->reveal()); } else { $this->entityTypeManager->getStorage(Argument::any())->shouldNotBeCalled(); } - if (!$success) { - $this->setExpectedException(\InvalidArgumentException::class); - } - - $result = $this->plugin->getSectionListFromId($id); + $method = new \ReflectionMethod($this->plugin, 'extractEntityFromRoute'); + $method->setAccessible(TRUE); + $result = $method->invoke($this->plugin, $value, $defaults); if ($success) { - $this->assertEquals('the_return_value', $result); + $this->assertInstanceOf(FieldableEntityInterface::class, $result); + } + else { + $this->assertNull($result); } } /** - * Provides data for ::testGetSectionListFromId(). + * Provides data for ::testExtractEntityFromRoute(). */ - public function providerTestGetSectionListFromId() { + public function providerTestExtractEntityFromRoute() { $data = []; $data['with value, with layout'] = [ TRUE, @@ -152,6 +107,15 @@ public function providerTestGetSectionListFromId() { 'my_entity_type', 'my_entity_type.entity_without_layout', ]; + $data['empty value, populated defaults'] = [ + TRUE, + 'my_entity_type', + '', + [ + 'entity_type_id' => 'my_entity_type', + 'my_entity_type' => 'entity_with_layout', + ], + ]; $data['empty value, empty defaults'] = [ FALSE, NULL, diff --git a/core/modules/layout_builder/tests/src/Unit/SectionStorageManagerTest.php b/core/modules/layout_builder/tests/src/Unit/SectionStorageManagerTest.php index 392cd8939e..10f41e937c 100644 --- a/core/modules/layout_builder/tests/src/Unit/SectionStorageManagerTest.php +++ b/core/modules/layout_builder/tests/src/Unit/SectionStorageManagerTest.php @@ -2,10 +2,10 @@ namespace Drupal\Tests\layout_builder\Unit; +use Drupal\Component\Plugin\Context\ContextInterface; use Drupal\Component\Plugin\Factory\FactoryInterface; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Extension\ModuleHandlerInterface; -use Drupal\layout_builder\SectionListInterface; use Drupal\layout_builder\SectionStorage\SectionStorageManager; use Drupal\layout_builder\SectionStorageInterface; use Drupal\Tests\UnitTestCase; @@ -58,30 +58,15 @@ public function testLoadEmpty() { $this->assertInstanceOf(SectionStorageInterface::class, $result); } - /** - * @covers ::loadFromStorageId - */ - public function testLoadFromStorageId() { - $section_list = $this->prophesize(SectionListInterface::class); - $this->plugin->setSectionList($section_list->reveal())->will(function () { - return $this; - }); - $this->plugin->getSectionListFromId('the_storage_id')->willReturn($section_list->reveal()); - - $result = $this->manager->loadFromStorageId('the_plugin_id', 'the_storage_id'); - $this->assertInstanceOf(SectionStorageInterface::class, $result); - } - /** * @covers ::loadFromRoute */ public function testLoadFromRoute() { - $section_list = $this->prophesize(SectionListInterface::class); - $this->plugin->extractIdFromRoute('the_value', [], 'the_parameter_name', [])->willReturn('the_storage_id'); - $this->plugin->getSectionListFromId('the_storage_id')->willReturn($section_list->reveal()); - $this->plugin->setSectionList($section_list->reveal())->will(function () { - return $this; - }); + $contexts = [ + 'the_context' => $this->prophesize(ContextInterface::class)->reveal(), + ]; + $this->plugin->getContextsFromRoute('the_value', [], 'the_parameter_name', [])->willReturn($contexts); + $this->plugin->setContext('the_context', $contexts['the_context'])->shouldBeCalled(); $result = $this->manager->loadFromRoute('the_plugin_id', 'the_value', [], 'the_parameter_name', []); $this->assertInstanceOf(SectionStorageInterface::class, $result); @@ -91,7 +76,7 @@ public function testLoadFromRoute() { * @covers ::loadFromRoute */ public function testLoadFromRouteNull() { - $this->plugin->extractIdFromRoute('the_value', [], 'the_parameter_name', ['_route' => 'the_route_name'])->willReturn(NULL); + $this->plugin->getContextsFromRoute('the_value', [], 'the_parameter_name', ['_route' => 'the_route_name'])->willReturn([]); $result = $this->manager->loadFromRoute('the_plugin_id', 'the_value', [], 'the_parameter_name', ['_route' => 'the_route_name']); $this->assertNull($result);