Построитель запросов, когда метод when() выдает ошибочно обусловленный запрос

#laravel #laravel-8 #laravel-query-builder

Вопрос:

Я опубликовал то, что, по моему мнению, является ошибкой Laravel (https://github.com/laravel/framework/issues/39398 ) но он был закрыт, потому что я не уверен на 100%, что это действительно ошибка. Может ли кто-нибудь еще сказать мне, является ли это поведение ошибочным?

У меня есть следующий фрагмент кода:

 $user=251;
$description='created';
Activity::when($user, function ($query, $user) {
    return $query->where('causer_id', $user)
    ->orWhere(function($query) use ($user) {
        $query->where('subject_id', $user)
        ->where('subject_type', "App\User");
    });
})->when($description, function ($query, $description) {
    return $query->where('description', $description);
});
 

Логически я думаю, что он должен выдавать запрос, который возвращает все строки с правильным пользователем и правильным описанием, но я получаю все строки с этим пользователем независимо от описания.

Приведенный выше код создает этот sql-запрос:

 select * from activity_log where causer_id = '251' or (subject_id = '251' and subject_type = 'AppUser') and description = 'created'
 

… в то время как я думаю, что он должен выдавать:

 select * from activity_log where (causer_id = '251' or (subject_id = '251' and subject_type = 'AppUser')) and (description = 'created')
 

Короче говоря: разве функция when() не должна заключать возвращаемый результат в круглые скобки, чтобы не влиять на другие части запроса? Является ли это ошибкой, что это не так?

Ответ №1:

Я не думаю, что это ошибка. Это не предполагаемое поведение, которое нарушается. Это была бы новая функция, которая будет добавлена в фреймворк. Вы можете перейти на сервер discord и попытаться изложить свое мнение или просто отправить PR с изменением и аргументировать его, но это не «сломанная» существующая функциональность.

when() Метод — это просто синтаксический сахар, так что вы можете условно изменять запрос в цепочке методов без необходимости выделять его в отдельный if оператор. Следующий код был бы эквивалентен:

 $query = Activity::query();

if ($user) {
    $query->where('causer_id', $user)
        ->orWhere(function($query) use ($user) {
            $query->where('subject_id', $user)
                ->where('subject_type', "App\User");
        });
}

if ($description) {
    $query->where('description', $description);
}
 

Как вы можете видеть в приведенном выше коде, не следует ожидать, что какие-либо предложения, добавленные условно, будут заключены в круглые скобки (за исключением случаев, когда это явно указано в вашем orWhere() вызове).

Одна из проблем, связанных с тем, что вы ожидаете, заключается в том, что when() метод можно использовать для изменения запроса любым способом, а не просто для добавления дополнительных условий. Итак, если бы вы добавили orderBy() или limit() внутри when() предложения, что бы вы ожидали?

Итак, сам when() метод не собирается изменять ваш запрос, но он будет выполнять именно то, что вы ему укажете. Поэтому, чтобы сделать то, что вы ищете, вам нужно будет самостоятельно обернуть запрос внутри вашего when() предложения:

 Activity::when($user, function ($query, $user) {
              return $query->where(function($query) use ($user) {
                  return $query->where('causer_id', $user)
                               ->orWhere(function($query) use ($user) {
                                   $query->where('subject_id', $user)
                                         ->where('subject_type', "App\User");
                               });
              });
       })
       ->when($description, function ($query, $description) {
           return $query->where('description', $description);
       });
 

Комментарии:

1. ОК. Прочитав то, что вы говорите, я понимаю, что ошибаюсь. Конечно, when() не должен влиять на запрос. Одна вещь, однако, заключается в том, что, возможно, это может быть более понятным в документации. Но теперь я перестану ныть 🙂