Problem

In a config entity query, when you add an IS NOT NULL condition (e.g., with exists()) on a field, but the entity's field value has an array value for that field, a warning will be triggered:

Warning: mb_strtolower() expects parameter 1 to be string, array given in Drupal\Component\Utility\Unicode::strtolower() (line 326 of core/lib/Drupal/Component/Utility/Unicode.php).

This happens because of this code in \Drupal\Core\Config\Entity\Query\Condition::match():

      // We always want a case-insensitive match.
      if (!is_bool($value)) {
        $value = Unicode::strtolower($value);
      }

Obviously, an array is no boolean value, but we also don't want to convert it to lowercase. Convert its values to lowercase, maybe – that would also support matching array field values to array condition values. Not sure if that should be supported, but I don't see any reason not to at least fix it for IS NOT NULL.

Example scenario

I have a config entity with several plugin settings in a property, with this structure:

PROPERTY => [
  PLUGIN_ID => [
    # PLUGIN_SETTINGS
  ],
  PLUGIN_ID => [
    # PLUGIN_SETTINGS
  ],
  # …
]

Now, if I want to find entities that use a certain plugin, I need exactly this: an entity query with ->exists('PROPERTY.PLUGIN_ID'), where the value found for the field will be an array.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

drunken monkey created an issue. See original summary.

drunken monkey’s picture

This should do it.

tstoeckler’s picture

Issue tags: +Needs tests

Fix looks good to me, thanks!

I think we can do the same for IS NULL, though, as well. Also would be nice to add a quick test. I see we already have \Drupal\KernelTests\Core\Entity\ConfigEntityQueryTest::testConfigEntityQuery() which does some testing with ->exists() so hopefully it's not too much work.

tstoeckler’s picture

Status: Needs review » Needs work

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

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now 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.

drunken monkey’s picture

tstoeckler’s picture

Status: Needs work » Needs review

tstoeckler’s picture

Status: Needs review » Needs work

Thanks the patch looks great, including the added test coverage. I have one minor quibble:

+++ b/core/lib/Drupal/Core/Config/Entity/Query/Condition.php
@@ -154,6 +154,14 @@ protected function matchArray(array $condition, array $data, array $needs_matchi
+      $is_set = isset($value);
+      return $should_be_set === $is_set;

I think the putting $is_set into a variable is a bit unnecessary.

Would be awesome if you could quickly fix that, then I can RTBC.

tstoeckler’s picture

+++ b/core/lib/Drupal/Core/Config/Entity/Query/Condition.php
@@ -154,6 +154,14 @@ protected function matchArray(array $condition, array $data, array $needs_matchi
+    if (in_array($condition['operator'], ['IS NULL', 'IS NOT NULL'])) {

Also, if you don't mind, it would be nice to add the

$strict =
 TRUE

third parameter here, to ensure this actually does what it should. Otherwise this will return unexpected things when e.g. integer 1 is passed as $condition['operator'].

Dinesh18’s picture

Assigned: Unassigned » Dinesh18
Dinesh18’s picture

Here is an updated patch and interdiff which implements #9 and #10

Dinesh18’s picture

Status: Needs work » Needs review
tstoeckler’s picture

Status: Needs review » Needs work

Thanks @Dinesh18!

The patch looks great, but I guess I failed at explaining what I meant in #10.

+++ b/core/lib/Drupal/Core/Config/Entity/Query/Condition.php
@@ -154,6 +154,13 @@ protected function matchArray(array $condition, array $data, array $needs_matchi
+    if (in_array($condition['operator'], ['IS NULL', 'IS NOT NULL'], $strict = TRUE)) {

I put the

$strict =
 

part in my previous comment only to explain what the third parameter is for, it's not actually needed in the code. So where you put

$strict =
 TRUE

, you can just put TRUE, that would be perfect.

Thanks again and sorry for not taking the time to explain this properly earlier.

Dinesh18’s picture

Status: Needs work » Needs review
FileSize
864 bytes
2.97 KB

Here is the updated patch and interdiff which implements #14

tstoeckler’s picture

Status: Needs review » Reviewed & tested by the community

That's perfect, thank you!

  • catch committed 9a666c1 on 8.5.x
    Issue #2899014 by drunken monkey, Dinesh18, tstoeckler: Config entity...

  • catch committed e3250a5 on 8.4.x
    Issue #2899014 by drunken monkey, Dinesh18, tstoeckler: Config entity...

catch credited catch.

catch’s picture

Version: 8.5.x-dev » 8.4.x-dev
Status: Reviewed & tested by the community » Fixed

Committed/pushed to 8.5.x and cherry-picked to 8.4.x. Thanks!

xjm’s picture

+++ b/core/lib/Drupal/Core/Config/Entity/Query/Condition.php
@@ -154,6 +154,13 @@ protected function matchArray(array $condition, array $data, array $needs_matchi
+    // "IS NULL" and "IS NOT NULL" conditions can also deal with array values,
+    // so we return early for them to avoid problems.
+    if (in_array($condition['operator'], ['IS NULL', 'IS NOT NULL'], TRUE)) {
+      $should_be_set = $condition['operator'] === 'IS NOT NULL';
+      return $should_be_set === isset($value);
+    }
...
       // We always want a case-insensitive match.

I would have just written this as

if ($condition['operator'] === 'IS NULL') {
  return FALSE;
}
elseif ($condition['operator'] === 'IS NOT NULL') { 
  return TRUE;
}

Way less complicated logic and easier to read, and no creating both an array and a scalar we don't need.

tstoeckler’s picture

#21 is missing two isset()s. Either way would be fine by me, but I actually find the way it went in clearer.

Status: Fixed » Closed (fixed)

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