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.

Comments

drunken monkey created an issue. See original summary.

drunken monkey’s picture

Status: Active » Needs review
StatusFileSize
new1.05 KB

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

StatusFileSize
new961 bytes
new2.98 KB

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
StatusFileSize
new864 bytes
new2.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.