Commit 7d6f560d authored by catch's avatar catch

Issue #2543334 by Wim Leers, lauriii, Fabianx: Auto-placeholdering for...

Issue #2543334 by Wim Leers, lauriii, Fabianx: Auto-placeholdering for #lazy_builder with bubbling of contexts and tags
parent 5c943b5c
......@@ -1491,12 +1491,16 @@ services:
tags:
- { name: mime_type_guesser }
lazy: true
render_placeholder_generator:
class: Drupal\Core\Render\PlaceholderGenerator
arguments: ['%renderer.config%']
public: false
render_cache:
class: Drupal\Core\Render\RenderCache
arguments: ['@request_stack', '@cache_factory', '@cache_contexts_manager']
class: Drupal\Core\Render\PlaceholderingRenderCache
arguments: ['@request_stack', '@cache_factory', '@cache_contexts_manager', '@render_placeholder_generator']
renderer:
class: Drupal\Core\Render\Renderer
arguments: ['@controller_resolver', '@theme.manager', '@plugin.manager.element_info', '@render_cache', '@request_stack', '%renderer.config%']
arguments: ['@controller_resolver', '@theme.manager', '@plugin.manager.element_info', '@render_placeholder_generator', '@render_cache', '@request_stack', '%renderer.config%']
early_rendering_controller_wrapper_subscriber:
class: Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber
arguments: ['@controller_resolver', '@renderer']
......
<?php
/**
* @file
* Contains \Drupal\Core\Render\Placeholder.
*/
namespace Drupal\Core\Render;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Cache\Cache;
/**
* Turns a render array into a placeholder.
*/
class PlaceholderGenerator implements PlaceholderGeneratorInterface {
/**
* The renderer configuration array.
*
* @var array
*/
protected $rendererConfig;
/**
* Constructs a new Placeholder service.
*
* @param array $renderer_config
* The renderer configuration array.
*/
public function __construct(array $renderer_config) {
$this->rendererConfig = $renderer_config;
}
/**
* {@inheritdoc}
*/
public function canCreatePlaceholder(array $element) {
return
// If generated by a #lazy_builder callback, placeholdering is possible.
isset($element['#lazy_builder'])
&&
// If #create_placeholder === FALSE, placeholdering is disallowed.
(!isset($element['#create_placeholder']) || $element['#create_placeholder'] !== FALSE);
}
/**
* {@inheritdoc}
*/
public function shouldAutomaticallyPlaceholder(array $element) {
$conditions = $this->rendererConfig['auto_placeholder_conditions'];
// Auto-placeholder if max-age is at or below the configured threshold.
if (isset($element['#cache']['max-age']) && $element['#cache']['max-age'] !== Cache::PERMANENT && $element['#cache']['max-age'] <= $conditions['max-age']) {
return TRUE;
}
// Auto-placeholder if a high-cardinality cache context is set.
if (isset($element['#cache']['contexts']) && array_intersect($element['#cache']['contexts'], $conditions['contexts'])) {
return TRUE;
}
// Auto-placeholder if a high-invalidation frequency cache tag is set.
if (isset($element['#cache']['tags']) && array_intersect($element['#cache']['tags'], $conditions['tags'])) {
return TRUE;
}
return FALSE;
}
/**
* {@inheritdoc}
*/
public function createPlaceholder(array $element) {
$placeholder_render_array = array_intersect_key($element, [
// Placeholders are replaced with markup by executing the associated
// #lazy_builder callback, which generates a render array, and which the
// Renderer will render and replace the placeholder with.
'#lazy_builder' => TRUE,
// The cacheability metadata for the placeholder. The rendered result of
// the placeholder may itself be cached, if [#cache][keys] are specified.
'#cache' => TRUE,
]);
// Generate placeholder markup. Note that the only requirement is that this
// is unique markup that isn't easily guessable. The #lazy_builder callback
// and its arguments are put in the placeholder markup solely to simplify<<<
// debugging.
$callback = $placeholder_render_array['#lazy_builder'][0];
$arguments = UrlHelper::buildQuery($placeholder_render_array['#lazy_builder'][1]);
$token = hash('crc32b', serialize($placeholder_render_array));
$placeholder_markup = '<drupal-render-placeholder callback="' . $callback . '" arguments="' . $arguments . '" token="' . $token . '"></drupal-render-placeholder>';
// Build the placeholder element to return.
$placeholder_element = [];
$placeholder_element['#markup'] = SafeString::create($placeholder_markup);
$placeholder_element['#attached']['placeholders'][$placeholder_markup] = $placeholder_render_array;
return $placeholder_element;
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Render\PlaceholderGeneratorInterface.
*/
namespace Drupal\Core\Render;
/**
* Defines an interface for turning a render array into a placeholder.
*
* This encapsulates logic related to generating placeholders.
*
* Makes it possible to determine whether a render array can be placeholdered
* (it can be reconstructed independently of the request context), whether a
* render array should be placeholdered (its cacheability meets the conditions),
* and to create a placeholder.
*
* @see \Drupal\Core\Render\RendererInterface
*/
interface PlaceholderGeneratorInterface {
/**
* Analyzes whether the given render array can be placeholdered.
*
* @param array $element
* A render array. Its #lazy_builder and #create_placeholder properties are
* analyzed.
*
* @return bool
*/
public function canCreatePlaceholder(array $element);
/**
* Whether the given render array should be automatically placeholdered.
*
* @param array $element
* The render array whose cacheability to analyze.
*
* @return bool
* Whether the given render array's cacheability meets the placeholdering
* conditions.
*/
public function shouldAutomaticallyPlaceholder(array $element);
/**
* Turns the given element into a placeholder.
*
* Placeholdering allows us to avoid "poor cacheability contamination": this
* maps the current render array to one that only has #markup and #attached,
* and #attached contains a placeholder with this element's prior cacheability
* metadata. In other words: this placeholder is perfectly cacheable, the
* placeholder replacement logic effectively cordons off poor cacheability.
*
* @param array $element
* The render array to create a placeholder for.
*
* @return array
* Render array with placeholder markup and the attached placeholder
* replacement metadata.
*/
public function createPlaceholder(array $element);
}
<?php
/**
* @file
* Contains \Drupal\Core\Render\PlaceholderingRenderCache.
*/
namespace Drupal\Core\Render;
use Drupal\Core\Cache\CacheFactoryInterface;
use Drupal\Core\Cache\Context\CacheContextsManager;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Adds automatic placeholdering to the RenderCache.
*
* This automatic placeholdering is performed to ensure the containing elements
* and overarching response are as cacheable as possible. Elements whose subtree
* bubble either max-age=0 or high-cardinality cache contexts (such as 'user'
* and 'session') are considered poorly cacheable.
*
* @see sites/default/default.services.yml
*
* Automatic placeholdering is performed only on elements whose subtree was
* generated using a #lazy_builder callback and whose bubbled cacheability meets
* the auto-placeholdering conditions as configured in the renderer.config
* container parameter.
*
* This RenderCache implementation automatically replaces an element with a
* placeholder:
* - on render cache hit, i.e. ::get()
* - on render cache miss, i.e. ::set() (in subsequent requests, it will be a
* cache hit)
*
* In either case, the render cache is guaranteed to contain the to-be-rendered
* placeholder, so replacing (rendering) the placeholder will be very fast.
*
* Finally, in case the render cache item disappears between the time it is
* decided to automatically placeholder the element and the time where the
* placeholder is replaced (rendered), that is guaranteed to not be problematic.
* Because this only automatically placeholders elements that have a
* #lazy_builder callback set, which means that in the worst case, it will need
* to be re-rendered.
*/
class PlaceholderingRenderCache extends RenderCache {
/**
* The placeholder generator.
*
* @var \Drupal\Core\Render\PlaceholderGeneratorInterface
*/
protected $placeholderGenerator;
/**
* Stores rendered results for automatically placeholdered elements.
*
* This allows us to avoid talking to the cache twice per auto-placeholdered
* element, or in case of an uncacheable element, to render it twice.
*
* Scenario A. The double cache read would happen because:
* 1. when rendering, cache read, but auto-placeholdered
* 2. when rendering placeholders, again cache read
*
* Scenario B. The cache write plus read would happen because:
* 1. when rendering, cache write, but auto-placeholdered
* 2. when rendering placeholders, cache read
*
* Scenario C. The double rendering for an uncacheable element would happen because:
* 1. when rendering, not cacheable, but auto-placeholdered
* 2. when rendering placeholders, rendered again
*
* In all three scenarios, this static cache avoids the second step, thus
* avoiding expensive work.
*
* @var array
*/
protected $placeholderResultsCache = [];
/**
* Constructs a new PlaceholderingRenderCache object.
*
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
* @param \Drupal\Core\Cache\CacheFactoryInterface $cache_factory
* The cache factory.
* @param \Drupal\Core\Cache\Context\CacheContextsManager $cache_contexts_manager
* The cache contexts manager.
* @param \Drupal\Core\Render\PlaceholderGeneratorInterface $placeholder_generator
* The placeholder generator.
*/
public function __construct(RequestStack $request_stack, CacheFactoryInterface $cache_factory, CacheContextsManager $cache_contexts_manager, PlaceholderGeneratorInterface $placeholder_generator) {
parent::__construct($request_stack, $cache_factory, $cache_contexts_manager);
$this->placeholderGenerator = $placeholder_generator;
}
/**
* {@inheritdoc}
*/
public function get(array $elements) {
// When rendering placeholders, special case auto-placeholdered elements:
// avoid retrieving them from cache again, or rendering them again.
if (isset($elements['#create_placeholder']) && $elements['#create_placeholder'] === FALSE) {
$cached_placeholder_result = $this->getFromPlaceholderResultsCache($elements);
if ($cached_placeholder_result !== FALSE) {
return $cached_placeholder_result;
}
}
$cached_element = parent::get($elements);
if ($cached_element === FALSE) {
return FALSE;
}
else {
if ($this->placeholderGenerator->canCreatePlaceholder($elements) && $this->placeholderGenerator->shouldAutomaticallyPlaceholder($cached_element)) {
return $this->createPlaceholderAndRemember($cached_element, $elements);
}
return $cached_element;
}
}
/**
* {@inheritdoc}
*/
public function set(array &$elements, array $pre_bubbling_elements) {
$result = parent::set($elements, $pre_bubbling_elements);
if ($this->placeholderGenerator->canCreatePlaceholder($pre_bubbling_elements) && $this->placeholderGenerator->shouldAutomaticallyPlaceholder($elements)) {
// Overwrite $elements with a placeholder. The Renderer (which called this
// method) will update the context with the bubbleable metadata of the
// overwritten $elements.
$elements = $this->createPlaceholderAndRemember($this->getCacheableRenderArray($elements), $pre_bubbling_elements);
}
return $result;
}
/**
* Create a placeholder for a renderable array and remember in a static cache.
*
* @param array $rendered_elements
* A fully rendered renderable array.
* @param array $pre_bubbling_elements
* A renderable array corresponding to the state (in particular, the
* cacheability metadata) of $rendered_elements prior to the beginning of
* its rendering process, and therefore before any bubbling of child
* information has taken place. Only the #cache property is used by this
* function, so the caller may omit all other properties and children from
* this array.
*
* @return array
* Renderable array with placeholder markup and the attached placeholder
* replacement metadata.
*/
protected function createPlaceholderAndRemember(array $rendered_elements, array $pre_bubbling_elements) {
$placeholder_element = $this->placeholderGenerator->createPlaceholder($pre_bubbling_elements);
// Remember the result for this placeholder to avoid double work.
$placeholder = (string) $placeholder_element['#markup'];
$this->placeholderResultsCache[$placeholder] = $rendered_elements;
return $placeholder_element;
}
/**
* Retrieves an auto-placeholdered renderable array from the static cache.
*
* @param array $elements
* A renderable array.
*
* @return array|false
* A renderable array, with the original element and all its children pre-
* rendered, or FALSE if no cached copy of the element is available.
*/
protected function getFromPlaceholderResultsCache(array $elements) {
$placeholder_element = $this->placeholderGenerator->createPlaceholder($elements);
$placeholder = (string) $placeholder_element['#markup'];
if (isset($this->placeholderResultsCache[$placeholder])) {
return $this->placeholderResultsCache[$placeholder];
}
return FALSE;
}
}
......@@ -9,13 +9,11 @@
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Controller\ControllerResolverInterface;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Theme\ThemeManagerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
......@@ -45,6 +43,13 @@ class Renderer implements RendererInterface {
*/
protected $elementInfo;
/**
* The placeholder generator.
*
* @var \Drupal\Core\Render\PlaceholderGeneratorInterface
*/
protected $placeholderGenerator;
/**
* The render cache service.
*
......@@ -99,6 +104,8 @@ class Renderer implements RendererInterface {
* The theme manager.
* @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info
* The element info.
* @param \Drupal\Core\Render\PlaceholderGeneratorInterface $placeholder_generator
* The placeholder generator.
* @param \Drupal\Core\Render\RenderCacheInterface $render_cache
* The render cache service.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
......@@ -106,10 +113,11 @@ class Renderer implements RendererInterface {
* @param array $renderer_config
* The renderer configuration array.
*/
public function __construct(ControllerResolverInterface $controller_resolver, ThemeManagerInterface $theme, ElementInfoManagerInterface $element_info, RenderCacheInterface $render_cache, RequestStack $request_stack, array $renderer_config) {
public function __construct(ControllerResolverInterface $controller_resolver, ThemeManagerInterface $theme, ElementInfoManagerInterface $element_info, PlaceholderGeneratorInterface $placeholder_generator, RenderCacheInterface $render_cache, RequestStack $request_stack, array $renderer_config) {
$this->controllerResolver = $controller_resolver;
$this->theme = $theme;
$this->elementInfo = $element_info;
$this->placeholderGenerator = $placeholder_generator;
$this->renderCache = $render_cache;
$this->rendererConfig = $renderer_config;
$this->requestStack = $request_stack;
......@@ -295,12 +303,15 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
return $elements['#markup'];
}
}
// Two-tier caching: track pre-bubbling elements' #cache for later
// comparison.
// Two-tier caching: track pre-bubbling elements' #cache, #lazy_builder and
// #create_placeholder for later comparison.
// @see \Drupal\Core\Render\RenderCacheInterface::get()
// @see \Drupal\Core\Render\RenderCacheInterface::set()
$pre_bubbling_elements = [];
$pre_bubbling_elements['#cache'] = isset($elements['#cache']) ? $elements['#cache'] : [];
$pre_bubbling_elements = array_intersect_key($elements, [
'#cache' => TRUE,
'#lazy_builder' => TRUE,
'#create_placeholder' => TRUE,
]);
// If the default values for this element have not been loaded yet, populate
// them.
......@@ -342,7 +353,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
}
}
// Determine whether to do auto-placeholdering.
if (isset($elements['#lazy_builder']) && (!isset($elements['#create_placeholder']) || $elements['#create_placeholder'] !== FALSE) && $this->shouldAutomaticallyPlaceholder($elements)) {
if ($this->placeholderGenerator->canCreatePlaceholder($elements) && $this->placeholderGenerator->shouldAutomaticallyPlaceholder($elements)) {
$elements['#create_placeholder'] = TRUE;
}
// If instructed to create a placeholder, and a #lazy_builder callback is
......@@ -352,7 +363,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
if (!isset($elements['#lazy_builder'])) {
throw new \LogicException('When #create_placeholder is set, a #lazy_builder callback must be present as well.');
}
$elements = $this->createPlaceholder($elements);
$elements = $this->placeholderGenerator->createPlaceholder($elements);
}
// Build the element if it is still empty.
if (isset($elements['#lazy_builder'])) {
......@@ -531,6 +542,12 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
throw new \LogicException('Cache keys may not be changed after initial setup. Use the contexts property instead to bubble additional metadata.');
}
$this->renderCache->set($elements, $pre_bubbling_elements);
// Update the render context; the render cache implementation may update
// the element, and it may have different bubbleable metadata now.
// @see \Drupal\Core\Render\PlaceholderingRenderCache::set()
$context->pop();
$context->push(new BubbleableMetadata());
$context->update($elements);
}
// Only when we're in a root (non-recursive) Renderer::render() call,
......@@ -643,80 +660,6 @@ protected function replacePlaceholders(array &$elements) {
return TRUE;
}
/**
* Whether the given render array should be automatically placeholdered.
*
* @param array $element
* The render array whose cacheability to analyze.
*
* @return bool
* Whether the given render array's cacheability meets the placeholdering
* conditions.
*/
protected function shouldAutomaticallyPlaceholder(array $element) {
$conditions = $this->rendererConfig['auto_placeholder_conditions'];
// Auto-placeholder if max-age is at or below the configured threshold.
if (isset($element['#cache']['max-age']) && $element['#cache']['max-age'] !== Cache::PERMANENT && $element['#cache']['max-age'] <= $conditions['max-age']) {
return TRUE;
}
// Auto-placeholder if a high-cardinality cache context is set.
if (isset($element['#cache']['contexts']) && array_intersect($element['#cache']['contexts'], $conditions['contexts'])) {
return TRUE;
}
// Auto-placeholder if a high-invalidation frequency cache tag is set.
if (isset($element['#cache']['tags']) && array_intersect($element['#cache']['tags'], $conditions['tags'])) {
return TRUE;
}
return FALSE;
}
/**
* Turns this element into a placeholder.
*
* Placeholdering allows us to avoid "poor cacheability contamination": this
* maps the current render array to one that only has #markup and #attached,
* and #attached contains a placeholder with this element's prior cacheability
* metadata. In other words: this placeholder is perfectly cacheable, the
* placeholder replacement logic effectively cordons off poor cacheability.
*
* @param array $element
* The render array to create a placeholder for.
*
* @return array
* Render array with placeholder markup and the attached placeholder
* replacement metadata.
*/
protected function createPlaceholder(array $element) {
$placeholder_render_array = array_intersect_key($element, [
// Placeholders are replaced with markup by executing the associated
// #lazy_builder callback, which generates a render array, and which the
// Renderer will render and replace the placeholder with.
'#lazy_builder' => TRUE,
// The cacheability metadata for the placeholder. The rendered result of
// the placeholder may itself be cached, if [#cache][keys] are specified.
'#cache' => TRUE,
]);
// Generate placeholder markup. Note that the only requirement is that this
// is unique markup that isn't easily guessable. The #lazy_builder callback
// and its arguments are put in the placeholder markup solely to simplify<<<
// debugging.
$callback = $placeholder_render_array['#lazy_builder'][0];
$arguments = UrlHelper::buildQuery($placeholder_render_array['#lazy_builder'][1]);
$token = hash('crc32b', serialize($placeholder_render_array));
$placeholder_markup = '<drupal-render-placeholder callback="' . $callback . '" arguments="' . $arguments . '" token="' . $token . '"></drupal-render-placeholder>';
// Build the placeholder element to return.
$placeholder_element = [];
$placeholder_element['#markup'] = SafeString::create($placeholder_markup);
$placeholder_element['#attached']['placeholders'][$placeholder_markup] = $placeholder_render_array;
return $placeholder_element;
}
/**
* {@inheritdoc}
*/
......
......@@ -102,6 +102,7 @@ function testPageCacheTags() {
'config:block.block.bartik_local_tasks',
'node_view',
'node:' . $node_1->id(),
'user:0',
'user:' . $author_1->id(),
'config:filter.format.basic_html',
'config:system.menu.account',
......@@ -152,6 +153,7 @@ function testPageCacheTags() {
// FinishResponseSubscriber adds this cache tag to responses that have the
// 'user.permissions' cache context for anonymous users.
'config:user.role.anonymous',
'user:0',
));
}
......
......@@ -90,6 +90,12 @@ function testTrackerAll() {
// Assert cache tags for the action/tabs blocks, visible node, and node list
// cache tag.
$expected_tags = Cache::mergeTags($published->getCacheTags(), $published->getOwner()->getCacheTags());
// Because the 'user.permissions' cache context is being optimized away.
$role_tags = [];
foreach ($this->user->getRoles() as $rid) {
$role_tags[] = "config:user.role.$rid";
}
$expected_tags = Cache::mergeTags($expected_tags, $role_tags);
$block_tags = [
'block_view',
'config:block.block.page_actions_block',
......@@ -170,6 +176,12 @@ function testTrackerUser() {
$expected_tags = Cache::mergeTags($my_published->getCacheTags(), $my_published->getOwner()->getCacheTags());
$expected_tags = Cache::mergeTags($expected_tags, $other_published_my_comment->getCacheTags());
$expected_tags = Cache::mergeTags($expected_tags, $other_published_my_comment->getOwner()->getCacheTags());
// Because the 'user.permissions' cache context is being optimized away.
$role_tags = [];
foreach ($this->user->getRoles() as $rid) {
$role_tags[] = "config:user.role.$rid";
}
$expected_tags = Cache::mergeTags($expected_tags, $role_tags);
$block_tags = [
'block_view',
'config:block.block.page_actions_block',
......@@ -201,11 +213,13 @@ function testTrackerUser() {
// Test escaping of title on user's tracker tab.
\Drupal::service('module_installer')->install(['user_hooks_test']);
Cache::invalidateTags(['rendered']);
\Drupal::state()->set('user_hooks_test_user_format_name_alter', TRUE);
$this->drupalGet('user/' . $this->user->id() . '/activity');
$this->assertEscaped('<em>' . $this->user->id() . '</em>');
\Drupal::state()->set('user_hooks_test_user_format_name_alter_safe', TRUE);
Cache::invalidateTags(['rendered']);
$this->drupalGet('user/' . $this->user->id() . '/activity');
$this->assertNoEscaped('<em>' . $this->user->id() . '</em>');
$this->assertRaw('<em>' . $this->user->id() . '</em>');
......
......@@ -97,6 +97,7 @@ protected function setUp() {
$this->getMock('\Drupal\Core\Controller\ControllerResolverInterface'),
$this->getMock('\Drupal\Core\Theme\ThemeManagerInterface'),
$element_info_manager,
$this->getMock('\Drupal\Core\Render\PlaceholderGeneratorInterface'),
$this->getMock('\Drupal\Core\Render\RenderCacheInterface'),
$request_stack,
[
......
......@@ -12,7 +12,8 @@
use Drupal\Core\Cache\Context\ContextCacheKeys;
use Drupal\Core\Cache\MemoryBackend;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\RenderCache;
use Drupal\Core\Render\PlaceholderGenerator;
use Drupal\Core\Render\PlaceholderingRenderCache;
use Drupal\Core\Render\Renderer;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\DependencyInjection\ContainerBuilder;
......@@ -34,10 +35,17 @@ class RendererTestBase extends UnitTestCase {
/**
* The tested render cache.
*
* @var \Drupal\Core\Render\RenderCache
* @var \Drupal\Core\Render\PlaceholderingRenderCache
*/
protected $renderCache;
/**
* The tested placeholder generator.
*
* @var \Drupal\Core\Render\PlaceholderGenerator
*/
protected $placeholderGenerator;
/**
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
......@@ -158,8 +166,9 @@ protected function setUp() {
}
return new ContextCacheKeys($keys, new CacheableMetadata());
});
$this->renderCache = new RenderCache($this->requestStack, $this->cacheFactory, $this->cacheContextsManager);
$this->renderer = new Renderer($this->controllerResolver, $this->themeManager, $this->elementInfo, $this->renderCache, $this->requestStack, $this->rendererConfig);
$this->placeholderGenerator = new PlaceholderGenerator($this->rendererConfig);
$this->renderCache = new PlaceholderingRenderCache($this->requestStack, $this->cacheFactory, $this->cacheContextsManager, $this->placeholderGenerator);
$this->renderer = new Renderer($this->controllerResolver, $this->themeManager, $this->elementInfo, $this->placeholderGenerator, $this->renderCache, $this->requestStack, $this->rendererConfig);
$container = new ContainerBuilder();
$container->set('cache_contexts_manager', $this->cacheContextsManager);
......@@ -269,4 +278,34 @@ public static function callback($animal, $use_animal_as_array_key = FALSE) {
];
}
/**
* #lazy_builder callback; attaches setting, generates markup, user-specific.
*
* @param string $animal
* An animal.
*
* @return array
* A renderable array.
*/
public static function callbackPerUser($animal) {
$build = static::callback($animal);
$build['#cache']['contexts'][] = 'user';
return $build;
}
/**
* #lazy_builder callback; attaches setting, generates markup, cache tag.
*
* @param string $animal
* An animal.
*
* @return array
* A renderable array.
*/
public static function callbackTagCurrentTemperature($animal) {
$build = static::callback($animal);
$build['#cache']['tags'][] = 'current-temperature';
return $build;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment