Problem/Motivation

Our documentation specifies that #ajax items in render arrays must be arrays. RenderElement::preRenderAjaxForm() confirms this behavior. Yet in three places we set '#ajax' => TRUE in render arrays.

Error:

Cannot use a scalar value as an array in Drupal\Core\Render\Element\RenderElement::preRenderAjaxForm() (line 315 of /<redacted>/web/core/lib/Drupal/Core/Render/Element/RenderElement.php)

Steps to reproduce

Run MR's test without a fix.
Or try related issue's MR without this change/fix https://git.drupalcode.org/project/drupal/-/merge_requests/4053/diffs#01...

      // Workaround for https://www.drupal.org/project/drupal/issues/2651418.
      // @todo Remove the below when the referenced issue is fixed.
      foreach (Element::children($sub_handler_settings) as $key) {
        if (\array_key_exists('#ajax', $sub_handler_settings[$key]) &&
          !\is_array($sub_handler_settings[$key]['#ajax'])
        ) {
          $sub_handler_settings[$key]['#ajax'] = [];
        }

        foreach (Element::children($sub_handler_settings[$key]) as $sub_key) {
          if (\array_key_exists('#ajax', $sub_handler_settings[$key][$sub_key]) &&
            !\is_array($sub_handler_settings[$key][$sub_key]['#ajax'])
          ) {
            $sub_handler_settings[$key][$sub_key]['#ajax'] = [];
          }
        }
      }

Proposed resolution

- Update \Drupal\Core\Render\Element\Ajax documenation.
- Make sure '#ajax' => TRUE are accepted - transform into array in core/lib/Drupal/Core/Render/Element/RenderElement.php

Remaining tasks

- Update drupal.org and api.drupal.org documentations after commit.

User interface changes

None.

API changes

None.

Data model changes

None.

Issue fork drupal-2651418

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:

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Xano created an issue. See original summary.

Xano’s picture

Status: Active » Needs review
FileSize
2.09 KB

Status: Needs review » Needs work

The last submitted patch, 2: drupal_2651418_2.patch, failed testing.

dawehner’s picture

Are we sure we don't need any kind of manual testing for it?

Xano’s picture

I first wanted to see if these items did not actually do anything, but the test failures seem to indicate they do. I'd need to analyze the test to say anything useful.

tim.plunkett’s picture

So #ajax => TRUE is shorthand for "I want all of the default values".
In \Drupal\Core\Render\Element\RenderElement::preRenderAjaxForm(), we first check if (empty($element['#ajax'])) {
So an empty array wouldn't work there.
Then later we have if (isset($element['#ajax']) && !isset($element['#ajax']['event'])) {
And it derives the $element['#ajax']['event'] and other values based on $element['#type']

So we just need to fix our docs.

Xano’s picture

Where in the code do we explicitly allow TRUE? The first line in RenderElement::preRenderAjaxForm() that accesses specific #ajax contents is if (!empty($element['#ajax']['disable-refocus'])) {, which does not fail if #ajax, but before this there's no code that checks for boolean values either.

Then further down, in the switch statement, #ajax array items are written to, but at that point #ajax is not confirmed to be an array yet. This went wrong when I was testing https://github.com/bartfeenstra/drupal-verf, because line 316 in RenderElement::preRenderAjaxForm() was reached, which is $element['#ajax']['event'] = 'change';, even though $element['#ajax'] == TRUE, which comes from DefaultSelection.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.5.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

Bug reports should be targeted against the 8.9.x-dev branch from now on, and new development or disruptive changes should be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

larowlan’s picture

Version: 8.9.x-dev » 9.1.x-dev
Status: Needs work » Postponed (maintainer needs more info)
Issue tags: +Needs issue summary update, +Bug Smash Initiative

Does this issue still apply?

If so, can someone update the issue to use the new issue summary template.

Judging by the last comments, it looks like this is a documentation issue, however there's come conjecture about whether that actually works.

I found some instances of this in modules I maintain and can confirm they work, so that feels like it is a docs issue.

Version: 9.1.x-dev » 9.3.x-dev

Drupal 9.1.10 (June 4, 2021) and Drupal 9.2.10 (November 24, 2021) were the last bugfix releases of those minor version series. Drupal 9 bug reports should be targeted for the 9.3.x-dev branch from now on, and new development or disruptive changes should be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

quietone’s picture

Version: 9.3.x-dev » 10.0.x-dev
Assigned: Xano » Unassigned
Issue summary: View changes
Status: Postponed (maintainer needs more info) » Needs review
FileSize
868 bytes

Here is an attempt to add documentation by someone who doesn't know render arrays and #ajax. Even with that I reckon it will be easier for others to comment if there is something here to start with. Not running tests because this is only documentation.

DieterHolvoet made their first commit to this issue’s fork.

DieterHolvoet’s picture

I'm encountering this issue when adding an entity reference views filter as being worked on in #2429699: Add Views EntityReference filter to be available for all entity reference fields. The stack trace:

Error: Cannot use a scalar value as an array in Drupal\Core\Render\Element\RenderElement::preRenderAjaxForm() (line 315 of /<redacted>/web/core/lib/Drupal/Core/Render/Element/RenderElement.php)
#0 /<redacted>/web/core/lib/Drupal/Core/Render/Element/RenderElement.php(232): Drupal\Core\Render\Element\RenderElement::preRenderAjaxForm(Array)
#1 [internal function]: Drupal\Core\Render\Element\RenderElement::processAjaxForm(Array, Object(Drupal\Core\Form\FormState), Array)
#2 /<redacted>/web/core/lib/Drupal/Core/Form/FormBuilder.php(1007): call_user_func_array(Array, Array)

I opened a MR fixing the issue.

a.dmitriiev’s picture

Status: Needs review » Reviewed & tested by the community

I have the same problem as DieterHolvoet . I am also using patch from #2429699: Add Views EntityReference filter to be available for all entity reference fields issue. MR fixes the problem. It seems that tests are failing for some infrastructure reason.

catch’s picture

Status: Reviewed & tested by the community » Needs work

The MR is missing the documentation change from the patch in #18, looks to me like we need both. Could also still use an issue summary update.

Graber’s picture

While here, I'd also change that '#ajax' to array in /lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/DefaultSelection.php.

rodrigo panchiniak fernandes’s picture

MR in #20 also fixes

Error: Cannot use a scalar value as an array in Drupal\Core\Render\Element\RenderElement::preRenderAjaxForm() (line 315 of /<redacted>/web/core/lib/Drupal/Core/Render/Element/RenderElement.php)

when adding a custom entity reference field (PHP 8.1, Drupal 10.0.8).

rpayanm made their first commit to this issue’s fork.

rpayanm’s picture

I added the documentation from #18. Please review.

vasike’s picture

- updated summary
- add related issue

Also drupal.org and api.drupal.org documentations should be updated.

smustgrave’s picture

Exactly same as MR. To run against 11.x

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Passed 11.x also

Reviewing the changes seem fine. MAY need a test but will let committer decide.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 29: 2651418-29.patch, failed testing. View results

vasike’s picture

Status: Needs work » Reviewed & tested by the community

run re-test ... pass ... "revert the issue status"

larowlan’s picture

Version: 10.0.x-dev » 10.1.x-dev

Evaluating need for tests against https://www.drupal.org/project/drupal/issues/2972776#heuristics (Which is not yet adopted, just as a thought exercise).

  1. Are there clear steps to reproduce on the issue?Yes
  2. Is it easy to verify via manual testing that the bug is fixed?Yes
  3. Is it a 'trivial' patch with small, easy to understand changes?Yes
  4. Is the code being changed in self-contained/@internal code that we don't expect contrib modules to interact with significantly? (plugins, controllers etc.)Yes
  5. In the case that the bug is committed with test coverage then later regresses, is the impact likely to be minimal or at least no worse than leaving the bug unfixed (for example site were unlikely to be actively mitigating the bug with workarounds/patches that would be removed when it's fixed)?Yes
  6. Is the bug fix achieved without adding new, untested, code paths?Yes
  7. To add test coverage, would it need an explicit 'regression' test that mainly prevents us reverting the patch rather than improving coverage in general?Yes
  8. If the test coverage was deferrred to a follow-up, would it be easy for someone who didn't work on the original bug report to pick it up?Yes
  9. Does the issue expose a general lack of test coverage for the specific subsystem, and if so would it be better to add generic test coverage for that subsystem in a separate issue so that the test coverage can be added in a maintainable way, rather than a regression test?Yes

So on that basis I think this is a good candidate for not adding tests, but I'll ask for second opinion

+++ b/core/lib/Drupal/Core/Render/Element/Ajax.php
@@ -5,7 +5,21 @@
+ * set to true to use default values.

nit I think this should be TRUE not true, can be fixed on commit

Moving to 10.1.x as 10.0.x is now security only

larowlan credited lauriii.

larowlan’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/lib/Drupal/Core/Render/Element/RenderElement.php
@@ -268,6 +268,11 @@ public static function preRenderAjaxForm($element) {
+    // If set to TRUE, default values should be applied.
+    if ($element['#ajax'] === TRUE) {
+      $element['#ajax'] = [];
+    }

Is this actually needed?

We have instances of this in core in selection handlers, and they work - so I suspect not?

If they work without this change, this is just a docs issue and therefore we don't need tests

Crediting some others who helped me to review this

vasike’s picture

Status: Needs work » Needs review

@larowlan as core introduces '#ajax' => TRUE, in several places for some elements
And as discussions on the issue and also on related issues say,
i would say we need to make sure it doesn't bring issues with it.

I updated the MR adding just some small tests ... about element wit no ajax but also with no empty AJAX ( '#ajax' => TRUE,)
And without this fix ... it will fail.

catch’s picture

On #33:

Is the code being changed in self-contained/@internal code that we don't expect contrib modules to interact with significantly? (plugins, controllers etc.)Yes

This should be a NO - the changes are in the render/AJAX system and the bug can be triggered by contrib usage of the API. Maybe we should re-word that one to make it more explicit?

To add test coverage, would it need an explicit 'regression' test that mainly prevents us reverting the patch rather than improving coverage in general?Yes

There is RenderElementTest so it might be a case of adding a method here, I'm not sure how straightforward that is though so probably a maybe?

larowlan’s picture

Its sounds like #36 added tests, so this one is ready for review again I think

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Can confirm #36 that without the fix the additional tests do fail.

Error : Cannot use a scalar value as an array

lauriii’s picture

Status: Reviewed & tested by the community » Needs work

Posted comment to the MR

DieterHolvoet’s picture

Status: Needs work » Needs review

Moved the new docs to core.api.php.

smustgrave’s picture

Status: Needs review » Needs work

Hiding patches if fix is going to be in MR.

MR needs to be updated to 11.x

vasike’s picture

hmm ... this issue seems to be for 10.1.x ... not 11.x

but this still means we need to update the target branch .. so is there anyone with the permissions to update the target source

or i could create new MR(s) ... for 10.1.x and 11.x?

DieterHolvoet’s picture

Version: 10.1.x-dev » 11.x-dev
DieterHolvoet’s picture

Status: Needs work » Needs review

I rebased against 11.x.

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: +Needs Review Queue Initiative

Thanks! Happy to see that update the target branch didn't cause much issue, know sometimes it does.

Looking at the changes, the comments seem fine

Running test-only feature I get

There was 1 error:
1) Drupal\Tests\Core\Render\Element\RenderElementTest::testPreRenderAjaxForm
Error: Cannot use a scalar value as an array
/builds/issue/drupal-2651418/core/lib/Drupal/Core/Render/Element/RenderElement.php:322
/builds/issue/drupal-2651418/core/tests/Drupal/Tests/Core/Render/Element/RenderElementTest.php:63
/builds/issue/drupal-2651418/vendor/phpunit/phpunit/src/Framework/TestResult.php:728
/builds/issue/drupal-2651418/vendor/phpunit/phpunit/src/Framework/TestSuite.php:684
/builds/issue/drupal-2651418/vendor/phpunit/phpunit/src/TextUI/TestRunner.php:651
/builds/issue/drupal-2651418/vendor/phpunit/phpunit/src/TextUI/Command.php:144
/builds/issue/drupal-2651418/vendor/phpunit/phpunit/src/TextUI/Command.php:97
ERRORS!

Think this is good

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

So the reason ['#ajax'] = TRUE works in the entity reference items is because it has special code that changes it into an array. It is not meaning to change it to default values at all :)

See \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem::fieldSettingsAjaxProcessElement() for where this happens. If this did not occur then treating TRUE like an array would produce errors on PHP 8.1.

I think we could consider not making this change but instead documenting the places in the entity reference system where #ajax is being set to TRUE with an

@see \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem::fieldSettingsAjaxProcessElement() 
vasike’s picture

Issue summary: View changes
Status: Needs work » Needs review

@alexpott this is not related with

\Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem

But "others" as
\Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection
(Maybe also with) \Drupal\user\Plugin\EntityReferenceSelection\UserSelection

Details about the issue/errors are in the comments and also related issue
Also updated the issue info.

Could you please check/review again?
Thanks

alexpott’s picture

@vasike I promise you this has everything to do with EntityReferenceItem::fieldSettingsAjaxProcessElement().

The Plugin\EntityReferenceSelection\DefaultSelection::buildConfigurationForm() and Plugin\EntityReferenceSelection\UserSelection::buildConfigurationForm() are called by \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem::fieldSettingsForm() - specifically from the line:

  $form['handler']['handler_settings'] += $handler->buildConfigurationForm([], $form_state);

The ::fieldSettingsForm() method is the one that adds '#process' => [[static::class, 'fieldSettingsAjaxProcess']], too... which results in

      $element['#ajax'] = TRUE;

being converted to

      $element['#ajax'] = [
        'trigger_as' => ['name' => 'handler_settings_submit'],
        'wrapper' => 'field-combined',
        'element' => $main_form['#array_parents'],
      ];
alexpott’s picture

This #ajax => TRUE is just a shorthand for Entity selection handlers - so that they can get the correct [‘#ajax’][‘element’] set without having to repeat the #process logic everywhere.

smustgrave’s picture

Status: Needs review » Needs work

Thanks a ton @alexpott for taking a look!

vasike’s picture

Status: Needs work » Needs review

New MR https://git.drupalcode.org/project/drupal/-/merge_requests/6458
and new approach - make sure we have an array for #ajax element property ... get rid of '#ajax' => TRUE

And it seems it works (All Green)
Also checked with the related issue MR ... and we don't get the errors.

What do you think @alexpott?

And of course thank you.

smustgrave changed the visibility of the branch 11.x to hidden.

smustgrave changed the visibility of the branch 2651418-non-array-values-for to hidden.

smustgrave’s picture

Status: Needs review » Needs work

Possible to get a test for the new approach?

alexpott’s picture

The current approach would be a BC break since before a value of FALSE would not add ajax but now it would. Also a value of TRUE would be not add the ajax default so custom and contrib would be broken.

I don't think it is worth changing the behaviour here. I think it is worth added @see's to where #ajax is being set to TRUE so we don't wonder the whys and wherefores in the future.

vasike’s picture

Status: Needs work » Needs review

ok. there could BC breaks
small update for TRUE cases.

imho, there could a change records to invite custom devs and contrib maintainers to use [] instead of TRUE.
To avoid some cases as described in the issue and the "use case" from related issue.

alexpott’s picture

@vasike I do not understand why we want to change this. We can add the @see's so people are not confused in the future and be done.

vasike’s picture

@alexpott thanks a lot for your persistence: I was blind, but not ... I can see
Sorry I didn't listen ... I was blinded by the "edge case" in Views Modal ... which it doesn't work ("properly") with form validation or ajax ... but this is another story/issue (Drupal chapter).

So, for now 2 updates:
1. new "documentation" MR
2. Update the related issue MR ... removing the ajax, as we did for #required property
within this commit https://git.drupalcode.org/project/drupal/-/merge_requests/4053/diffs?co...

How are we ... now?

p.s. Maybe in another places, this could work
$form['#process'][] = [EntityReferenceItem::class, 'fieldSettingsAjaxProcess'];
But do we need to mention ... somewhere?

vasike changed the visibility of the branch 2651418-ajax-array-only to hidden.

smustgrave’s picture

Status: Needs review » Needs work

Thanks @alexpott for taking another look.

vasike’s picture

Status: Needs work » Needs review

MR threads addressed.

smustgrave’s picture

Status: Needs review » Needs work

Believe the feedback from @alexpott has been addressed here but left 1 comment about one of the comments.

vasike’s picture

Status: Needs work » Needs review

indeed ... missed to include the change in the previous commit

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Thanks for addressing!

alexpott’s picture

Version: 11.x-dev » 10.2.x-dev
Status: Reviewed & tested by the community » Fixed

Backported to 10.2.x as this is a docs fix.

Committed and pushed fe3aa496f7 to 11.x and 2404bcd91a to 10.3.x and b825f8e2e5 to 10.2.x. Thanks!

For those like @DieterHolvoet who experience bugs due to this - it must be happening because your render array contains an unprocessed form. Forms must be processed before the prerender is called so toy need to track down why that is occurring.

  • alexpott committed b825f8e2 on 10.2.x
    Issue #2651418 by vasike, DieterHolvoet, smustgrave, Xano, rpayanm,...

  • alexpott committed 2404bcd9 on 10.3.x
    Issue #2651418 by vasike, DieterHolvoet, smustgrave, Xano, rpayanm,...

  • alexpott committed fe3aa496 on 11.x
    Issue #2651418 by vasike, DieterHolvoet, smustgrave, Xano, rpayanm,...

Status: Fixed » Closed (fixed)

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