function RenderExampleController::arrays

Same name in other branches
  1. 3.x modules/render_example/src/Controller/RenderExampleController.php \Drupal\render_example\Controller\RenderExampleController::arrays()

Examples of defining content using renderable arrays.

Methods on a controller that are the target of a route should return a renderable array which contains any content to display for that route.

1 string reference to 'RenderExampleController::arrays'
render_example.routing.yml in modules/render_example/render_example.routing.yml
modules/render_example/render_example.routing.yml

File

modules/render_example/src/Controller/RenderExampleController.php, line 60

Class

RenderExampleController
Provides module description page and examples of building render arrays.

Namespace

Drupal\render_example\Controller

Code

public function arrays() {
    // The core structure of the Render API is the render array, which is a
    // hierarchical associative array containing data to be rendered and
    // properties describing how the data should be rendered. Whenever a module
    // needs to output content it should do so be defining that content as a
    // renderable array. Below we'll look at some common examples of how render
    // arrays can be used to define content.
    $build = [];
    // CSS and JavaScript libraries can be attached to elements in a renderable
    // array. This way, if the element ends up being rendered and displayed you
    // know for sure the CSS/JavaScript will also be included. But, if for
    // some reason the element isn't ever rendered then Drupal can skip the
    // unnecessary extra files.
    //
    // Learn more about attaching CSS and JavaScript libraries with the
    // #attached property here:
    // https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21theme.api.php/group/theme_render/#sec_attached
    $build['#attached'] = [
        'library' => [
            'render_example/render-example.library',
        ],
    ];
    // 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 translated to a string. Children are all
    // elements whose keys do not start with a '#'. Their values should be
    // renderable arrays themselves.
    //
    // This example defines a new element, 'simple', that contains two
    // properties; '#markup' and '#description'. This is the quickest way to
    // output a string of HTML.
    $build['simple'] = [
        '#markup' => '<p>' . $this->t('This page contains examples of various content elements described using render arrays. Read the code and comments in \\Drupal\\render_example\\Controller\\RenderExampleController::arrays() for more information.') . '</p>',
        '#description' => $this->t('Example of using #markup'),
    ];
    // Additional properties can be used to further define the content. In this
    // case '#prefix' and '#suffix' are being used to provide strings to add
    // before, and after, the main content. This is useful because now the tag
    // being used to wrap the block of content can be easily changed without
    // having to worry about the content. Or, the tag can easily be left out
    // during rendering if for example the content is being output as JSON.
    //
    // There is a set of common properties that can be used for all elements in
    // a render array. These are defined by
    // \Drupal\Core\Render\Element\RenderElement. Most elements also have
    // additional element type specific properties.
    //
    // Figuring out what additional properties are available requires first
    // determining what sort of render element you're dealing with. Look for the
    // presence of one of these properties to start:
    // - #markup, or #plain_text: These are the simplest render arrays, and are
    //   used to display simple strings of text. In addition to the common set
    //   of properties available for all elements #markup elements can use the
    //   #allowed_tags property, an array of additional tags to allow when the
    //   HTML string is run through \Drupal\Component\Utility\Xss::filterAdmin()
    //   to strip out possible XSS vectors.
    // - #theme: The presence of #theme indicates that the array contains data
    //   to be themed by a particular theme hook. The available properties will
    //   depend on the specific theme hook. See the example below for more about
    //   determining what properties to use.
    // - #type: The presence of #type indicates that the array contains data and
    //   options for a particular type of "render element" (for example, 'form',
    //   'textfield', 'submit', for HTML form element types; 'table', for a
    //   table with rows, columns, and headers). The additional properties will
    //   depend on the render element type, and are documented on the class that
    //   defines the element type.
    //
    $build['simple_extras'] = [
        '#description' => $this->t('Example of using #prefix and #suffix'),
        // Note the addition of '#type' => 'markup' in this example compared to
        // the one above. Because #markup is such a commonly used element type you
        // can exclude the '#type' => 'markup' line and it will be assumed
        // automatically if the '#markup' property is present.
'#type' => 'markup',
        '#markup' => '<p>' . $this->t('This one adds a prefix and suffix, which put a blockqoute tag around the item.') . '</p>',
        '#prefix' => '<blockquote>',
        '#suffix' => '</blockquote>',
    ];
    // In addition to #markup, you can also use #plain_text to output, you
    // guessed it, strings of plain text. This indicates that the array contains
    // text which should be escaped before it is displayed.
    $build['simple_text'] = [
        '#plain_text' => '<em>This is escaped</em>',
        '#description' => $this->t('Example of using #plain_text'),
    ];
    // Using the '#theme' property for an element specifies that the array
    // contains data to be themed by a particular theme hook. Essentially using
    // a Twig template to generate the HTML for an element. Modules define theme
    // hooks by implementing hook_theme(), which specifies the input "variables"
    // used to provide data and options; if a hook_theme() implementation
    // specifies variable 'separator', then in a render array, you would provide
    // this data using the '#separator' property.
    //
    // @see hook_theme()
    $build['theme_element'] = [
        // The '#theme' property can be set to any valid theme hook. For more
        // information about theme hooks, and to discover available theme hooks
        // that you can use when creating render arrays see the documentation for
        // hook_theme().
        //
        // Many of the most commonly used theme hooks are defined in
        // drupal_common_theme().
'#theme' => 'item_list',
        '#title' => $this->t('Example of using #theme'),
        // The #items property is specific to the 'item_list' theme hook, and
        // corresponds to the variable {{ items }} in the item-list.twig.html
        // template file.
'#items' => [
            $this->t('This is an item in the list'),
            $this->t('This is some more text that we need in the list'),
        ],
    ];
    // Using the '#type' property for an element specifies that the array
    // contains data and options for a particular type of "render element".
    // Render element types can be thought of as prepackaged render arrays that
    // provide default values for a set of properties as well as code that
    // will perform additional processing on the array before it is rendered.
    //
    // As an example take a look at the code in
    // \Drupal\Core\Render\Element\Table::getInfo(). Notice that it is defining
    // values for #theme, and #process? These values will be merged with
    // whatever properties you define in your code.
    //
    // In addition, most render element types have type specific properties. A
    // table for example has #header, and #rows properties. The easiest way to
    // determine what element type specific properties exist is to read the
    // documentation for the class that defines the element type. Don't forget
    // that it will also inherit properties used by any class it is extending.
    //
    // There are two types of render element types:
    // - Generic elements: Generic render element types encapsulate logic for
    //   generating HTML and attaching relevant CSS and JavaScript to the page.
    //   These include things like link, table, and drop button elements.
    // - Form elements: Most of the render element types provided by core
    //   represent the various widgets you might use on a form. Text fields,
    //   password fields, and file upload buttons for example. These elements
    //   are intended to be used in conjunction with a form controller class
    //   and have additional properties such as `#required`, and
    //   `#element_validate`, related to their use as part of a form. For more
    //   on form elements check out the fapi_example module.
    $build['table'] = [
        // The value used for #type is the ID of the plugin that implements the
        // element type you want to use. This can be inferred from the annotation
        // for the element.
        // You can also find a list of element types provided by Drupal core here
        // https://api.drupal.org/api/drupal/elements.
'#type' => 'table',
        '#caption' => $this->t('Our favorite colors.'),
        '#header' => [
            $this->t('Name'),
            $this->t('Favorite color'),
        ],
        '#rows' => [
            [
                $this->t('Amber'),
                $this->t('teal'),
            ],
            [
                $this->t('Addi'),
                $this->t('green'),
            ],
            [
                $this->t('Blake'),
                $this->t('#063'),
            ],
            [
                $this->t('Enid'),
                $this->t('indigo'),
            ],
            [
                $this->t('Joe'),
                $this->t('green'),
            ],
        ],
        '#description' => $this->t('Example of using #type.'),
    ];
    // Render arrays can be nested any level deep. This allows you to group
    // like things together. A great example of this is the $page array used in
    // conjunction with the page.html.twig template. The top level contains all
    // the regions, each of which contain the blocks placed in that region,
    // which in turn contain their own content. In fact, when this array is
    // ultimately displayed on a page it will be as part of the $page array.
    $build['nested_example'] = [
        '#description' => $this->t('Example of nesting elements'),
        '#markup' => '<p>' . $this->t('Render arrays can contain any number of nested elements. During rendering, the innermost elements are rendered first, and their output is incorporated into the parent element.') . '</p>',
        'nested_child_element' => [
            // An un-ordered list of links.
            // See /core/modules/system/templates/item-list.html.twig.
'#theme' => 'item_list',
            '#title' => $this->t('Links'),
            '#list_type' => 'ol',
            '#items' => [
                Link::fromTextAndUrl($this->t('Drupal'), Url::fromUri('https://www.drupal.org')),
                Link::fromTextAndUrl($this->t('Not Drupal'), Url::fromUri('https://wordpress.org/')),
            ],
        ],
    ];
    // Example of adding a link using the #link element type.
    $build['nested_example']['another_nested_child'] = [
        // See \Drupal\Core\Render\Element\Link.
'#type' => 'link',
        '#title' => $this->t('A link to example.com'),
        '#url' => Url::fromUri('https://example.com'),
    ];
    // The #theme_wrappers property can be used to provide an array of theme
    // hooks which provide the envelope or "wrapper" of a set of child elements.
    // The theme function finds its element children (the sub-arrays) already
    // rendered in '#children'.
    $build['theme_wrappers demonstration'] = [
        '#description' => $this->t('Example of using #theme_wrappers'),
        'child1' => [
            '#markup' => $this->t('Markup for child1'),
        ],
        'child2' => [
            '#markup' => $this->t('Markup for child2'),
        ],
        '#theme_wrappers' => [
            'render_example_add_div',
        ],
    ];
    // Use the #access property to control who can see what content. If an
    // element in an render array has its #access property set to FALSE it will
    // be removed from the array before rendering. And thus not visible.
    $build['access_example'] = [
        '#description' => $this->t('Example of using #access to control visibility'),
        '#markup' => $this->t('This text is only visible to authenticated users.'),
        '#access' => $this->currentUser
            ->isAuthenticated(),
    ];
    // Some properties define callbacks, which are callable functions or methods
    // that are triggered at specific points during the rendering pipeline.
    $build['pre_render_and_post_render'] = [
        '#description' => $this->t('Example of using #pre_render and #post_render'),
        '#markup' => '<div style="color:green">' . $this->t('markup for pre_render and post_render example') . '</div>',
        // #pre_render callbacks are triggered early in the rendering process,
        // they get access to the element in the array where the callback is
        // named, and all of its children. They can be used to do things like
        // conditionally alter the value of a property prior to the array being
        // rendered to HTML.
'#pre_render' => [
            static::class . '::preRenderAddSuffix',
        ],
        // #post_render callbacks are triggered after the array has been rendered
        // and can operate on the rendered HTML. They also have access to the
        // original array for context.
'#post_render' => [
            static::class . '::postRenderAddPrefix',
        ],
    ];
    // Properties that contain callbacks can also reference methods on a class
    // in addition to functions. See
    // \Drupal\render_example\Controller\RenderExampleController::preRender()
    // @todo This doesn't work, we need to fix it.
    // https://www.drupal.org/project/examples/issues/2986435
    // $build['#pre_render'] = [static::class, 'preRender'];.
    // Caching is an important part of the Render API, converting an array to a
    // string of HTML can be an expensive process, and therefore whenever
    // possible the Render API will cache the results of rendering an array in
    // order to improve performance.
    //
    // When defining a render array you should use the #cache property to define
    // the cachability of an element.
    $build['cache_demonstration'] = [
        '#description' => $this->t('#cache demonstration'),
        // This string contains information that is specific to the user who is
        // currently viewing the page. We can cache it, and re-use the string any
        // time the same user views the page again. However, if the user changes,
        // or if the user changes their name, we need to expire the cached data
        // and rebuild it so that it is accurate.
'#markup' => $this->t('Hello @name, welcome to the #cache example.', [
            '@name' => $this->currentUser
                ->getAccountName(),
        ]),
        // The #cache property is used to provide metadata about the element being
        // cached, and the conditions under which it should be expired. This can
        // be time based, or context based. You can read more about caching
        // render arrays here
        // https://www.drupal.org/docs/8/api/render-api/cacheability-of-render-arrays
'#cache' => [
            // The "current user" is used above, which depends on the request, so
            // we tell Drupal to vary by the 'user' cache context.
'contexts' => [
                'user',
            ],
        ],
    ];
    // A #lazy_builder callback can be used to build a highly dynamic section of
    // a render array from scratch. This, combined with the use of placeholders,
    // allows the renderer to cache some, but not all, portions of a render
    // array. Without #lazy_builders, if any element in the render tree is
    // uncacheable the whole tree would need to be re-rendered every time.
    //
    // The general rendering flow is as follows:
    // - Check for cached version of output from previous rendering, if it
    //   exists replace any placeholders in the rendered output with their
    //   dynamic content as generated by the #lazy_builder callback, and return
    //   the resulting HTML.
    // - If no cached version exists render the array to HTML, when an element
    //   that can be placeholdered is encountered insert a placeholder, cache
    //   the HTML after rendering for next time, replace the placeholders with
    //   their dynamic content, and return the resulting HTML.
    //
    // This is especially noticeable when used in conjunction with modules like
    // Big Pipe which do rendering of a page in multiple passes vs. the default
    // single flush renderer.
    //
    // See \Drupal\block\BlockViewBuilder::viewMultiple() for an example from
    // core.
    $build['lazy_builder'] = [
        // Set the value of the #lazy_builder property to an array, the first key
        // of the array is the method, service, or function, to call in oder to
        // generate the dynamic data. The second argument is an array of any
        // arguments to pass to the callback. Arguments can be only primitive
        // types (string, bool, int, float, NULL).
'#lazy_builder' => [
            static::class . '::lazyBuilder',
            [
                $this->currentUser
                    ->id(),
                'Y-m-d',
            ],
        ],
        // #lazy_builder callbacks can be used in conjunction with
        // #create_placeholder to tell the renderer that instead of simply calling
        // the #lazy_builder code right away, to instead insert a placeholder and
        // delay execution of the #lazy_builder code until it's needed.
        //
        // This is somewhat analogous to the way Drupal uses the PSR-4 autoloading
        // standard to "lazy" load PHP files that contain the definition of a
        // class only if, and when, that class is used.
        //
        // To force a element to use a placeholder set #create_placeholder to
        // TRUE.
        //
        // Alternatively you could include #cache metadata (see above) and allow
        // the Render API to use that metadata to automatically determine based on
        // the existence of high-cardinality cache contexts in the subtree whether
        // or not the element should use a placeholder.
'#create_placeholder' => TRUE,
    ];
    // Example of the marquee element type defined by
    // \Drupal\render_example\Element\Marquee.
    $build['marquee'] = [
        '#description' => $this->t('Example custom element type'),
        '#type' => 'markup',
        'marquee_element' => [
            '#type' => 'marquee',
            '#content' => $this->t('Hello world!'),
        ],
    ];
    $output = [];
    // We are going to create a new output render array that pairs each
    // example with a set of helper render arrays. These are used to display
    // the description as a title and the unrendered content alongside the
    // examples.
    foreach (Element::children($build) as $key) {
        if (isset($build[$key])) {
            $output[$key] = [
                '#theme' => 'render_array',
                'description' => [
                    '#type' => 'markup',
                    '#markup' => $build[$key]['#description'] ?? '',
                ],
                'rendered' => $build[$key],
                'unrendered' => [
                    '#type' => 'markup',
                    '#markup' => htmlentities(Variable::export($build[$key])),
                ],
            ];
        }
    }
    foreach (Element::properties($build) as $key) {
        $output[$key] = $build[$key];
    }
    return $output;
}