diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php index 1b0811f..560ebbb 100644 --- a/core/lib/Drupal/Core/Render/Renderer.php +++ b/core/lib/Drupal/Core/Render/Renderer.php @@ -37,7 +37,7 @@ class Renderer { /** * Constructs a new Renderer. * - * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver . + * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver * The controller resolver. * @param \Drupal\Core\Theme\ThemeManagerInterface $theme * The theme manager. @@ -53,40 +53,41 @@ public function __construct(ControllerResolverInterface $controller_resolver, T /** * 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. + * 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 the bubbling of rendering metadata: cache - * tags, attached assets and #post_render_cache metadata all need to be bubbled - * up. That information is needed once the rendering to a HTML string is - * completed: the resulting HTML for the page must know by which cache tags it - * should be invalidated, which (CSS and JavaScript) assets must be loaded, and - * which #post_render_cache callbacks should be executed. A stack data structure - * is used to perform this bubbling. + * An important aspect of rendering is the bubbling of rendering metadata: + * cache tags, attached assets and #post_render_cache metadata all need to be + * bubbled up. That information is needed once the rendering to a HTML string + * is completed: the resulting HTML for the page must know by which cache tags + * it should be invalidated, which (CSS and JavaScript) assets must be loaded, + * and which #post_render_cache callbacks should be executed. A stack data + * structure is used to perform this bubbling. * * The process of rendering an element is recursive unless the element defines - * an implemented theme hook in #theme. During each call to drupal_render(), the - * outermost renderable array (also known as an "element") is processed using - * the following steps: + * 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 stack data structure has been created yet, it is done now. Next, * an empty \Drupal\Core\Render\RenderStackFrame is pushed onto the stack. * - If this element has #cache defined then the cached markup for this - * element will be returned if it exists in drupal_render()'s cache. To use - * drupal_render() caching, set the element's #cache property to an + * 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. - * Cache keys may either be static (just strings) or tokens (placeholders - * that are converted to static keys by the @cache_contexts service, - * depending on the request). See drupal_render_cid_create(). + * Cache keys may either be static (just strings) or tokens + * (placeholders that are converted to static keys by the + * @cache_contexts service, depending on the request). See + * drupal_render_cid_create(). * - 'cid': Specify the cache ID directly. Either 'keys' or 'cid' is * required. If 'cid' is set, 'keys' is ignored. Use only if you have * special requirements. @@ -97,56 +98,64 @@ public function __construct(ControllerResolverInterface $controller_resolver, T * 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. + * frame is bubbled: the two topmost frames are popped from the stack, + * they are merged, and the result is pushed back onto the stack. * - 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 in hook_element_info(), * are merged into the array. #defaults_loaded is set by functions that - * process render arrays and call element_info() before passing the array to - * drupal_render(), such as form_builder() in the Form API. + * process render arrays and call element_info() before passing the array + * to Renderer::render(), such as form_builder() in the Form API. * - If this element has an array of #pre_render functions defined, they are - * called sequentially to modify the element before rendering. After all the - * #pre_render functions have been called, #printed is checked a second time - * in case a #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 drupal_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: + * called sequentially to modify the element before rendering. After all + * the #pre_render functions have been called, #printed is checked a + * second time in case a #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 _theme() is called and must render both the element - * and its children. If #render_children is set, _theme() will not be - * called. #render_children is usually only set internally by _theme() so - * that we can avoid the situation where drupal_render() called from + * 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 - * drupal_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. + * 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_process_states(). + * - If this element has #states defined then JavaScript state information + * is added to this element's #attached attribute by + * drupal_process_states(). * - If this element has #attached defined then any required libraries, * JavaScript, CSS, or other custom data are added to the current page by * drupal_process_attached(). * - 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 _theme() successively for each item in + * #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. + * 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: * @code * array( @@ -168,60 +177,61 @@ public function __construct(ControllerResolverInterface $controller_resolver, T * ), * ); * @endcode - * - 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. + * - 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 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 + * 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. + * 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 drupal_render()'s internal cache. This includes the changes - * made by #post_render. - * - If this element has an array of #post_render_cache functions defined, or - * any of its children has (which we would know thanks to the stack having - * been updated just before the render caching step), they are called - * sequentially to replace placeholders in the final #markup and extend - * #attached. Placeholders must contain a unique token, to guarantee that - * e.g. samples of placeholders are not replaced also. - * But, since #post_render_cache callbacks add attach additional assets, the + * is saved to Renderer::render()'s internal cache. This includes the + * changes made by #post_render. + * - If this element has an array of #post_render_cache functions defined, + * or any of its children has (which we would know thanks to the stack + * having been updated just before the render caching step), they are + * called sequentially to replace placeholders in the final #markup and + * extend #attached. Placeholders must contain a unique token, to + * guarantee that e.g. samples of placeholders are not replaced also. But, + * since #post_render_cache callbacks add attach additional assets, the * correct bubbling of those must once again be taken into account. This - * final stage of rendering should be considered as if it were the parent of - * the current element, because it takes that as its input, and then alters - * its #markup. Hence, just before calling the #post_render_cache callbacks, - * a new empty frame is pushed onto the stack, where all assets #attached - * during the execution of those callbacks will end up in. Then, after the - * execution of those callbacks, we merge that back into the element. - * Note that these callbacks run always: when hitting the render cache, when - * missing, or when render caching is not used at all. This is done to allow - * any Drupal module to customize other render arrays without breaking the - * render cache if it is enabled, and to not require it to use other logic - * when render caching is disabled. - * - 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 this element e.g. 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. + * final stage of rendering should be considered as if it were the parent + * of the current element, because it takes that as its input, and then + * alters its #markup. Hence, just before calling the #post_render_cache + * callbacks, a new empty frame is pushed onto the stack, where all assets + * #attached during the execution of those callbacks will end up in. Then, + * after the execution of those callbacks, we merge that back into the + * element. Note that these callbacks run always: when hitting the render + * cache, when missing, or when render caching is not used at all. This is + * done to allow any Drupal module to customize other render arrays + * without breaking the render cache if it is enabled, and to not require + * it to use other logic when render caching is disabled. + * - 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 this element e.g. 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. + * - The final value of #children for this element is returned as the + * rendered output. * * @param array $elements * The structured array describing the data to be rendered. @@ -288,23 +298,24 @@ public function render(&$elements, $is_recursive_call = FALSE) { } $stack->push(new RenderStackFrame()); - // Try to fetch the prerendered element from cache, run any #post_render_cache - // callbacks and return the final markup. + // Try to fetch the prerendered element from cache, run any + // #post_render_cache callbacks and return the final markup. if (isset($elements['#cache'])) { $cached_element = drupal_render_cache_get($elements); if ($cached_element !== FALSE) { $elements = $cached_element; - // Only when we're not in a recursive drupal_render() call, - // #post_render_cache callbacks must be executed, to prevent breaking the - // render cache in case of nested elements with #cache set. + // Only when we're not in a recursive Renderer::render() call, + // #post_render_cache callbacks must be executed, to prevent breaking + // the render cache in case of nested elements with #cache set. if (!$is_recursive_call) { $this->processPostRenderCache($elements); } $elements['#markup'] = SafeMarkup::set($elements['#markup']); - // The render cache item contains all the bubbleable rendering metadata for - // the subtree. + // The render cache item contains all the bubbleable rendering metadata + // for the subtree. $update_stack($elements); - // Render cache hit, so rendering is finished, all necessary info collected! + // Render cache hit, so rendering is finished, all necessary info + // collected! $bubble_stack(); return $elements['#markup']; } @@ -351,8 +362,8 @@ public function render(&$elements, $is_recursive_call = FALSE) { // Get the children of the element, sorted by weight. $children = Element::children($elements, TRUE); - // Initialize this element's #children, unless a #pre_render callback already - // preset #children. + // Initialize this element's #children, unless a #pre_render callback + // already preset #children. if (!isset($elements['#children'])) { $elements['#children'] = ''; } @@ -372,15 +383,15 @@ public function render(&$elements, $is_recursive_call = FALSE) { if ($theme_is_implemented && !isset($elements['#render_children'])) { $elements['#children'] = $this->theme->render($elements['#theme'], $elements); - // If _theme() returns FALSE this means that the hook in #theme was not - // found in the registry and so we need to update our flag accordingly. This - // is common for theme suggestions. + // If ThemeManagerInterface::render() returns FALSE this means that the + // hook in #theme was not found in the registry and so we need to update + // our flag accordingly. This is common for theme suggestions. $theme_is_implemented = ($elements['#children'] !== FALSE); } - // If #theme is not implemented or #render_children is set and the element has - // an empty #children attribute, render the children now. This is the same - // process as drupal_render_children() but is inlined for speed. + // If #theme is not implemented or #render_children is set and the element + // has an empty #children attribute, render the children now. This is the + // 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->render($elements[$key], TRUE); @@ -401,19 +412,20 @@ public function render(&$elements, $is_recursive_call = FALSE) { // Let the theme functions in #theme_wrappers add markup around the rendered // children. - // #states and #attached have to be processed before #theme_wrappers, because - // the #type 'page' render array from drupal_prepare_page() would render the - // $page and wrap it into the html.html.twig template without the attached - // assets otherwise. + // #states and #attached have to be processed before #theme_wrappers, + // because the #type 'page' render array from drupal_prepare_page() would + // render the $page and wrap it into the html.html.twig template without the + // attached assets otherwise. // If the internal #render_children property is set, do not call the // #theme_wrappers function(s) to prevent infinite recursion. if (isset($elements['#theme_wrappers']) && !isset($elements['#render_children'])) { foreach ($elements['#theme_wrappers'] as $key => $value) { - // If the value of a #theme_wrappers item is an array then the theme hook - // is found in the key of the item and the value contains attribute - // overrides. Attribute overrides replace key/value pairs in $elements for - // only this _theme() call. This allows #theme hooks and #theme_wrappers - // hooks to share variable names without conflict or ambiguity. + // If the value of a #theme_wrappers item is an array then the theme + // hook is found in the key of the item and the value contains attribute + // overrides. Attribute overrides replace key/value pairs in $elements + // for only this ThemeManagerInterface::render() call. This allows + // #theme hooks and #theme_wrappers hooks to share variable names + // without conflict or ambiguity. $wrapper_elements = $elements; if (is_string($key)) { $wrapper_hook = $key; @@ -429,9 +441,9 @@ public function render(&$elements, $is_recursive_call = FALSE) { } } - // Filter the outputted content and make any last changes before the - // content is sent to the browser. The changes are made on $content - // which allows the outputted text to be filtered. + // Filter the outputted content and make any last changes before the content + // is sent to the browser. The changes are made on $content which allows the + // outputted text to be filtered. if (isset($elements['#post_render'])) { foreach ($elements['#post_render'] as $callable) { if (is_string($callable) && strpos($callable, '::') === FALSE) { @@ -457,7 +469,7 @@ public function render(&$elements, $is_recursive_call = FALSE) { drupal_render_cache_set($elements['#markup'], $elements); } - // Only when we're not in a recursive drupal_render() call, + // Only when we're not in a recursive Renderer::render() call, // #post_render_cache callbacks must be executed, to prevent breaking the // render cache in case of nested elements with #cache set. // @@ -465,11 +477,11 @@ public function render(&$elements, $is_recursive_call = FALSE) { // - 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 drupal_render(). + // that is handled earlier in Renderer::render(). if (!$is_recursive_call) { // We've already called $update_stack() earlier, which updated both the // element and current stack frame. However, - // _drupal_render_process_post_render_cache() can both change the element + // Renderer::processPostRenderCache() can both change the element // 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. @@ -498,13 +510,12 @@ public function render(&$elements, $is_recursive_call = FALSE) { * - #attached: to add libraries or JavaScript settings * * Note that in either of these cases, #post_render_cache callbacks are - * implicitly idempotent: a placeholder that has been replaced can't be replaced - * again, and duplicate attachments are ignored. + * implicitly idempotent: a placeholder that has been replaced can't be + * replaced again, and duplicate attachments are ignored. * * @param array &$elements * The structured array describing the data being rendered. * - * @see drupal_render() * @see drupal_render_collect_post_render_cache */ protected function processPostRenderCache(array &$elements) {