【问题标题】:Eloquent "Query Builder" with "OR" operator neutralizes my Global Scope - Laravel带有“OR”运算符的雄辩的“查询生成器”中和了我的全局范围 - Laravel
【发布时间】:2019-09-09 12:24:10
【问题描述】:

⛳ 我需要什么:

我正在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
]);

如果我在$qBuilderCars 上调用toSql() 方法,我发现它“正确”将AND 运算符添加到查询末尾。

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

也许你已经注意到我的问题...如果元素的构建器,在本例中为cars,使用了OR 运算符,那么全局范围将无济于事,因为@987654338 之间没有括号@。因此,生成的查询将类似于以下内容:

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 solution

  • 我还将 leave my answer,它直接在 global scope 内部起作用,我认为能够看到 \Illuminate\Database\Eloquent\Builder 对象如何工作很有趣

【问题讨论】:

  • 哪个是laravel版本?因为在 laravel 5.2 中,当应用全局范围时,默认情况下会根据需要添加括号。
  • 是Laravel 5.4 并且不会自动添加括号:-S
  • @yrv16 你能给我看看这方面的文档吗?
  • 我已经安装了 5.4 并且在 5.2 版本中的查询中确实没有括号。我发现全局范围实现已被重写的信息。 laravel.com/docs/5.3/upgrade
  • @yrv15 首先,感谢您抽出宝贵时间。哇,可惜Laravel 5.4 全局范围发生了变化。我认为我唯一能做的就是尝试看看他们如何引入这些括号以在我的应用程序中复制它......

标签: php laravel eloquent scope


【解决方案1】:

? 解决方案...?

我找到了一个“解决方案”,在应用我的全局范围之前,我循环遍历所有类型为 raw 的 where 子句:

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 &amp;$where) { [...] $where["sql"] = [...]
  • @JonasStaudenmeir 是的,它不是很优雅我想找到一些其他解决方案来解决这个问题。 ? 关于传递它作为参考,这是我的第一次尝试;但由于某种原因它不起作用。 (我刚刚用你的评论代码又试了一次)
  • @JonasStaudenmeir 我同意你的看法。虽然我看到这个系统(或任何其他自动添加括号)的优点是,即使某些程序员犯了错误并且没有在他们的whereRaw中使用它们,@987654327 @ 将继续工作。你不觉得?也许有一些方法可以达到同样的效果;但雄辩地......
  • IMO,你不应该在你的应用程序中有像whereRaw("name like ? or id = ?") 这样没有括号的东西。你基本上不能在不破坏它的情况下向这个查询添加任何约束。
  • @JonasStaudenmeir 我同意你的看法。不幸的是,并不是每个人都意识到这一点,也许像这样的代码可以作为一个“障碍”,让不良做法影响像global scope 这样重要的东西,对吧?顺便说一句,对于您对 Laravel 的贡献,我看到您深入了解 eloquent 的工作原理。您认为workaround 在生产环境中使用是否有效?你能想到任何其他更优雅/有效的方式吗? P.S.:感谢您的宝贵时间。
【解决方案2】:

好吧,您添加括号的解决方案似乎是最好的解决方法,但我有一个建议,如何做到这一点稍微好一点。

  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 orWhereRaw 在内部调用 whereRaw,因此也将涵盖 :)
  • @tomloprod 对不起,搞砸了解释。我们的两种方法都将应用于从BaseEloquentModel 扩展的所有模型,但我的方法没有 foreach/modify 开销。
  • 你已经说服我了。使用该解决方案,我不会对循环执行查询感到内疚? 我会在几分钟内尝试您的解决方案。谢谢!
  • 是的,我想是的。至少现在......?谢谢你所有的时间。我更喜欢你的答案而不是我的答案,所以我会将其标记为已接受。 :-)
猜你喜欢
  • 1970-01-01
  • 2017-08-21
  • 1970-01-01
  • 2015-01-02
  • 2018-02-13
  • 1970-01-01
  • 1970-01-01
  • 2014-08-14
  • 1970-01-01
相关资源
最近更新 更多