Mercurial > hg > isophonics-drupal-site
diff core/lib/Drupal/Core/Render/RendererInterface.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/lib/Drupal/Core/Render/RendererInterface.php Wed Nov 29 16:09:58 2017 +0000 @@ -0,0 +1,414 @@ +<?php + +namespace Drupal\Core\Render; + +/** + * Defines an interface for turning a render array into a string. + */ +interface RendererInterface { + + /** + * Renders final HTML given a structured array tree. + * + * Calls ::render() in such a way that placeholders are replaced. + * + * Should therefore only be used in occasions where the final rendering is + * happening, just before sending a Response: + * - 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. + * + * @return \Drupal\Component\Render\MarkupInterface + * The rendered HTML. + * + * @throws \LogicException + * When called from inside another renderRoot() call. + * + * @see \Drupal\Core\Render\RendererInterface::render() + */ + public function renderRoot(&$elements); + + /** + * Renders final HTML in situations where no assets are needed. + * + * Calls ::render() in such a way that placeholders are replaced. + * + * Useful for instance when rendering the values of tokens or emails, which + * need a render array being turned into a string, but do not need any of the + * bubbleable metadata (the attached assets and cache tags). + * + * Some of these are a relatively common use case and happen *within* a + * ::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 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. + * + * @return \Drupal\Component\Render\MarkupInterface + * The rendered HTML. + * + * @see \Drupal\Core\Render\RendererInterface::renderRoot() + * @see \Drupal\Core\Render\RendererInterface::render() + */ + public function renderPlain(&$elements); + + /** + * Renders final HTML for a placeholder. + * + * Renders the placeholder in isolation. + * + * @param string $placeholder + * An attached placeholder to render. (This must be a key of one of the + * values of $elements['#attached']['placeholders'].) + * @param array $elements + * The structured array describing the data to be rendered. + * + * @return array + * The updated $elements. + * + * @see \Drupal\Core\Render\RendererInterface::render() + */ + public function renderPlaceholder($placeholder, array $elements); + + /** + * 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_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\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: + * @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 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. + * + * @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 + * ::renderRoot(). + * + * @return \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). + * @throws \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. + * + * @see \Drupal\Core\Render\ElementInfoManagerInterface::getInfo() + * @see \Drupal\Core\Theme\ThemeManagerInterface::render() + * @see drupal_process_states() + * @see \Drupal\Core\Render\AttachmentsResponseProcessorInterface::processAttachments() + * @see \Drupal\Core\Render\RendererInterface::renderRoot() + */ + public function render(&$elements, $is_root_call = FALSE); + + /** + * Checks whether a render context is active. + * + * This is useful only in very specific situations to determine whether the + * system is already capable of collecting bubbleable metadata. Normally it + * should not be necessary to be concerned about this. + * + * @return bool + * TRUE if the renderer has a render context active, FALSE otherwise. + */ + public function hasRenderContext(); + + /** + * Executes a callable within a render context. + * + * Only for very advanced use cases. Prefer using ::renderRoot() and + * ::renderPlain() instead. + * + * All rendering must happen within a render context. Within a render context, + * all bubbleable metadata is bubbled and hence tracked. Outside of 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. + * + * @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. + * + * @throws \LogicException + * In case bubbling has failed, can only happen in case of broken code. + * + * @see \Drupal\Core\Render\RenderContext + * @see \Drupal\Core\Render\BubbleableMetadata + */ + public function executeInRenderContext(RenderContext $context, callable $callable); + + /** + * Merges the bubbleable rendering metadata o/t 2nd render array with the 1st. + * + * @param array $a + * A render array. + * @param array $b + * A render array. + * + * @return array + * The first render array, modified to also contain the bubbleable rendering + * metadata of the second render array. + * + * @see \Drupal\Core\Render\BubbleableMetadata + */ + public function mergeBubbleableMetadata(array $a, array $b); + + /** + * Adds a dependency on an object: merges its cacheability metadata. + * + * For instance, when a render array depends on some configuration, an entity, + * or an access result, we must make sure their cacheability metadata is + * present on the render array. This method makes doing that simple. + * + * @param array &$elements + * The render array to update. + * @param \Drupal\Core\Cache\CacheableDependencyInterface|mixed $dependency + * The dependency. If the object implements CacheableDependencyInterface, + * then its cacheability metadata will be used. Otherwise, the passed in + * object must be assumed to be uncacheable, so max-age 0 is set. + * + * @see \Drupal\Core\Cache\CacheableMetadata::createFromObject() + */ + public function addCacheableDependency(array &$elements, $dependency); + +}