Support from Acquia helps fund testing for Drupal Acquia logo

Comments

tim.plunkett’s picture

FileSize
1.56 KB

Here is an attempt at a test. Except that the drupal_set_message doesn't seem to be a part of the page. debug(drupal_get_messages()) showed the message just fine, but debug($this->drupalGetContent()); showed nothing.

tim.plunkett’s picture

Per xjm's suggestion, just resorting to using in_array and assertTrue.
Attaching both test, and test with fix.

bfroehle’s picture

Status: Needs review » Needs work
+++ b/modules/simpletest/tests/common.testundefined
@@ -1708,6 +1708,20 @@ class DrupalRenderTestCase extends DrupalWebTestCase {
+    // Define an element with an invalid key.
+    $key = 'not valid';
+    $element = array(
+      $key => 'fail',

A first reading of this would lead the user to believe that one cannot use the key 'not valid' in a render array. However this is not the case. The real issue is that the value of the key is a string (and not an array).

Since the tests are a valuable resource for figuring out how to write Drupal code properly, I suggest we change the comments to reflect the real cause of the error:

$value = 'should be an array, not a string';
$element = array(
  'child' => $value,
);

(or similar).

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
1.89 KB

#3 is very true. Also, it should likely be an error, not a status.

fakingfantastic’s picture

+1'ing, if anyone is looking for a way to reproduce. Open up node_view_multiple() in node.module, take out the "#" in ['#weight'] and view /node on your site. You should get a bunch of ugly errors. This patch turns those errors into tasty messages.

tim.plunkett’s picture

This patch already exposed an invalid render api usage in toolbar: #1285098: Toolbar Drawer uses invalid Render API key

bfroehle’s picture

Hmm, on re-reading I understand now the original intent about invalid keys --- '#weight' is a valid key, but 'weight' is not.

Essentially we'd like the test to show that keys which start with a # are generically valid, but things which don't are (usually) treated as a child element which must be a render array. Hmf, confusing!

tim.plunkett’s picture

I guess I could add similar assertFalse()'s for $element['#key'] = 'string'; and $element['key'] = array();?
Is that overkill?

bfroehle’s picture

I think just a nice comment would suffice, or test something like

$element = array(
  '#key' => 'value', // okay
  'weight' => 10, // wrong, missing #
  'child1' => array(), // okay
  'child2' => 'fail', // wrong, not a renderable array
);

Or whatever. Sorry for chiming in here to only add some extra confusion. The original patch was probably good enough.

tim.plunkett’s picture

FileSize
2.01 KB

Expanded the comment a bit. I think this is ready to go.

Status: Needs review » Needs work

The last submitted patch, drupal-1283892-10.patch, failed testing.

tim.plunkett’s picture

Status: Needs work » Needs review

#10: drupal-1283892-10.patch queued for re-testing.

Status: Needs review » Needs work

The last submitted patch, drupal-1283892-10.patch, failed testing.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
1.98 KB

Ugh sorry for the noise, I'd left a debug_backtrace in there.

tim.plunkett’s picture

Issue tags: +Needs backport to D7

To clarify, the current error for a FAPI-based invalid key is

Warning: Invalid argument supplied for foreach() in element_children() (line 6141 of /var/www/d7/includes/common.inc).
Warning: Cannot use a scalar value as an array in drupal_render() (line 5629 of /var/www/d7/includes/common.inc).

which doesn't actually tell you what broke, and continues to try to use the key.

This patch prevents that error, and displays

$key is an invalid render array key.

where $key is the offending key.

In certain cases of standalone Render API usage (non-FAPI), there is currently no error, the key is silently skipped. After the patch, the key is still skipped, but the error is shown.

Tagging for possible backport.

BTMash’s picture

Status: Needs review » Reviewed & tested by the community

The tests pass and the code makes sense while doing a readthrough. Marking it at RTBC.

catch’s picture

Status: Reviewed & tested by the community » Needs review

I don't agree with using drupal_set_message() here, that means nothing in watchdog and it will always show to end users regardless of error reporting settings.

Why not trigger_error() or similar?

chx’s picture

Status: Needs review » Needs work

So, you are in the do or do not, there is no try camp? Sure! Let's throw exceptions and add array typing to everything that expects an element. Leave no argument behind.

chx’s picture

A quick review shows for example render() accepting strings so I would say only the two most important ones need an array marker: drupal_render and form_builder. If you add that then my patch becomes totally unnecessary.

chx’s picture

Hrm, that would leave out the $key. OK. Let's try then trigger_error with an E_USER_ERROR level and dont add arrays.

chx’s picture

Status: Needs work » Needs review
FileSize
1.98 KB

The test will fail btw.

Status: Needs review » Needs work

The last submitted patch, drupal-1283892-21.patch, failed testing.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
3.05 KB

Switching the render over to a menu callback was the only way I could catch the error.

Status: Needs review » Needs work

The last submitted patch, drupal-1283892-23.patch, failed testing.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
5.05 KB

The last patch exposed 3 new bugs. One in theme_aggregator_categorize_items(), one in theme_system_date_time_settings(), and one menu_overview_form_submit(). The last has it's own issue #1291528: menu_overview_form_submit() breaks element_children(), the first two do not yet. All three fixes are included to see if the tests pass.

webchick’s picture

Issue tags: -Needs backport to D7

Based on the diff, there's no way we could possibly backport this to D7. :\ Looks like contributed modules would almost certainly require updating.

tim.plunkett’s picture

Leaving off the backport tag, but for D7 I can reroll without the trigger_error in element_children, and without the test, and we'll be in good shape.

bfroehle’s picture

Looks like contributed modules would almost certainly require updating.

This is only half true... a correctly functioning contributed module shouldn't need updating. Only the broken ones would.

webchick’s picture

Yes, but poor end users are victim to Drupal spewing out errors suddenly on a minor point release of modules that were 'working' (for some values of working) fine before then. We keep doing that (like in 7.8 when suddenly Media module exploded in flames) and people will stop using Drupal 7...

However, I'm fully supportive of some means via, say, Devel module or similar, to provide this kind of extra checking in D7.

catch’s picture

With media it was an exception which gets spewed everywhere, this is just a php warning which should not. One option would be to make it E_STRICT for D7 then it's likely to be ignored for anyone who doesn't care about it.

bfroehle’s picture

Ooh, E_STRICT... brilliant!

tim.plunkett’s picture

E_STRICT doesn't work with trigger_error. Only E_USER_ERROR, E_USER_WARNING, and E_USER_NOTICE do.
http://php.net/manual/en/function.trigger-error.php

chx’s picture

We could add a recursive walker to devel module to check for erroneous keys. It'd be blatantly trivial. Very very nice work here with that array_intersect_key . I do not get the aggregator problem because drupal_render starts with

function drupal_render(&$elements) {
  if (!isset($elements)
    return NULL;
tim.plunkett’s picture

WHOA crazy interesting bug. At least to me.

Simplification of theme_aggregator_categorize_items:

$output = '';
$element = array();
$element['something'] = array('#markup' => 'Valid');
$output .= drupal_render($element['missing']);
print_r($element);
$output .= drupal_render_children($element);
print_r($output);

First print_r:

Array
(
    [something] => Array
        (
            [#markup] => Valid
        )

    [missing] => 
)

Second print_r:

Valid

$element['missing'] doesn't exist at first. But because drupal_render works by reference, and returns NULL, all of a sudden it IS there. And then drupal_render_children fails for the same reason.

tim.plunkett’s picture

The only solution I can think of is silently ignoring 'key' => NULL. So changing the else to elseif (isset()), or the like.

chx’s picture

Hrm. OK. We can add that elseif isset. While form_builder and _form_validate doesnt have the kind of passed-in NULL protection drupal_render has, those arent called from the outside so this artefact wont affect them. Congrats on finding a huge PHP WTF.

tim.plunkett’s picture

FileSize
5.05 KB

Turns out two of the earlier "fixes" are covered by the added isset(), so this is now just the change to element_children, the test, and the array_intersect_key fix.

tim.plunkett’s picture

FileSize
3.79 KB

ARGH. Wrong patch.

tim.plunkett’s picture

FileSize
3.89 KB

Added a comment.

Also, see http://www.phpwtf.org/more-array-references for a discussion of why this is crazy.

tim.plunkett’s picture

To note, this cannot be backported to D7 unless #1285098: Toolbar Drawer uses invalid Render API key is committed first.

chx’s picture

Status: Needs review » Reviewed & tested by the community

This is now ready.

catch’s picture

Would it be possible to do the test with set_error_handler() and errorException - seems like we could catch the error and pass/fail based on that, would make this nearly a unit test then rather than having to make the http request to read the error message off it.

See #1247666: Replace @function calls with try/catch ErrorException blocks for more.

If the answer is "no", or "after that patch is in" then I can live with it as is though.

catch’s picture

Status: Reviewed & tested by the community » Needs review
tim.plunkett’s picture

Status: Needs review » Reviewed & tested by the community

Using ErrorException seems like it will be a big improvement in the future, but nothing like drupal_exception_error_handler exists yet. Posting to the other issue to revisit this if we move in that direction.

catch’s picture

Status: Reviewed & tested by the community » Fixed

OK that's fair enough, adding the new API just for the test is a bit much, and we've already got that issue open.

Committed to 8.x, thanks!

Status: Fixed » Closed (fixed)

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

rfay’s picture

Status: Closed (fixed) » Needs work

Hmm.. This is a pretty major API change against render arrays, and broke both the Render Example and the Theming Example for D8. Is there an API change notice on this one?

User error: "0" is an invalid render array key in element_children() (line 6166 of includes/common.inc).

The related issue is #1304502: [critical] Theming Example and Render Example are broken

tim.plunkett’s picture

As I understood it, it wasn't technically an API change, but a clarification/enforcement.

The relevant breaking code in the Examples module is here: http://drupalcode.org/project/examples.git/blob/refs/heads/8.x-1.x:/rend...

That example doesn't follow the strict rules of render arrays, though it seems like a reasonable usage.

tim.plunkett’s picture

If the following code should be allowed, then this has to be rolled back and rethought.
Otherwise, this does need an API change notice, and Examples will need to change.

$element = array(
  t('theme for an element') => array(
    'child' => array(
      t('This is some text that should be put together'),
      t('This is some more text that we need'),
    ),
    '#separator' => ' | ',  // Made up for this theme function.
    '#theme' => 'render_example_aggregate',
  ),
);
function theme_render_example_aggregate($variables) {
  $output = '';
  foreach (element_children($variables['element']['child']) as $item) {
    $output .= $variables['element']['child'][$item] . $variables['element']['#separator'];
  }
  return $output;
}
chx’s picture

That's a completely illegal render element. Just because you rape the system by writing a theme function that renders it , doesn't mean it's valid :) edit: legal render elements render without any theme functions. theme functions are not supposed to change structure.

tim.plunkett’s picture

I posted [#1311610].
My first change notice, so I'm not sure that I did it properly.

tim.plunkett’s picture

Status: Needs work » Fixed

With the change record created, now its time to figure out how to fix this in Examples...

Status: Fixed » Closed (fixed)

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

Anonymous’s picture

What is the best way to override this in a custom case for form rendering? I have some elements in my $form array that are from $form_state that a custom module uses (Inline Entity Form). The reason they are there is to pre-populate the references on a cloned form...

The error is triggering because the form elements aren't "renderable", but it doesn't seem to actually matter.

q11q11’s picture

Issue summary: View changes

@vilepickle

I have faced your custom case (and maybe this might be useful to someone else too).
Quick way to override - is to convert all custom array values to array of arrays containing "#markup" => $value,
Something like this:

// FROM:
$data = [
  'user_id'   => $user->id(),
  'image_url' => $image_url,
];
// TO:
$data = [
  'user_id'   => ['#markup' => $user->id()],
  'image_url' => ['#markup' => $image_url],
];

At least this helped me getting over "fatal: user_id is an invalid render array key".