Issue #2543332 by Wim Leers, effulgentsia, Fabianx, Crell, dawehner, borisson_: Auto-placeholdering for #lazy_builder without bubbling
parent
c3e2000be2
commit
b2303acfd6
|
@ -3,6 +3,10 @@ parameters:
|
|||
twig.config: {}
|
||||
renderer.config:
|
||||
required_cache_contexts: ['languages:language_interface', 'theme', 'user.permissions']
|
||||
auto_placeholder_conditions:
|
||||
max-age: 0
|
||||
contexts: ['session', 'user']
|
||||
tags: []
|
||||
factory.keyvalue:
|
||||
default: keyvalue.database
|
||||
factory.keyvalue.expirable:
|
||||
|
|
|
@ -170,6 +170,9 @@ class Renderer implements RendererInterface {
|
|||
// Get the render array for the given placeholder
|
||||
$placeholder_elements = $elements['#attached']['placeholders'][$placeholder];
|
||||
|
||||
// Prevent the render array from being auto-placeholdered again.
|
||||
$placeholder_elements['#create_placeholder'] = FALSE;
|
||||
|
||||
// Render the placeholder into markup.
|
||||
$markup = $this->renderPlain($placeholder_elements);
|
||||
|
||||
|
@ -337,6 +340,10 @@ class Renderer implements RendererInterface {
|
|||
throw new \DomainException(sprintf('When a #lazy_builder callback is specified, no properties can exist; all properties must be generated by the #lazy_builder callback. You specified the following properties: %s.', implode(', ', $unsupported_keys)));
|
||||
}
|
||||
}
|
||||
// Determine whether to do auto-placeholdering.
|
||||
if (isset($elements['#lazy_builder']) && (!isset($elements['#create_placeholder']) || $elements['#create_placeholder'] !== FALSE) && $this->shouldAutomaticallyPlaceholder($elements)) {
|
||||
$elements['#create_placeholder'] = TRUE;
|
||||
}
|
||||
// If instructed to create a placeholder, and a #lazy_builder callback is
|
||||
// present (without such a callback, it would be impossible to replace the
|
||||
// placeholder), replace the current element with a placeholder.
|
||||
|
@ -635,6 +642,37 @@ class Renderer implements RendererInterface {
|
|||
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.
|
||||
*
|
||||
|
|
|
@ -78,6 +78,16 @@ class CommentForm extends ContentEntityForm {
|
|||
$field_definition = $this->entityManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle())[$comment->getFieldName()];
|
||||
$config = $this->config('user.settings');
|
||||
|
||||
// In several places within this function, we vary $form on:
|
||||
// - The current user's permissions.
|
||||
// - Whether the current user is authenticated or anonymous.
|
||||
// - The 'user.settings' configuration.
|
||||
// - The comment field's definition.
|
||||
$form['#cache']['contexts'][] = 'user.permissions';
|
||||
$form['#cache']['contexts'][] = 'user.roles:authenticated';
|
||||
$this->renderer->addCacheableDependency($form, $config);
|
||||
$this->renderer->addCacheableDependency($form, $field_definition->getConfig($entity->bundle()));
|
||||
|
||||
// Use #comment-form as unique jump target, regardless of entity type.
|
||||
$form['#id'] = Html::getUniqueId('comment_form');
|
||||
$form['#theme'] = array('comment_form__' . $entity->getEntityTypeId() . '__' . $entity->bundle() . '__' . $field_name, 'comment_form');
|
||||
|
@ -90,10 +100,6 @@ class CommentForm extends ContentEntityForm {
|
|||
$form['#attributes']['data-user-info-from-browser'] = TRUE;
|
||||
}
|
||||
|
||||
// Vary per role, because we check a permission above and attach an asset
|
||||
// library only for authenticated users.
|
||||
$form['#cache']['contexts'][] = 'user.roles';
|
||||
|
||||
// If not replying to a comment, use our dedicated page callback for new
|
||||
// Comments on entities.
|
||||
if (!$comment->id() && !$comment->hasParentComment()) {
|
||||
|
@ -164,6 +170,7 @@ class CommentForm extends ContentEntityForm {
|
|||
$form['author']['name']['#value'] = $form['author']['name']['#default_value'];
|
||||
$form['author']['name']['#theme'] = 'username';
|
||||
$form['author']['name']['#account'] = $this->currentUser;
|
||||
$form['author']['name']['#cache']['contexts'][] = 'user';
|
||||
}
|
||||
elseif($this->currentUser->isAnonymous()) {
|
||||
$form['author']['name']['#attributes']['data-drupal-default-value'] = $config->get('anonymous');
|
||||
|
@ -210,10 +217,6 @@ class CommentForm extends ContentEntityForm {
|
|||
'#access' => $is_admin,
|
||||
);
|
||||
|
||||
$this->renderer->addCacheableDependency($form, $config);
|
||||
// The form depends on the field definition.
|
||||
$this->renderer->addCacheableDependency($form, $field_definition->getConfig($entity->bundle()));
|
||||
|
||||
return parent::form($form, $form_state, $comment);
|
||||
}
|
||||
|
||||
|
|
|
@ -184,30 +184,23 @@ class CommentDefaultFormatter extends FormatterBase implements ContainerFactoryP
|
|||
// Only show the add comment form if the user has permission.
|
||||
$elements['#cache']['contexts'][] = 'user.roles';
|
||||
if ($this->currentUser->hasPermission('post comments')) {
|
||||
// All users in the "anonymous" role can use the same form: it is fine
|
||||
// for this form to be stored in the render cache.
|
||||
if ($this->currentUser->isAnonymous()) {
|
||||
$comment = $this->storage->create(array(
|
||||
'entity_type' => $entity->getEntityTypeId(),
|
||||
'entity_id' => $entity->id(),
|
||||
'field_name' => $field_name,
|
||||
'comment_type' => $this->getFieldSetting('comment_type'),
|
||||
'pid' => NULL,
|
||||
));
|
||||
$output['comment_form'] = $this->entityFormBuilder->getForm($comment);
|
||||
}
|
||||
// All other users need a user-specific form, which would break the
|
||||
// render cache: hence use a #lazy_builder callback.
|
||||
else {
|
||||
$output['comment_form'] = [
|
||||
'#lazy_builder' => ['comment.lazy_builders:renderForm', [
|
||||
$entity->getEntityTypeId(),
|
||||
$entity->id(),
|
||||
$field_name,
|
||||
$this->getFieldSetting('comment_type'),
|
||||
]],
|
||||
'#create_placeholder' => TRUE,
|
||||
];
|
||||
$output['comment_form'] = [
|
||||
'#lazy_builder' => ['comment.lazy_builders:renderForm', [
|
||||
$entity->getEntityTypeId(),
|
||||
$entity->id(),
|
||||
$field_name,
|
||||
$this->getFieldSetting('comment_type'),
|
||||
]],
|
||||
];
|
||||
|
||||
// @todo Remove this in https://www.drupal.org/node/2543334. Until
|
||||
// then, \Drupal\Core\Render\Renderer::hasPoorCacheability() isn't
|
||||
// integrated with cache context bubbling, so this duplicates the
|
||||
// contexts added by \Drupal\comment\CommentForm::form().
|
||||
$output['comment_form']['#cache']['contexts'][] = 'user.permissions';
|
||||
$output['comment_form']['#cache']['contexts'][] = 'user.roles:authenticated';
|
||||
if ($this->currentUser->isAuthenticated()) {
|
||||
$output['comment_form']['#cache']['contexts'][] = 'user';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,8 +70,7 @@ class CommentDefaultFormatterCacheTagsTest extends EntityUnitTestBase {
|
|||
->getViewBuilder('entity_test')
|
||||
->view($commented_entity);
|
||||
$renderer->renderRoot($build);
|
||||
$cache_context_tags = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($build['#cache']['contexts'])->getCacheTags();
|
||||
$expected_cache_tags = Cache::mergeTags($cache_context_tags, [
|
||||
$expected_cache_tags = [
|
||||
'entity_test_view',
|
||||
'entity_test:' . $commented_entity->id(),
|
||||
'comment_list',
|
||||
|
@ -80,7 +79,7 @@ class CommentDefaultFormatterCacheTagsTest extends EntityUnitTestBase {
|
|||
'config:field.field.entity_test.entity_test.comment',
|
||||
'config:field.storage.comment.comment_body',
|
||||
'config:user.settings',
|
||||
]);
|
||||
];
|
||||
sort($expected_cache_tags);
|
||||
$this->assertEqual($build['#cache']['tags'], $expected_cache_tags);
|
||||
|
||||
|
@ -113,8 +112,7 @@ class CommentDefaultFormatterCacheTagsTest extends EntityUnitTestBase {
|
|||
->getViewBuilder('entity_test')
|
||||
->view($commented_entity);
|
||||
$renderer->renderRoot($build);
|
||||
$cache_context_tags = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($build['#cache']['contexts'])->getCacheTags();
|
||||
$expected_cache_tags = Cache::mergeTags($cache_context_tags, [
|
||||
$expected_cache_tags = [
|
||||
'entity_test_view',
|
||||
'entity_test:' . $commented_entity->id(),
|
||||
'comment_list',
|
||||
|
@ -128,7 +126,7 @@ class CommentDefaultFormatterCacheTagsTest extends EntityUnitTestBase {
|
|||
'config:field.field.entity_test.entity_test.comment',
|
||||
'config:field.storage.comment.comment_body',
|
||||
'config:user.settings',
|
||||
]);
|
||||
];
|
||||
sort($expected_cache_tags);
|
||||
$this->assertEqual($build['#cache']['tags'], $expected_cache_tags);
|
||||
}
|
||||
|
|
|
@ -38,10 +38,9 @@ class CommentTranslationUITest extends ContentTranslationUITestBase {
|
|||
protected $defaultCacheContexts = [
|
||||
'languages:language_interface',
|
||||
'theme',
|
||||
'user.permissions',
|
||||
'timezone',
|
||||
'url.query_args.pagers:0',
|
||||
'user.roles'
|
||||
'user'
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -27,13 +27,12 @@ class NodeTranslationUITest extends ContentTranslationUITestBase {
|
|||
protected $defaultCacheContexts = [
|
||||
'languages:language_interface',
|
||||
'theme',
|
||||
'user.permissions',
|
||||
'route.menu_active_trails:account',
|
||||
'route.menu_active_trails:footer',
|
||||
'route.menu_active_trails:main',
|
||||
'route.menu_active_trails:tools',
|
||||
'timezone',
|
||||
'user.roles'
|
||||
'user'
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -37,13 +37,24 @@ class RendererPlaceholdersTest extends RendererTestBase {
|
|||
* Also, different types:
|
||||
* - A) automatically generated placeholder
|
||||
* - 1) manually triggered (#create_placeholder = TRUE)
|
||||
* - 2) automatically triggered (based on max-age = 0 in its subtree)
|
||||
* - 2) automatically triggered (based on max-age = 0 at the top level)
|
||||
* - 3) automatically triggered (based on high cardinality cache contexts at
|
||||
* the top level)
|
||||
* - 4) automatically triggered (based on high-invalidation frequency cache
|
||||
* tags at the top level)
|
||||
* - 5) automatically triggered (based on max-age = 0 in its subtree, i.e.
|
||||
* via bubbling)
|
||||
* - 6) automatically triggered (based on high cardinality cache contexts in
|
||||
* its subtree, i.e. via bubbling)
|
||||
* - 7) automatically triggered (based on high-invalidation frequency cache
|
||||
* tags in its subtree, i.e. via bubbling)
|
||||
* - B) manually generated placeholder
|
||||
*
|
||||
* So, in total 2*3 = 6 permutations.
|
||||
* So, in total 2*5 = 10 permutations.
|
||||
*
|
||||
* @todo Case A2 is not yet supported by core. So that makes for only 4
|
||||
* permutations currently.
|
||||
* @todo Cases A5, A6 and A7 are not yet supported by core. So that makes for
|
||||
* only 10 permutations currently, instead of 16. That will be done in
|
||||
* https://www.drupal.org/node/2543334
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
|
@ -52,15 +63,11 @@ class RendererPlaceholdersTest extends RendererTestBase {
|
|||
|
||||
$generate_placeholder_markup = function($cache_keys = NULL) use ($args) {
|
||||
$token_render_array = [
|
||||
'#cache' => [],
|
||||
'#lazy_builder' => ['Drupal\Tests\Core\Render\PlaceholdersTest::callback', $args],
|
||||
];
|
||||
if (is_array($cache_keys)) {
|
||||
$token_render_array['#cache']['keys'] = $cache_keys;
|
||||
}
|
||||
else {
|
||||
unset($token_render_array['#cache']);
|
||||
}
|
||||
$token = hash('sha1', serialize($token_render_array));
|
||||
return SafeMarkup::format('<drupal-render-placeholder callback="@callback" arguments="@arguments" token="@token"></drupal-render-placeholder>', [
|
||||
'@callback' => 'Drupal\Tests\Core\Render\PlaceholdersTest::callback',
|
||||
|
@ -69,6 +76,11 @@ class RendererPlaceholdersTest extends RendererTestBase {
|
|||
]);
|
||||
};
|
||||
|
||||
$extract_placeholder_render_array = function ($placeholder_render_array) {
|
||||
return array_intersect_key($placeholder_render_array, ['#lazy_builder' => TRUE, '#cache' => TRUE]);
|
||||
};
|
||||
|
||||
// Note the presence of '#create_placeholder'.
|
||||
$base_element_a1 = [
|
||||
'#attached' => [
|
||||
'drupalSettings' => [
|
||||
|
@ -83,9 +95,55 @@ class RendererPlaceholdersTest extends RendererTestBase {
|
|||
'#lazy_builder' => ['Drupal\Tests\Core\Render\PlaceholdersTest::callback', $args],
|
||||
],
|
||||
];
|
||||
// Note the absence of '#create_placeholder', presence of max-age=0 at the
|
||||
// top level.
|
||||
$base_element_a2 = [
|
||||
// @todo, see docblock
|
||||
'#attached' => [
|
||||
'drupalSettings' => [
|
||||
'foo' => 'bar',
|
||||
],
|
||||
],
|
||||
'placeholder' => [
|
||||
'#cache' => [
|
||||
'contexts' => [],
|
||||
'max-age' => 0,
|
||||
],
|
||||
'#lazy_builder' => ['Drupal\Tests\Core\Render\PlaceholdersTest::callback', $args],
|
||||
],
|
||||
];
|
||||
// Note the absence of '#create_placeholder', presence of high cardinality
|
||||
// cache context at the top level.
|
||||
$base_element_a3 = [
|
||||
'#attached' => [
|
||||
'drupalSettings' => [
|
||||
'foo' => 'bar',
|
||||
],
|
||||
],
|
||||
'placeholder' => [
|
||||
'#cache' => [
|
||||
'contexts' => ['user'],
|
||||
],
|
||||
'#lazy_builder' => ['Drupal\Tests\Core\Render\PlaceholdersTest::callback', $args],
|
||||
],
|
||||
];
|
||||
// Note the absence of '#create_placeholder', presence of high-invalidation
|
||||
// frequency cache tag at the top level.
|
||||
$base_element_a4 = [
|
||||
'#attached' => [
|
||||
'drupalSettings' => [
|
||||
'foo' => 'bar',
|
||||
],
|
||||
],
|
||||
'placeholder' => [
|
||||
'#cache' => [
|
||||
'contexts' => [],
|
||||
'tags' => ['current-temperature'],
|
||||
],
|
||||
'#lazy_builder' => ['Drupal\Tests\Core\Render\PlaceholdersTest::callback', $args],
|
||||
],
|
||||
];
|
||||
// Note the absence of '#create_placeholder', but the presence of
|
||||
// '#attached[placeholders]'.
|
||||
$base_element_b = [
|
||||
'#markup' => $generate_placeholder_markup(),
|
||||
'#attached' => [
|
||||
|
@ -94,7 +152,6 @@ class RendererPlaceholdersTest extends RendererTestBase {
|
|||
],
|
||||
'placeholders' => [
|
||||
$generate_placeholder_markup() => [
|
||||
'#cache' => [],
|
||||
'#lazy_builder' => ['Drupal\Tests\Core\Render\PlaceholdersTest::callback', $args],
|
||||
],
|
||||
],
|
||||
|
@ -109,9 +166,11 @@ class RendererPlaceholdersTest extends RendererTestBase {
|
|||
// - automatically created, but manually triggered (#create_placeholder = TRUE)
|
||||
// - uncacheable
|
||||
$element_without_cache_keys = $base_element_a1;
|
||||
$expected_placeholder_render_array = $extract_placeholder_render_array($base_element_a1['placeholder']);
|
||||
$cases[] = [
|
||||
$element_without_cache_keys,
|
||||
$args,
|
||||
$expected_placeholder_render_array,
|
||||
FALSE,
|
||||
[],
|
||||
];
|
||||
|
@ -121,9 +180,11 @@ class RendererPlaceholdersTest extends RendererTestBase {
|
|||
// - cacheable
|
||||
$element_with_cache_keys = $base_element_a1;
|
||||
$element_with_cache_keys['placeholder']['#cache']['keys'] = $keys;
|
||||
$expected_placeholder_render_array['#cache']['keys'] = $keys;
|
||||
$cases[] = [
|
||||
$element_with_cache_keys,
|
||||
$args,
|
||||
$expected_placeholder_render_array,
|
||||
$keys,
|
||||
[
|
||||
'#markup' => '<p>This is a rendered placeholder!</p>',
|
||||
|
@ -141,31 +202,148 @@ class RendererPlaceholdersTest extends RendererTestBase {
|
|||
];
|
||||
|
||||
// Case three: render array that has a placeholder that is:
|
||||
// - manually created
|
||||
// - automatically created, and automatically triggered due to max-age=0
|
||||
// - uncacheable
|
||||
$x = $base_element_b;
|
||||
unset($x['#attached']['placeholders'][$generate_placeholder_markup()]['#cache']);
|
||||
$element_without_cache_keys = $base_element_a2;
|
||||
$expected_placeholder_render_array = $extract_placeholder_render_array($base_element_a2['placeholder']);
|
||||
$cases[] = [
|
||||
$x,
|
||||
$element_without_cache_keys,
|
||||
$args,
|
||||
$expected_placeholder_render_array,
|
||||
FALSE,
|
||||
[],
|
||||
];
|
||||
|
||||
// Case four: render array that has a placeholder that is:
|
||||
// - automatically created, but automatically triggered due to max-age=0
|
||||
// - cacheable
|
||||
$element_with_cache_keys = $base_element_a2;
|
||||
$element_with_cache_keys['placeholder']['#cache']['keys'] = $keys;
|
||||
$expected_placeholder_render_array['#cache']['keys'] = $keys;
|
||||
$cases[] = [
|
||||
$element_with_cache_keys,
|
||||
$args,
|
||||
$expected_placeholder_render_array,
|
||||
FALSE,
|
||||
[]
|
||||
];
|
||||
|
||||
// Case five: render array that has a placeholder that is:
|
||||
// - automatically created, and automatically triggered due to high
|
||||
// cardinality cache contexts
|
||||
// - uncacheable
|
||||
$element_without_cache_keys = $base_element_a3;
|
||||
$expected_placeholder_render_array = $extract_placeholder_render_array($base_element_a3['placeholder']);
|
||||
$cases[] = [
|
||||
$element_without_cache_keys,
|
||||
$args,
|
||||
$expected_placeholder_render_array,
|
||||
FALSE,
|
||||
[],
|
||||
];
|
||||
|
||||
// Case six: render array that has a placeholder that is:
|
||||
// - automatically created, and automatically triggered due to high
|
||||
// cardinality cache contexts
|
||||
// - cacheable
|
||||
$element_with_cache_keys = $base_element_a3;
|
||||
$element_with_cache_keys['placeholder']['#cache']['keys'] = $keys;
|
||||
$expected_placeholder_render_array['#cache']['keys'] = $keys;
|
||||
// The CID parts here consist of the cache keys plus the 'user' cache
|
||||
// context, which in this unit test is simply the given cache context token,
|
||||
// see \Drupal\Tests\Core\Render\RendererTestBase::setUp().
|
||||
$cid_parts = array_merge($keys, ['user']);
|
||||
$cases[] = [
|
||||
$element_with_cache_keys,
|
||||
$args,
|
||||
$expected_placeholder_render_array,
|
||||
$cid_parts,
|
||||
[
|
||||
'#markup' => '<p>This is a rendered placeholder!</p>',
|
||||
'#attached' => [
|
||||
'drupalSettings' => [
|
||||
'dynamic_animal' => $args[0],
|
||||
],
|
||||
],
|
||||
'#cache' => [
|
||||
'contexts' => ['user'],
|
||||
'tags' => [],
|
||||
'max-age' => Cache::PERMANENT,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// Case seven: render array that has a placeholder that is:
|
||||
// - automatically created, and automatically triggered due to high
|
||||
// invalidation frequency cache tags
|
||||
// - uncacheable
|
||||
$element_without_cache_keys = $base_element_a4;
|
||||
$expected_placeholder_render_array = $extract_placeholder_render_array($base_element_a4['placeholder']);
|
||||
$cases[] = [
|
||||
$element_without_cache_keys,
|
||||
$args,
|
||||
$expected_placeholder_render_array,
|
||||
FALSE,
|
||||
[],
|
||||
];
|
||||
|
||||
// Case eight: render array that has a placeholder that is:
|
||||
// - automatically created, and automatically triggered due to high
|
||||
// invalidation frequency cache tags
|
||||
// - cacheable
|
||||
$element_with_cache_keys = $base_element_a4;
|
||||
$element_with_cache_keys['placeholder']['#cache']['keys'] = $keys;
|
||||
$expected_placeholder_render_array['#cache']['keys'] = $keys;
|
||||
$cases[] = [
|
||||
$element_with_cache_keys,
|
||||
$args,
|
||||
$expected_placeholder_render_array,
|
||||
$keys,
|
||||
[
|
||||
'#markup' => '<p>This is a rendered placeholder!</p>',
|
||||
'#attached' => [
|
||||
'drupalSettings' => [
|
||||
'dynamic_animal' => $args[0],
|
||||
],
|
||||
],
|
||||
'#cache' => [
|
||||
'contexts' => [],
|
||||
'tags' => ['current-temperature'],
|
||||
'max-age' => Cache::PERMANENT,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// Case nine: render array that has a placeholder that is:
|
||||
// - manually created
|
||||
// - uncacheable
|
||||
$x = $base_element_b;
|
||||
$expected_placeholder_render_array = $x['#attached']['placeholders'][$generate_placeholder_markup()];
|
||||
unset($x['#attached']['placeholders'][$generate_placeholder_markup()]['#cache']);
|
||||
$cases[] = [
|
||||
$x,
|
||||
$args,
|
||||
$expected_placeholder_render_array,
|
||||
FALSE,
|
||||
[],
|
||||
];
|
||||
|
||||
// Case ten: render array that has a placeholder that is:
|
||||
// - manually created
|
||||
// - cacheable
|
||||
$x = $base_element_b;
|
||||
$x['#markup'] = $generate_placeholder_markup($keys);
|
||||
$x['#markup'] = $placeholder_markup = $generate_placeholder_markup($keys);
|
||||
$x['#attached']['placeholders'] = [
|
||||
$generate_placeholder_markup($keys) => [
|
||||
$placeholder_markup => [
|
||||
'#lazy_builder' => ['Drupal\Tests\Core\Render\PlaceholdersTest::callback', $args],
|
||||
'#cache' => ['keys' => $keys],
|
||||
],
|
||||
];
|
||||
$expected_placeholder_render_array = $x['#attached']['placeholders'][$placeholder_markup];
|
||||
$cases[] = [
|
||||
$x,
|
||||
$args,
|
||||
$expected_placeholder_render_array,
|
||||
$keys,
|
||||
[
|
||||
'#markup' => '<p>This is a rendered placeholder!</p>',
|
||||
|
@ -224,8 +402,8 @@ class RendererPlaceholdersTest extends RendererTestBase {
|
|||
*
|
||||
* @dataProvider providerPlaceholders
|
||||
*/
|
||||
public function testUncacheableParent($element, $args, $placeholder_cid_keys, array $placeholder_expected_render_cache_array) {
|
||||
if ($placeholder_cid_keys) {
|
||||
public function testUncacheableParent($element, $args, array $expected_placeholder_render_array, $placeholder_cid_parts, array $placeholder_expected_render_cache_array) {
|
||||
if ($placeholder_cid_parts) {
|
||||
$this->setupMemoryCache();
|
||||
}
|
||||
else {
|
||||
|
@ -244,7 +422,7 @@ class RendererPlaceholdersTest extends RendererTestBase {
|
|||
'dynamic_animal' => $args[0],
|
||||
];
|
||||
$this->assertSame($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the one added by the placeholder #lazy_builder callback exist.');
|
||||
$this->assertPlaceholderRenderCache($placeholder_cid_keys, $placeholder_expected_render_cache_array);
|
||||
$this->assertPlaceholderRenderCache($placeholder_cid_parts, $placeholder_expected_render_cache_array);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -256,25 +434,12 @@ class RendererPlaceholdersTest extends RendererTestBase {
|
|||
*
|
||||
* @dataProvider providerPlaceholders
|
||||
*/
|
||||
public function testCacheableParent($test_element, $args, $placeholder_cid_keys, array $placeholder_expected_render_cache_array) {
|
||||
public function testCacheableParent($test_element, $args, array $expected_placeholder_render_array, $placeholder_cid_parts, array $placeholder_expected_render_cache_array) {
|
||||
$element = $test_element;
|
||||
$this->setupMemoryCache();
|
||||
|
||||
$this->setUpRequest('GET');
|
||||
|
||||
// Generate the expected placeholder render array, so that we can generate
|
||||
// the expected placeholder markup.
|
||||
$expected_placeholder_render_array = [];
|
||||
// When there was a child element that created a placeholder, the Renderer
|
||||
// automatically initializes #cache[contexts].
|
||||
if (Element::children($test_element)) {
|
||||
$expected_placeholder_render_array['#cache']['contexts'] = [];
|
||||
}
|
||||
// When the placeholder itself is cacheable, its cache keys are present.
|
||||
if ($placeholder_cid_keys) {
|
||||
$expected_placeholder_render_array['#cache']['keys'] = $placeholder_cid_keys;
|
||||
}
|
||||
$expected_placeholder_render_array['#lazy_builder'] = ['Drupal\Tests\Core\Render\PlaceholdersTest::callback', $args];
|
||||
$token = hash('sha1', serialize($expected_placeholder_render_array));
|
||||
$expected_placeholder_markup = '<drupal-render-placeholder callback="Drupal\Tests\Core\Render\PlaceholdersTest::callback" arguments="0=' . $args[0] . '" token="' . $token . '"></drupal-render-placeholder>';
|
||||
$this->assertSame($expected_placeholder_markup, Html::normalize($expected_placeholder_markup), 'Placeholder unaltered by Html::normalize() which is used by FilterHtmlCorrector.');
|
||||
|
@ -291,7 +456,7 @@ class RendererPlaceholdersTest extends RendererTestBase {
|
|||
'dynamic_animal' => $args[0],
|
||||
];
|
||||
$this->assertSame($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the one added by the placeholder #lazy_builder callback exist.');
|
||||
$this->assertPlaceholderRenderCache($placeholder_cid_keys, $placeholder_expected_render_cache_array);
|
||||
$this->assertPlaceholderRenderCache($placeholder_cid_parts, $placeholder_expected_render_cache_array);
|
||||
|
||||
// GET request: validate cached data.
|
||||
$cached_element = $this->memoryCache->get('placeholder_test_GET')->data;
|
||||
|
|
|
@ -89,6 +89,11 @@ class RendererTestBase extends UnitTestCase {
|
|||
'languages:language_interface',
|
||||
'theme',
|
||||
],
|
||||
'auto_placeholder_conditions' => [
|
||||
'max-age' => 0,
|
||||
'contexts' => ['session', 'user'],
|
||||
'tags' => ['current-temperature'],
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -84,6 +84,37 @@ parameters:
|
|||
#
|
||||
# @default ['languages:language_interface', 'theme', 'user.permissions']
|
||||
required_cache_contexts: ['languages:language_interface', 'theme', 'user.permissions']
|
||||
# Renderer automatic placeholdering conditions:
|
||||
#
|
||||
# Drupal allows portions of the page to be automatically deferred when
|
||||
# rendering to improve cache performance. That is especially helpful for
|
||||
# cache contexts that vary widely, such as the active user. On some sites
|
||||
# those may be different, however, such as sites with only a handful of
|
||||
# users. If you know what the high-cardinality cache contexts are for your
|
||||
# site, specify those here. If you're not sure, the defaults are fairly safe
|
||||
# in general.
|
||||
#
|
||||
# For more information about rendering optimizations see
|
||||
# https://www.drupal.org/developing/api/8/render/arrays/cacheability#optimizing
|
||||
auto_placeholder_conditions:
|
||||
# Max-age at or below which caching is not considered worthwhile.
|
||||
#
|
||||
# Disable by setting to -1.
|
||||
#
|
||||
# @default 0
|
||||
max-age: 0
|
||||
# Cache contexts with a high cardinality.
|
||||
#
|
||||
# Disable by setting to [].
|
||||
#
|
||||
# @default ['session', 'user']
|
||||
contexts: ['session', 'user']
|
||||
# Tags with a high invalidation frequency.
|
||||
#
|
||||
# Disable by setting to [].
|
||||
#
|
||||
# @default []
|
||||
tags: []
|
||||
factory.keyvalue:
|
||||
{}
|
||||
# Default key/value storage service to use.
|
||||
|
|
Loading…
Reference in New Issue