Problem/Motivation

#2478483: Introduce placeholders (#lazy_builder) to replace #post_render_cache introduced the concept of "placeholders". It allowed us to remove #post_render_cache, and all the special handling for that, and just have placeholders be another type of attachment. So, we now have #attached[placeholders].

Because placeholders are an attachment, they should be handled in a HtmlResponseAttachmentsProcessor, which specifically is the place that takes attachments and ensures the HtmlResponse is updated accordingly.

(This issue & code originate from #2429617: Make D8 2x as fast: Dynamic Page Cache: context-dependent page caching (for *all* users!). This blocks that issue, as well as #2349011: Support placeholder render strategies so contrib can support BigPipe, ESI….)

Proposed resolution

Make it so.

Remaining tasks

Review.

User interface changes

None.

API changes

None.

But, notable change:
HtmlResponseAttachmentsProcessor now renders the attached placeholders (#attached[placeholders]). Which means finally all attachments are truly processed in the AttachmentsResponseProcessorInterface implementation for HTML responses.
This means HtmlRenderer no longer needs to render attached placeholders. Which means HtmlResponse objects are now perfectly cacheable, because they don't contain anything user-specific.

(This is not an API change, just an implementation detail change.)

Data model changes

None.

Beta phase evaluation

Reference: https://www.drupal.org/core/beta-changes
Issue category Task because not broken, but an important improvement.
Issue priority Major because it blocks significant other issues.
Prioritized changes The main goal of this issue is performance/cacheability.
Disruption Zero disruption.

Comments

Wim Leers created an issue. See original summary.

wim leers’s picture

Status: Active » Needs review
StatusFileSize
new11.14 KB
wim leers’s picture

Issue summary: View changes

Added beta evaluation.

Crell’s picture

Status: Needs review » Needs work
  1. +++ b/core/lib/Drupal/Core/Render/HtmlResponse.php
    @@ -36,12 +36,13 @@ public function setContent($content) {
    -      $content += ['#attached' => ['html_response_placeholders' => []]];
    +      $content += ['#attached' => ['html_response_attachment_placeholders' => [], 'placeholders' => []]];
    

    Nit: This would be easier to read if split to multiple lines.

  2. +++ b/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php
    @@ -128,6 +134,35 @@ public function processAttachments(AttachmentsInterface $response) {
       /**
    +   * Renders placeholders (#attached['placeholders']).
    +   *
    +   * @param \Drupal\Core\Render\HtmlResponse $response
    +   *   The HTML response whose placeholders are being replaced.
    +   *
    +   * @see \Drupal\Core\Render\Renderer::replacePlaceholders()
    +   * @see \Drupal\Core\Render\Renderer::renderPlaceholder()
    +   */
    

    Let me make sure I am following the logic of this method, as it's rather inside out...

    Given an HtmlResponse object, whose body contains placeholders and has placeholder replacement attachments...

    Convert the HtmlResponse to an equivalent render array.

    renderRoot() the render array, which will swap in the placeholders.

    Reset the body of the rendered array back on the Response, which MAY include new dependencies and other attachments based on the now-executed placeholders.

    Right? If so, let's add some version of the above to the docblock because it took me 10 minutes to write this summary. :-)

    Also, this method should return the modified $response rather than just letting it be an input/output parameter. Both work, but explicit returns make the code easier to follow.

  3. +++ b/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php
    @@ -128,6 +134,35 @@ public function processAttachments(AttachmentsInterface $response) {
    +  protected function renderPlaceholders(HtmlResponse $response) {
    

    Shouldn't this be checking against an interface, not a class?

  4. +++ b/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php
    @@ -128,6 +134,35 @@ public function processAttachments(AttachmentsInterface $response) {
    +    // RendererInterface::renderRoot() renders the $build render array, it
    +    // updates $build by reference. We don't care about the return value (which
    +    // is just $build['#markup']), but about the resulting render array.
    +    // @todo Simplify this when https://www.drupal.org/node/2495001 lands.
    +    $this->renderer->renderRoot($build);
    

    First sentence is a runon. I think you mean "renderRoot() renders the $build render array and updates it in place."

  5. +++ b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
    @@ -125,11 +139,28 @@ public function renderResponse(array $main_content, Request $request, RouteMatch
    +      // RendererInterface::render() renders the $html render array, it updates
    +      // $html by reference. We don't care about the return value (which is just
    +      // $html['#markup']), but about the resulting render array.
    +      // @todo Simplify this when https://www.drupal.org/node/2495001 lands.
    

    Same language correction as above.

  6. +++ b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
    @@ -125,11 +139,28 @@ public function renderResponse(array $main_content, Request $request, RouteMatch
    +    // Also associate the required cache contexts.
    +    // (Because we use ::render() above and not ::renderRoot(), we manually must
    +    // ensure the HTML response varies by the required cache contexts.)
    

    So why don't we use renderRoot()?

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new12.27 KB
new5.75 KB
  1. Done.
  2. (Yes, I agree it's "rather inside out" due to the Response object -> render array translation. If only we had some value object instead of render arrays… :P)

    Your understanding is 100% correct.

    Docblock updated.

    +1 on this returning a response object. I wonder why I didn't do that before :/

  3. This was asked before, so let me quote my previous answer from #2429617-207: Make D8 2x as fast: Dynamic Page Cache: context-dependent page caching (for *all* users!):

    We don't have that interface (see point 1).
    We could use AttachmentsInterface, but I think it's semantically wrong. This is only for HTML responses.

    Given that Symfony only has Response and not ResponseInterface. Similarly, we can just have HtmlResponse, and BigPipe could provide BigPipeResponse extends HtmlResponse.

    Introducing HtmlResponseInterface can be done in a separate issue.

  4. Fixed.
  5. Fixed.
  6. Now documented why.
Crell’s picture

Status: Needs review » Reviewed & tested by the community
+++ b/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php
@@ -128,6 +134,55 @@ public function processAttachments(AttachmentsInterface $response) {
+    // updates it in place.. We don't care about the return value (which is just

Extra period. Can fix on commit.

I think that's well-documented enough now. :-) Thanks, Wim.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

I've spent some time on IRC discussing this change on IRC with @Wim Leers I'm that happy that we're injecting and using renderer.config into the HtmlRenderer and using it. We discussed several work around including:

  • Relying on HtmlResponseAttachmentsProcessor to add them, This does not work because HtmlRenderer needs to return a properly cacheable response with all cache contexts
  • Adding a new method on the Renderer that does renderRoot without the placeholdering. This seems suboptimal ... it is alreadt hard to work out when to use renderRoot(), renderPlain() and just plain ol' render().
  • Maybe we should rename renderer.config to something that does not seem so specific.

I think only the last one is actually worth pursuing and we can do that as a followup. Fixed the double fullstop on commit. Committed 0549d59 and pushed to 8.0.x. Thanks!

Thanks for adding the beta evaluation to the issue summary.

  • alexpott committed 0549d59 on 8.0.x
    Issue #2551989 by Wim Leers, Crell: Move replacing of placeholders from...
wim leers’s picture

Many thanks! This unblocks #2349011: Support placeholder render strategies so contrib can support BigPipe, ESI… and helps make #2429617: Make D8 2x as fast: Dynamic Page Cache: context-dependent page caching (for *all* users!) more focused, so yay! :)

Follow-up created: #2553373: Consider renaming renderer.config. Expect it to become more meaty tomorrow. In meetings all day today.

Status: Fixed » Closed (fixed)

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