Problem/Motivation

AjaxRenderer::renderResponse() accesses $main_content['#attached'] directly after calling renderRoot(), without checking whether the key exists:

// AjaxRenderer.php line 52
$html = $this->renderer->renderRoot($main_content);
$response->setAttachments($main_content['#attached']);

When the render array has no attachments, renderRoot() does not guarantee that #attached is set on the array. This produces:

Warning: Undefined array key "#attached" in Drupal\Core\Render\MainContent\AjaxRenderer->renderResponse() (AjaxRenderer.php line 52)

Steps to reproduce

  1. Create a route whose _controller returns a render array with no explicit #attached key.
  2. Request that route with ?_wrapper_format=drupal_ajax (or any request that routes through AjaxRenderer).
  3. Observe the PHP warning.

Proposed resolution

Use the null coalescing operator to fall back to an empty array:

- $response->setAttachments($main_content['#attached']);
+ $response->setAttachments($main_content['#attached'] ?? []);

Remaining tasks

  • Add a test covering an AJAX request to a controller whose render array has no #attached key.

User interface changes

None.

Introduced terminology

None.

API changes

None.

Data model changes

None.

Release notes snippet

None.

Issue fork drupal-3614993

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

macsim created an issue. See original summary.

macsim’s picture

Status: Active » Needs review

Status should be "Needs work" but I need an advice so I am quickly setting it to "Needs review"

The fix adds ?? [] to line 52 of AjaxRenderer::renderResponse(). This prevents a PHP warning when a render array has no #attached key.

A test for this case needs a renderer that does not set #attached. The existing setUp() renderer always adds it via $elements += ['#attached' => []] by reference. So the new test must use its own AjaxRenderer instance with a different renderer stub.

This means setUp()'s mock — which expects renderRoot to be called at least once — is never invoked in the new test. PHPUnit then reports a failing expectation.

Three options:

  1. Change atLeastOnce() to any() in setUp(). This removes the call-count assertion from all test methods in the class.
  2. Place the new test in a separate file with its own class. The existing AjaxRendererTest and its setUp() stay unchanged.
  3. Change createMock() to createStub() in setUp(). With a stub, expects() calls are not verified — atLeastOnce() becomes a silent no-op.

Which option is preferred?

smustgrave’s picture

Lets do #3

smustgrave’s picture

Status: Needs review » Needs work

NW for that.

Clicked save too fast but #3 seems least disruptive.

macsim’s picture

thx @smustgrave

macsim’s picture

Status: Needs work » Needs review
smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Test coverage looks good! Thanks for knocking that out so quickly!

quietone’s picture

Title: AjaxRenderer::renderResponse() triggers "Undefined array key #attached" when render array has no attachments » AjaxRenderer::renderResponse() should handle a render array with no attachments

Just tweaking the title

  • amateescu committed e882b59c on main
    fix: #3614993 AjaxRenderer::renderResponse() should handle a render...
amateescu’s picture

Version: main » 11.x-dev
Status: Reviewed & tested by the community » Patch (to be ported)

Committed e882b59 and pushed to main. Thanks!

Needs a rebased MR for 11.x.