Issue #2382667 by Wim Leers, Berdir: #post_render_callback's that result from other #post_render_calback are not processed

8.0.x
Nathaniel Catchpole 2014-11-28 15:06:51 +00:00
parent 07a6a686a1
commit f6e69bc810
3 changed files with 75 additions and 27 deletions

View File

@ -323,12 +323,14 @@ class Renderer implements RendererInterface {
// further and create and render new child elements, so provide a fresh
// stack frame to collect those additions, merge them back to the element,
// and then update the current frame to match the modified element state.
self::$stack->push(new RenderStackFrame());
$this->processPostRenderCache($elements);
$post_render_additions = self::$stack->pop();
$elements['#cache']['tags'] = Cache::mergeTags($elements['#cache']['tags'], $post_render_additions->tags);
$elements['#attached'] = drupal_merge_attached($elements['#attached'], $post_render_additions->attached);
$elements['#post_render_cache'] = NestedArray::mergeDeep($elements['#post_render_cache'], $post_render_additions->postRenderCache);
do {
self::$stack->push(new RenderStackFrame());
$this->processPostRenderCache($elements);
$post_render_additions = self::$stack->pop();
$elements['#cache']['tags'] = Cache::mergeTags($elements['#cache']['tags'], $post_render_additions->tags);
$elements['#attached'] = drupal_merge_attached($elements['#attached'], $post_render_additions->attached);
$elements['#post_render_cache'] = $post_render_additions->postRenderCache;
} while (!empty($elements['#post_render_cache']));
if (self::$stack->count() !== 1) {
throw new \LogicException('A stray drupal_render() invocation with $is_root_call = TRUE is causing bubbling of attached assets to break.');
}
@ -400,6 +402,7 @@ class Renderer implements RendererInterface {
* #post_render_cache callbacks may modify:
* - #markup: to replace placeholders
* - #attached: to add libraries or JavaScript settings
* - #post_render_cache: to execute additional #post_render_cache callbacks
*
* Note that in either of these cases, #post_render_cache callbacks are
* implicitly idempotent: a placeholder that has been replaced can't be
@ -407,8 +410,6 @@ class Renderer implements RendererInterface {
*
* @param array &$elements
* The structured array describing the data being rendered.
*
* @see drupal_render_collect_post_render_cache
*/
protected function processPostRenderCache(array &$elements) {
if (isset($elements['#post_render_cache'])) {

View File

@ -796,6 +796,10 @@ class RenderTest extends DrupalUnitTestBase {
function testDrupalRenderRenderCachePlaceholder() {
$context = array(
'bar' => $this->randomContextValue(),
// Provide a token instead of letting one be generated by
// drupal_render_cache_generate_placeholder(), otherwise we cannot know
// what the token is.
'token' => \Drupal\Component\Utility\Crypt::randomBytesBase64(55),
);
$callback = 'common_test_post_render_cache_placeholder';
$placeholder = drupal_render_cache_generate_placeholder($callback, $context);
@ -837,7 +841,7 @@ class RenderTest extends DrupalUnitTestBase {
$this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; JavaScript setting is added to page.');
// GET request: validate cached data.
$expected_token = $element['#post_render_cache']['common_test_post_render_cache_placeholder'][0]['token'];
$expected_token = $context['token'];
$element = array('#cache' => array('cid' => 'render_cache_placeholder_test_GET'));
$cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data;
// Parse unique token out of the cached markup.
@ -883,6 +887,10 @@ class RenderTest extends DrupalUnitTestBase {
function testDrupalRenderChildElementRenderCachePlaceholder() {
$context = array(
'bar' => $this->randomContextValue(),
// Provide a token instead of letting one be generated by
// drupal_render_cache_generate_placeholder(), otherwise we cannot know
// what the token is.
'token' => \Drupal\Component\Utility\Crypt::randomBytesBase64(55),
);
$callback = 'common_test_post_render_cache_placeholder';
$placeholder = drupal_render_cache_generate_placeholder($callback, $context);
@ -920,15 +928,13 @@ class RenderTest extends DrupalUnitTestBase {
$element['foo']['#cache'] = array('cid' => 'render_cache_placeholder_test_child_GET');
// Render, which will use the common-test-render-element.html.twig template.
$output = drupal_render_root($element);
$this->assertIdentical($output, $expected_output); //, 'Placeholder was replaced in output');
$this->assertIdentical($output, $expected_output, 'Placeholder was replaced in output');
$this->assertTrue(isset($element['#printed']), 'No cache hit');
$this->assertIdentical($element['#markup'], $expected_output, 'Placeholder was replaced in #markup.');
$this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified; JavaScript setting is added to page.');
// GET request: validate cached data for child element.
$child_tokens = $element['foo']['#post_render_cache']['common_test_post_render_cache_placeholder'][0]['token'];
$parent_tokens = $element['#post_render_cache']['common_test_post_render_cache_placeholder'][0]['token'];
$expected_token = $child_tokens;
$expected_token = $context['token'];
$element = array('#cache' => array('cid' => 'render_cache_placeholder_test_child_GET'));
$cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data;
// Parse unique token out of the cached markup.
@ -978,7 +984,7 @@ class RenderTest extends DrupalUnitTestBase {
),
'#cache' => array('tags' => array('rendered')),
);
$this->assertIdentical($cached_element, $expected_element); //, 'The correct data is cached for the parent element: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.');
$this->assertIdentical($cached_element, $expected_element, 'The correct data is cached for the parent element: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.');
// GET request: validate cached data.
// Check the cache of the child element again after the parent has been
@ -987,7 +993,6 @@ class RenderTest extends DrupalUnitTestBase {
$cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data;
// Verify that the child element contains the correct
// render_cache_placeholder markup.
$expected_token = $child_tokens;
$dom = Html::load($cached_element['#markup']);
$xpath = new \DOMXPath($dom);
$nodes = $xpath->query('//*[@token]');
@ -1024,6 +1029,31 @@ class RenderTest extends DrupalUnitTestBase {
\Drupal::request()->setMethod($request_method);
}
/**
* Tests a #post_render_cache callback that adds another #post_render_cache
* callback.
*
* E.g. when rendering a node in a #post_render_cache callback, the rendering
* of that node needs a #post_render_cache callback of its own to be executed
* (to render the node links).
*/
function testRecursivePostRenderCache() {
$context = array('foo' => $this->randomContextValue());
$element = [];
$element['#markup'] = '';
$element['#post_render_cache']['common_test_post_render_cache_recursion'] = array(
$context
);
$output = drupal_render_root($element);
$this->assertEqual('<p>overridden</p>', $output, 'The output has been modified by the indirect, recursive #post_render_cache callback.');
$this->assertIdentical($element['#markup'], '<p>overridden</p>', '#markup is overridden by the indirect, recursive #post_render_cache callback.');
$expected_js = [
['type' => 'setting', 'data' => ['common_test' => $context]],
];
$this->assertIdentical($element['#attached']['js'], $expected_js, '#attached is modified by the indirect, recursive #post_render_cache callback.');
}
/**
* #pre_render callback for testDrupalRenderBubbling().
*/
@ -1123,18 +1153,7 @@ class RenderTest extends DrupalUnitTestBase {
),
);
$this->assertEqual($expected_attached, $test_element['#attached'], 'Expected assets found.');
$expected_post_render_cache = array(
'Drupal\\system\\Tests\\Common\\RenderTest::bubblingPostRenderCache' => array(
0 => array (
'foo' => 'bar',
'baz' => 'qux',
),
),
);
$post_render_cache = $test_element['#post_render_cache'];
// We don't care about the exact token.
unset($post_render_cache['Drupal\\system\\Tests\\Common\\RenderTest::bubblingPostRenderCache'][0]['token']);
$this->assertEqual($expected_post_render_cache, $post_render_cache, 'Expected post-render cache data found.');
$this->assertEqual([], $test_element['#post_render_cache'], '#post_render_cache property is empty after rendering');
// Ensure that #pre_render callbacks are only executed if they don't have
// a render cache hit.

View File

@ -254,6 +254,34 @@ function common_test_post_render_cache_placeholder(array $element, array $contex
return $element;
}
/**
* #post_render_cache callback; bubbles another #post_render_cache callback.
*
* @param array $element
* A render array with the following keys:
* - #markup
* - #attached
* @param array $context
* An array with the following keys:
* - foo: contains a random string.
*
* @return array $element
* The updated $element.
*/
function common_test_post_render_cache_recursion(array $element, array $context) {
// Render a child which itself also has a #post_render_cache callback that
// must be bubbled.
$child = [];
$child['#markup'] = 'foo';
$child['#post_render_cache']['common_test_post_render_cache'][] = $context;
// Render the child.
$element['#markup'] = drupal_render($child);
return $element;
}
/**
* Implements hook_page_attachments().
*