Laravel Query:为 Query Builder 实现 Eloquent Scope

Posted

技术标签:

【中文标题】Laravel Query:为 Query Builder 实现 Eloquent Scope【英文标题】:Laravel Query: implement Eloquent Scope for Query Builder 【发布时间】:2019-08-11 12:55:27 【问题描述】:

在 Laravel Query Builder 中,我想在 Eloquent 中实现类似 Scope 的东西。 参考:Laravel Queries: Adding custom feature like Soft Deletes.

我有一些复杂的查询(有连接和什么没有),但我希望能够轻松应用 WHERE 条件,其工作原理如下:

原文:

Select * from t1 join t2 ... join t3 ... etc
Where t1.c1 = x OR t3.c4 like "%like" 

想要:

Select * from t1 join t2 ... join t3 ... etc
Where (t1.c1 = x OR t3.c4 like "%like") AND (t1.isTest = false AND t3.isTest = false) 

我写了如下方法:

public static function scopeNoTest($query, $tables=[false])
    
        if (!is_array($tables)) $tables = [$tables];
            foreach ($tables as $table)
            
                $field = ($table) ? $table . '.isTest' : 'isTest';
                $query = $query->where(function ($q) use ($query, $field)
                    
                        $q->where($field, false)
                          ->orWhereNull($field);
                    
                );
            
            return $query;
    

它是这样运行的:

$select       = <parameter driven select statement>
$where[$role] = <array of different where condition based on passed in parameter?
$bindings     = <query bindings based on passed in parameters>  

$query = DB::table('Transactions AS trans')
       ->leftJoin('Buyers AS b', 'trans.ID', '=', 'b.Transactions_ID')
       ->leftJoin('Sellers AS s', 'trans.ID', '=', 's.Transactions_ID')
       ->leftJoin('Agents AS ba', 'trans.BuyersAgent_ID', '=', 'ba.ID')
       ->leftJoin('Agents AS sa', 'trans.SellersAgent_ID', '=', 'sa.ID')
       ->leftJoin('TransactionCoordinators AS btc', 'trans.BuyersTransactionCoordinators_ID', '=', 'btc.ID')
       ->leftJoin('TransactionCoordinators AS stc', 'trans.SellersTransactionCoordinators_ID', '=', 'stc.ID')
       ->leftJoin('lu_UserRoles AS lu_ur', 'trans.ClientRole', '=', 'lu_ur.Value')
       ->leftJoin('Properties AS p', 'trans.Properties_ID', '=', 'p.ID')
       ->selectRaw($select);

// ... Adds code to Only Select records with isTest NOT True
$query = Model_Parent::scopeNoTest($query, ['trans', 'ba', 'sa', ]);

$query->whereRaw($where[$role].$whereUser, $bindings)->distinct();
$transactions = $query->get();

此代码的问题在于它没有将原始 [passed in] 查询放在括号中 - 因此查询是错误的!

代码创建的位置是:

where 
     (`trans`.`isTest` = 0 or `trans`.`isTest` is null) 
 and (`ba`.`isTest` = 0 or `ba`.`isTest` is null) 
 and (`sa`.`isTest` = 0 or `sa`.`isTest` is null) 
 and trans.BuyersTransactionCoordinators_ID = 1 OR trans.SellersTransactionCoordinators_ID = 1 
 OR  trans.CreatedByUsers_ID = 1 OR trans.OwnedByUsers_ID = 1

我想要

where 
     (`trans`.`isTest` = 0 or `trans`.`isTest` is null) 
 and (`ba`.`isTest` = 0 or `ba`.`isTest` is null) 
 and (`sa`.`isTest` = 0 or `sa`.`isTest` is null) 
 and (trans.BuyersTransactionCoordinators_ID = 1 OR trans.SellersTransactionCoordinators_ID = 1 
      OR  trans.CreatedByUsers_ID = 1 OR trans.OwnedByUsers_ID = 1)

有没有办法做到这一点?

【问题讨论】:

【参考方案1】:

看起来是以下行导致了这种情况:

$query->whereRaw($where[$role].$whereUser, $bindings)->distinct();

我认为有两种方法可以解决这个问题:

// 1
->whereRaw('(' . $where[$role].$whereUser . ')', $bindings)->

// 2
->where(function ($query) use (...) 
    $query->whereRaw(...);
)->

【讨论】:

谢谢,两种解决方案都有效。我非常专注于不需要我更改现有查询(实际上是查询 - 我有几个太复杂而无法使用 Eloquent)的解决方案,以至于我错过了这个更简单的解决方案。

以上是关于Laravel Query:为 Query Builder 实现 Eloquent Scope的主要内容,如果未能解决你的问题,请参考以下文章

如何将此 SQL 转换为 Laravel Query Builder 查询?

如何在 DB::select(query) 上使用分页 - Laravel

如何使用 laravel Query Builder 进行复杂查询

Laravel 查询错误 - 调用未定义的方法 Illuminate\Database\Query\Builder::query()

Laravel:啥是 $query->time 时间单位?

Laravel DB::select($query)