You are here

public function Renderer::render in Drupal 8

Same name and namespace in other branches
  1. 9 core/lib/Drupal/Core/Render/Renderer.php \Drupal\Core\Render\Renderer::render()

Renders HTML given a structured array tree.

Renderable arrays have two kinds of key/value pairs: properties and children. Properties have keys starting with '#' and their values influence how the array will be rendered. Children are all elements whose keys do not start with a '#'. Their values should be renderable arrays themselves, which will be rendered during the rendering of the parent array. The markup provided by the children is typically inserted into the markup generated by the parent array.

An important aspect of rendering is caching the result, when appropriate. Because the HTML of a rendered item includes all of the HTML of the rendered children, caching it requires certain information to bubble up from child elements to their parents:

  • Cache contexts, so that the render cache is varied by every context that affects the rendered HTML. Because cache contexts affect the cache ID, and therefore must be resolved for cache hits as well as misses, it is up to the implementation of this interface to decide how to implement the caching of items whose children specify cache contexts not directly specified by the parent. \Drupal\Core\Render\Renderer implements this with a lazy two-tier caching strategy. Alternate strategies could be to not cache such parents at all or to cache them with the child elements replaced by placeholder tokens that are dynamically rendered after cache retrieval.
  • Cache tags, so that cached renderings are invalidated when site content 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 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, and this must also bubble from child to parent. Therefore, \Drupal\Core\Render\BubbleableMetadata includes that data as well.

The process of rendering an element is recursive unless the element defines an implemented theme hook in #theme. During each call to Renderer::render(), the outermost renderable array (also known as an "element") is processed using the following steps:

  • 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 render context is set yet, an exception is thrown. Otherwise, an empty \Drupal\Core\Render\BubbleableMetadata is pushed onto the 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 associative array with one or several of the following keys:

    • 'keys': An array of one or more keys that identify the element. If 'keys' is set, the cache ID is created automatically from these keys.
    • 'contexts': An array of one or more cache context IDs. These are converted to a final value depending on the request. (For instance, 'user' is mapped to the current user's ID.)
    • 'max-age': A time in seconds. Zero seconds means it is not cacheable. \Drupal\Core\Cache\Cache::PERMANENT means it is cacheable forever.
    • 'bin': Specify a cache bin to cache the element in. Default is 'default'.

    When there is a render cache hit, there is no rendering work left to be done, so the stack must be updated. The empty (and topmost) frame that was just pushed onto the stack is updated with all bubbleable rendering metadata from the element retrieved from render cache. Then, this stack frame is bubbled: the two topmost frames are popped from the stack, they are merged, and the result is pushed back onto the stack. However, also in case of a cache miss we have to do something. Note that a Renderer renders top-down, which means that we try to render a parent first, and we try to avoid the work of rendering the children by using the render cache. Though in this case, we are dealing with a cache miss. So a Renderer traverses down the tree, rendering all children. In doing so, the render stack is updated with the bubbleable metadata of the children. That means that once the children are rendered, we can render cache this element. But the cache ID may have *changed* at that point, because the children's cache contexts have been bubbled! It is for that case that we must store the current (pre-bubbling) cache ID, so that we can compare it with the new (post-bubbling) cache ID when writing to the cache. We store the current cache ID in $pre_bubbling_cid.

  • If this element has #type defined and the default attributes for this element have not already been merged in (#defaults_loaded = TRUE) then the defaults for this type of element, defined by an element plugin, are merged into the array. #defaults_loaded is set by functions that process render arrays and call the element info service before passing the array to Renderer::render(), such as form_builder() in the Form API.
  • If this element has #create_placeholder set to TRUE, and it has a #lazy_builder callback, then the element is replaced with another element that has only two properties: #markup and #attached. #markup will contain placeholder markup, and #attached contains the placeholder metadata, that will be used for replacing this placeholder. That metadata contains a very compact render array (containing only #lazy_builder and #cache) that will be rendered to replace the placeholder with its final markup. This means that when the #lazy_builder callback is called, it received a render array to add to that only contains #cache.
  • If this element has a #lazy_builder or an array of #pre_render functions defined, they are called sequentially to modify the element before rendering. #lazy_builder is preferred, since it allows for placeholdering (see previous step), but #pre_render is still supported. Both have their use case: #lazy_builder is for building a render array, #pre_render is for decorating an existing render array. After the #lazy_builder function is called, #lazy_builder is removed, and #built is set to TRUE. After the #lazy_builder and all #pre_render functions have been called, #printed is checked a second time in case a #lazy_builder or #pre_render function flags the element as printed. If #printed is set, we return early and hence no rendering work is left to be done, similarly to a render cache hit. Once again, the empty (and topmost) frame that was just pushed onto the stack is updated with all bubbleable rendering metadata from the element whose #printed = TRUE. Then, this stack frame is bubbled: the two topmost frames are popped from the stack, they are merged, and the result is pushed back onto the stack.
  • The child elements of this element are sorted by weight using uasort() in \Drupal\Core\Render\Element::children(). Since this is expensive, when passing already sorted elements to Renderer::render(), for example from a database query, set $elements['#sorted'] = TRUE to avoid sorting them a second time.
  • The main render phase to produce #children for this element takes place:

    • If this element has #theme defined and #theme is an implemented theme hook/suggestion then ThemeManagerInterface::render() is called and must render both the element and its children. If #render_children is set, ThemeManagerInterface::render() will not be called. #render_children is usually only set internally by ThemeManagerInterface::render() so that we can avoid the situation where Renderer::render() called from within a theme preprocess function creates an infinite loop.
    • If this element does not have a defined #theme, or the defined #theme hook is not implemented, or #render_children is set, then Renderer::render() is called recursively on each of the child elements of this element, and the result of each is concatenated onto #children. This is skipped if #children is not empty at this point.
    • Once #children has been rendered for this element, if #theme is not implemented and #markup is set for this element, #markup will be prepended to #children.
  • If this element has #states defined then JavaScript state information is added to this element's #attached attribute by \Drupal\Core\Form\FormHelper::processStates().
  • If this element has #attached defined then any required libraries, JavaScript, CSS, or other custom data are added to the current page by \Drupal\Core\Render\AttachmentsResponseProcessorInterface::processAttachments().
  • If this element has an array of #theme_wrappers defined and #render_children is not set, #children is then re-rendered by passing the element in its current state to ThemeManagerInterface::render() successively for each item in #theme_wrappers. Since #theme and #theme_wrappers hooks often define variables with the same names it is possible to explicitly override each attribute passed to each #theme_wrappers hook by setting the hook name as the key and an array of overrides as the value in #theme_wrappers array. For example, if we have a render element as follows:
array(
  '#theme' => 'image',
  '#attributes' => array(
    'class' => array(
      'foo',
    ),
  ),
  '#theme_wrappers' => array(
    'container',
  ),
);

and we need to pass the class 'bar' as an attribute for 'container', we can rewrite our element thus:

array(
  '#theme' => 'image',
  '#attributes' => array(
    'class' => array(
      'foo',
    ),
  ),
  '#theme_wrappers' => array(
    'container' => array(
      '#attributes' => array(
        'class' => array(
          'bar',
        ),
      ),
    ),
  ),
);
  • If this element has an array of #post_render functions defined, they are called sequentially to modify the rendered #children. Unlike #pre_render functions, #post_render functions are passed both the rendered #children attribute as a string and the element itself.
  • If this element has #prefix and/or #suffix defined, they are concatenated to #children.
  • The rendering of this element is now complete. The next step will be render caching. So this is the perfect time to update the stack. At this point, children of this element (if any), have been rendered also, and if there were any, their bubbleable rendering metadata will have been bubbled up into the stack frame for the element that is currently being rendered. The render cache item for this element must contain the bubbleable rendering metadata for this element and all of its children. However, right now, the topmost stack frame (the one for this element) currently only contains the metadata for the children. Therefore, the topmost stack frame is updated with this element's metadata, and then the element's metadata is replaced with the metadata in the topmost stack frame. This element now contains all bubbleable rendering metadata for this element and all its children, so it's now ready for render caching.
  • If this element has #cache defined, the rendered output of this element is saved to Renderer::render()'s internal cache. This includes the changes made by #post_render. At the same time, if $pre_bubbling_cid is set, it is compared to the calculated cache ID. If they are different, then a redirecting cache item is created, containing the #cache metadata of the current element, and written to cache using the value of $pre_bubbling_cid as the cache ID. This ensures the pre-bubbling ("wrong") cache ID redirects to the post-bubbling ("right") cache ID.
  • If this element also has #cache_properties defined, all the array items matching the specified property names will be cached along with the element markup. If properties include children names, the system assumes only children's individual markup is relevant and ignores the parent markup. This approach is normally not needed and should be adopted only when dealing with very advanced use cases.
  • If this element has attached placeholders ([#attached][placeholders]), or any of its children has (which we would know thanks to the stack having been updated just before the render caching step), its placeholder element containing a #lazy_builder function is rendered in isolation. The resulting markup is used to replace the placeholder, and any bubbleable metadata is merged. Placeholders must be unique, to guarantee that for instance, samples of placeholders are not replaced as well.
  • Just before finishing the rendering of this element, this element's stack frame (the topmost one) is bubbled: the two topmost frames are popped from the stack, they are merged and the result is pushed back onto the stack. So if for instance this element was a child element, then a new frame was pushed onto the stack element at the beginning of rendering this element, it was updated when the rendering was completed, and now we merge it with the frame for the parent, so that the parent now has the bubbleable rendering metadata for its child.
  • #printed is set to TRUE for this element to ensure that it is only rendered once.
  • The final value of #children for this element is returned as the rendered output.

Parameters

array $elements: The structured array describing the data to be rendered.

bool $is_root_call: (Internal use only.) Whether this is a recursive call or not. See ::renderRoot().

Return value

\Drupal\Component\Render\MarkupInterface The rendered HTML.

Throws

\LogicException When called outside of a render context (i.e. outside of a renderRoot(), renderPlain() or executeInRenderContext() call).

\Exception If a #pre_render callback throws an exception, it is caught to mark the renderer as no longer being in a root render call, if any. Then the exception is rethrown.

Overrides RendererInterface::render

See also

\Drupal\Core\Render\ElementInfoManagerInterface::getInfo()

\Drupal\Core\Theme\ThemeManagerInterface::render()

\Drupal\Core\Form\FormHelper::processStates()

\Drupal\Core\Render\AttachmentsResponseProcessorInterface::processAttachments()

\Drupal\Core\Render\RendererInterface::renderRoot()

2 calls to Renderer::render()
Renderer::renderPlain in core/lib/Drupal/Core/Render/Renderer.php
Renders final HTML in situations where no assets are needed.
Renderer::renderRoot in core/lib/Drupal/Core/Render/Renderer.php
Renders final HTML given a structured array tree.

File

core/lib/Drupal/Core/Render/Renderer.php, line 187

Class

Renderer
Turns a render array into a HTML string.

Namespace

Drupal\Core\Render

Code

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);
  } catch (\Exception $e) {

    // Mark the ::rootRender() call finished due to this exception & re-throw.
    $this->isRenderingRoot = FALSE;
    throw $e;
  }
}