Since it is important in D8 for module developers to create render arrays (controller functions build and return either a render array or a Symfony Response object), we should document clearly how to create a render array and what are the basic elements it should specify.
I've been looking at the following but still have some questions.
- Render API Overview
- function Renderer::render
- Render API
core/lib/Drupal/Core/Render/BareHtmlPageRenderer.php
core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
Render API mentions a few things:
Render arrays (at each level in the hierarchy) will usually have one of the following three properties defined:
- #type type of element: 'page', 'form', 'table', etc.
- #theme name of theme; by existing indicates that render array contains data to be themed by a particular theme hook
- #markup HTML markup
Questions:
1) Is it required to have #type
specified?
2) Is it required to have one of #theme
or #markup
in the render array? Are there other properties that would be alternatives to these two?
3) Do any of #type
, #theme
, #markup
conflict with each other? It would appear that #theme
and #markup
do.
4) It doesn't sound useful for anything, but could a render array be empty? e.g. $render_array = array();
5) Is there a list of all possible values for #type
specified somewhere?
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.
6) Is this accurate? (Double-checking because, if true, there technically wouldn't be a reason to use '#'; perhaps it's for historical reasons?)
7) Are there other properties that are highly-recommended to be added to a basic render array?
for ex., it appears in Cacheability of render arrays that #cache
is.
8) Does Drupal add default properties or children to a render array? Is it simple to document what these are, or does it vary a lot depending on the context? Are there any properties/children that are added to every render array?
Comments
Comment #1
Mark_L6n CreditAttribution: Mark_L6n commentedComment #2
tim.plunkett1,2,3) You must have
#type
,#theme
, or#markup
, but only one. If you have#markup
, it converts that to'#type' => 'markup'
for you4) That won't do anything
5) See #1617948: [policy for now] New standard for documenting form/render elements and properties
6) Correct
7) No, nothing is highly recommended
8) Default properties are added for each #type
Comment #3
Mark_L6n CreditAttribution: Mark_L6n commentedThanks for responding to everything! However, in the meantime I was experimenting and found a different result than:
It appears that
#type
and#theme
can be used together, at least in the following case:Results:
contains
'#type' => 'page'
:<body class="layout-no-sidebars ...">
no
'#type' => 'page'
:<body class="layout-one-sidebar layout-sidebar-first ...">
The theme twig file was used in both cases.
Although this is a different result from what is suggested, this is very useful in this case! I wanted some full screen images without sidebars, and this allowed it. So, at least in this case, it's beneficial that both
#type
and#theme
are able to be specified.So now the question arises, can
#type
and#theme
be used together for all or specific other values of#type
. Perhaps, if as suggested in #1617948: [policy for now] New standard for documenting form/render elements and properties that each#type
will be documented, a part of that could be whether or not that#type
of element works with#theme
. (That is, if not all of them do.)Comment #4
Fabianx CreditAttribution: Fabianx commentedIt works like this:
#type can define a default #theme, e.g. #type => pager, adds #theme => pager automatically.
This was done in Drupal 7 via hook_element_info() (where this is the same).
However this additions are done via:
$array += $defaults;
Hence any things you set in the render array will overwrite any default definition that your type sets.
I hope this helps :).
Comment #5
Mark_L6n CreditAttribution: Mark_L6n commentedThanks! Saying that '#type can define a default #theme' helped me figure out what was going on here.
'#type' => 'page'
indeed by default defines'#theme' => 'page'
.This would cause the twig template
core/modules/system/templates/page.html.twig
to get loaded, unless a theme replaced it. I was using the Bartik theme, socore/themes/bartik/templates/page.html.twig
would get loaded instead by default.Then, when I placed in the render array:
that caused
mytheme.html.twig
to be used instead ofcore/themes/bartik/templates/page.html.twig
.However, that still doesn't fully explain the difference noted above:
contains
'#type' => 'page'
:<body class="layout-no-sidebars ...">
no
'#type' => 'page'
:<body class="layout-one-sidebar layout-sidebar-first ...">
The cause was found in
bartik_preprocess_html()
in filebartik.theme
Comment #6
Fabianx CreditAttribution: Fabianx commentedWhat you want is a theme suggestion:
#type => 'page',
#theme => array('page__mytheme'),
Then use page--mytheme.html.twig
That will ensure that hook_preprocess_page() continues to be called.
Comment #7
Mark_L6n CreditAttribution: Mark_L6n commentedThanks again!
So would it be accurate to say that there are 3 ways to create the basic content of a render array?
1) use a
#type
that has already been defineda
#type
typically provides a default value for#theme
[does it always?]you can specify a value for
#theme
that overrides the default value2) assign a string of HTML to
#markup
You can specify
'#type' => 'markup'
, or it will automatically be provided if you have#markup
defined3) use your own twig template, specified by
'#theme' => 'mytheme'
(or'#theme' => array('mytheme'
?) and by not setting#type
.It would make sense to treat this in the same way as
'#markup'
and provide default value like'#type' => 'template'
, but I don't think this is done.Comment #8
Fabianx CreditAttribution: Fabianx commentedNot really,
- #type can also have a #pre_render call that sets #markup. (e.g. #type => link)
- #markup is a special case, yes
- #theme corresponds to things defined via hook_theme()
This all has almost not been changed from D7.
See also:
https://www.drupal.org/node/930760
Comment #9
joelpittet@Mark_L6n with all this detail maybe you can update the theme docs to make them more clear for yourself and others?
Comment #10
Mark_L6n CreditAttribution: Mark_L6n commented@joelpittet Yes, hopefully I can write up the basics in a simple way! Hopefully you guys can proofread it here—each time I think I'm making progress understanding it there's something I'm still missing ;-)
Comment #11
Mark_L6n CreditAttribution: Mark_L6n commentedBelow is a sample template for creating the 3 types of render arrays outlined in post #7. (I'm not following the comment in post #8 "#type can also have a #pre_render call that sets #markup. (e.g. #type => link)"; I'll ask about that below. ) I'd like to suggest something along these lines to the Drupal Console project. Does this look accurate for what it covers?
Comment #12
Mark_L6n CreditAttribution: Mark_L6n commentedI'm not following, from post #8
1) Will
'#type' => 'link'
and#markup
co-exist on the same page?If so, what happens then? Is the markup only part of the rendered HTML? Which part (i.e. between
<a>
and</a>
, inside<a .....>
etc.)?2) Could this be specified directly, without using
#pre_render
? If not, why not?Comment #13
Mark_L6n CreditAttribution: Mark_L6n commentedIt appears there (for now) is a 4th way to create a render array, using
'#type' => 'inline_template'
along with'#template' => <Twig code>
:(Ex. from
core/modules/system/system.install
. For more examples, run from the Drupal rootegrep -ir "inline_template" .
)However, this method appears to be under discussion at #2273925: Ensure #markup is XSS escaped in Renderer::doRender().
Thus, it appears you can currently call Twig code:
Comment #14
Mark_L6n CreditAttribution: Mark_L6n commentedAnswers to questions in post #12, which were referring to this quote from post #8:
Found the code referred to in
core/lib/Drupal/Core/Render/Element/Link.php
, which has functionfunction preRenderLink()
and a render array referring to it:'#pre_render' => array( array($class, 'preRenderLink'), ),
An example render array produced for a link is:1) Will
'#type' => 'link'
and#markup
co-exist on the same page? Yes.If so, what happens then? It appears that whatever is in #markup, regardless of what is elsewhere, is regarded as the rendered HTML of the render array. In fact, when a render array is rendered into HTML, the result is placed in #markup. To save processing time, if something is in #markup, that render array will not be rendered again, with #markup being the final rendered HTML (unless #post_render functions modify it). (Could someone verify that all parts of this answer are correct?)
Is the markup only part of the rendered HTML? No, it is all of the rendered HTML. Follow-up Q: will #prefix/#suffix get added to this, and is this different in D7 and D8?
Which part (i.e. between
<a>
and</a>
, inside<a .....>
etc.)? Q not applicable, as #markup is all of the rendered HTML.2) Could this be specified directly, without using
#pre_render
? If not, why not? Yes. From the following render array, the #markup is returned as is, despite #type => link: