123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722 |
- <?php
- /**
- * @file
- * Contains \Drupal\Tests\Core\Render\RendererBubblingTest.
- */
- namespace Drupal\Tests\Core\Render;
- use Drupal\Core\Cache\MemoryBackend;
- use Drupal\Core\KeyValueStore\KeyValueMemoryFactory;
- use Drupal\Core\Security\TrustedCallbackInterface;
- use Drupal\Core\State\State;
- use Drupal\Core\Cache\Cache;
- /**
- * @coversDefaultClass \Drupal\Core\Render\Renderer
- * @group Render
- */
- class RendererBubblingTest extends RendererTestBase {
- /**
- * {@inheritdoc}
- */
- protected function setUp() {
- // Disable the required cache contexts, so that this test can test just the
- // bubbling behavior.
- $this->rendererConfig['required_cache_contexts'] = [];
- parent::setUp();
- }
- /**
- * Tests bubbling of assets when NOT using #pre_render callbacks.
- */
- public function testBubblingWithoutPreRender() {
- $this->setUpRequest();
- $this->setupMemoryCache();
- $this->cacheContextsManager->expects($this->any())
- ->method('convertTokensToKeys')
- ->willReturnArgument(0);
- // Create an element with a child and subchild. Each element loads a
- // different library using #attached.
- $element = [
- '#type' => 'container',
- '#cache' => [
- 'keys' => ['simpletest', 'renderer', 'children_attached'],
- ],
- '#attached' => ['library' => ['test/parent']],
- '#title' => 'Parent',
- ];
- $element['child'] = [
- '#type' => 'container',
- '#attached' => ['library' => ['test/child']],
- '#title' => 'Child',
- ];
- $element['child']['subchild'] = [
- '#attached' => ['library' => ['test/subchild']],
- '#markup' => 'Subchild',
- ];
- // Render the element and verify the presence of #attached JavaScript.
- $this->renderer->renderRoot($element);
- $expected_libraries = ['test/parent', 'test/child', 'test/subchild'];
- $this->assertEquals($element['#attached']['library'], $expected_libraries, 'The element, child and subchild #attached libraries are included.');
- // Load the element from cache and verify the presence of the #attached
- // JavaScript.
- $element = ['#cache' => ['keys' => ['simpletest', 'renderer', 'children_attached']]];
- $this->assertTrue(strlen($this->renderer->renderRoot($element)) > 0, 'The element was retrieved from cache.');
- $this->assertEquals($element['#attached']['library'], $expected_libraries, 'The element, child and subchild #attached libraries are included.');
- }
- /**
- * Tests cache context bubbling with a custom cache bin.
- */
- public function testContextBubblingCustomCacheBin() {
- $bin = $this->randomMachineName();
- $this->setUpRequest();
- $this->memoryCache = new MemoryBackend();
- $custom_cache = new MemoryBackend();
- $this->cacheFactory->expects($this->atLeastOnce())
- ->method('get')
- ->with($bin)
- ->willReturnCallback(function ($requested_bin) use ($bin, $custom_cache) {
- if ($requested_bin === $bin) {
- return $custom_cache;
- }
- else {
- throw new \Exception();
- }
- });
- $this->cacheContextsManager->expects($this->any())
- ->method('convertTokensToKeys')
- ->willReturnArgument(0);
- $build = [
- '#cache' => [
- 'keys' => ['parent'],
- 'contexts' => ['foo'],
- 'bin' => $bin,
- ],
- '#markup' => 'parent',
- 'child' => [
- '#cache' => [
- 'contexts' => ['bar'],
- 'max-age' => 3600,
- ],
- ],
- ];
- $this->renderer->renderRoot($build);
- $this->assertRenderCacheItem('parent:foo', [
- '#cache_redirect' => TRUE,
- '#cache' => [
- 'keys' => ['parent'],
- 'contexts' => ['bar', 'foo'],
- 'tags' => [],
- 'bin' => $bin,
- 'max-age' => 3600,
- ],
- ], $bin);
- }
- /**
- * Tests cache context bubbling in edge cases, because it affects the CID.
- *
- * ::testBubblingWithPrerender() already tests the common case.
- *
- * @dataProvider providerTestContextBubblingEdgeCases
- */
- public function testContextBubblingEdgeCases(array $element, array $expected_top_level_contexts, array $expected_cache_items) {
- $this->setUpRequest();
- $this->setupMemoryCache();
- $this->cacheContextsManager->expects($this->any())
- ->method('convertTokensToKeys')
- ->willReturnArgument(0);
- $this->renderer->renderRoot($element);
- $this->assertEquals($expected_top_level_contexts, $element['#cache']['contexts'], 'Expected cache contexts found.');
- foreach ($expected_cache_items as $cid => $expected_cache_item) {
- $this->assertRenderCacheItem($cid, $expected_cache_item);
- }
- }
- public function providerTestContextBubblingEdgeCases() {
- $data = [];
- // Cache contexts of inaccessible children aren't bubbled (because those
- // children are not rendered at all).
- $test_element = [
- '#cache' => [
- 'keys' => ['parent'],
- 'contexts' => [],
- ],
- '#markup' => 'parent',
- 'child' => [
- '#access' => FALSE,
- '#cache' => [
- 'contexts' => ['foo'],
- ],
- ],
- ];
- $expected_cache_items = [
- 'parent' => [
- '#attached' => [],
- '#cache' => [
- 'contexts' => [],
- 'tags' => [],
- 'max-age' => Cache::PERMANENT,
- ],
- '#markup' => 'parent',
- ],
- ];
- $data[] = [$test_element, [], $expected_cache_items];
- // Assert cache contexts are sorted when they are used to generate a CID.
- // (Necessary to ensure that different render arrays where the same keys +
- // set of contexts are present point to the same cache item. Regardless of
- // the contexts' order. A sad necessity because PHP doesn't have sets.)
- $test_element = [
- '#cache' => [
- 'keys' => ['set_test'],
- 'contexts' => [],
- ],
- ];
- $expected_cache_items = [
- 'set_test:bar:baz:foo' => [
- '#attached' => [],
- '#cache' => [
- 'contexts' => [],
- 'tags' => [],
- 'max-age' => Cache::PERMANENT,
- ],
- '#markup' => '',
- ],
- ];
- $context_orders = [
- ['foo', 'bar', 'baz'],
- ['foo', 'baz', 'bar'],
- ['bar', 'foo', 'baz'],
- ['bar', 'baz', 'foo'],
- ['baz', 'foo', 'bar'],
- ['baz', 'bar', 'foo'],
- ];
- foreach ($context_orders as $context_order) {
- $test_element['#cache']['contexts'] = $context_order;
- sort($context_order);
- $expected_cache_items['set_test:bar:baz:foo']['#cache']['contexts'] = $context_order;
- $data[] = [$test_element, $context_order, $expected_cache_items];
- }
- // A parent with a certain set of cache contexts is unaffected by a child
- // that has a subset of those contexts.
- $test_element = [
- '#cache' => [
- 'keys' => ['parent'],
- 'contexts' => ['foo', 'bar', 'baz'],
- ],
- '#markup' => 'parent',
- 'child' => [
- '#cache' => [
- 'contexts' => ['foo', 'baz'],
- 'max-age' => 3600,
- ],
- ],
- ];
- $expected_cache_items = [
- 'parent:bar:baz:foo' => [
- '#attached' => [],
- '#cache' => [
- 'contexts' => ['bar', 'baz', 'foo'],
- 'tags' => [],
- 'max-age' => 3600,
- ],
- '#markup' => 'parent',
- ],
- ];
- $data[] = [$test_element, ['bar', 'baz', 'foo'], $expected_cache_items];
- // A parent with a certain set of cache contexts that is a subset of the
- // cache contexts of a child gets a redirecting cache item for the cache ID
- // created pre-bubbling (without the child's additional cache contexts). It
- // points to a cache item with a post-bubbling cache ID (i.e. with the
- // child's additional cache contexts).
- // Furthermore, the redirecting cache item also includes the children's
- // cache tags, since changes in the children may cause those children to get
- // different cache contexts and therefore cause different cache contexts to
- // be stored in the redirecting cache item.
- $test_element = [
- '#cache' => [
- 'keys' => ['parent'],
- 'contexts' => ['foo'],
- 'tags' => ['yar', 'har'],
- ],
- '#markup' => 'parent',
- 'child' => [
- '#cache' => [
- 'contexts' => ['bar'],
- 'tags' => ['fiddle', 'dee'],
- ],
- '#markup' => '',
- ],
- ];
- $expected_cache_items = [
- 'parent:foo' => [
- '#cache_redirect' => TRUE,
- '#cache' => [
- // The keys + contexts this redirects to.
- 'keys' => ['parent'],
- 'contexts' => ['bar', 'foo'],
- 'tags' => ['dee', 'fiddle', 'har', 'yar'],
- 'bin' => 'render',
- 'max-age' => Cache::PERMANENT,
- ],
- ],
- 'parent:bar:foo' => [
- '#attached' => [],
- '#cache' => [
- 'contexts' => ['bar', 'foo'],
- 'tags' => ['dee', 'fiddle', 'har', 'yar'],
- 'max-age' => Cache::PERMANENT,
- ],
- '#markup' => 'parent',
- ],
- ];
- $data[] = [$test_element, ['bar', 'foo'], $expected_cache_items];
- // Ensure that bubbleable metadata has been collected from children and set
- // correctly to the main level of the render array. That ensures that correct
- // bubbleable metadata exists if render array gets rendered multiple times.
- $test_element = [
- '#cache' => [
- 'keys' => ['parent'],
- 'tags' => ['yar', 'har'],
- ],
- '#markup' => 'parent',
- 'child' => [
- '#render_children' => TRUE,
- 'subchild' => [
- '#cache' => [
- 'contexts' => ['foo'],
- 'tags' => ['fiddle', 'dee'],
- ],
- '#attached' => [
- 'library' => ['foo/bar'],
- ],
- '#markup' => '',
- ],
- ],
- ];
- $expected_cache_items = [
- 'parent:foo' => [
- '#attached' => ['library' => ['foo/bar']],
- '#cache' => [
- 'contexts' => ['foo'],
- 'tags' => ['dee', 'fiddle', 'har', 'yar'],
- 'max-age' => Cache::PERMANENT,
- ],
- '#markup' => 'parent',
- ],
- ];
- $data[] = [$test_element, ['foo'], $expected_cache_items];
- return $data;
- }
- /**
- * Tests the self-healing of the redirect with conditional cache contexts.
- */
- public function testConditionalCacheContextBubblingSelfHealing() {
- $current_user_role = &$this->currentUserRole;
- $this->setUpRequest();
- $this->setupMemoryCache();
- $test_element = [
- '#cache' => [
- 'keys' => ['parent'],
- 'tags' => ['a'],
- ],
- '#markup' => 'parent',
- 'child' => [
- '#cache' => [
- 'contexts' => ['user.roles'],
- 'tags' => ['b'],
- ],
- 'grandchild' => [
- '#access_callback' => function () use (&$current_user_role) {
- // Only role A cannot access this subtree.
- return $current_user_role !== 'A';
- },
- '#cache' => [
- 'contexts' => ['foo'],
- 'tags' => ['c'],
- // A lower max-age; the redirecting cache item should be updated.
- 'max-age' => 1800,
- ],
- 'grandgrandchild' => [
- '#access_callback' => function () use (&$current_user_role) {
- // Only role C can access this subtree.
- return $current_user_role === 'C';
- },
- '#cache' => [
- 'contexts' => ['bar'],
- 'tags' => ['d'],
- // A lower max-age; the redirecting cache item should be updated.
- 'max-age' => 300,
- ],
- ],
- ],
- ],
- ];
- // Request 1: role A, the grandchild isn't accessible => bubbled cache
- // contexts: user.roles.
- $element = $test_element;
- $current_user_role = 'A';
- $this->renderer->renderRoot($element);
- $this->assertRenderCacheItem('parent', [
- '#cache_redirect' => TRUE,
- '#cache' => [
- 'keys' => ['parent'],
- 'contexts' => ['user.roles'],
- 'tags' => ['a', 'b'],
- 'bin' => 'render',
- 'max-age' => Cache::PERMANENT,
- ],
- ]);
- $this->assertRenderCacheItem('parent:r.A', [
- '#attached' => [],
- '#cache' => [
- 'contexts' => ['user.roles'],
- 'tags' => ['a', 'b'],
- 'max-age' => Cache::PERMANENT,
- ],
- '#markup' => 'parent',
- ]);
- // Request 2: role B, the grandchild is accessible => bubbled cache
- // contexts: foo, user.roles + merged max-age: 1800.
- $element = $test_element;
- $current_user_role = 'B';
- $this->renderer->renderRoot($element);
- $this->assertRenderCacheItem('parent', [
- '#cache_redirect' => TRUE,
- '#cache' => [
- 'keys' => ['parent'],
- 'contexts' => ['foo', 'user.roles'],
- 'tags' => ['a', 'b', 'c'],
- 'bin' => 'render',
- 'max-age' => 1800,
- ],
- ]);
- $this->assertRenderCacheItem('parent:foo:r.B', [
- '#attached' => [],
- '#cache' => [
- 'contexts' => ['foo', 'user.roles'],
- 'tags' => ['a', 'b', 'c'],
- 'max-age' => 1800,
- ],
- '#markup' => 'parent',
- ]);
- // Request 3: role A again, the grandchild is inaccessible again => bubbled
- // cache contexts: user.roles; but that's a subset of the already-bubbled
- // cache contexts, so nothing is actually changed in the redirecting cache
- // item. However, the cache item we were looking for in request 1 is
- // technically the same one we're looking for now (it's the exact same
- // request), but with one additional cache context. This is necessary to
- // avoid "cache ping-pong". (Requests 1 and 3 are identical, but without the
- // right merging logic to handle request 2, the redirecting cache item would
- // toggle between only the 'user.roles' cache context and both the 'foo'
- // and 'user.roles' cache contexts, resulting in a cache miss every time.)
- $element = $test_element;
- $current_user_role = 'A';
- $this->renderer->renderRoot($element);
- $this->assertRenderCacheItem('parent', [
- '#cache_redirect' => TRUE,
- '#cache' => [
- 'keys' => ['parent'],
- 'contexts' => ['foo', 'user.roles'],
- 'tags' => ['a', 'b', 'c'],
- 'bin' => 'render',
- 'max-age' => 1800,
- ],
- ]);
- $this->assertRenderCacheItem('parent:foo:r.A', [
- '#attached' => [],
- '#cache' => [
- 'contexts' => ['foo', 'user.roles'],
- 'tags' => ['a', 'b'],
- // Note that the max-age here is unaffected. When role A, the grandchild
- // is never rendered, so neither is its max-age of 1800 present here,
- // despite 1800 being the max-age of the redirecting cache item.
- 'max-age' => Cache::PERMANENT,
- ],
- '#markup' => 'parent',
- ]);
- // Request 4: role C, both the grandchild and the grandgrandchild are
- // accessible => bubbled cache contexts: foo, bar, user.roles + merged
- // max-age: 300.
- $element = $test_element;
- $current_user_role = 'C';
- $this->renderer->renderRoot($element);
- $final_parent_cache_item = [
- '#cache_redirect' => TRUE,
- '#cache' => [
- 'keys' => ['parent'],
- 'contexts' => ['bar', 'foo', 'user.roles'],
- 'tags' => ['a', 'b', 'c', 'd'],
- 'bin' => 'render',
- 'max-age' => 300,
- ],
- ];
- $this->assertRenderCacheItem('parent', $final_parent_cache_item);
- $this->assertRenderCacheItem('parent:bar:foo:r.C', [
- '#attached' => [],
- '#cache' => [
- 'contexts' => ['bar', 'foo', 'user.roles'],
- 'tags' => ['a', 'b', 'c', 'd'],
- 'max-age' => 300,
- ],
- '#markup' => 'parent',
- ]);
- // Request 5: role A again, verifying the merging like we did for request 3.
- $element = $test_element;
- $current_user_role = 'A';
- $this->renderer->renderRoot($element);
- $this->assertRenderCacheItem('parent', $final_parent_cache_item);
- $this->assertRenderCacheItem('parent:bar:foo:r.A', [
- '#attached' => [],
- '#cache' => [
- 'contexts' => ['bar', 'foo', 'user.roles'],
- 'tags' => ['a', 'b'],
- // Note that the max-age here is unaffected. When role A, the grandchild
- // is never rendered, so neither is its max-age of 1800 present here,
- // nor the grandgrandchild's max-age of 300, despite 300 being the
- // max-age of the redirecting cache item.
- 'max-age' => Cache::PERMANENT,
- ],
- '#markup' => 'parent',
- ]);
- // Request 6: role B again, verifying the merging like we did for request 3.
- $element = $test_element;
- $current_user_role = 'B';
- $this->renderer->renderRoot($element);
- $this->assertRenderCacheItem('parent', $final_parent_cache_item);
- $this->assertRenderCacheItem('parent:bar:foo:r.B', [
- '#attached' => [],
- '#cache' => [
- 'contexts' => ['bar', 'foo', 'user.roles'],
- 'tags' => ['a', 'b', 'c'],
- // Note that the max-age here is unaffected. When role B, the
- // grandgrandchild is never rendered, so neither is its max-age of 300
- // present here, despite 300 being the max-age of the redirecting cache
- // item.
- 'max-age' => 1800,
- ],
- '#markup' => 'parent',
- ]);
- }
- /**
- * Tests bubbling of bubbleable metadata added by #pre_render callbacks.
- *
- * @dataProvider providerTestBubblingWithPrerender
- */
- public function testBubblingWithPrerender($test_element) {
- $this->setUpRequest();
- $this->setupMemoryCache();
- // Mock the State service.
- $memory_state = new State(new KeyValueMemoryFactory());
- \Drupal::getContainer()->set('state', $memory_state);
- $this->controllerResolver->expects($this->any())
- ->method('getControllerFromDefinition')
- ->willReturnArgument(0);
- // Simulate the theme system/Twig: a recursive call to Renderer::render(),
- // just like the theme system or a Twig template would have done.
- $this->themeManager->expects($this->any())
- ->method('render')
- ->willReturnCallback(function ($hook, $vars) {
- return $this->renderer->render($vars['foo']);
- });
- // ::bubblingPreRender() verifies that a #pre_render callback for a render
- // array that is cacheable and …
- // - … is cached does NOT get called. (Also mock a render cache item.)
- // - … is not cached DOES get called.
- \Drupal::state()->set('bubbling_nested_pre_render_cached', FALSE);
- \Drupal::state()->set('bubbling_nested_pre_render_uncached', FALSE);
- $this->memoryCache->set('cached_nested', ['#markup' => 'Cached nested!', '#attached' => [], '#cache' => ['contexts' => [], 'tags' => []]]);
- // Simulate the rendering of an entire response (i.e. a root call).
- $output = $this->renderer->renderRoot($test_element);
- // First, assert the render array is of the expected form.
- $this->assertEquals('Cache context!Cache tag!Asset!Placeholder!barquxNested!Cached nested!', trim($output), 'Expected HTML generated.');
- $this->assertEquals(['child.cache_context'], $test_element['#cache']['contexts'], 'Expected cache contexts found.');
- $this->assertEquals(['child:cache_tag'], $test_element['#cache']['tags'], 'Expected cache tags found.');
- $expected_attached = [
- 'drupalSettings' => ['foo' => 'bar'],
- 'placeholders' => [],
- ];
- $this->assertEquals($expected_attached, $test_element['#attached'], 'Expected attachments found.');
- // Second, assert that #pre_render callbacks are only executed if they don't
- // have a render cache hit (and hence a #pre_render callback for a render
- // cached item cannot bubble more metadata).
- $this->assertTrue(\Drupal::state()->get('bubbling_nested_pre_render_uncached'));
- $this->assertFalse(\Drupal::state()->get('bubbling_nested_pre_render_cached'));
- }
- /**
- * Provides two test elements: one without, and one with the theme system.
- *
- * @return array
- */
- public function providerTestBubblingWithPrerender() {
- $data = [];
- // Test element without theme.
- $data[] = [
- [
- 'foo' => [
- '#pre_render' => [__NAMESPACE__ . '\\BubblingTest::bubblingPreRender'],
- ],
- ],
- ];
- // Test element with theme.
- $data[] = [
- [
- '#theme' => 'common_test_render_element',
- 'foo' => [
- '#pre_render' => [__NAMESPACE__ . '\\BubblingTest::bubblingPreRender'],
- ],
- ],
- ];
- return $data;
- }
- /**
- * Tests that an element's cache keys cannot be changed during its rendering.
- */
- public function testOverWriteCacheKeys() {
- $this->setUpRequest();
- $this->setupMemoryCache();
- // Ensure a logic exception
- $data = [
- '#cache' => [
- 'keys' => ['llama', 'bar'],
- ],
- '#pre_render' => [__NAMESPACE__ . '\\BubblingTest::bubblingCacheOverwritePrerender'],
- ];
- $this->expectException(\LogicException::class);
- $this->expectExceptionMessage('Cache keys may not be changed after initial setup. Use the contexts property instead to bubble additional metadata.');
- $this->renderer->renderRoot($data);
- }
- }
- class BubblingTest implements TrustedCallbackInterface {
- /**
- * #pre_render callback for testBubblingWithPrerender().
- */
- public static function bubblingPreRender($elements) {
- $elements += [
- 'child_cache_context' => [
- '#cache' => [
- 'contexts' => ['child.cache_context'],
- ],
- '#markup' => 'Cache context!',
- ],
- 'child_cache_tag' => [
- '#cache' => [
- 'tags' => ['child:cache_tag'],
- ],
- '#markup' => 'Cache tag!',
- ],
- 'child_asset' => [
- '#attached' => [
- 'drupalSettings' => ['foo' => 'bar'],
- ],
- '#markup' => 'Asset!',
- ],
- 'child_placeholder' => [
- '#create_placeholder' => TRUE,
- '#lazy_builder' => [__CLASS__ . '::bubblingPlaceholder', ['bar', 'qux']],
- ],
- 'child_nested_pre_render_uncached' => [
- '#cache' => ['keys' => ['uncached_nested']],
- '#pre_render' => [__CLASS__ . '::bubblingNestedPreRenderUncached'],
- ],
- 'child_nested_pre_render_cached' => [
- '#cache' => ['keys' => ['cached_nested']],
- '#pre_render' => [__CLASS__ . '::bubblingNestedPreRenderCached'],
- ],
- ];
- return $elements;
- }
- /**
- * #pre_render callback for testBubblingWithPrerender().
- */
- public static function bubblingNestedPreRenderUncached($elements) {
- \Drupal::state()->set('bubbling_nested_pre_render_uncached', TRUE);
- $elements['#markup'] = 'Nested!';
- return $elements;
- }
- /**
- * #pre_render callback for testBubblingWithPrerender().
- */
- public static function bubblingNestedPreRenderCached($elements) {
- \Drupal::state()->set('bubbling_nested_pre_render_cached', TRUE);
- return $elements;
- }
- /**
- * #lazy_builder callback for testBubblingWithPrerender().
- */
- public static function bubblingPlaceholder($foo, $baz) {
- return [
- '#markup' => 'Placeholder!' . $foo . $baz,
- ];
- }
- /**
- * #pre_render callback for testOverWriteCacheKeys().
- */
- public static function bubblingCacheOverwritePrerender($elements) {
- // Overwrite the #cache entry with new data.
- $elements['#cache'] = [
- 'keys' => ['llama', 'foo'],
- ];
- $elements['#markup'] = 'Setting cache keys just now!';
- return $elements;
- }
- /**
- * {@inheritdoc}
- */
- public static function trustedCallbacks() {
- return ['bubblingPreRender', 'bubblingNestedPreRenderUncached', 'bubblingNestedPreRenderCached', 'bubblingPlaceholder', 'bubblingCacheOverwritePrerender'];
- }
- }
|