Problem/Motivation

When trying to render something with drupal_render_root() while already being within such a call, an exception is thrown.

While that should generally be avoided and anyone who can should just return render arrays, that is not always possible/correct.

Two examples:

1. Tokens

It is impossible to return a render array within the token system (at the moment), so anything that needs to be rendered within hook_tokens() has no way to do it properly and needs to render things manually.

This can also happen indirectly, for example in node_tokens(), $node->body->processed is called, which calls check_markup(), which calls drupal_render_root().

2. Rendering something to be sent as an e-mail (r something else)

Anything that is rendered and then *not* part of the returned html page must be rendered separately and nested assets must not bubble up to the main page (For example a html mail with it's own CSS)

Both those things are currently impossible and when called while being rendered, throw an exception right now.

I think this makes this critical.

Proposed resolution

Add a new method that allows to render something of the current render stack (but in a new, separate render stack).

Remaining tasks

User interface changes

API changes

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Wim Leers’s picture

Status: Active » Needs review
Issue tags: +Needs tests
FileSize
11.42 KB

A solution, based on an extensive IRC discussion between Berdir, Fabianx, catch and I.

(Reusing parts of #2364381: Exception thrown in drupal_render() causes an exception during the rendering of exceptions — if this lands first, we'll be able to reduce that other patch to a single hunk; if the other lands first, then this will be reduced to three hunks.)

Fabianx’s picture

Status: Needs review » Needs work

RTBC - if tests pass, the patch looks great.

Except that we also need tests for the new functionality ...

The last submitted patch, 1: renderer_render_plain-2382503-1.patch, failed testing.

Wim Leers’s picture

Same failure as #2364381: Exception thrown in drupal_render() causes an exception during the rendering of exceptions, which I've been totally unable to find the root cause of :(

Berdir’s picture

Sounds like I've got myself a challenge :)

Fabianx’s picture

My theory is that the renderStack needs to be a synthetic service to keep the state when ConfigUIImportTest rebuilds the container.

I _think_ (and am researching that currently) that a static in a class function is working like a static in a function and global and not per instance ...

That is the theory at least.

Thanks for taking the challenge, Berdir!

Fabianx’s picture

My theory is correct:

http://php.net/manual/en/language.variables.scope.php#59676

"Static variables in a function are shared across all instances of that class :)."

That means the stack at the moment is kinda "synthetic".

So we can make the service synthetic or push the stack to a static var for now. (or at least re-initialize it from a static:: var when the stack is NULL).

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
11.43 KB
4.17 KB

Wow.

Fabianx++
Fabianx++
Fabianx++
Fabianx++
Fabianx++

:)


Making the service synthetic causes this:

wim.leers at wimleers-acquia in ~/Work/drupal-tres on 8.0.x*
$ drush cr
You have requested a synthetic service ("renderer"). The DIC does not know how to construct this service. 

So I went with the other option: a static $stack property.

I can't get ConfigImportAllTest to pass locally, but ConfigImportUITest now does pass. Let's see what testbot says.

Wim Leers’s picture

Issue tags: -Needs tests
FileSize
15.89 KB
4.5 KB

Now with tests.

Fabianx’s picture

Status: Needs review » Reviewed & tested by the community

RTBC, looks great!

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/lib/Drupal/Core/Render/Renderer.php
    @@ -354,6 +343,55 @@ public function render(&$elements, $is_root_call = FALSE) {
       /**
    +   * {@inheritdoc}
    +   */
    +  public function resetStack() {
    +    self::$stack = NULL;
    +  }
    

    I'm not sure that this needs to be public. Discussed with @Wim Leers in IRC and he agreed.

  2. +++ b/core/modules/system/src/Tests/Common/RenderTest.php
    @@ -1197,4 +1197,106 @@ public function testDrupalProcessAttached() {
    +    $this->pass('Renderer::renderRoot() may not be called inside of another Renderer::renderRoot() call, this must trigger an exception.');
    ...
    +      $this->fail('No exception triggered.');
    ...
    +      $this->pass('Exception triggered.');
    

    Do you not mean to set a $message here and use in the $this->fail()/$this->pass() later? Same with subsequent assertions in this test... Nice tests though :)

Fabianx’s picture

1. resetStack will need to be public for things where the ExceptionSubscriber will need to reset the stack before displaying the Bare HTML Page.

But we can make it protected first and then back public in the other issue. I am fine either way.

Wim Leers’s picture

Status: Needs work » Reviewed & tested by the community
FileSize
15.69 KB
1.49 KB

#12: I thought the same, but then I realized the other issue could also just use renderPlain()… :) In case that I'm wrongly thinking that'll work, we can indeed just make it public again.

#11.2: Heh, thanks :) I specifically chose not to do that, I find that having too many variables in tests easily becomes cumbersome; just hardcoding strings makes tests more legible, because you don't need to look up what that variable contains. I left that as-is, hope that's okay.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

This issue addresses a critical bug and is allowed per https://www.drupal.org/core/beta-changes. Committed 91b4829 and pushed to 8.0.x. Thanks!

  • alexpott committed 91b4829 on 8.0.x
    Issue #2382503 by Wim Leers: Not possible to render self-contained...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.