控制器只负责接收请求、校验参数、转发业务;业务逻辑应移至Service层,验证规则抽到FormRequest,查询条件封装为模型作用域,横切逻辑交由中间件处理。

控制器里别写业务逻辑
控制器只该做三件事:接收请求、校验参数、转发给真正干活的人。一旦看到 DB::transaction、$user->assignRole() 或连续调用多个模型方法,说明逻辑已经溢出了边界。
实际操作建议:
- 新建
app/Services/UserRegistrationService.php,把用户创建、角色分配、邮件发送打包进一个register(array $data)方法 - 控制器构造函数注入该服务:
private UserRegistrationService $service - 原
store()方法只剩两行:$user = $this->service->register($request->validated());和返回响应 - 别在控制器里互相调用方法(比如
$this->store($array)),store()明确依赖Request实例,硬传数组会直接报ArgumentCountError
验证规则必须抽到 FormRequest
写 $request->validate([...]) 是临时解法,不是精简——它锁死在控制器里,无法复用,改一处要翻七八个地方。
正确做法:
- 运行
php artisan make:request StoreUserRequest - 在
rules()里定义字段规则,在authorize()控制权限 - 控制器方法签名直接接收该类:
public function store(StoreUserRequest $request) - Laravel 自动拦截非法请求并返回 422,不用手写
if ($validator->fails()) - 同一个
StoreUserRequest可被update()复用,只需在子类中覆盖个别字段的规则
重复查询条件全扔进模型作用域
“查状态为 active 的用户”“按创建时间倒序”这类条件,每写一次就多一份维护成本。它们不该散落在控制器或 Blade 模板里。
实操要点:
- 在模型中定义以
scope开头的方法,例如:public function scopeActive($query)返回$query->where('status', 'active') - 调用时去掉
scope前缀:User::active()->latest()->get() - 支持链式调用,也能和
with()、select()混用,比如User::active()->with(['posts:id,title,user_id'])->select('id','name')->get() - 避免裸用
orWhere——必须用闭包包裹,否则 SQL 优先级错乱,查出不该查的数据
中间件接管横切逻辑
登录检查、权限校验、租户隔离这些和具体业务无关的逻辑,塞进控制器就是典型冗余。每个需要校验的方法都补一遍 if (!auth()->check()),等于把同一段代码复制粘贴了十几次。
落地方式:
- 执行
php artisan make:middleware CheckAdmin - 在
handle()里写核心判断,如if (!auth()->user()->hasRole('admin')) { return response()->json(['error' => 'Forbidden'], 403); } - 注册到
app/Http/Kernel.php的$routeMiddleware数组,起个短名如'admin' - 路由分组批量挂载:
Route::middleware('admin')->group(...),而不是每个控制器方法单独加
真正容易被忽略的是:全局作用域(比如软删除、租户 ID 过滤)和中间件一样,属于“写一次,永远生效”的机制——但它们作用于数据层,而中间件作用于请求流,混用或漏用都会导致逻辑断层。


















