Eloquent «Построитель запросов» с оператором «ИЛИ» нейтрализует мою глобальную область видимости — Laravel

⛳ Что мне нужно:

Я разрабатываю приложение в Laravel 5.4 и мне нужен global scope, который позволит мне фильтровать различные элементы приложения в зависимости от пользователя, который их создал.


? Мой глобальный охват:

У меня есть класс BaseEloquentModel.php, который расширяет Eloquent, и все мои модели происходят от этого класса. У меня есть global scope следующим образом:

protected static function boot()
{
    parent::boot();
    static::addGlobalScope('', function(\Illuminate\Database\Eloquent\Builder $builder) use($userId) {
        /**
        * I get the name of the table with <code>(with(new static))->getTable()</code> 
        * and then filter the query for the <b>user_id</b> field
        */
        $builder->where(
            (with(new static))->getTable() . '.user_id', 
            '=',
            $userId
        );
    });
}

⛔ Проблема

Когда у меня есть такой запрос с оператором or, глобальная область действия «нейтрализуется»:

$qBuilderCars = Car::whereRaw("name like ? or id = ?", [
    '%' . $searchCriteria. '%',
    $searchCriteria
]);

Если я вызову метод toSql() для $qBuilderCars, я увижу, что он "правильно" добавляет оператор AND в конец запроса.

select * from `cars` where name like ? or id = ? and user_id = ?

Возможно, вы уже заметили мою проблему... Если построитель элемента, в данном случае cars, использовал оператор OR, то глобальная область видимости не поможет, так как между where name like ? or id = ? нет скобки. Таким образом, результирующий запрос будет примерно таким:

select * from `cars` where name like ? (or id = ? and user_id = ?)

Таким образом, этот запрос вернет все автомобили, чье имя совпадает или чей идентификатор совпадает с полученным и созданным пользователем...

Когда мне нужно:

select * from `cars` where (name like ? or id = ?) and user_id = ?

? Мои попытки

Я попытался изменить свой global scope, чтобы попытаться сделать оператор AND, который я добавляю, наиболее ограничительным в запросе, но безуспешно.

Я не могу вручную добавлять скобки ко всем запросам приложения, поэтому... Есть ли способ добавить глобальные скобки из глобальной области видимости в builder?


? Решение

Решение состоит в том, чтобы добавить круглые скобки ко всем необработанным запросам.

  • ✅✅ Вы можете посмотреть @Стикс решение который я считаю самым удачным

  • Я также буду использовать оставить мой ответ, который действует непосредственно внутри global scope и который я считаю интересным, чтобы увидеть, как работает объект \Illuminate\Database\Eloquent\Builder.

Какая версия ларавеля? Потому что в laravel 5.2 скобки добавляются по мере необходимости по умолчанию, когда применяется глобальная область.

yrv16 19.04.2019 12:20

Есть Laravel 5.4 и скобки не добавляются автоматически :-S

tomloprod 19.04.2019 12:21

@ yrv16 Не могли бы вы показать мне документацию об этом?

tomloprod 19.04.2019 12:37

Я установил 5.4, и действительно в запросе нет скобок, как в версии 5.2. Я нашел информацию о том, что реализация глобальной области видимости была переписана. laravel.com/docs/5.3/обновление

yrv16 19.04.2019 12:47

@ yrv15 Прежде всего, спасибо за ваше время. Вау, как жаль, что Laravel 5.4 глобальная область видимости изменилась. Я думаю, что единственное, что я могу сделать, это попытаться увидеть, как они ввели эти круглые скобки, чтобы воспроизвести их в моем приложении...

tomloprod 19.04.2019 12:49

Пожалуйста, если я найду решение, я отвечу.

yrv16 19.04.2019 12:53

@yrv16 Спасибо. Давайте посмотрим, если между ними мы найдем решение. В любом случае, вы можете переместить свой комментарий в ответ, чтобы я мог его оценить :-)

tomloprod 19.04.2019 12:55

Какую именно версию Laravel вы используете?

Styx 19.04.2019 14:11

@Стикс Ларавель 5.4

tomloprod 19.04.2019 14:12

Вы имеете в виду 5.4.0?

Styx 19.04.2019 14:13

@Styx Прежде всего, спасибо за ваше время. Я думаю, что из Laravel 5.4 есть только две версии 5.4 и 5.4.22, последняя исправляет уязвимость. Я использую 5.4.22.

tomloprod 19.04.2019 14:44

Покажите, пожалуйста, как именно вы определяете свою глобальную область видимости, и какой именно код неправильный SQL. Я просто не могу воспроизвести эту ошибку.

Styx 19.04.2019 14:56

@Styx, я обновил свой ответ. Если вам нужны другие детали, пожалуйста, спросите меня.

tomloprod 19.04.2019 18:40

Вы показали, как вы "нейтрализуете", но какой код выдает неправильно SQL?

Styx 19.04.2019 19:12

Мы не смогли воспроизвести это из-за whereRaw(). С where('name', 'like', '%'.$searchCriteria.'%')->orWhere('id', $searchCriteria) Laravel автоматически добавляет скобки. Я предполагаю, что вы должны использовать whereRaw()?

Jonas Staudenmeir 19.04.2019 19:13

@Styx @Jonas Staudenmeir Я использую whereRaw, пожалуйста, смотрите мой $qBuilderCars в моем вопросе

tomloprod 19.04.2019 19:14

@Styx, Йонас Стауденмейр Не могли бы вы высказать свое мнение об ответе, который я опубликовал на свой вопрос? Очевидно, это работает; но я не знаю, может ли это иметь последствия, которые я не принимаю во внимание.

tomloprod 19.04.2019 19:16

@tomloprod Почему бы вам просто не использовать обычные where и orWhere вместо whereRaw?

Styx 19.04.2019 19:17

@Styx Я применяю это global scope в очень большом приложении со слишком большим количеством whereRaw, поэтому я не могу их изменить и проверить стабильность приложения. Кроме того, нет автоматических тестов ☹...

tomloprod 19.04.2019 19:18
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
8
19
811
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

? Решение...?

Я нашел «решение», прежде чем применить свою глобальную область действия, я перебираю все предложения where, тип которых raw:

protected static function boot()
{
    parent::boot();
    static::addGlobalScope('', function(\Illuminate\Database\Eloquent\Builder $builder) use($userId) {

       /**
        * My workaround to prevent a raw query from neutralizing the global scope.
        * We go through all the queries and, if they are raw, encapsulate them in parentheses.
        */
        $wheres = $builder->getQuery()->wheres;
        foreach ($wheres as $iWhere => $where) {
            // If where clause is "raw" I wrap with parenthesis.
            if ($where['type'] == 'raw') {
                $builder->getQuery()->wheres[$iWhere]["sql"] = "({$where['sql']})";
            }                                                      
        }

       /**
        * I get the name of the table with <code>(with(new static))->getTable()</code> 
        * and then filter the query for the <b>user_id</b> field
        */
        $builder->where(
            (with(new static))->getTable() . '.user_id', 
            '=',
            $userId
        );
    });
}

Я не знаю, будет ли это решение иметь какие-либо неожиданные последствия или сильно повлияет на производительность запросов...

Вернет следующий SQL:

select * from `cars` where (name like ? or id = ?) and `cars`.`user_id` = ?"

Я не вижу явных проблем. Конечно, это не очень элегантно, но, вероятно, лучший обходной путь для вашей ситуации. Вы можете упростить это с помощью ссылки: foreach($wheres as &$where) { [...] $where["sql"] = [...]

Jonas Staudenmeir 19.04.2019 19:35

@JonasStaudenmeir Да, это не очень элегантно, я хотел бы найти другое решение, которое решает эту проблему. ? Что касается передачи референса, то это была моя первая попытка; но почему-то не работает. (Я только что попробовал еще раз с вашим кодом комментария)

tomloprod 19.04.2019 19:39

@JonasStaudenmeir Я согласен с тобой. Хотя преимущество, которое я вижу в этой системе (или любой другой, который автоматически добавляет скобки), заключается в том, что даже если какой-то программист сделает ошибку и не использует их в своих whereRaw, global scope продолжит работать. Вам не кажется? Может быть, есть какой-то способ добиться того же результата; но красноречиво...

tomloprod 19.04.2019 19:43

ИМО, вы никогда не должны иметь что-то вроде whereRaw("name like ? or id = ?") без круглых скобок в вашем приложении. В принципе, вы не можете добавить больше ограничений к этому запросу, не нарушая его.

Jonas Staudenmeir 19.04.2019 19:53

@JonasStaudenmeir Я согласен с тобой. К сожалению, не все знают об этом, и, возможно, такой код может служить «барьером» для того, чтобы плохая практика повлияла на что-то столь важное, как global scope, верно? Кстати, за ваш вклад в Laravel я вижу, что вы хорошо понимаете, как работает eloquent. Считаете ли вы это workaround чем-то подходящим для использования в производственной среде? Можете ли вы придумать какой-либо другой более элегантный/эффективный способ? P.S.: Спасибо за ваше время.

tomloprod 19.04.2019 19:56
Ответ принят как подходящий

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

  1. Создайте новый класс QueryBuilder. Например, в пространстве имен \App\Models\ (папке app/Models/):

    namespace App\Models;
    
    use Illuminate\Database\Query\Builder as EloquentQueryBuilder;
    
    class QueryBuilder extends EloquentQueryBuilder {
    
      public function whereRaw($sql, $bindings = [], $boolean = 'and')
      {
        return parent::whereRaw('('.$sql.')', $bindings, $boolean);
      }
    
    }
    
  2. Добавьте этот код в свой класс BaseEloquentModel:

    use Illuminate\Database\Eloquent\Model;
    use App\Models\QueryBuilder; // <-- addition
    
    class BaseEloquentModel extends Model {
      // ...
      protected function newBaseQueryBuilder()
      {
        $connection = $this->getConnection();
    
        return new QueryBuilder(
            $connection,
            $connection->getQueryGrammar(),
            $connection->getPostProcessor()
        );
      }
      // ...
    }
    

Теперь все вызовы whereRaw() будут автоматически заключаться в круглые скобки вокруг запроса.

+1 Кажется, интересный способ сделать это! Единственное, чего мне не хватает, так это того, что вы не охватили бы другие eloquent методы, такие как orWhereRaw(). Я понимаю, что с моим решением, находящимся в глобальной области действия моего BaseEloquentModel, оно будет применяться ко всем необработанным запросам, независимо от того, как они были сгенерированы, верно?

tomloprod 19.04.2019 20:05

@tomloprod orWhereRaw звонит whereRaw внутренне, так что это тоже будет учтено :)

Styx 19.04.2019 20:12

@tomloprod Извините, перепутал с объяснением. Оба наших метода будут применяться ко всем моделям, расширенным из BaseEloquentModel, но мой не требует затрат на foreach/modify.

Styx 19.04.2019 20:18

Вы закончили убеждать меня. И, используя это решение, я не чувствую себя таким виноватым из-за того, что выполняю запросы в цикле ? Я попробую ваше решение через несколько минут. Спасибо!

tomloprod 19.04.2019 20:19

@tomloprod Кстати, если ваш код использует havingRaw, вы также можете переопределить этот метод.

Styx 19.04.2019 20:22

Работает как шарм! Вы знаете, если ->select(DB::raw("...")) вызвать whereRaw внутренне? Что меня беспокоит, так это возможность того, что приложение использует какой-то другой метод, например whereRaw или havingRaw, и что по незнанию я не переопределил его в своем «CustomQueryBuilder».

tomloprod 19.04.2019 20:27

@tomloprod select() определенно нет вызывает любой whereRaw() внутренне :) И вам не следует беспокоиться о select() или DB:raw(), этот код не повлияет на них (и не должен).

Styx 19.04.2019 20:30

Я запутался, когда писал этот комментарий. Слишком много движений между иностранным кодом сегодня ?? Я вас больше не беспокою, последний вопрос. Знаете ли вы какой-либо другой метод, такой как whereRaw или havingRaw, который может «изменять» запросы необработанным образом?

tomloprod 19.04.2019 20:33

@tomloprod Да, есть orderByRaw, но это не должно вызывать у вас таких проблем со скобками. Ваша проблема только с where, верно?

Styx 19.04.2019 20:35

Да, я так думаю. По крайней мере, пока...? Спасибо за все ваше время. Я предпочитаю ваш ответ моему, поэтому я отмечу его как принятый. :-)

tomloprod 19.04.2019 20:38

Другие вопросы по теме