
本文介绍在 Laravel 5.8 中通过本地作用域(Local Scope)和全局作用域(Global Scope)统一处理多个组件模型对 course 模型的可见性约束,避免在每个查询中重复编写 whereHas('course', ...) 逻辑,提升代码复用性与可维护性。
本文介绍在 laravel 5.8 中通过本地作用域(local scope)和全局作用域(global scope)统一处理多个组件模型对 `course` 模型的可见性约束,避免在每个查询中重复编写 `wherehas('course', ...)` 逻辑,提升代码复用性与可维护性。
在实际开发中,当多个模型(如 Material、Quiz、Assignment 等)均通过 Eloquent 关联到同一 Course 模型,且业务要求:仅返回所属课程未被隐藏(即 course.show = 1)的记录,手动在每个控制器或服务类中重复书写 whereHas('course', fn($q) => $q->where('show', 1)) 不仅冗余,还易遗漏、难维护。
Laravel 提供了两种优雅解法:本地作用域(Local Scope) 和 全局作用域(Global Scope),二者适用场景不同,可根据需求灵活选用。
✅ 方案一:使用本地作用域(推荐用于按需控制)
在共享关联逻辑的模型基类(如 App\Models\Component)或各具体模型(如 Material)中定义一个可复用的本地作用域:
// 在 Material.php 或其他组件模型中
public function scopeWithVisibleCourse(Builder $builder)
{
return $builder->whereHas('course', function ($q) {
$q->where('show', 1);
});
}调用时简洁明了:
$materials = Material::withVisibleCourse()
->with(['lesson', 'course.attachment'])
->whereIn('lesson_id', $lessons)
->get();若需支持权限判断(如仅普通用户需过滤,管理员可查看隐藏课程),可将逻辑升级为闭包参数化作用域:
public function scopeWithVisibleCourse(Builder $builder, ?User $user = null)
{
if ($user && !$user->can('course/show-hidden-courses')) {
$builder->whereHas('course', function ($q) {
$q->where('show', 1);
});
}
return $builder;
}调用时传入当前用户即可:
Laravel Cloud(cloud.laravel.com)。此技能用于处理所有 Laravel Cloud 请求,包括搜索和读取数据。只要任务涉及 Laravel Cloud,即使用此技能。
$materials = Material::withVisibleCourse($request->user())
->with(['lesson', 'course.attachment'])
->get();✅ 方案二:使用全局作用域(适用于强制默认行为)
若所有查询都应默认过滤隐藏课程(例如系统严格禁止前端/API 暴露隐藏课程),则适合使用全局作用域。新建作用域类:
// app/Model/Scope/CourseVisibilityScope.php
<?php
namespace App\Model\Scope;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
class CourseVisibilityScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
// 注意:此处假设模型必须有 'course' 关系,且 course 表存在 'show' 字段
$builder->whereHas('course', function ($q) {
$q->where('show', 1);
});
}
}然后在目标模型(如 Material)中注册该全局作用域:
// app/Models/Material.php
use App\Model\Scope\CourseVisibilityScope;
protected static function boot()
{
parent::boot();
static::addGlobalScope(new CourseVisibilityScope);
}此后所有 Material::query() 查询(包括 all()、get()、with() 等)都会自动应用该过滤条件:
$materials = Material::with(['lesson', 'course'])->get(); // 自动只取 show=1 的课程关联数据
⚠️ 重要提示:全局作用域不可绕过,但 Laravel 允许临时禁用:
// 绕过全局作用域(例如后台管理页需显示全部)
$allMaterials = Material::withoutGlobalScopes()
->with(['lesson', 'course'])
->get();⚠️ 注意事项与最佳实践
-
性能考量:
whereHas会生成子查询,若数据量大,建议确保course.id和course.show字段已建立联合索引(如INDEX (show, id))。 -
关系命名一致性:确保所有组件模型中关联
Course的方法名统一(如均命名为course()),否则全局作用域将失效。 -
权限与作用域分离:全局作用域应聚焦「数据可见性」而非「权限控制」;用户权限(如
can('course/show-hidden-courses'))建议在 Controller 层或 Policy 中判断,再决定是否启用/禁用作用域。 - Laravel 5.8 兼容性:上述写法完全兼容 Laravel 5.8,无需升级版本。
通过本地或全局作用域,你只需维护一份课程可见性逻辑,即可覆盖全部 8+ 个组件模型,彻底告别重复代码,让模型层更健壮、更专注业务本质。

















