Skip to content

Instantly share code, notes, and snippets.

@LionsAd
Last active September 1, 2015 10:55
Show Gist options
  • Select an option

  • Save LionsAd/cbf84e5e70b05c1ca11e to your computer and use it in GitHub Desktop.

Select an option

Save LionsAd/cbf84e5e70b05c1ca11e to your computer and use it in GitHub Desktop.
Cleanup renderer, prepare for passing just one cacheable metadata to doRender()
diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index f2be9ce..82f6228 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -131,10 +131,23 @@ public function renderRoot(&$elements) {
// Render in its own render context.
$this->isRenderingRoot = TRUE;
- $output = $this->executeInRenderContext(new RenderContext(), function () use (&$elements) {
- return $this->render($elements, TRUE);
- });
- $this->isRenderingRoot = FALSE;
+ try {
+ $output = $this->renderPlain($elements);
+ }
+ // Since #pre_render, #post_render, #lazy_builder callbacks and theme
+ // functions or templates may be used for generating a render array's
+ // content, and we might be rendering the main content for the page, it is
+ // possible that any of them throw an exception that will cause a different
+ // page to be rendered (e.g. throwing
+ // \Symfony\Component\HttpKernel\Exception\NotFoundHttpException will cause
+ // the 404 page to be rendered). That page might also use
+ // Renderer::renderRoot() but if exceptions aren't caught here, it will be
+ // impossible to call Renderer::renderRoot() again.
+ // Hence, catch all exceptions, reset the isRenderingRoot property and
+ // re-throw exceptions.
+ finally {
+ $this->isRenderingRoot = FALSE;
+ }
return $output;
}
@@ -143,8 +156,9 @@ public function renderRoot(&$elements) {
* {@inheritdoc}
*/
public function renderPlain(&$elements) {
- return $this->executeInRenderContext(new RenderContext(), function () use (&$elements) {
- return $this->render($elements, TRUE);
+ $context = new RenderContext();
+ return $this->executeInRenderContext($context, function () use (&$elements, $context) {
+ return $this->doRenderRoot($elements, $context);
});
}
@@ -163,10 +177,8 @@ public function renderPlain(&$elements) {
* The updated $elements.
*
* @see ::replacePlaceholders()
- *
- * @todo Make public as part of https://www.drupal.org/node/2469431
*/
- protected function renderPlaceholder($placeholder, array $elements) {
+ public function renderPlaceholder($placeholder, array $elements) {
// Get the render array for the given placeholder
$placeholder_elements = $elements['#attached']['placeholders'][$placeholder];
@@ -192,31 +204,56 @@ protected function renderPlaceholder($placeholder, array $elements) {
* {@inheritdoc}
*/
public function render(&$elements, $is_root_call = FALSE) {
- // Since #pre_render, #post_render, #lazy_builder callbacks and theme
- // functions or templates may be used for generating a render array's
- // content, and we might be rendering the main content for the page, it is
- // possible that any of them throw an exception that will cause a different
- // page to be rendered (e.g. throwing
- // \Symfony\Component\HttpKernel\Exception\NotFoundHttpException will cause
- // the 404 page to be rendered). That page might also use
- // Renderer::renderRoot() but if exceptions aren't caught here, it will be
- // impossible to call Renderer::renderRoot() again.
- // Hence, catch all exceptions, reset the isRenderingRoot property and
- // re-throw exceptions.
- try {
- return $this->doRender($elements, $is_root_call);
+ $context = $this->getCurrentRenderContext();
+ if (!isset($context)) {
+ throw new \LogicException("Render context is empty, because render() was called outside of a renderRoot() or renderPlain() call. Use renderPlain()/renderRoot() or #lazy_builder/#pre_render instead.");
}
- catch (\Exception $e) {
- // Mark the ::rootRender() call finished due to this exception & re-throw.
- $this->isRenderingRoot = FALSE;
- throw $e;
+
+ if ($is_root_call) {
+ trigger_error('render() with $is_root_call is deprecated; use renderRoot() instead.');
+ return $this->doRenderRoot($elements, $context);
}
+
+ return $this->doRender($elements, $context);
}
/**
* See the docs for ::render().
*/
- protected function doRender(&$elements, $is_root_call = FALSE) {
+ protected function doRenderRoot(&$elements, $context) {
+ // Set the bubbleable rendering metadata that has configurable defaults, if:
+ // - this is the root call, to ensure that the final render array definitely
+ // has these configurable defaults, even when no subtree is render cached.
+ $required_cache_contexts = $this->rendererConfig['required_cache_contexts'];
+
+ if (isset($elements['#cache']['contexts'])) {
+ $elements['#cache']['contexts'] = Cache::mergeContexts($elements['#cache']['contexts'], $required_cache_contexts);
+ }
+ else {
+ $elements['#cache']['contexts'] = $required_cache_contexts;
+ }
+
+ // Render the elements normally.
+ $return = $this->doRender($elements, $context);
+
+ // If there is no output, return early as placeholders can't make a
+ // difference.
+ if ($return === '') {
+ return $return;
+ }
+
+ // Only when we're in a root (non-recursive) Renderer::render() call,
+ // placeholders must be processed, to prevent breaking the render cache in
+ // case of nested elements with #cache set.
+ $this->replacePlaceholders($elements);
+
+ return $elements['#markup'];
+ }
+
+ /**
+ * See the docs for ::render().
+ */
+ protected function doRender(&$elements, $context) {
if (empty($elements)) {
return '';
}
@@ -248,10 +285,6 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
return '';
}
- $context = $this->getCurrentRenderContext();
- if (!isset($context)) {
- throw new \LogicException("Render context is empty, because render() was called outside of a renderRoot() or renderPlain() call. Use renderPlain()/renderRoot() or #lazy_builder/#pre_render instead.");
- }
$context->push(new BubbleableMetadata());
// Set the bubbleable rendering metadata that has configurable defaults, if:
@@ -259,7 +292,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
// has these configurable defaults, even when no subtree is render cached.
// - this is a render cacheable subtree, to ensure that the cached data has
// the configurable defaults (which may affect the ID and invalidation).
- if ($is_root_call || isset($elements['#cache']['keys'])) {
+ if (isset($elements['#cache']['keys'])) {
$required_cache_contexts = $this->rendererConfig['required_cache_contexts'];
if (isset($elements['#cache']['contexts'])) {
$elements['#cache']['contexts'] = Cache::mergeContexts($elements['#cache']['contexts'], $required_cache_contexts);
@@ -275,12 +308,6 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
$cached_element = $this->renderCache->get($elements);
if ($cached_element !== FALSE) {
$elements = $cached_element;
- // Only when we're in a root (non-recursive) Renderer::render() call,
- // placeholders must be processed, to prevent breaking the render cache
- // in case of nested elements with #cache set.
- if ($is_root_call) {
- $this->replacePlaceholders($elements);
- }
// Mark the element markup as safe if is it a string.
if (is_string($elements['#markup'])) {
$elements['#markup'] = SafeString::create($elements['#markup']);
@@ -291,6 +318,8 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
// Render cache hit, so rendering is finished, all necessary info
// collected!
$context->bubble();
+
+
return $elements['#markup'];
}
}
@@ -452,7 +481,21 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
// same process as Renderer::render() but is inlined for speed.
if ((!$theme_is_implemented || isset($elements['#render_children'])) && empty($elements['#children'])) {
foreach ($children as $key) {
- $elements['#children'] .= $this->doRender($elements[$key]);
+ $child_element = &$elements[$key];
+ if (isset($child_element['#cache']['keys'])) {
+ $new_context = new RenderContext();
+ $elements['#children'] .= $this->executeInRenderContext($new_context, function () use (&$child_element, $new_context) {
+ return $this->doRender($child_element, $new_context);
+ });
+ // @todo This should not be necessary.
+ if (!$new_context->isEmpty()) {
+ $frame = $context->pop()->merge($new_context->pop());
+ $context->push($frame);
+ }
+ }
+ else {
+ $elements['#children'] .= $this->doRender($elements[$key], $context);
+ }
}
$elements['#children'] = SafeString::create($elements['#children']);
}
@@ -532,23 +575,6 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
$this->renderCache->set($elements, $pre_bubbling_elements);
}
- // Only when we're in a root (non-recursive) Renderer::render() call,
- // placeholders must be processed, to prevent breaking the render cache in
- // case of nested elements with #cache set.
- //
- // By running them here, we ensure that:
- // - they run when #cache is disabled,
- // - they run when #cache is enabled and there is a cache miss.
- // Only the case of a cache hit when #cache is enabled, is not handled here,
- // that is handled earlier in Renderer::render().
- if ($is_root_call) {
- $this->replacePlaceholders($elements);
- // @todo remove as part of https://www.drupal.org/node/2511330.
- if ($context->count() !== 1) {
- throw new \LogicException('A stray drupal_render() invocation with $is_root_call = TRUE is causing bubbling of attached assets to break.');
- }
- }
-
// Rendering is finished, all necessary info collected!
$context->bubble();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment