D8,9,10: EntityQuery condition: множественное поле типа checkboxes НЕ СОДЕРЖИТ значение - как проще?

Главные вкладки

Аватар пользователя OldWarrior OldWarrior 16 января в 0:59

Доброго дня и всем мира.

Вообще - этот момент меня парит с самого появления entityQuery. С тех пор так и не появилось внятных методов для инвертирования условия отбора сущностей с целью сделать выборку по ОТСУТСТВИЮ ЗАДАННОГО ЗНАЧЕНИЯ в полях МНОЖЕСТВЕННОГО ПОЛЯ (в моём случае 'checkboxes').

Допустим, выбрать из одиночного (single) поля чекбокса с наличием или отсутствием значения - не вопрос:

<?php
// Наличие.
$query->condition('field_is_paid'1);
// Отсутствие.
$query->notExists('field_is_paid');
?>

Также, выбрать из multiple-поля типа 'checkboxes' с наличием значения - не особый вопрос:

<?php
// Наличие во множественном.
$query->condition(
  
$query->andConditionGroup()->condition('field_passed_statuses''paid')
);
?>

Однако, как поступить если нужно выбрать те сущности, у которых в множественном поле типа 'checkboxes' не установлен заданный флаг/ключ? То есть, когда в случае из предыдущего примера нужно выбрать по полю 'field_passed_statuses', но только где НЕ УСТАНОВЛЕНО нужное значение (например, пусть так же: 'paid').

И вот тут начинаются танцы с бубном. Поскольку раз не установлен чекбокс - значит нет значения в рядах, а, значит - нет и результата. Кроме того, тут заведомо мешают "картине" другие значения из других дельт, которые могут присутствовать, а могут и отсутствовать. Т.е. поле может содержать значения других установленных ключей 'checkboxes', что уже будет означать, что оно не пустое и тогда под простые условия типа notExists() или 'IS NOT NULL' уже не попадёт.

К слову, я одно время выезжал именно по дельтам, но это способ работает, когда есть чёткое знание, что какая-то определённая часть из них обязательно заполнена, что позволяет нам точно адресоваться к нужному ключу по индексу дельты ключа. Например:

<?php
        $query
->condition(
          
$query->andConditionGroup()
            ->
notExists('field_passed_statuses.3.value'// Not printed.
            
->notExists('field_passed_statuses.4.value'// Not sent.
            
->condition($query->andConditionGroup()->condition('field_passed_statuses''paid')) // Paid.
        
);
?>

Но это, разумеется, в некотором роде и мазохизм и костыли.

Затем одно время я делал фильтрующие exclude-массивы, предварительно отправляя entity-запрос на НАЛИЧИЕ значения заданного значения во множественном поле, а затем повторяя запрос и инвертируя условие отбора наподобие:

<?php
$query
->condition('nid'$exclude_array'NOT IN');
?>

Это работало, но плодило много предварительных запросов и усложняло/запутывало код, что меня не устраивало.

В итоге я пришёл (отсюда: https://www.drupal.org/docs/8/api/database-api/dynamic-queries/conditions - см. "Using NOT IN With Multi Value field like roles > user entity") к более универсальной конструкции, при которой условие "НЕ СОДЕРЖИТ ЗНАЧЕНИЕ" применяется ко всем возможным дельтам поля. Однако, чтобы пройти по всем возможным дельтам поля в случае с множественными чекбоксами - нужно знать макс. возможное кол-во дельт для поля, т.е. - нужно предварительно получать все допустимые значения для поля (как они определены в настройках поля через админку). Выглядит это примерно так:

<?php
// Получаем доступные значения чекбоксов (кол-во ключей массива эквивалентно максимуму дельт).
$node_prototype   = \Drupal::service('entity_type.manager')->getStorage('node')->create(['type' => 'order']);
$passed_statuses  $node_prototype->getFieldDefinition('field_passed_statuses')->getFieldStorageDefinition()->getSetting('allowed_values');

// Строим запрос.
$query = \Drupal::entityQuery('node');
$query->condition('type''order');
...
// Вот здесь то, о чём речь.
$andAllStatuses $query->andConditionGroup();
$delta 0;
foreach (
$passed_statuses as $status => $title) {
  
// Последовательно добавляем условия для всех дельт 
  // вида "НЕ СОДЕРЖИТ ЗНАЧЕНИЕ или НЕ СУЩЕСТВУЕТ"
  
$orExcludeStatus $query->orConditionGroup()
    ->
condition('field_passed_statuses.' $delta '.value''received''NOT IN')
    ->
condition('field_passed_statuses.' $delta '.value'NULL'IS NULL');
  
$andAllStatuses->condition($orExcludeStatus);
  
$delta ++;
}
$query->condition($andAllStatuses);
...
$orders $query->execute();
?>

Это работает, но раздувает и делает менее понятным/разборчивым как запрос, так и код в целом. Да и не очень-то гибкое решение - в плане того, что нужно заведомо знать или получать (как в этом примере) массив всех возможных значений - только для того, чтобы узнать макс. кол-во допустимых дельт для поля.

Мне это тоже не очень нравится. Я много копал источников по этому вопросу, но внятного и более универсального решения, чем вышеприведённое, не нашёл.

Кто что может посоветовать в решении этого вопроса? Есть ли что-то более простое и удобное? В идеале хотелось бы видеть что-то короткое и удобочитаемое, наподобие:

<?php
// Как гипотетический пример отсутствия значения 'paid' во множественном поле чекбоксов.
$query->condition(
  
$query->andConditionGroup()->condition('field_passed_statuses''paid''NOT CONTAINS')
);
?>

Комментарии

Аватар пользователя jason32 jason32 30 октября в 10:00

Сдается мне никто особо и не думал , тем более когда нихрена непонятна терминология, какие ещё к черту дельты, что это за дельты, у Дуная видимо или у Нила?
Но как видим никто не уточнял, видимо и не читал. Потому что телепатов нет

Аватар пользователя gun_dose gun_dose 31 октября в 11:38

Затем одно время я делал фильтрующие exclude-массивы, предварительно отправляя entity-запрос на НАЛИЧИЕ значения заданного значения во множественном поле, а затем повторяя запрос и инвертируя условие отбора наподобие:

А почему нельзя сразу сделать один запрос с NOT IN?

А ещё вот тут есть интересные примеры

Аватар пользователя OldWarrior OldWarrior 31 октября в 21:27

gun_dose wrote: А почему нельзя сразу сделать один запрос с NOT IN?

Я тут не совсем понял, что подразумевается. NOT IN - вы здесь имеете в виду по значениям полей? По причине множественного поля. Мне же нужно условие "ОТСУТСТВУЕТ ОДНО УКАЗАННОЕ ЗНАЧЕНИЕ". В этом-то весь и цимес, так сказать. Здесь NOT IN не даст нужного эффекта, поскольку другие значения в этом поле будут тоже попадать под условия. Ну, к примеру имеем в рядах полей двух разных сущностей одного типа по две дельты 'key1' и 'key2':

[0] => [
  'key1' => 'one',
  'key2' => 'two',
]

и

[1] => [
  'key1' => 'one',
  'key2' => 'three',
]

И здесь ->condition('some_field', ['two'], 'NOT IN') даст в результате оба ряда (0, 1), поскольку в первом ряду [0]['key1'] не содержит 'two' и во втором ряду [1]['key1'] и [1]['key2'] тоже не содержит 'two'. Что неправильно, поскольку в первой сущности [1]['key2'] равен именно 'two'.

Или вы что-то другое подразумевали?

gun_dose wrote: А ещё вот тут есть интересные примеры

Да, спасибо. Я когда-то просматривал и эти примеры (просмотрел и сейчас), но не нашёл ничего подходящего под указанный случай. Проблема именно в отсутствии значения в множественных рядах. Не случайно на орге вытащили этот случай в отдельный пример: https://www.drupal.org/docs/8/api/database-api/dynamic-queries/condition... . Но они там именно создают условия под каждую дельту.