diff --git a/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php index 1a89c4f854..fd4d85d7f3 100644 --- a/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php +++ b/core/modules/layout_builder/src/Entity/LayoutBuilderEntityViewDisplay.php @@ -2,6 +2,7 @@ namespace Drupal\layout_builder\Entity; +use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Entity\Entity\EntityViewDisplay as BaseEntityViewDisplay; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\FieldableEntityInterface; @@ -242,7 +243,13 @@ public function buildMultiple(array $entities) { /** @var \Drupal\Core\Entity\EntityInterface $entity */ foreach ($entities as $id => $entity) { - $sections = $this->getRuntimeSections($entity); + $cacheability = new CacheableMetadata(); + $sections = $this->getRuntimeSections($entity, $cacheability); + + // Apply cacheability metadata to the build array. + $build_list[$id]['_layout_builder'] = []; + $cacheability->applyTo($build_list[$id]['_layout_builder']); + if ($sections) { foreach ($build_list[$id] as $name => $build_part) { $field_definition = $this->getFieldDefinition($name); @@ -289,12 +296,15 @@ protected function getContextsForEntity(FieldableEntityInterface $entity) { * * @param \Drupal\Core\Entity\FieldableEntityInterface $entity * The entity. + * @param \Drupal\Core\Cache\CacheableMetadata|null $cacheability + * (optional) Cacheability metadata object, which will be populated based on + * the cacheability of each section storage candidate. * * @return \Drupal\layout_builder\Section[] * The sections. */ - protected function getRuntimeSections(FieldableEntityInterface $entity) { - $storage = $this->sectionStorageManager()->findByContext($this->getContextsForEntity($entity)); + protected function getRuntimeSections(FieldableEntityInterface $entity, CacheableMetadata &$cacheability = NULL) { + $storage = $this->sectionStorageManager()->findByContext($this->getContextsForEntity($entity), $cacheability); return $storage ? $storage->getSections() : []; } diff --git a/core/modules/layout_builder/src/SectionStorage/SectionStorageManager.php b/core/modules/layout_builder/src/SectionStorage/SectionStorageManager.php index 232bbe95e1..50fa28f40c 100644 --- a/core/modules/layout_builder/src/SectionStorage/SectionStorageManager.php +++ b/core/modules/layout_builder/src/SectionStorage/SectionStorageManager.php @@ -3,6 +3,7 @@ namespace Drupal\layout_builder\SectionStorage; use Drupal\Component\Plugin\Exception\ContextException; +use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Plugin\Context\ContextHandlerInterface; @@ -92,11 +93,15 @@ public function load($type, array $contexts = []) { /** * {@inheritdoc} */ - public function findByContext(array $contexts) { + public function findByContext(array $contexts, CacheableMetadata &$cacheability = NULL) { $storage_types = array_keys($this->contextHandler->filterPluginDefinitionsByContexts($contexts, $this->getDefinitions())); foreach ($storage_types as $type) { $plugin = $this->load($type, $contexts); + // Each plugin used in this loop must be added as a cacheable dependency. + if ($cacheability) { + $cacheability->addCacheableDependency($plugin); + } if ($plugin && $plugin->isApplicable()) { return $plugin; } diff --git a/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php b/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php index 636d533027..60f08f3469 100644 --- a/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php +++ b/core/modules/layout_builder/src/SectionStorage/SectionStorageManagerInterface.php @@ -3,6 +3,7 @@ namespace Drupal\layout_builder\SectionStorage; use Drupal\Component\Plugin\Discovery\DiscoveryInterface; +use Drupal\Core\Cache\CacheableMetadata; /** * Provides the interface for a plugin manager of section storage types. @@ -32,11 +33,14 @@ public function load($type, array $contexts = []); * * @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts * The contexts which should be used to determine which storage to return. + * @param \Drupal\Core\Cache\CacheableMetadata|null $cacheability + * (optional) Cacheability metadata object, which will be populated based on + * the cacheability of each section storage candidate. * * @return \Drupal\layout_builder\SectionStorageInterface|null * The section storage if one matched all contexts, or NULL otherwise. */ - public function findByContext(array $contexts); + public function findByContext(array $contexts, CacheableMetadata &$cacheability = NULL); /** * Loads a section storage with no associated section list. diff --git a/core/modules/layout_builder/tests/modules/layout_builder_overrides_test/src/Plugin/SectionStorage/TestOverridesSectionStorage.php b/core/modules/layout_builder/tests/modules/layout_builder_overrides_test/src/Plugin/SectionStorage/TestOverridesSectionStorage.php index 9320b0162f..7a40eb33c4 100644 --- a/core/modules/layout_builder/tests/modules/layout_builder_overrides_test/src/Plugin/SectionStorage/TestOverridesSectionStorage.php +++ b/core/modules/layout_builder/tests/modules/layout_builder_overrides_test/src/Plugin/SectionStorage/TestOverridesSectionStorage.php @@ -39,6 +39,14 @@ public function isApplicable() { return \Drupal::state()->get('layout_builder_overrides_test', FALSE); } + /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + // This cannot be cached as applicability is determined by state. + return 0; + } + /** * {@inheritdoc} */ diff --git a/core/modules/layout_builder/tests/src/Unit/SectionStorageManagerTest.php b/core/modules/layout_builder/tests/src/Unit/SectionStorageManagerTest.php index f6f90719bb..7789399b18 100644 --- a/core/modules/layout_builder/tests/src/Unit/SectionStorageManagerTest.php +++ b/core/modules/layout_builder/tests/src/Unit/SectionStorageManagerTest.php @@ -6,6 +6,8 @@ use Drupal\Component\Plugin\Discovery\DiscoveryInterface; use Drupal\Component\Plugin\Exception\ContextException; use Drupal\Component\Plugin\Factory\FactoryInterface; +use Drupal\Core\Cache\CacheableDependencyInterface; +use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Plugin\Context\Context; @@ -256,4 +258,46 @@ public function providerTestFindByContext() { return $data; } + /** + * @covers ::findByContext + */ + public function testFindByContextCacheableSectionStorage() { + $contexts = [ + 'foo' => new Context(new ContextDefinition('foo')), + ]; + + $definitions = [ + 'first' => new SectionStorageDefinition(), + 'second' => new SectionStorageDefinition(), + ]; + $this->discovery->getDefinitions()->willReturn($definitions); + + $first_plugin = $this->prophesize(SectionStorageInterface::class); + $first_plugin->willImplement(CacheableDependencyInterface::class); + $first_plugin->getCacheContexts()->willReturn([]); + $first_plugin->getCacheTags()->willReturn(['first_plugin']); + $first_plugin->getCacheMaxAge()->willReturn(-1); + $first_plugin->isApplicable()->willReturn(FALSE); + + $second_plugin = $this->prophesize(SectionStorageInterface::class); + $second_plugin->willImplement(CacheableDependencyInterface::class); + $second_plugin->getCacheContexts()->willReturn([]); + $second_plugin->getCacheTags()->willReturn(['second_plugin']); + $second_plugin->getCacheMaxAge()->willReturn(-1); + $second_plugin->isApplicable()->willReturn(TRUE); + + $this->factory->createInstance('first', [])->willReturn($first_plugin->reveal()); + $this->factory->createInstance('second', [])->willReturn($second_plugin->reveal()); + + // Do not do any filtering based on context. + $this->contextHandler->filterPluginDefinitionsByContexts($contexts, $definitions)->willReturnArgument(1); + $this->contextHandler->applyContextMapping($first_plugin, $contexts)->shouldBeCalled(); + $this->contextHandler->applyContextMapping($second_plugin, $contexts)->shouldBeCalled(); + + $cacheability = new CacheableMetadata(); + $result = $this->manager->findByContext($contexts, $cacheability); + $this->assertSame($second_plugin->reveal(), $result); + $this->assertSame(['first_plugin', 'second_plugin'], $cacheability->getCacheTags()); + } + }