Skip to content
Snippets Groups Projects
Commit 7ec35d9a authored by catch's avatar catch
Browse files

Issue #3304772 by tstoeckler, kksandr, Murz, smustgrave: Cache tags from...

Issue #3304772 by tstoeckler, kksandr, Murz, smustgrave: Cache tags from Computed fields do not bubble up to Entity render array
parent a126d042
No related branches found
No related tags found
No related merge requests found
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
namespace Drupal\Core\Field; namespace Drupal\Core\Field;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
...@@ -88,8 +90,16 @@ public function view(FieldItemListInterface $items, $langcode = NULL) { ...@@ -88,8 +90,16 @@ public function view(FieldItemListInterface $items, $langcode = NULL) {
} }
$elements = $this->viewElements($items, $langcode); $elements = $this->viewElements($items, $langcode);
// Field item lists, in particular for computed fields, may carry cacheable
// metadata which must be bubbled.
if ($items instanceof CacheableDependencyInterface) {
(new CacheableMetadata())
->addCacheableDependency($items)
->applyTo($elements);
}
// If there are actual renderable children, use #theme => field, otherwise, // If there are actual renderable children, use #theme => field, otherwise,
// let access cacheability metadata pass through for correct bubbling. // let cacheability metadata pass through for correct bubbling.
if (Element::children($elements)) { if (Element::children($elements)) {
$entity = $items->getEntity(); $entity = $items->getEntity();
$entity_type = $entity->getEntityTypeId(); $entity_type = $entity->getEntityTypeId();
......
...@@ -114,6 +114,7 @@ protected function getExpectedDocument() { ...@@ -114,6 +114,7 @@ protected function getExpectedDocument() {
'drupal_internal__id' => 1, 'drupal_internal__id' => 1,
'computed_string_field' => NULL, 'computed_string_field' => NULL,
'computed_test_cacheable_string_field' => 'computed test cacheable string field', 'computed_test_cacheable_string_field' => 'computed test cacheable string field',
'computed_test_cacheable_integer_field' => 0,
], ],
'relationships' => [ 'relationships' => [
'computed_reference_field' => [ 'computed_reference_field' => [
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\entity_test\Plugin\Field\ComputedReferenceTestFieldItemList; use Drupal\entity_test\Plugin\Field\ComputedReferenceTestFieldItemList;
use Drupal\entity_test\Plugin\Field\ComputedTestCacheableIntegerItemList;
use Drupal\entity_test\Plugin\Field\ComputedTestCacheableStringItemList; use Drupal\entity_test\Plugin\Field\ComputedTestCacheableStringItemList;
use Drupal\entity_test\Plugin\Field\ComputedTestFieldItemList; use Drupal\entity_test\Plugin\Field\ComputedTestFieldItemList;
...@@ -49,12 +50,22 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ...@@ -49,12 +50,22 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
->setSetting('target_type', 'entity_test') ->setSetting('target_type', 'entity_test')
->setClass(ComputedReferenceTestFieldItemList::class); ->setClass(ComputedReferenceTestFieldItemList::class);
// Cacheable metadata can either be provided via the field item properties
// or via the field item list class directly. Add a computed string field
// which does the former and a computed integer field which does the latter.
$fields['computed_test_cacheable_string_field'] = BaseFieldDefinition::create('computed_test_cacheable_string_item') $fields['computed_test_cacheable_string_field'] = BaseFieldDefinition::create('computed_test_cacheable_string_item')
->setLabel(new TranslatableMarkup('Computed Cacheable String Field Test')) ->setLabel(new TranslatableMarkup('Computed Cacheable String Field Test'))
->setComputed(TRUE) ->setComputed(TRUE)
->setClass(ComputedTestCacheableStringItemList::class) ->setClass(ComputedTestCacheableStringItemList::class)
->setReadOnly(FALSE) ->setReadOnly(FALSE)
->setInternal(FALSE); ->setInternal(FALSE);
$fields['computed_test_cacheable_integer_field'] = BaseFieldDefinition::create('integer')
->setLabel(new TranslatableMarkup('Computed Cacheable Integer Field Test'))
->setComputed(TRUE)
->setClass(ComputedTestCacheableIntegerItemList::class)
->setReadOnly(FALSE)
->setInternal(FALSE)
->setDisplayOptions('view', ['weight' => 10]);
return $fields; return $fields;
} }
......
<?php
namespace Drupal\entity_test\Plugin\Field;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Cache\CacheableDependencyTrait;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Field\FieldItemList;
use Drupal\Core\TypedData\ComputedItemListTrait;
/**
* Item list class for computed cacheable string field.
*
* This class sets the cacheable metadata on the field item list directly.
*
* @see \Drupal\entity_test\Plugin\Field\ComputedTestCacheableStringItemList
*/
class ComputedTestCacheableIntegerItemList extends FieldItemList implements CacheableDependencyInterface {
use CacheableDependencyTrait, ComputedItemListTrait;
/**
* {@inheritdoc}
*/
protected function computeValue() {
$value = \Drupal::state()->get('entity_test_computed_integer_value', 0);
$item = $this->createItem(0, $value);
$cacheability = (new CacheableMetadata())
->setCacheContexts(['url.query_args:computed_test_cacheable_integer_field'])
->setCacheTags(['field:computed_test_cacheable_integer_field'])
->setCacheMaxAge(31536000);
$this->setCacheability($cacheability);
$this->list[0] = $item;
}
}
...@@ -8,6 +8,10 @@ ...@@ -8,6 +8,10 @@
/** /**
* Item list class for computed cacheable string field. * Item list class for computed cacheable string field.
*
* This class sets the cacheable metadata on the field item properties.
*
* @see \Drupal\entity_test\Plugin\Field\ComputedTestCacheableIntegerItemList
*/ */
class ComputedTestCacheableStringItemList extends FieldItemList { class ComputedTestCacheableStringItemList extends FieldItemList {
......
...@@ -56,6 +56,12 @@ protected function getExpectedNormalizedEntity() { ...@@ -56,6 +56,12 @@ protected function getExpectedNormalizedEntity() {
'value' => 'computed test cacheable string field', 'value' => 'computed test cacheable string field',
], ],
]; ];
// @see \Drupal\entity_test\Plugin\Field\ComputedTestCacheableIntegerItemList::computeValue().
$expected['computed_test_cacheable_integer_field'] = [
[
'value' => 0,
],
];
$expected['uuid'] = [ $expected['uuid'] = [
0 => [ 0 => [
......
<?php
namespace Drupal\Tests\system\Functional\Entity;
use Drupal\Core\Cache\Cache;
use Drupal\Core\State\StateInterface;
use Drupal\entity_test\Entity\EntityTestComputedField;
use Drupal\Tests\BrowserTestBase;
/**
* Tests that entities with computed fields work correctly.
*
* @group Entity
*/
class EntityComputedFieldTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['entity_test'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'olivero';
/**
* The state service.
*
* @var \Drupal\Core\State\StateInterface
*/
protected StateInterface $state;
protected function setUp(): void {
parent::setUp();
$this->state = $this->container->get('state');
}
/**
* Tests that formatters bubble the cacheable metadata of computed fields.
*/
public function testFormatterComputedFieldCacheableMetadata() {
$this->drupalLogin($this->drupalCreateUser(['administer entity_test content']));
$entity = EntityTestComputedField::create([
'name' => 'Test entity with a cacheable, computed field',
]);
$entity->save();
$this->state->set('entity_test_computed_integer_value', 2024);
$this->drupalGet($entity->toUrl('canonical')->toString());
$field_item_selector = '.field--name-computed-test-cacheable-integer-field .field__item';
$this->assertSession()->elementTextEquals('css', $field_item_selector, 2024);
$this->assertSession()->responseHeaderContains('X-Drupal-Cache-Contexts', 'url.query_args:computed_test_cacheable_integer_field');
$this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', 'field:computed_test_cacheable_integer_field');
$this->assertSession()->responseHeaderEquals('X-Drupal-Cache-Max-Age', "31536000");
$this->state->set('entity_test_computed_integer_value', 2025);
$this->drupalGet($entity->toUrl('canonical')->toString());
$this->assertSession()->elementTextEquals('css', $field_item_selector, 2024);
$this->assertSession()->responseHeaderContains('X-Drupal-Cache-Contexts', 'url.query_args:computed_test_cacheable_integer_field');
$this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', 'field:computed_test_cacheable_integer_field');
$this->assertSession()->responseHeaderEquals('X-Drupal-Cache-Max-Age', "31536000");
Cache::invalidateTags(['field:computed_test_cacheable_integer_field']);
$this->drupalGet($entity->toUrl('canonical')->toString());
$this->assertSession()->elementTextEquals('css', $field_item_selector, 2025);
$this->assertSession()->responseHeaderContains('X-Drupal-Cache-Contexts', 'url.query_args:computed_test_cacheable_integer_field');
$this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', 'field:computed_test_cacheable_integer_field');
$this->assertSession()->responseHeaderEquals('X-Drupal-Cache-Max-Age', "31536000");
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment