diff --git a/core/core.services.yml b/core/core.services.yml index 43c7948..ff9ee96 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -667,7 +667,7 @@ services: - { name: event_subscriber } main_content_renderer.html: class: Drupal\Core\Render\MainContent\HtmlRenderer - arguments: ['@title_resolver', '@plugin.manager.display_variant', '@event_dispatcher', '@module_handler'] + arguments: ['@title_resolver', '@plugin.manager.display_variant', '@event_dispatcher', '@module_handler', '@renderer'] tags: - { name: render.main_content_renderer, format: html } main_content_renderer.ajax: diff --git a/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php b/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php index 2b55e24..156e749 100644 --- a/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php +++ b/core/lib/Drupal/Core/Entity/Controller/EntityViewController.php @@ -11,6 +11,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Render\RendererInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -26,13 +27,23 @@ class EntityViewController implements ContainerInjectionInterface { protected $entityManager; /** + * The renderer service. + * + * @var \Drupal\Core\Render\RendererInterface + */ + protected $renderer; + + /** * Creates an EntityViewController object. * * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager. + * @param \Drupal\Core\Render\RendererInterface $renderer + * The renderer service. */ - public function __construct(EntityManagerInterface $entity_manager) { + public function __construct(EntityManagerInterface $entity_manager, RendererInterface $renderer) { $this->entityManager = $entity_manager; + $this->renderer = $renderer; } /** @@ -40,7 +51,8 @@ public function __construct(EntityManagerInterface $entity_manager) { */ public static function create(ContainerInterface $container) { return new static( - $container->get('entity.manager') + $container->get('entity.manager'), + $container->get('renderer') ); } @@ -79,7 +91,7 @@ public function view(EntityInterface $_entity, $view_mode = 'full', $langcode = $build = $this->entityManager->getTranslationFromContext($_entity) ->get($label_field) ->view($view_mode); - $page['#title'] = drupal_render($build); + $page['#title'] = $this->renderer->render($build); } } diff --git a/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php b/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php index 9b414bb..ea1d0b4 100644 --- a/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php +++ b/core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php @@ -22,10 +22,10 @@ class BareHtmlPageRenderer implements BareHtmlPageRendererInterface { /** * Constructs a new BareHtmlPageRenderer. * - * @param \Drupal\Core\Render\Renderer $renderer + * @param \Drupal\Core\Render\RendererInterface $renderer * The renderer service. */ - public function __construct(Renderer $renderer) { + public function __construct(RendererInterface $renderer) { $this->renderer = $renderer; } diff --git a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php index 4bd0f2e..f68e46a 100644 --- a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php +++ b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php @@ -14,6 +14,7 @@ use Drupal\Core\Display\PageVariantInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Render\PageDisplayVariantSelectionEvent; +use Drupal\Core\Render\RendererInterface; use Drupal\Core\Render\RenderEvents; use Drupal\Core\Routing\RouteMatchInterface; use Symfony\Component\DependencyInjection\ContainerAwareTrait; @@ -55,6 +56,13 @@ class HtmlRenderer implements MainContentRendererInterface { protected $moduleHandler; /** + * The renderer service. + * + * @var \Drupal\Core\Render\RendererInterface + */ + protected $renderer; + + /** * Constructs a new HtmlRenderer. * * @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver @@ -65,12 +73,15 @@ class HtmlRenderer implements MainContentRendererInterface { * The event dispatcher. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler. + * @param \Drupal\Core\Render\RendererInterface $renderer + * The renderer service. */ - public function __construct(TitleResolverInterface $title_resolver, PluginManagerInterface $display_variant_manager, EventDispatcherInterface $event_dispatcher, ModuleHandlerInterface $module_handler) { + public function __construct(TitleResolverInterface $title_resolver, PluginManagerInterface $display_variant_manager, EventDispatcherInterface $event_dispatcher, ModuleHandlerInterface $module_handler, RendererInterface $renderer) { $this->titleResolver = $title_resolver; $this->displayVariantManager = $display_variant_manager; $this->eventDispatcher = $event_dispatcher; $this->moduleHandler = $module_handler; + $this->renderer = $renderer; } /** @@ -108,14 +119,14 @@ public function renderResponse(array $main_content, Request $request, RouteMatch // and hence may not execute any #post_render_cache_callbacks (because they // might add yet more assets to be attached), and therefore it must be // rendered with drupal_render(), not drupal_render_root(). - drupal_render_root($html['page']); + $this->renderer->render($html['page'], TRUE); if (isset($html['page_top'])) { - drupal_render_root($html['page_top']); + $this->renderer->render($html['page_top'], TRUE); } if (isset($html['page_bottom'])) { - drupal_render_root($html['page_bottom']); + $this->renderer->render($html['page_bottom'], TRUE); } - $content = drupal_render($html); + $content = $this->renderer->render($html); // Store the cache tags associated with this page in a X-Drupal-Cache-Tags // header. Also associate the "rendered" cache tag. This allows us to @@ -176,7 +187,7 @@ protected function prepare(array $main_content, Request $request, RouteMatchInte // ::renderContentIntoResponse(). // @todo Remove this once https://www.drupal.org/node/2359901 lands. if (!empty($main_content)) { - drupal_render($main_content, FALSE); + $this->renderer->render($main_content, FALSE); $main_content = [ '#markup' => $main_content['#markup'], '#attached' => $main_content['#attached'], diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php index 7aeae2b..a2d4e5a 100644 --- a/core/lib/Drupal/Core/Render/Renderer.php +++ b/core/lib/Drupal/Core/Render/Renderer.php @@ -1,5 +1,10 @@ controllerResolver = $controller_resolver; $this->theme = $theme; $this->elementInfo = $element_info; } /** - * 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 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 - * 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 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(). - * - '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. - * - 'expire': Set to one of the cache lifetime constants. - * - '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. - * - 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 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 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_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 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: - * @code - * array( - * '#theme' => 'image', - * '#attributes' => array('class' => array('foo')), - * '#theme_wrappers' => array('container'), - * ); - * @endcode - * and we need to pass the class 'bar' as an attribute for 'container', we - * can rewrite our element thus: - * @code - * array( - * '#theme' => 'image', - * '#attributes' => array('class' => array('foo')), - * '#theme_wrappers' => array( - * 'container' => array( - * '#attributes' => array('class' => array('bar')), - * ), - * ), - * ); - * @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. - * - 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 - * 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. - * - 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. - * - #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. - * - * @param array $elements - * The structured array describing the data to be rendered. - * @param bool $is_root_call - * (Internal use only.) Whether this is a recursive call or not. See - * drupal_render_root(). - * - * @return string - * The rendered HTML. - * - * @throws \LogicException - * If a root call to drupal_render() does not result in an empty stack, this - * indicates an erroneous drupal_render() root call (a root call within a root - * call, which makes no sense). Therefore, a logic exception is thrown. - * @throws \Exception - * If a #pre_render callback throws an exception, it is caught to reset the - * stack used for bubbling rendering metadata, and then the exception is re- - * thrown. - * - * @see \Drupal\Core\Render\ElementInfoManagerInterface::getInfo() - * @see \Drupal\Core\Theme\ThemeManagerInterface::render() - * @see drupal_process_states() - * @see drupal_process_attached() - * @see drupal_render_root() + * {@inheritdoc} */ public function render(&$elements, $is_root_call = FALSE) { static $stack; @@ -345,10 +147,10 @@ public function render(&$elements, $is_root_call = FALSE) { if (is_string($callable) && strpos($callable, '::') === FALSE) { $callable = $this->controllerResolver->getControllerFromDefinition($callable); } - // Since #pre_render callbacks 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 a #pre_render callback throws an exception that will - // cause a different page to be rendered (e.g. throwing + // Since #pre_render callbacks 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 a #pre_render callback throws 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 // drupal_render(), but if exceptions aren't caught here, the stack will @@ -544,7 +346,6 @@ public function render(&$elements, $is_root_call = FALSE) { return $elements['#markup']; } - /** * Processes #post_render_cache callbacks. * diff --git a/core/lib/Drupal/Core/Render/RendererInterface.php b/core/lib/Drupal/Core/Render/RendererInterface.php new file mode 100644 index 0000000..897ab19 --- /dev/null +++ b/core/lib/Drupal/Core/Render/RendererInterface.php @@ -0,0 +1,223 @@ + 'image', + * '#attributes' => array('class' => array('foo')), + * '#theme_wrappers' => array('container'), + * ); + * @endcode + * and we need to pass the class 'bar' as an attribute for 'container', we + * can rewrite our element thus: + * @code + * array( + * '#theme' => 'image', + * '#attributes' => array('class' => array('foo')), + * '#theme_wrappers' => array( + * 'container' => array( + * '#attributes' => array('class' => array('bar')), + * ), + * ), + * ); + * @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. + * - 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 + * 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. + * - 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. + * - #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. + * + * @param array $elements + * The structured array describing the data to be rendered. + * @param bool $is_root_call + * (Internal use only.) Whether this is a recursive call or not. See + * drupal_render_root(). + * + * @return string + * The rendered HTML. + * + * @throws \LogicException + * If a root call to drupal_render() does not result in an empty stack, this + * indicates an erroneous drupal_render() root call (a root call within a + * root call, which makes no sense). Therefore, a logic exception is thrown. + * @throws \Exception + * If a #pre_render callback throws an exception, it is caught to reset the + * stack used for bubbling rendering metadata, and then the exception is re- + * thrown. + * + * @see \Drupal\Core\Render\ElementInfoManagerInterface::getInfo() + * @see \Drupal\Core\Theme\ThemeManagerInterface::render() + * @see drupal_process_states() + * @see drupal_process_attached() + * @see drupal_render_root() + */ + public function render(&$elements, $is_root_call = FALSE); + +}