Красноречивый «Query Builder» с оператором «ИЛИ» нейтрализует мой глобальный охват - Laravel - PullRequest
8 голосов
/ 19 апреля 2019

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

Я занимаюсь разработкой приложения на 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 = ?)

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

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

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

? Мои попытки

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

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


? Решение

Решение заключается в добавлении скобок ко всем необработанным запросам.

  • ✅✅Вы можете увидеть решение @ Styx , которое я считаю наиболее успешным

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

Ответы [ 2 ]

2 голосов
/ 19 апреля 2019

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

  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() вызовы будут автоматически иметь круглые скобки вокруг запроса.

0 голосов
/ 19 апреля 2019

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

Я нашел "решение", прежде чем применить свою глобальную область, я перебираю все предложения 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` = ?"
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...