4

Since Drupal 8.0.0-beta12 "all rendering must happen in a render context", as stated by this change record.

This is to avoid bubbleable metadata lost. So far, so good.

Let's say I'm implementing a Commerce payment notification. The important part is a server calls a Drupal URL to notify something. The controller, as far as I know, doesn't need any rendering context because the answer is simple (almost just a status response). Good.

Let's say I want to send an email on this event. I create an EventSubscriber that subscribes to the payment event that happens on this callback. I hook on the event, gather the email data, and call the send function. During sending, Drupal renders the content (is an HTML email!) and I got the error: since there's no rendering context Drupal throws an exception. The point the error is triggered is when Token::replace() is called:

$body = $this->token->replace($this->getBody(), $this->getTokenData());

How to fix this?

Option 1: Use the BubbleableMetadata $bubbleable_metadata fourth param of Token::replace(). This param docs states:

An object to which static::generate() and the hooks and functions that it invokes will add their required bubbleable metadata.

To ensure that the metadata associated with the token replacements gets attached to the same render array that contains the token-replaced text, callers of this method are encouraged to pass in a BubbleableMetadata object and apply it to the corresponding render array.

But because $body is just a plain string I guess the bubbleable metadata is lost, so what's the point of caring?

Option 2: Follow the change record suggestion and use RenderContext::executeInRenderContext(). In that case I'd wrap the whole process:

$renderer = \Drupal::service('renderer');
$context = new RenderContext();
$that = $this;
$response = $renderer->executeInRenderContext($context, function() use ($that, $recipient) {
  $that->mailer->send($recipient);
  });

Again, I'm discarding the cacheable metadata.

I guess discarding that cacheable data is ok because the email is totally uncacheable, the whole body and its parts (because the replaced strings with tokens have URLs that would change on every email).

Are both approaches ok? Is one better than the other? Is there a third approach more suitable for this problem? I guess the problem can be summarized as how to deal with Bubbleable Metadata when there's no rendering context and no render array.

5

Yes, when rendering email data you have to take care that you don't bubble up metadata to the render context of the main response.

There is a third option, and in most cases the best option: The method Renderer::renderPlain, which not only discards bubbleable metadata, but also makes sure your email doesn't contain leftover placeholders:

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.)

And it's always a good idea to build a render array to produce HTML. For example if you have a formatted text field then build a processed text render element: How to safely render node body on a custom variable?

  • Thanks for your answer! It happens that on the token replace we are using custom tokens, and those custom tokens use renders. I understand that we should use renderPlain() method tu render our custom tokens instead of just render. This what Token module seems to do. – sanzante Aug 13 at 10:06
  • 1
    Not all tokens discard metadata, but you can do this if they are only used in emails. – 4k4 Aug 13 at 11:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.