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
- Create a route whose
_controllerreturns a render array with no explicit#attachedkey. - Request that route with
?_wrapper_format=drupal_ajax(or any request that routes throughAjaxRenderer). - 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
#attachedkey.
User interface changes
None.
Introduced terminology
None.
API changes
None.
Data model changes
None.
Release notes snippet
None.
Issue fork drupal-3614993
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
Comment #3
macsim commentedStatus should be "Needs work" but I need an advice so I am quickly setting it to "Needs review"
The fix adds
?? []to line 52 ofAjaxRenderer::renderResponse(). This prevents a PHP warning when a render array has no#attachedkey.A test for this case needs a renderer that does not set
#attached. The existingsetUp()renderer always adds it via$elements += ['#attached' => []]by reference. So the new test must use its ownAjaxRendererinstance with a different renderer stub.This means
setUp()'s mock — which expectsrenderRootto be called at least once — is never invoked in the new test. PHPUnit then reports a failing expectation.Three options:
atLeastOnce()toany()insetUp(). This removes the call-count assertion from all test methods in the class.AjaxRendererTestand itssetUp()stay unchanged.createMock()tocreateStub()insetUp(). With a stub,expects()calls are not verified —atLeastOnce()becomes a silent no-op.Which option is preferred?
Comment #4
smustgrave commentedLets do #3
Comment #5
smustgrave commentedNW for that.
Clicked save too fast but #3 seems least disruptive.
Comment #6
macsim commentedthx @smustgrave
Comment #7
macsim commentedComment #8
smustgrave commentedTest coverage looks good! Thanks for knocking that out so quickly!
Comment #9
quietone commentedJust tweaking the title
Comment #11
amateescu commentedCommitted e882b59 and pushed to main. Thanks!
Needs a rebased MR for 11.x.