core/core.services.yml | 5 +
core/includes/common.inc | 2 +-
core/lib/Drupal/Core/Ajax/AjaxResponse.php | 6 +-
.../EarlyRenderingControllerWrapper.php | 136 +++++++++++++++++++++
.../Drupal/Core/Render/BareHtmlPageRenderer.php | 4 +-
core/lib/Drupal/Core/Render/Element/Actions.php | 2 +-
.../Core/Render/MainContent/HtmlRenderer.php | 10 +-
core/lib/Drupal/Core/Render/RenderContext.php | 60 +++++++++
core/lib/Drupal/Core/Render/Renderer.php | 136 ++++++++++-----------
core/lib/Drupal/Core/Render/RendererInterface.php | 43 ++++++-
.../block/src/Tests/BlockViewBuilderTest.php | 33 +++--
.../src/Tests/BlockContentCacheTagsTest.php | 2 +-
core/modules/book/src/BookOutline.php | 3 +-
.../ckeditor/src/Plugin/Editor/CKEditor.php | 18 ++-
core/modules/comment/comment.module | 2 +-
.../Tests/CommentDefaultFormatterCacheTagsTest.php | 17 ++-
core/modules/config/src/Form/ConfigSync.php | 18 ++-
core/modules/contact/src/MessageViewBuilder.php | 6 +-
.../contextual/src/ContextualController.php | 2 +-
.../datetime/src/Tests/DateTimeFieldTest.php | 2 +-
core/modules/field/field.module | 2 +-
.../field/src/Tests/Boolean/BooleanFieldTest.php | 2 +-
.../field/src/Tests/Email/EmailFieldTest.php | 2 +-
.../EntityReferenceFormatterTest.php | 10 +-
.../field/src/Tests/String/StringFieldTest.php | 2 +-
.../field_ui/src/Tests/ManageDisplayTest.php | 2 +-
core/modules/file/file.field.inc | 2 +-
.../src/Plugin/Field/FieldWidget/FileWidget.php | 2 +-
.../file/src/Tests/FileFieldDisplayTest.php | 2 +-
core/modules/file/src/Tests/FileItemTest.php | 2 +-
core/modules/forum/src/Tests/ForumTest.php | 4 +-
.../src/Tests/Views/HistoryTimestampTest.php | 2 +-
.../src/Plugin/Field/FieldWidget/ImageWidget.php | 2 +-
.../image/src/Tests/ImageDimensionsTest.php | 2 +-
.../image/src/Tests/ImageFieldDisplayTest.php | 21 ++--
.../image/src/Tests/ImageThemeFunctionTest.php | 25 ++--
core/modules/link/src/Tests/LinkFieldTest.php | 2 +-
core/modules/node/src/Tests/SummaryLengthTest.php | 7 +-
.../modules/node/src/Tests/Views/RowPluginTest.php | 6 +-
.../quickedit/src/Tests/QuickEditLoadingTest.php | 2 +-
.../Tests/EntityReferenceFieldAttributesTest.php | 2 +-
.../rdf/src/Tests/Field/FieldRdfaTestBase.php | 2 +-
.../rdf/src/Tests/FileFieldAttributesTest.php | 2 +-
.../rdf/src/Tests/ImageFieldAttributesTest.php | 2 +-
.../src/Tests/ResponsiveImageFieldDisplayTest.php | 6 +-
.../simpletest/src/Form/SimpletestResultsForm.php | 10 +-
.../simpletest/src/Form/SimpletestTestForm.php | 32 ++++-
.../simpletest/src/Tests/KernelTestBaseTest.php | 6 +-
core/modules/system/src/Tests/Ajax/DialogTest.php | 2 +-
.../system/src/Tests/Common/AddFeedTest.php | 2 +-
.../src/Tests/Common/RenderElementTypesTest.php | 8 +-
.../modules/system/src/Tests/Common/RenderTest.php | 2 +-
core/modules/system/src/Tests/Common/UrlTest.php | 18 ++-
.../src/Tests/Entity/EntityTranslationTest.php | 11 +-
.../src/Tests/Entity/EntityViewBuilderTest.php | 17 ++-
.../modules/system/src/Tests/Form/CheckboxTest.php | 2 +-
core/modules/system/src/Tests/Form/FormTest.php | 2 +-
.../system/src/Tests/Theme/FunctionsTest.php | 6 +-
.../system/src/Tests/Theme/TwigDebugMarkupTest.php | 10 +-
.../system/src/Tests/Theme/TwigEnvironmentTest.php | 8 +-
.../system/src/Tests/Theme/TwigFilterTest.php | 2 +-
.../system/src/Tests/Theme/TwigNamespaceTest.php | 2 +-
.../modules/system/src/Tests/Theme/TwigRawTest.php | 4 +-
.../src/Controller/CommonTestController.php | 3 +-
.../text/src/Tests/Formatter/TextFormatterTest.php | 2 +-
core/modules/text/src/Tests/TextFieldTest.php | 7 +-
.../src/Plugin/views/style/StylePluginBase.php | 2 +-
.../src/Tests/Entity/RowEntityRenderersTest.php | 2 +-
.../views/src/Tests/Handler/AreaEntityTest.php | 8 +-
core/modules/views/src/Tests/Handler/AreaTest.php | 4 +-
.../views/src/Tests/Handler/AreaTextTest.php | 8 +-
.../views/src/Tests/Handler/AreaViewTest.php | 6 +-
.../src/Tests/Handler/FieldFieldAccessTestBase.php | 4 +-
.../views/src/Tests/Handler/FieldUnitTest.php | 6 +-
.../views/src/Tests/Handler/FieldWebTest.php | 18 +--
.../modules/views/src/Tests/Plugin/DisplayTest.php | 17 ++-
.../views/src/Tests/Plugin/ExposedFormTest.php | 2 +-
core/modules/views/src/Tests/Plugin/PagerTest.php | 2 +-
.../views/src/Tests/Plugin/StyleGridTest.php | 2 +-
.../views/src/Tests/Plugin/StyleHtmlListTest.php | 4 +-
.../views/src/Tests/Plugin/StyleMappingTest.php | 2 +-
.../views/src/Tests/Plugin/StyleTableUnitTest.php | 6 +-
core/modules/views/src/Tests/Plugin/StyleTest.php | 9 +-
.../src/Tests/Plugin/StyleUnformattedTest.php | 2 +-
core/modules/views/src/Tests/ViewElementTest.php | 12 +-
core/modules/views/src/Tests/ViewRenderTest.php | 2 +-
core/modules/views/src/Tests/ViewsTemplateTest.php | 2 +-
.../views_ui/src/Tests/CustomBooleanTest.php | 2 +-
.../Tests/Core/Render/RendererBubblingTest.php | 22 ++--
.../Drupal/Tests/Core/Render/RendererTest.php | 22 ++--
90 files changed, 660 insertions(+), 284 deletions(-)
diff --git a/core/core.services.yml b/core/core.services.yml
index 6c2c78f..8461990 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -1378,6 +1378,11 @@ services:
renderer:
class: Drupal\Core\Render\Renderer
arguments: ['@controller_resolver', '@theme.manager', '@plugin.manager.element_info', '@render_cache', '%renderer.config%']
+ early_rendering_controller_wrapper:
+ class: Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapper
+ arguments: ['@controller_resolver', '@renderer']
+ tags:
+ - { name: event_subscriber }
email.validator:
class: Egulias\EmailValidator\EmailValidator
diff --git a/core/includes/common.inc b/core/includes/common.inc
index 9337556..a68fa5a 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -195,7 +195,7 @@ function drupal_get_html_head($render = TRUE) {
$elements = _drupal_add_html_head();
\Drupal::moduleHandler()->alter('html_head', $elements);
if ($render) {
- return drupal_render($elements);
+ return \Drupal::service('renderer')->renderPlain($elements);
}
else {
return $elements;
diff --git a/core/lib/Drupal/Core/Ajax/AjaxResponse.php b/core/lib/Drupal/Core/Ajax/AjaxResponse.php
index 1acf03a..be1b640 100644
--- a/core/lib/Drupal/Core/Ajax/AjaxResponse.php
+++ b/core/lib/Drupal/Core/Ajax/AjaxResponse.php
@@ -198,15 +198,15 @@ protected function ajaxRender(Request $request) {
$renderer = $this->getRenderer();
if (!empty($css_assets)) {
$css_render_array = \Drupal::service('asset.css.collection_renderer')->render($css_assets);
- $resource_commands[] = new AddCssCommand($renderer->render($css_render_array));
+ $resource_commands[] = new AddCssCommand($renderer->renderPlain($css_render_array));
}
if (!empty($js_assets_header)) {
$js_header_render_array = \Drupal::service('asset.js.collection_renderer')->render($js_assets_header);
- $resource_commands[] = new PrependCommand('head', $renderer->render($js_header_render_array));
+ $resource_commands[] = new PrependCommand('head', $renderer->renderPlain($js_header_render_array));
}
if (!empty($js_assets_footer)) {
$js_footer_render_array = \Drupal::service('asset.js.collection_renderer')->render($js_assets_footer);
- $resource_commands[] = new AppendCommand('body', $renderer->render($js_footer_render_array));
+ $resource_commands[] = new AppendCommand('body', $renderer->renderPlain($js_footer_render_array));
}
foreach (array_reverse($resource_commands) as $resource_command) {
$this->addCommand($resource_command, TRUE);
diff --git a/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapper.php b/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapper.php
new file mode 100644
index 0000000..a39c261
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapper.php
@@ -0,0 +1,136 @@
+controllerResolver = $controller_resolver;
+ $this->renderer = $renderer;
+ }
+
+ /**
+ * Ensures bubbleable metadata from early rendering is not lost.
+ *
+ * @param \Symfony\Component\HttpKernel\Event\FilterControllerEvent $event
+ * The controller event.
+ */
+ public function onController(FilterControllerEvent $event) {
+ $controller = $event->getController();
+
+ // See \Symfony\Component\HttpKernel\HttpKernel::handleRaw().
+ $arguments = $this->controllerResolver->getArguments($event->getRequest(), $controller);
+
+ $event->setController(function() use ($controller, $arguments) {
+ $context = new RenderContext();
+
+ $response = $this->renderer->executeInRenderContext($context, function() use ($controller, $arguments) {
+ // Now call the actual controller, just like HttpKernel does.
+ return call_user_func_array($controller, $arguments);
+ });
+
+ // If early rendering happened, i.e. if code in the controller called
+ // drupal_render(), then the bubbleable metadata for that is stored in
+ // the current render context.
+ if (!$context->isEmpty()) {
+ if (is_array($response)) {
+ // Merging the "lost" bubbleable metadata onto the render array
+ // returned by the controller.
+ $early_rendering_bubbleable_metadata = $context->pop();
+ BubbleableMetadata::createFromRenderArray($response)
+ ->merge($early_rendering_bubbleable_metadata)
+ ->applyTo($response);
+ }
+ else {
+ throw new \LogicException(sprintf('Early rendering is not permitted for controllers returning anything else than render arrays. Response class: %s.', get_class($response)));
+ }
+ }
+
+ return $response;
+ });
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getSubscribedEvents() {
+ $events[KernelEvents::CONTROLLER][] = ['onController'];
+
+ return $events;
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php b/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php
index c8012c6..58c5507 100644
--- a/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php
+++ b/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php
@@ -59,12 +59,12 @@ public function renderBarePage(array $content, $title, $page_theme_property, arr
// \Drupal\Core\Render\MainContent\HtmlRenderer::renderResponse() for more
// information about this; the exact same pattern is used there and
// explained in detail there.
- $this->renderer->render($html['page'], TRUE);
+ $this->renderer->renderRoot($html['page']);
// Add the bare minimum of attachments from the system module and the
// current maintenance theme.
system_page_attachments($html['page']);
- return $this->renderer->render($html);
+ return $this->renderer->renderPlain($html);
}
}
diff --git a/core/lib/Drupal/Core/Render/Element/Actions.php b/core/lib/Drupal/Core/Render/Element/Actions.php
index 754f7f8..674e229 100644
--- a/core/lib/Drupal/Core/Render/Element/Actions.php
+++ b/core/lib/Drupal/Core/Render/Element/Actions.php
@@ -94,7 +94,7 @@ public static function preRenderActionsDropbutton(&$element, FormStateInterface
// Add this button to the corresponding dropbutton.
// @todo Change #type 'dropbutton' to be based on item-list.html.twig
// instead of links.html.twig to avoid this preemptive rendering.
- $button = drupal_render($element[$key]);
+ $button = \Drupal::service('renderer')->renderPlain($element[$key]);
$dropbuttons[$dropbutton]['#links'][$key] = array(
'title' => $button,
);
diff --git a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
index d61bb24..05e38ec 100644
--- a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
+++ b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
@@ -126,14 +126,14 @@ public function renderResponse(array $main_content, Request $request, RouteMatch
// and hence may not replace any placeholders (because they might add yet
// more assets to be attached), and therefore it must be rendered with
// drupal_render(), not drupal_render_root().
- $this->renderer->render($html['page'], TRUE);
+ $this->renderer->renderRoot($html['page']);
if (isset($html['page_top'])) {
- $this->renderer->render($html['page_top'], TRUE);
+ $this->renderer->renderRoot($html['page_top']);
}
if (isset($html['page_bottom'])) {
- $this->renderer->render($html['page_bottom'], TRUE);
+ $this->renderer->renderRoot($html['page_bottom']);
}
- $content = $this->renderer->render($html);
+ $content = $this->renderer->renderPlain($html, FALSE);
$response = new CacheableResponse($content, 200,[
'Content-Type' => 'text/html; charset=UTF-8',
@@ -197,7 +197,7 @@ protected function prepare(array $main_content, Request $request, RouteMatchInte
// ::renderResponse().
// @todo Remove this once https://www.drupal.org/node/2359901 lands.
if (!empty($main_content)) {
- $this->renderer->render($main_content, FALSE);
+ $this->renderer->renderPlain($main_content, FALSE);
$main_content = $this->renderCache->getCacheableRenderArray($main_content) + [
'#title' => isset($main_content['#title']) ? $main_content['#title'] : NULL
];
diff --git a/core/lib/Drupal/Core/Render/RenderContext.php b/core/lib/Drupal/Core/Render/RenderContext.php
new file mode 100644
index 0000000..e255005
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/RenderContext.php
@@ -0,0 +1,60 @@
+pop();
+ // Update the frame, but also update the current element, to ensure it
+ // contains up-to-date information in case it gets render cached.
+ $updated_frame = BubbleableMetadata::createFromRenderArray($element)->merge($frame);
+ $updated_frame->applyTo($element);
+ $this->push($updated_frame);
+ }
+
+ /**
+ * Bubbles the stack.
+ *
+ * Whenever another level in the render array has been rendered, the stack
+ * must be bubbled, to merge its rendering metadata with that of the parent
+ * element.
+ */
+ public function bubble() {
+ // If there's only one frame on the stack, then this is the root call, and
+ // we can't bubble up further. ::renderRoot() will reset the stack, but we
+ // must not reset it here to allow users of ::executeInRenderContext() to
+ // access the stack directly.
+ if ($this->count() === 1) {
+ return;
+ }
+
+ // Merge the current and the parent stack frame.
+ $current = $this->pop();
+ $parent = $this->pop();
+ $this->push($current->merge($parent));
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index d24b7d7..3fd99f2 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -57,11 +57,21 @@ class Renderer implements RendererInterface {
protected $rendererConfig;
/**
- * The stack containing bubbleable rendering metadata.
+ * The render context.
*
- * @var \SplStack|null
+ * This must be static as long as some controllers rebuild the container
+ * during a request.
+ *
+ * @var \Drupal\Core\Render\RenderContext|null
+ */
+ protected static $context;
+
+ /**
+ * Whether we're currently in a ::renderRoot() call.
+ *
+ * @var bool
*/
- protected static $stack;
+ protected $isRenderingRoot = FALSE;
/**
* Constructs a new Renderer.
@@ -89,18 +99,31 @@ public function __construct(ControllerResolverInterface $controller_resolver, Th
* {@inheritdoc}
*/
public function renderRoot(&$elements) {
- return $this->render($elements, TRUE);
+ // Disallow calling ::renderRoot() from within another ::renderRoot() call.
+ if ($this->isRenderingRoot) {
+ $this->isRenderingRoot = FALSE;
+ throw new \LogicException('A stray renderRoot() invocation is causing bubbling of attached assets to break.');
+ }
+
+ // 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;
+
+ return $output;
}
/**
* {@inheritdoc}
+ *
+ * @todo Rename to ::renderInIsolation()
*/
- public function renderPlain(&$elements) {
- $current_stack = static::$stack;
- $this->resetStack();
- $output = $this->renderRoot($elements);
- static::$stack = $current_stack;
- return $output;
+ public function renderPlain(&$elements, $is_root_call = TRUE) {
+ return $this->executeInRenderContext(new RenderContext(), function () use (&$elements, $is_root_call) {
+ return $this->render($elements, $is_root_call);
+ });
}
/**
@@ -150,16 +173,17 @@ public function render(&$elements, $is_root_call = FALSE) {
// 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::render()
- // but if exceptions aren't caught here, the stack will be left in an
- // inconsistent state.
- // Hence, catch all exceptions and reset the stack and re-throw them.
+ // 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);
}
catch (\Exception $e) {
- // Reset stack and re-throw exception.
- $this->resetStack();
+ // Mark the ::rootRender() call finished due to this exception & re-throw.
+ $this->isRenderingRoot = FALSE;
throw $e;
}
}
@@ -185,10 +209,10 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
return '';
}
- if (!isset(static::$stack)) {
- static::$stack = new \SplStack();
+ if (!isset(static::$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.");
}
- static::$stack->push(new BubbleableMetadata());
+ static::$context->push(new BubbleableMetadata());
// Set the bubbleable rendering metadata that has configurable defaults, if:
// - this is the root call, to ensure that the final render array definitely
@@ -229,10 +253,10 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
}
// The render cache item contains all the bubbleable rendering metadata
// for the subtree.
- $this->updateStack($elements);
+ static::$context->update($elements);
// Render cache hit, so rendering is finished, all necessary info
// collected!
- $this->bubbleStack();
+ static::$context->bubble();
return $elements['#markup'];
}
}
@@ -330,9 +354,9 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
if (!empty($elements['#printed'])) {
// The #printed element contains all the bubbleable rendering metadata for
// the subtree.
- $this->updateStack($elements);
+ static::$context->update($elements);
// #printed, so rendering is finished, all necessary info collected!
- $this->bubbleStack();
+ static::$context->bubble();
return '';
}
@@ -458,8 +482,8 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
$elements['#markup'] = $prefix . $elements['#children'] . $suffix;
- // We've rendered this element (and its subtree!), now update the stack.
- $this->updateStack($elements);
+ // We've rendered this element (and its subtree!), now update the context.
+ static::$context->update($elements);
// Cache the processed element if both $pre_bubbling_elements and $elements
// have the metadata necessary to generate a cache ID.
@@ -481,13 +505,13 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
// that is handled earlier in Renderer::render().
if ($is_root_call) {
$this->replacePlaceholders($elements);
- if (static::$stack->count() !== 1) {
+ if (static::$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!
- $this->bubbleStack();
+ static::$context->bubble();
$elements['#printed'] = TRUE;
$elements['#markup'] = SafeMarkup::set($elements['#markup']);
@@ -495,52 +519,24 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
}
/**
- * Resets the renderer service's internal stack (used for bubbling metadata).
- *
- * Only necessary in very rare/advanced situations, such as when rendering an
- * error page if an exception occurred *during* rendering.
- */
- protected function resetStack() {
- static::$stack = NULL;
- }
-
- /**
- * Updates the stack.
- *
- * @param array &$element
- * The element of the render array that has just been rendered. The stack
- * frame for this element will be updated with the bubbleable rendering
- * metadata of this element.
- */
- protected function updateStack(&$element) {
- // The latest frame represents the bubbleable metadata for the subtree.
- $frame = static::$stack->pop();
- // Update the frame, but also update the current element, to ensure it
- // contains up-to-date information in case it gets render cached.
- $updated_frame = BubbleableMetadata::createFromRenderArray($element)->merge($frame);
- $updated_frame->applyTo($element);
- static::$stack->push($updated_frame);
- }
-
- /**
- * Bubbles the stack.
- *
- * Whenever another level in the render array has been rendered, the stack
- * must be bubbled, to merge its rendering metadata with that of the parent
- * element.
+ * {@inheritdoc}
*/
- protected function bubbleStack() {
- // If there's only one frame on the stack, then this is the root call, and
- // we can't bubble up further. Reset the stack for the next root call.
- if (static::$stack->count() === 1) {
- $this->resetStack();
- return;
+ public function executeInRenderContext(RenderContext $context, callable $callable) {
+ // Store the current render context.
+ $current_context = static::$context;
+
+ // Set the provided context and call the callable, it will use that context.
+ static::$context = $context;
+ $result = $callable();
+ // @todo Convert to an assertion in https://www.drupal.org/node/2408013
+ if (static::$context->count() > 1) {
+ throw new \LogicException('Bubbling failed.');
}
- // Merge the current and the parent stack frame.
- $current = static::$stack->pop();
- $parent = static::$stack->pop();
- static::$stack->push($current->merge($parent));
+ // Restore the original render context.
+ static::$context = $current_context;
+
+ return $result;
}
/**
diff --git a/core/lib/Drupal/Core/Render/RendererInterface.php b/core/lib/Drupal/Core/Render/RendererInterface.php
index 5d59d4d..1e420b1 100644
--- a/core/lib/Drupal/Core/Render/RendererInterface.php
+++ b/core/lib/Drupal/Core/Render/RendererInterface.php
@@ -22,6 +22,8 @@
* - system internals that are responsible for rendering the final HTML
* - render arrays for non-HTML responses, such as feeds
*
+ * (Cannot be executed within another render context.)
+ *
* @param array $elements
* The structured array describing the data to be rendered.
*
@@ -45,9 +47,11 @@ public function renderRoot(&$elements);
* ::renderRoot() call, but that is generally highly problematic (and hence an
* exception is thrown when a ::renderRoot() call happens within another
* ::renderRoot() call). However, in this case, we only care about the output,
- * not about the bubbling. Hence this uses a separate render stack, to not
+ * not about the bubbling. Hence this uses a separate render context, to not
* affect the parent ::renderRoot() call.
*
+ * (Can be executed within another render context: it runs in isolation.)
+ *
* @param array $elements
* The structured array describing the data to be rendered.
*
@@ -88,8 +92,8 @@ public function renderPlain(&$elements);
* or configuration that can affect that rendering changes.
* - Placeholders, with associated self-contained placeholder render arrays,
* for executing code to handle dynamic requirements that cannot be cached.
- * A stack of \Drupal\Core\Render\BubbleableMetadata objects can be used to
- * perform this bubbling.
+ * A render context (\Drupal\Core\Render\RenderContext) can be used to perform
+ * bubbling; it is a stack of \Drupal\Core\Render\BubbleableMetadata objects.
*
* Additionally, whether retrieving from cache or not, it is important to
* know all of the assets (CSS and JavaScript) required by the rendered HTML,
@@ -103,9 +107,9 @@ public function renderPlain(&$elements);
* - If this element has already been printed (#printed = TRUE) or the user
* does not have access to it (#access = FALSE), then an empty string is
* returned.
- * - If no stack data structure has been created yet, it is done now. Next,
+ * - If no render context is set yet, an exception is thrown. Otherwise,
* an empty \Drupal\Core\Render\BubbleableMetadata is pushed onto the
- * stack.
+ * render context.
* - If this element has #cache defined then the cached markup for this
* element will be returned if it exists in Renderer::render()'s cache. To
* use Renderer::render() caching, set the element's #cache property to an
@@ -316,6 +320,35 @@ public function renderPlain(&$elements);
public function render(&$elements, $is_root_call = FALSE);
/**
+ * Executes a callable within a render context.
+ *
+ * Only for very advanced use cases. Prefer to use ::renderRoot() and
+ * ::renderPlain() instead.
+ *
+ * All rendering must happen within a render context: within a render context,
+ * all bubbleable metadata is bubbled and hence tracked, without a render
+ * context, it would be lost. This could lead to missing assets, incorrect
+ * cache variations (and thus security issues), insufficient cache
+ * invalidations, and so on.
+ *
+ * Any and all rendering must therefore happen within a render context, and it
+ * is this method that provides that.
+ *
+ * @see \Drupal\Core\Render\BubbleableMetadata
+ *
+ * @param \Drupal\Core\Render\RenderContext $context
+ * The render context to execute the callable within.
+ * @param callable $callable
+ * The callable to execute.
+ * @return mixed
+ * The callable's return value.
+ *
+ * @see \Drupal\Core\Render\RenderContext
+ * @throws \LogicException
+ */
+ public function executeInRenderContext(RenderContext $context, callable $callable);
+
+ /**
* Merges the bubbleable rendering metadata o/t 2nd render array with the 1st.
*
* @param array $a
diff --git a/core/modules/block/src/Tests/BlockViewBuilderTest.php b/core/modules/block/src/Tests/BlockViewBuilderTest.php
index 7a7aa64..ccb53e4 100644
--- a/core/modules/block/src/Tests/BlockViewBuilderTest.php
+++ b/core/modules/block/src/Tests/BlockViewBuilderTest.php
@@ -44,6 +44,13 @@ class BlockViewBuilderTest extends KernelTestBase {
protected $controller;
/**
+ * The renderer.
+ *
+ * @var \Drupal\Core\Render\RendererInterface
+ */
+ protected $renderer;
+
+ /**
* {@inheritdoc}
*/
protected function setUp() {
@@ -64,6 +71,8 @@ protected function setUp() {
$this->block->save();
$this->container->get('cache.render')->deleteAll();
+
+ $this->renderer = $this->container->get('renderer');
}
/**
@@ -90,7 +99,7 @@ public function testBasicRendering() {
$expected[] = ' ';
$expected[] = '';
$expected_output = implode("\n", $expected);
- $this->assertEqual(drupal_render($output), $expected_output);
+ $this->assertEqual($this->renderer->renderRoot($output), $expected_output);
// Reset the HTML IDs so that the next render is not affected.
Html::resetSeenIds();
@@ -115,7 +124,7 @@ public function testBasicRendering() {
$expected[] = ' ';
$expected[] = '';
$expected_output = implode("\n", $expected);
- $this->assertEqual(drupal_render($output), $expected_output);
+ $this->assertEqual($this->renderer->renderRoot($output), $expected_output);
}
/**
@@ -144,7 +153,7 @@ public function testBlockViewBuilderCache() {
* @see ::testBlockViewBuilderCache()
*/
protected function verifyRenderCacheHandling() {
- // Force a request via GET so we can get drupal_render() cache working.
+ // Force a request via GET so we can test the render cache.
$request = \Drupal::request();
$request_method = $request->server->get('REQUEST_METHOD');
$request->setMethod('GET');
@@ -152,7 +161,7 @@ protected function verifyRenderCacheHandling() {
// Test that a cache entry is created.
$build = $this->getBlockRenderArray();
$cid = 'entity_view:block:test_block:en:core';
- drupal_render($build);
+ $this->renderer->renderRoot($build);
$this->assertTrue($this->container->get('cache.render')->get($cid), 'The block render element has been cached.');
// Re-save the block and check that the cache entry has been deleted.
@@ -166,7 +175,7 @@ protected function verifyRenderCacheHandling() {
// removes it.
$build['#block'] = $this->block;
- drupal_render($build);
+ $this->renderer->renderRoot($build);
$this->assertTrue($this->container->get('cache.render')->get($cid), 'The block render element has been cached.');
$this->block->delete();
$this->assertFalse($this->container->get('cache.render')->get($cid), 'The block render cache entry has been cleared when the block was deleted.');
@@ -181,17 +190,17 @@ protected function verifyRenderCacheHandling() {
public function testBlockViewBuilderAlter() {
// Establish baseline.
$build = $this->getBlockRenderArray();
- $this->assertIdentical(drupal_render($build), 'Llamas > unicorns!');
+ $this->assertIdentical($this->renderer->renderRoot($build), 'Llamas > unicorns!');
// Enable the block view alter hook that adds a suffix, for basic testing.
\Drupal::state()->set('block_test_view_alter_suffix', TRUE);
Cache::invalidateTags($this->block->getCacheTags());
$build = $this->getBlockRenderArray();
$this->assertTrue(isset($build['#suffix']) && $build['#suffix'] === '
Goodbye!', 'A block with content is altered.');
- $this->assertIdentical(drupal_render($build), 'Llamas > unicorns!
Goodbye!');
+ $this->assertIdentical($this->renderer->renderRoot($build), 'Llamas > unicorns!
Goodbye!');
\Drupal::state()->set('block_test_view_alter_suffix', FALSE);
- // Force a request via GET so we can get drupal_render() cache working.
+ // Force a request via GET so we can test the render cache.
$request = \Drupal::request();
$request_method = $request->server->get('REQUEST_METHOD');
$request->setMethod('GET');
@@ -209,7 +218,7 @@ public function testBlockViewBuilderAlter() {
$expected_keys = array_merge($default_keys, array($alter_add_key));
$build = $this->getBlockRenderArray();
$this->assertIdentical($expected_keys, $build['#cache']['keys'], 'An altered cacheable block has the expected cache keys.');
- $this->assertIdentical(drupal_render($build), '');
+ $this->assertIdentical($this->renderer->renderRoot($build), '');
$cache_entry = $this->container->get('cache.render')->get($cid);
$this->assertTrue($cache_entry, 'The block render element has been cached with the expected cache ID.');
$expected_tags = array_merge($default_tags, ['rendered']);
@@ -224,7 +233,7 @@ public function testBlockViewBuilderAlter() {
$build = $this->getBlockRenderArray();
sort($build['#cache']['tags']);
$this->assertIdentical($expected_tags, $build['#cache']['tags'], 'An altered cacheable block has the expected cache tags.');
- $this->assertIdentical(drupal_render($build), '');
+ $this->assertIdentical($this->renderer->renderRoot($build), '');
$cache_entry = $this->container->get('cache.render')->get($cid);
$this->assertTrue($cache_entry, 'The block render element has been cached with the expected cache ID.');
$expected_tags = array_merge($default_tags, [$alter_add_tag, 'rendered']);
@@ -236,8 +245,8 @@ public function testBlockViewBuilderAlter() {
// alter the eventual content.
\Drupal::state()->set('block_test_view_alter_append_pre_render_prefix', TRUE);
$build = $this->getBlockRenderArray();
- $this->assertFalse(isset($build['#prefix']), 'The appended #pre_render callback has not yet run before calling drupal_render().');
- $this->assertIdentical(drupal_render($build), 'Hiya!
');
+ $this->assertFalse(isset($build['#prefix']), 'The appended #pre_render callback has not yet run before rendering.');
+ $this->assertIdentical($this->renderer->renderRoot($build), 'Hiya!
');
$this->assertTrue(isset($build['#prefix']) && $build['#prefix'] === 'Hiya!
', 'A cached block without content is altered.');
// Restore the previous request method.
diff --git a/core/modules/block_content/src/Tests/BlockContentCacheTagsTest.php b/core/modules/block_content/src/Tests/BlockContentCacheTagsTest.php
index 582d832..fe2c789 100644
--- a/core/modules/block_content/src/Tests/BlockContentCacheTagsTest.php
+++ b/core/modules/block_content/src/Tests/BlockContentCacheTagsTest.php
@@ -84,7 +84,7 @@ public function testBlock() {
// Drupal\Core\Render\Renderer.
$request_stack = $this->container->get('request_stack');
$request_stack->push(new Request());
- $this->container->get('renderer')->render($build);
+ $this->container->get('renderer')->renderRoot($build);
$request_stack->pop();
// Expected keys, contexts, and tags for the block.
diff --git a/core/modules/book/src/BookOutline.php b/core/modules/book/src/BookOutline.php
index 484fd2c..c4b4533 100644
--- a/core/modules/book/src/BookOutline.php
+++ b/core/modules/book/src/BookOutline.php
@@ -126,8 +126,7 @@ public function childrenLinks(array $book_link) {
}
if ($children) {
- $elements = $this->bookManager->bookTreeOutput($children);
- return drupal_render($elements);
+ return $this->bookManager->bookTreeOutput($children);
}
return '';
}
diff --git a/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php b/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php
index 949c8e5..0e237b1 100644
--- a/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php
+++ b/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php
@@ -13,6 +13,7 @@
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Render\Element;
+use Drupal\Core\Render\RendererInterface;
use Drupal\editor\Plugin\EditorBase;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\editor\Entity\Editor as EditorEntity;
@@ -56,6 +57,13 @@ class CKEditor extends EditorBase implements ContainerFactoryPluginInterface {
protected $ckeditorPluginManager;
/**
+ * The renderer.
+ *
+ * @var \Drupal\Core\Render\RendererInterface
+ */
+ protected $renderer;
+
+ /**
* Constructs a Drupal\Component\Plugin\PluginBase object.
*
* @param array $configuration
@@ -70,12 +78,15 @@ class CKEditor extends EditorBase implements ContainerFactoryPluginInterface {
* The module handler to invoke hooks on.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
+ * @param \Drupal\Core\Render\RendererInterface $renderer
+ * The renderer.
*/
- public function __construct(array $configuration, $plugin_id, $plugin_definition, CKEditorPluginManager $ckeditor_plugin_manager, ModuleHandlerInterface $module_handler, LanguageManagerInterface $language_manager) {
+ public function __construct(array $configuration, $plugin_id, $plugin_definition, CKEditorPluginManager $ckeditor_plugin_manager, ModuleHandlerInterface $module_handler, LanguageManagerInterface $language_manager, RendererInterface $renderer) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->ckeditorPluginManager = $ckeditor_plugin_manager;
$this->moduleHandler = $module_handler;
$this->languageManager = $language_manager;
+ $this->renderer = $renderer;
}
/**
@@ -88,7 +99,8 @@ public static function create(ContainerInterface $container, array $configuratio
$plugin_definition,
$container->get('plugin.manager.ckeditor.plugin'),
$container->get('module_handler'),
- $container->get('language_manager')
+ $container->get('language_manager'),
+ $container->get('renderer')
);
}
@@ -145,7 +157,7 @@ public function settingsForm(array $form, FormStateInterface $form_state, Editor
'library' => array('ckeditor/drupal.ckeditor.admin'),
'drupalSettings' => [
'ckeditor' => [
- 'toolbarAdmin' => drupal_render($ckeditor_settings_toolbar),
+ 'toolbarAdmin' => $this->renderer->renderPlain($ckeditor_settings_toolbar),
],
],
),
diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index 319067d..8bcac2c 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -462,7 +462,7 @@ function comment_node_update_index(EntityInterface $node, $langcode) {
}
}
}
- return drupal_render($build);
+ return \Drupal::service('renderer')->renderPlain($build);
}
/**
diff --git a/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php b/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php
index 342765f..8e7bf27 100644
--- a/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php
+++ b/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php
@@ -57,6 +57,9 @@ protected function setUp() {
* Tests the bubbling of cache tags.
*/
public function testCacheTags() {
+ /** @var \Drupal\Core\Render\RendererInterface $renderer */
+ $renderer = $this->container->get('renderer');
+
// Create the entity that will be commented upon.
$commented_entity = entity_create('entity_test', array('name' => $this->randomMachineName()));
$commented_entity->save();
@@ -65,11 +68,16 @@ public function testCacheTags() {
$build = \Drupal::entityManager()
->getViewBuilder('entity_test')
->view($commented_entity);
- drupal_render($build);
+ $renderer->renderRoot($build);
$expected_cache_tags = array(
'entity_test_view',
'entity_test:' . $commented_entity->id(),
'comment_list',
+ 'config:core.entity_form_display.comment.comment.default',
+ 'config:field.field.comment.comment.comment_body',
+ '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, 'The test entity has the expected cache tags before it has comments.');
@@ -102,7 +110,7 @@ public function testCacheTags() {
$build = \Drupal::entityManager()
->getViewBuilder('entity_test')
->view($commented_entity);
- drupal_render($build);
+ $renderer->renderRoot($build);
$expected_cache_tags = array(
'entity_test_view',
'entity_test:' . $commented_entity->id(),
@@ -112,6 +120,11 @@ public function testCacheTags() {
'config:filter.format.plain_text',
'user_view',
'user:2',
+ 'config:core.entity_form_display.comment.comment.default',
+ 'config:field.field.comment.comment.comment_body',
+ '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, 'The test entity has the expected cache tags when it has comments.');
diff --git a/core/modules/config/src/Form/ConfigSync.php b/core/modules/config/src/Form/ConfigSync.php
index 2c71254..7f240e3 100644
--- a/core/modules/config/src/Form/ConfigSync.php
+++ b/core/modules/config/src/Form/ConfigSync.php
@@ -21,6 +21,7 @@
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Lock\LockBackendInterface;
use Drupal\Core\Config\StorageComparer;
+use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Url;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -101,6 +102,13 @@ class ConfigSync extends FormBase {
protected $moduleInstaller;
/**
+ * The renderer.
+ *
+ * @var \Drupal\Core\Render\RendererInterface
+ */
+ protected $renderer;
+
+ /**
* Constructs the object.
*
* @param \Drupal\Core\Config\StorageInterface $staging_storage
@@ -123,8 +131,10 @@ class ConfigSync extends FormBase {
* The module installer.
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* The theme handler.
+ * @param \Drupal\Core\Render\RendererInterface
+ * The renderer.
*/
- public function __construct(StorageInterface $staging_storage, StorageInterface $active_storage, StorageInterface $snapshot_storage, LockBackendInterface $lock, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, TypedConfigManagerInterface $typed_config, ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, ThemeHandlerInterface $theme_handler) {
+ public function __construct(StorageInterface $staging_storage, StorageInterface $active_storage, StorageInterface $snapshot_storage, LockBackendInterface $lock, EventDispatcherInterface $event_dispatcher, ConfigManagerInterface $config_manager, TypedConfigManagerInterface $typed_config, ModuleHandlerInterface $module_handler, ModuleInstallerInterface $module_installer, ThemeHandlerInterface $theme_handler, RendererInterface $renderer) {
$this->stagingStorage = $staging_storage;
$this->activeStorage = $active_storage;
$this->snapshotStorage = $snapshot_storage;
@@ -135,6 +145,7 @@ public function __construct(StorageInterface $staging_storage, StorageInterface
$this->moduleHandler = $module_handler;
$this->moduleInstaller = $module_installer;
$this->themeHandler = $theme_handler;
+ $this->renderer = $renderer;
}
/**
@@ -151,7 +162,8 @@ public static function create(ContainerInterface $container) {
$container->get('config.typed'),
$container->get('module_handler'),
$container->get('module_installer'),
- $container->get('theme_handler')
+ $container->get('theme_handler'),
+ $container->get('renderer')
);
}
@@ -209,7 +221,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
'#theme' => 'item_list',
'#items' => $change_list,
);
- $change_list_html = drupal_render($change_list_render);
+ $change_list_html = $this->renderer->renderPlain($change_list_render);
drupal_set_message($this->t('The following items in your active configuration have changes since the last import that may be lost on the next import. !changes', array('!changes' => $change_list_html)), 'warning');
}
}
diff --git a/core/modules/contact/src/MessageViewBuilder.php b/core/modules/contact/src/MessageViewBuilder.php
index 5b94582..a086f7a 100644
--- a/core/modules/contact/src/MessageViewBuilder.php
+++ b/core/modules/contact/src/MessageViewBuilder.php
@@ -65,9 +65,9 @@ public function view(EntityInterface $entity, $view_mode = 'full', $langcode = N
$build[$key]['#label_display'] = 'hidden';
}
}
- $build = array(
- '#markup' => MailFormatHelper::htmlToText(drupal_render($build)),
- );
+ $build['#post_render'][] = function ($html, array $elements) {
+ return MailFormatHelper::htmlToText($html);
+ };
}
return $build;
}
diff --git a/core/modules/contextual/src/ContextualController.php b/core/modules/contextual/src/ContextualController.php
index aeee4a6..975113d 100644
--- a/core/modules/contextual/src/ContextualController.php
+++ b/core/modules/contextual/src/ContextualController.php
@@ -44,7 +44,7 @@ public function render(Request $request) {
'#type' => 'contextual_links',
'#contextual_links' => _contextual_id_to_links($id),
);
- $rendered[$id] = drupal_render($element);
+ $rendered[$id] = $this->container->get('renderer')->renderRoot($element);
}
return new JsonResponse($rendered);
diff --git a/core/modules/datetime/src/Tests/DateTimeFieldTest.php b/core/modules/datetime/src/Tests/DateTimeFieldTest.php
index f3fcae1..c85f71f 100644
--- a/core/modules/datetime/src/Tests/DateTimeFieldTest.php
+++ b/core/modules/datetime/src/Tests/DateTimeFieldTest.php
@@ -490,7 +490,7 @@ protected function renderTestEntity($id, $view_mode = 'full', $reset = TRUE) {
$entity = entity_load('entity_test', $id);
$display = EntityViewDisplay::collectRenderDisplay($entity, $view_mode);
$build = $display->build($entity);
- $output = drupal_render($build);
+ $output = \Drupal::service('renderer')->renderRoot($build);
$this->setRawContent($output);
$this->verbose($output);
}
diff --git a/core/modules/field/field.module b/core/modules/field/field.module
index 88ccff9..d09049e 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -121,7 +121,7 @@ function field_help($route_name, RouteMatchInterface $route_match) {
'#theme' => 'item_list',
'#items' => $items,
);
- $output .= drupal_render($item_list);
+ $output .= \Drupal::service('renderer')->renderPlain($item_list);
$output .= '';
}
diff --git a/core/modules/field/src/Tests/Boolean/BooleanFieldTest.php b/core/modules/field/src/Tests/Boolean/BooleanFieldTest.php
index 647180c..2364a8a 100644
--- a/core/modules/field/src/Tests/Boolean/BooleanFieldTest.php
+++ b/core/modules/field/src/Tests/Boolean/BooleanFieldTest.php
@@ -114,7 +114,7 @@ function testBooleanField() {
$entity = entity_load('entity_test', $id);
$display = entity_get_display($entity->getEntityTypeId(), $entity->bundle(), 'full');
$content = $display->build($entity);
- $this->setRawContent(drupal_render($content));
+ $this->setRawContent(\Drupal::service('renderer')->renderRoot($content));
$this->assertRaw('
' . SafeMarkup::checkPlain($expected_html) . ' | ';
@@ -198,7 +198,7 @@ function testMoreLink() {
);
foreach($elements as $element) {
- $xml = new \SimpleXMLElement(drupal_render($element['value']));
+ $xml = new \SimpleXMLElement(\Drupal::service('renderer')->renderRoot($element['value']));
$result = $xml->xpath($element['expected']);
$this->assertTrue($result, '"' . $element['name'] . '" input rendered correctly by drupal_render().');
}
@@ -229,7 +229,7 @@ function testSystemCompactLink() {
);
foreach ($elements as $element) {
- $xml = new \SimpleXMLElement(drupal_render($element['value']));
+ $xml = new \SimpleXMLElement(\Drupal::service('renderer')->renderRoot($element['value']));
$result = $xml->xpath($element['expected']);
$this->assertTrue($result, '"' . $element['name'] . '" is rendered correctly by drupal_render().');
}
@@ -245,7 +245,7 @@ function testSystemCompactLink() {
'expected' => '//div[@class="compact-link"]/a[contains(@href, "admin/compact?") and text()="Show descriptions"]',
);
- $xml = new \SimpleXMLElement(drupal_render($element['value']));
+ $xml = new \SimpleXMLElement(\Drupal::service('renderer')->renderRoot($element['value']));
$result = $xml->xpath($element['expected']);
$this->assertTrue($result, '"' . $element['name'] . '" is rendered correctly by drupal_render().');
}
diff --git a/core/modules/system/src/Tests/Common/RenderTest.php b/core/modules/system/src/Tests/Common/RenderTest.php
index 66354a5..7ae448a 100644
--- a/core/modules/system/src/Tests/Common/RenderTest.php
+++ b/core/modules/system/src/Tests/Common/RenderTest.php
@@ -39,7 +39,7 @@ function testDrupalRenderThemePreprocessAttached() {
'#markup' => 'Kittens!',
],
];
- drupal_render($test_element);
+ \Drupal::service('renderer')->renderRoot($test_element);
$expected_attached = [
'library' => [
diff --git a/core/modules/system/src/Tests/Common/UrlTest.php b/core/modules/system/src/Tests/Common/UrlTest.php
index c095676..93f0503 100644
--- a/core/modules/system/src/Tests/Common/UrlTest.php
+++ b/core/modules/system/src/Tests/Common/UrlTest.php
@@ -63,7 +63,7 @@ function testLinkCacheability() {
'#options' => $options,
'#url' => Url::fromUri($uri),
];
- drupal_render($link);
+ \Drupal::service('renderer')->renderRoot($link);
$this->pass($title);
$this->assertEqual($expected_cacheability, $link['#cache']);
}
@@ -73,6 +73,9 @@ function testLinkCacheability() {
* Tests that default and custom attributes are handled correctly on links.
*/
function testLinkAttributes() {
+ /** @var \Drupal\Core\Render\RendererInterface $renderer */
+ $renderer = $this->container->get('renderer');
+
// Test that hreflang is added when a link has a known language.
$language = new Language(array('id' => 'fr', 'name' => 'French'));
$hreflang_link = array(
@@ -90,10 +93,10 @@ function testLinkAttributes() {
$hreflang_override_link = $hreflang_link;
$hreflang_override_link['#options']['attributes']['hreflang'] = 'foo';
- $rendered = drupal_render($hreflang_link);
+ $rendered = $renderer->renderRoot($hreflang_link);
$this->assertTrue($this->hasAttribute('hreflang', $rendered, $langcode), format_string('hreflang attribute with value @langcode is present on a rendered link when langcode is provided in the render array.', array('@langcode' => $langcode)));
- $rendered = drupal_render($hreflang_override_link);
+ $rendered = $renderer->renderRoot($hreflang_override_link);
$this->assertTrue($this->hasAttribute('hreflang', $rendered, 'foo'), format_string('hreflang attribute with value @hreflang is present on a rendered link when @hreflang is provided in the render array.', array('@hreflang' => 'foo')));
// Test the active class in links produced by _l() and #type 'link'.
@@ -149,7 +152,7 @@ function testLinkAttributes() {
),
),
);
- $link_theme = drupal_render($type_link);
+ $link_theme = $renderer->renderRoot($type_link);
$this->assertTrue($this->hasAttribute('class', $link_theme, $class_theme), format_string('Custom class @class is present on link when requested by #type', array('@class' => $class_theme)));
}
@@ -157,6 +160,9 @@ function testLinkAttributes() {
* Tests that link functions support render arrays as 'text'.
*/
function testLinkRenderArrayText() {
+ /** @var \Drupal\Core\Render\RendererInterface $renderer */
+ $renderer = $this->container->get('renderer');
+
// Build a link with _l() for reference.
$l = \Drupal::l('foo', Url::fromUri('https://www.drupal.org'));
@@ -171,7 +177,7 @@ function testLinkRenderArrayText() {
'#title' => 'foo',
'#url' => Url::fromUri('https://www.drupal.org'),
);
- $type_link_plain = drupal_render($type_link_plain_array);
+ $type_link_plain = $renderer->renderRoot($type_link_plain_array);
$this->assertEqual($type_link_plain, $l);
// Build a themed link with renderable 'text'.
@@ -180,7 +186,7 @@ function testLinkRenderArrayText() {
'#title' => array('#markup' => 'foo'),
'#url' => Url::fromUri('https://www.drupal.org'),
);
- $type_link_nested = drupal_render($type_link_nested_array);
+ $type_link_nested = $renderer->renderRoot($type_link_nested_array);
$this->assertEqual($type_link_nested, $l);
}
diff --git a/core/modules/system/src/Tests/Entity/EntityTranslationTest.php b/core/modules/system/src/Tests/Entity/EntityTranslationTest.php
index 7d513eb..138794f 100644
--- a/core/modules/system/src/Tests/Entity/EntityTranslationTest.php
+++ b/core/modules/system/src/Tests/Entity/EntityTranslationTest.php
@@ -559,6 +559,9 @@ function testLanguageFallback() {
* The entity type to run the tests with.
*/
protected function doTestLanguageFallback($entity_type) {
+ /** @var \Drupal\Core\Render\RendererInterface $renderer */
+ $renderer = $this->container->get('renderer');
+
$current_langcode = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId();
$this->langcodes[] = $current_langcode;
@@ -609,16 +612,16 @@ protected function doTestLanguageFallback($entity_type) {
// Get an view builder.
$controller = $this->entityManager->getViewBuilder($entity_type);
$entity2_build = $controller->view($entity2);
- $entity2_output = drupal_render($entity2_build);
+ $entity2_output = $renderer->renderRoot($entity2_build);
$translation = $this->entityManager->getTranslationFromContext($entity2, $default_langcode);
$translation_build = $controller->view($translation);
- $translation_output = drupal_render($translation_build);
+ $translation_output = $renderer->renderRoot($translation_build);
$this->assertIdentical($entity2_output, $translation_output, 'When the entity has no translation no fallback is applied.');
// Checks that entity translations are rendered properly.
$controller = $this->entityManager->getViewBuilder($entity_type);
$build = $controller->view($entity);
- drupal_render($build);
+ $renderer->renderRoot($build);
$this->assertEqual($build['label']['#markup'], $values[$current_langcode]['name'], 'By default the entity is rendered in the current language.');
$langcodes = array_combine($this->langcodes, $this->langcodes);
@@ -630,7 +633,7 @@ protected function doTestLanguageFallback($entity_type) {
// Unset the #cache key so that a fresh render is produced with each pass,
// making the renderable array keys available to compare.
unset($build['#cache']);
- drupal_render($build);
+ $renderer->renderRoot($build);
$this->assertEqual($build['label']['#markup'], $values[$expected]['name'], 'The entity is rendered in the expected language.');
}
}
diff --git a/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php b/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php
index 333dcda..a7aa63d 100644
--- a/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php
+++ b/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php
@@ -46,6 +46,8 @@ protected function setUp() {
* Tests entity render cache handling.
*/
public function testEntityViewBuilderCache() {
+ /** @var \Drupal\Core\Render\RendererInterface $renderer */
+ $renderer = $this->container->get('renderer');
$cache_contexts_manager = \Drupal::service("cache_contexts_manager");
// Force a request via GET so we can get drupal_render() cache working.
@@ -72,7 +74,7 @@ public function testEntityViewBuilderCache() {
$build['#markup'] = 'entity_render_test';
// Test that a cache entry is created.
- drupal_render($build);
+ $renderer->renderRoot($build);
$this->assertTrue($this->container->get('cache.' . $bin)->get($cid), 'The entity render element has been cached.');
// Re-save the entity and check that the cache entry has been deleted.
@@ -82,7 +84,7 @@ public function testEntityViewBuilderCache() {
// Rebuild the render array (creating a new cache entry in the process) and
// delete the entity to check the cache entry is deleted.
unset($build['#printed']);
- drupal_render($build);
+ $renderer->renderRoot($build);
$this->assertTrue($this->container->get('cache.' . $bin)->get($cid), 'The entity render element has been cached.');
$entity_test->delete();
$this->assertFalse($this->container->get('cache.' . $bin)->get($cid), 'The entity render cache has been cleared when the entity was deleted.');
@@ -95,6 +97,8 @@ public function testEntityViewBuilderCache() {
* Tests entity render cache with references.
*/
public function testEntityViewBuilderCacheWithReferences() {
+ /** @var \Drupal\Core\Render\RendererInterface $renderer */
+ $renderer = $this->container->get('renderer');
$cache_contexts_manager = \Drupal::service("cache_contexts_manager");
// Force a request via GET so we can get drupal_render() cache working.
@@ -120,7 +124,7 @@ public function testEntityViewBuilderCacheWithReferences() {
// Mock the build array to not require the theme registry.
unset($build['#theme']);
$build['#markup'] = 'entity_render_test';
- drupal_render($build);
+ $renderer->renderRoot($build);
// Test that a cache entry was created for the referenced entity.
$this->assertTrue($this->container->get('cache.' . $bin_reference)->get($cid_reference), 'The entity render element for the referenced entity has been cached.');
@@ -139,7 +143,7 @@ public function testEntityViewBuilderCacheWithReferences() {
// Mock the build array to not require the theme registry.
unset($build['#theme']);
$build['#markup'] = 'entity_render_test';
- drupal_render($build);
+ $renderer->renderRoot($build);
// Test that a cache entry is created.
$this->assertTrue($this->container->get('cache.' . $bin)->get($cid), 'The entity render element has been cached.');
@@ -180,6 +184,9 @@ public function testEntityViewBuilderCacheToggling() {
* Tests weighting of display components.
*/
public function testEntityViewBuilderWeight() {
+ /** @var \Drupal\Core\Render\RendererInterface $renderer */
+ $renderer = $this->container->get('renderer');
+
// Set a weight for the label component.
entity_get_display('entity_test', 'entity_test', 'full')
->setComponent('label', array('weight' => 20))
@@ -188,7 +195,7 @@ public function testEntityViewBuilderWeight() {
// Create and build a test entity.
$entity_test = $this->createTestEntity('entity_test');
$view = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
- drupal_render($view);
+ $renderer->renderRoot($view);
// Check that the weight is respected.
$this->assertEqual($view['label']['#weight'], 20, 'The weight of a display component is respected.');
diff --git a/core/modules/system/src/Tests/Form/CheckboxTest.php b/core/modules/system/src/Tests/Form/CheckboxTest.php
index da53a79..13b7d07 100644
--- a/core/modules/system/src/Tests/Form/CheckboxTest.php
+++ b/core/modules/system/src/Tests/Form/CheckboxTest.php
@@ -33,7 +33,7 @@ function testFormCheckbox() {
// @see \Drupal\Core\Render\Element\Checkbox::processCheckbox().
foreach (array('0', '', 1, '1', 'foobar', '1foobar') as $return_value) {
$form_array = \Drupal::formBuilder()->getForm('\Drupal\form_test\Form\FormTestCheckboxTypeJugglingForm', $default_value, $return_value);
- $form = drupal_render($form_array);
+ $form = \Drupal::service('renderer')->renderRoot($form_array);
if ($default_value === TRUE) {
$checked = TRUE;
}
diff --git a/core/modules/system/src/Tests/Form/FormTest.php b/core/modules/system/src/Tests/Form/FormTest.php
index be1fc26..910000b 100644
--- a/core/modules/system/src/Tests/Form/FormTest.php
+++ b/core/modules/system/src/Tests/Form/FormTest.php
@@ -124,7 +124,7 @@ function testRequiredFields() {
// when you try to render them like this, so we ignore those for
// testing the required marker.
// @todo Fix this work-around (https://www.drupal.org/node/588438).
- $form_output = ($type == 'radios') ? '' : drupal_render($form);
+ $form_output = ($type == 'radios') ? '' : \Drupal::service('renderer')->renderRoot($form);
if ($required) {
// Make sure we have a form error for this element.
$this->assertTrue(isset($errors[$element]), "Check empty($key) '$type' field '$element'");
diff --git a/core/modules/system/src/Tests/Theme/FunctionsTest.php b/core/modules/system/src/Tests/Theme/FunctionsTest.php
index a9a3dae..0ed135d 100644
--- a/core/modules/system/src/Tests/Theme/FunctionsTest.php
+++ b/core/modules/system/src/Tests/Theme/FunctionsTest.php
@@ -347,7 +347,7 @@ function testDrupalPreRenderLinks() {
// thing. We expect a single