From d41f1a0134de5d94f101d73852bc6859fa0b031c Mon Sep 17 00:00:00 2001 From: Alex Pott <alex.a.pott@googlemail.com> Date: Thu, 5 Oct 2023 23:37:17 +0100 Subject: [PATCH] Issue #3383449 by catch, Wim Leers, Kingdutch: Add Fibers support to Drupal\Core\Render\Renderer --- core/lib/Drupal/Core/Render/Renderer.php | 83 +++++++++++++++++-- .../Tests/Core/Render/RendererTestBase.php | 5 ++ 2 files changed, 79 insertions(+), 9 deletions(-) diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php index 942542716bc9..b6fb506600af 100644 --- a/core/lib/Drupal/Core/Render/Renderer.php +++ b/core/lib/Drupal/Core/Render/Renderer.php @@ -162,22 +162,43 @@ public function renderPlain(&$elements) { } /** - * {@inheritdoc} + * Renders a placeholder into markup. + * + * @param array $placeholder_element + * The placeholder element by reference. + * + * @return \Drupal\Component\Render\MarkupInterface|string + * The rendered HTML. */ - public function renderPlaceholder($placeholder, array $elements) { - // Get the render array for the given placeholder - $placeholder_elements = $elements['#attached']['placeholders'][$placeholder]; - + protected function doRenderPlaceholder(array &$placeholder_element): MarkupInterface|string { // Prevent the render array from being auto-placeholdered again. - $placeholder_elements['#create_placeholder'] = FALSE; + $placeholder_element['#create_placeholder'] = FALSE; // Render the placeholder into markup. - $markup = $this->renderPlain($placeholder_elements); + $markup = $this->renderPlain($placeholder_element); + return $markup; + } + /** + * Replaces a placeholder with its markup. + * + * @param string $placeholder + * The placeholder HTML. + * @param \Drupal\Component\Render\MarkupInterface|string $markup + * The markup to replace the placeholder with. + * @param array $elements + * The render array that the placeholder is from. + * @param array $placeholder_element + * The placeholder element render array. + * + * @return \Drupal\Component\Render\MarkupInterface|string + * The rendered HTML. + */ + protected function doReplacePlaceholder(string $placeholder, string|MarkupInterface $markup, array $elements, array $placeholder_element): array { // Replace the placeholder with its rendered markup, and merge its // bubbleable metadata with the main elements'. $elements['#markup'] = Markup::create(str_replace($placeholder, $markup, $elements['#markup'])); - $elements = $this->mergeBubbleableMetadata($elements, $placeholder_elements); + $elements = $this->mergeBubbleableMetadata($elements, $placeholder_element); // Remove the placeholder that we've just rendered. unset($elements['#attached']['placeholders'][$placeholder]); @@ -185,6 +206,16 @@ public function renderPlaceholder($placeholder, array $elements) { return $elements; } + /** + * {@inheritdoc} + */ + public function renderPlaceholder($placeholder, array $elements) { + // Get the render array for the given placeholder + $placeholder_element = $elements['#attached']['placeholders'][$placeholder]; + $markup = $this->doRenderPlaceholder($placeholder_element); + return $this->doReplacePlaceholder($placeholder, $markup, $elements, $placeholder_element); + } + /** * {@inheritdoc} */ @@ -665,13 +696,47 @@ protected function replacePlaceholders(array &$elements) { // First render all placeholders except 'status messages' placeholders. $message_placeholders = []; + $fibers = []; foreach ($elements['#attached']['placeholders'] as $placeholder => $placeholder_element) { if (isset($placeholder_element['#lazy_builder']) && $placeholder_element['#lazy_builder'][0] === 'Drupal\Core\Render\Element\StatusMessages::renderMessages') { $message_placeholders[] = $placeholder; } else { - $elements = $this->renderPlaceholder($placeholder, $elements); + // Get the render array for the given placeholder + $fibers[$placeholder] = new \Fiber(function () use ($placeholder_element) { + return [$this->doRenderPlaceholder($placeholder_element), $placeholder_element]; + }); + } + } + while (count($fibers) > 0) { + $iterations = 0; + foreach ($fibers as $placeholder => $fiber) { + if (!$fiber->isStarted()) { + $fiber->start(); + } + elseif ($fiber->isSuspended()) { + $fiber->resume(); + } + // If the Fiber hasn't terminated by this point, move onto the next + // placeholder, we'll resume this fiber again when we get back here. + if (!$fiber->isTerminated()) { + // If we've gone through the placeholders once already, and they're + // still not finished, then start to allow code higher up the stack to + // get on with something else. + if ($iterations) { + $fiber = \Fiber::getCurrent(); + if ($fiber !== NULL) { + $fiber->suspend(); + } + } + continue; + } + [$markup, $placeholder_element] = $fiber->getReturn(); + + $elements = $this->doReplacePlaceholder($placeholder, $markup, $elements, $placeholder_element); + unset($fibers[$placeholder]); } + $iterations++; } // Then render 'status messages' placeholders. diff --git a/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php b/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php index 631fccc56110..fb05980236de 100644 --- a/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php +++ b/core/tests/Drupal/Tests/Core/Render/RendererTestBase.php @@ -314,6 +314,11 @@ public static function callback($animal, $use_animal_as_array_key = FALSE) { * A renderable array. */ public static function callbackPerUser($animal) { + // As well as adding the user cache context, additionally suspend the + // current Fiber if there is one. + if ($fiber = \Fiber::getCurrent()) { + $fiber->suspend(); + } $build = static::callback($animal); $build['#cache']['contexts'][] = 'user'; return $build; -- GitLab