0

I have a simple hook_post_update function which fails with the following error:

[notice] Update started: ssc_custom_post_update_9001

[error] LogicException: Render context is empty, because render() was called outside of a renderRoot() or renderPlain() call. Use renderPlain()/renderRoot() or #lazy_builder/#pre_render instead. in Drupal\Core\Render\Renderer->doRender() (line 244 of E:\www\myssc\html\core\lib\Drupal\Core\Render\Renderer.php). [error] Render context is empty, because render() was called outside of a renderRoot() or renderPlain() call. Use renderPlain()/renderRoot() or #lazy_builder/#pre_render instead. [error] Update failed: ssc_custom_post_update_9001

In ProcessBase.php line 171:

Unable to decode output into JSON: Syntax error

[ERROR] [node] [2022-03-12T04:58:35] LogicException: Render context is empty, because render() was called outside of a renderRoot() or renderPlain() call. Use renderPlain()/renderRoot() or #lazy_builder/#pre_re nder instead. in Drupal\Core\Render\Renderer->doRender() (line 244 of E:\www\myssc\html\core\lib\Drupal\C ore\Render\Renderer.php). | uid: 0 | request-uri: http://default/ | refer: | ip: 127.0.0.1 | link:
{ "0": { "ssc_custom": { "9001": { "#abort": { "success": false, "query": "Drupal\Core\Entity\EntityStorageException: Ren der context is empty, because render() was called outside of a renderRoot() or renderPlain() call. Use re nderPlain()/renderRoot() or #lazy_builder/#pre_render instead. in Drupal\Core\Ent
ity\Sql\SqlContentEntityStorage->save() (line 810 of E:\www\myssc\html\core\lib\Drupal\Core\Entity\Sql\SqlContentEntityStorage.php)." } } }, "#abort": [ "ssc_custom_post_update_9001" ] }, "drush_batch_process_finished": true }

I have stripped the code down to the bare minimum:

use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\node\NodeInterface;

function ssc_custom_post_update_9001(&$sandbox) {
  /** @var \Drupal\node\NodeStorageInterface $node_storage */
  $node_storage = \Drupal::entityTypeManager()->getStorage('node');

  // How many entities to processed per batch.
  $limit = 5;

  $node_ids = $node_storage->getQuery()
    ->accessCheck(FALSE)
    ->condition('type', 'article')
    ->range(0, $limit)
    ->execute();
  // Load entities.
  $nodes = $node_storage->loadMultiple($node_ids);

  /** @var \Drupal\node\NodeInterface $node */
  foreach ($nodes as $node) {
    $node->setNewRevision();
    $node->save();
  }

  $sandbox['#finished'] = 1;

}

If I run the same code directly (not from drush updb) it runs fine. Running from the admin menu "Run updates"; it also fails (so it is not a drush issue).

Commenting out the $node->save() and the error does not occur.

7
  • Does this happen if you load and save a different content type?
    – sonfd
    Mar 12, 2022 at 5:20
  • This is not how batch works. Content updates should happen in hook_deploy_N running after configuration import when executing drush deploy.
    – leymannx
    Mar 12, 2022 at 6:53
  • 2
    If an error occurs in a Drupal function used in many different places you have to check the call stack from where it is called. This might point to a module which hooks into the node save process.
    – 4uk4
    Mar 12, 2022 at 10:30
  • Hmm, oddly i am no longer getting notifications from Drupal Answers.. @sonfd, yes, any content type. leymannx, not sure what hook_deploy is, will take a look; but hook_post_update states it is specifically for tasks like this when you need to update site content and examples even show using batch and node save: api.drupal.org/api/drupal/…
    – liquidcms
    Mar 12, 2022 at 17:25
  • 2
    It’s a drush hook @liquidcms
    – Clive
    Mar 12, 2022 at 19:25

1 Answer 1

2

You can reproduce the error with Drush:

# drush php

>>> $build = ['#markup' => 'TEST'];
=> [
     "#markup" => "TEST",
   ]

>>> $rendered = \Drupal::service('renderer')->render($build);
LogicException with message 'Render context is empty, because render() was called outside of a renderRoot() or renderPlain() call. Use renderPlain()/renderRoot() or #lazy_builder/#pre_render instead.'

>>> $rendered = \Drupal::service('renderer')->renderPlain($build);
=> Drupal\Core\Render\Markup {#4668
     markup: "TEST",
   }

So as the error message says modules which hook into the node save process should use renderPlain() instead. They shouldn't assume nodes are always saved within a render context of a themed Drupal page.

Code example for rendering a view in isolation:

$build = $view->buildRenderable();
$rendered = \Drupal::service('renderer')->renderPlain($build);
4
  • Turns out it is my custom code causing the issue. I have code which runs on node save/presave which is trying to get values from a View and is using advancedRender() which i assume is causing this issue. Disappointing that my code works as it should when nodes are normally being saved but in post_update i am blocked from doing this. Easy enough to work around as these functions dont need to be run during this update - but odd I would need to do this hack.
    – liquidcms
    Mar 14, 2022 at 16:03
  • If the render() function is not in your control, like here in a View, you can enclose the code in your own RenderContext and discard the bubbled up metadata. Drupal does this for example when rendering mails. See drupal.stackexchange.com/questions/245715/…
    – 4uk4
    Mar 14, 2022 at 16:11
  • thanks for the tip.. good to know. Although a lot of work to simply copy a field to a new field as a 1 time ting. For now i simply set a static var in the update script and then use that to ignore my save/presave functions as they don't need to be run when this update is being run.
    – liquidcms
    Mar 14, 2022 at 19:51
  • OK, but this doesn't solve the problem with leaking metadata into an unknown render context. Running render() or code containing render() should only bubble up metadata if you can make sure the render context belongs to the rendered content. Most times this is the page render context when theming page elements.
    – 4uk4
    Mar 14, 2022 at 21:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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