一. 需要解决什么问题
最近Laravel 项目中遇到一个需求,我有一个客户表,每个员工都有自己的客户,但是自己只能看自己的客户。
项目中,有很多功能需要查询客户列表,客户详情,查询客户入口很多,这些查询出来的数据,无一例外都只能查自己的。
问题来了,每个查询点
都去补充限制条件
,处理起来非常繁琐
,而且维护成本很高
,容易出现问题
,一旦出现条件不到位,就可能造成不可估量的损失。
类似以上这种需求,应该有很多相似的。大家自觉往上靠。
提出疑问,可有办法统一处理
,只维护一处即可,所有查询都
带上条件限制。
答案肯定是有,以下是其中一个解决思路,也是推荐的。
二. 什么是查询作用域
查询作用域(Query Scope
)是 Laravel Eloquent ORM
提供的一个强大功能,它允许你封装常用的查询逻辑,使代码更简洁、可重用。合理使用可以大幅提高代码质量和开发效率。
三. 作用域怎么用
3.1 全局作用域
全局作用域可以为模型的所有查询添加约束
。最常见的 软删除
功能就是利用全局范围仅从数据库中检索「未删除」模型。
3.1.1 开发全局作用域
编写作用域类,目录可以是任意目录
,我创建在 App\Models\Scopes
下面
<?phpnamespace App\Models\Scopes;use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;class ClientScope implements Scope
{public function apply(Builder $builder, Model $model): void{$builder->where('uid_code', '=', get_admin_code());}
}
代码解释:
- 需要实现
Illuminate\Database\Eloquent\Scope
接口 Scope
接口要求实现apply
方法。需要完善 apply 方法apply
方法里面补充的就是所需要的限制条件
,例如我的就是指定 uid_code 等于 当前登录用户code
在需要模型里面,配置全局作用域
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Models\Scopes\ClientScope;
use Illuminate\Database\Eloquent\Factories\HasFactory;class ClientModel extends Model
{use HasFactory;protected static function booted(): void{static::addGlobalScope(new ClientScope());}
}
代码解释:
- 重写模型的
booted
方法并使用addGlobalScope
方法 addGlobalScope
方法可以接受作用域
的一个实例参数
,也就是上面编写的作用域
完成上面的步骤即可,在使用 ClientModel
模型查询时,都会
带上作用域里面的条件。
3.1.2 匿名全局作用域
是不是感觉上面的代码繁琐
了,为了一个全局作用域还需要单独去编写一个类。那么使用闭包
来定义全局作用域,可以简化
流程。
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;class ClientModel extends Model
{use HasFactory;protected static function booted(): void{static::addGlobalScope('clientScope', function (Builder $builder) {$builder->where('uid_code', '=', get_admin_code());});}
}
代码解释:
- 重写模型的
booted
方法并使用addGlobalScope
方法 addGlobalScope
方法,第一个
参数为作用域名称
,第二个
参数为匿名函数
,里面补充就是相关条件。
注意:
匿名全局作用域适用于单独模型
使用,如果有多个
模型,都需要
同样的限制条件,则还是需要创建
作用域类的。方便统一维护
。视情况而定
3.1.3 取消全局作用域
如果某个查询不需要这个全局限制,那么就可以取消全局作用域
- 取消部分,或者指定作用域
//取消全局作用域类
ClientModel::withoutGlobalScope(ClientScope::class)->get();
//取消闭包作用域
ClientModel::withoutGlobalScope('clientScope')->get();
// 取消部分作用域...
ClientModel::withoutGlobalScopes([ClientScope::class, SecondScope::class
])->get();
- 取消全部作用域
// 取消全部全局作用域...
ClientModel::withoutGlobalScopes()->get();
3.2 局部作用域
局部作用域允许你定义通用的查询约束
,可以链式调用
。
// 在模型中定义
class ClientModel extends Model
{public function scopePopular($query){return $query->where('votes', '>', 100);}public function scopeActive($query){return $query->where('active', 1);}
}
使用方法:
// 单个作用域
$users = ClientModel::popular()->get();// 链式调用多个作用域
$users = ClientModel::popular()->active()->orderBy('created_at')->get();//使用orWhere实现
$users = ClientModel::popular()->orWhere->active()->get();//使用闭包实现
$users = ClientModel::popular()->orWhere(function (Builder $query) {$query->active();
})->get();
3.3 动态作用域
动态作用域允许你传递参数
给作用域:
// 定义
class ClientModel extends Model
{public function scopeOfType($query, $type){return $query->where('type', $type);}
}
注意:作用域参数要放在 $query 参数之后
// 使用
$users = ClientModel::ofType('admin')->get();
四. 总结
查询作用域是 Laravel Eloquent 中组织查询逻辑的强大工具,合理使用可以大幅提高代码质量和开发效率。
合理使用 全局作用域和局部作用域,可以使查询逻辑更清晰易懂,方便维护,降低后期的查看查看成本。