控制器不应写重复逻辑,须外移至服务类;trait和继承易破坏依赖注入与语义边界;验证用formrequest;作用域仅限查询条件复用。

直接在控制器里写重复逻辑,等于把胶水当建材用——短期能粘住,长期必开裂。Laravel 控制器本就不该承担业务复用职责,公共逻辑必须外移。
为什么不能在控制器里用 trait 或继承来共享方法
看似省事,实则埋雷:trait 会污染控制器的语义边界,继承链过深会让 __construct() 初始化混乱,且无法被容器自动解析依赖。比如你在 BaseController 里写了个 sendNotification() 方法,它依赖 MailManager,但父类构造函数不会自动注入——你得手动 new 或从容器取,破坏了 Laravel 的依赖解耦设计。
- 控制器继承链越长,测试越难 mock
-
trait中调用$this->validate()等方法时,$this上下文可能错乱(尤其在中间件之后) - IDE 无法准确跳转到真正实现处,因为 trait 可被多处 use
抽到服务类(App\Services)是首选方案
服务类天然支持依赖注入、可单独测试、能被多处调用,且符合 Laravel “控制器只调度,不干活”的定位。别纠结命名,按动作或领域来分,比如 UserExportService、OrderFulfillmentService。
- 新建类时,用
php artisan make:service User/EmailVerificationService(需自定义命令或手动建) - 构造函数中声明依赖,如
public function __construct(private Mailer $mailer, private UserRepository $users) - 方法返回明确类型,避免在服务里直接
return response()——那是控制器的事 - 若逻辑极轻(如格式化时间),可考虑放在
App\Support下的工具类,但不推荐放太多
哪些情况适合用 Eloquent 本地作用域(scopeXxx)
仅限“查询条件复用”,比如 scopeActive()、scopeByStatus()。它不是业务逻辑容器,而是 SQL 构建片段的封装。
- 作用域必须定义在模型里,且第一个参数固定为
$query - 不能在里面发邮件、调 API、写日志——这些属于服务层职责
- 链式调用时,多个作用域顺序会影响最终 SQL,注意执行先后(如
where和orderBy的位置) - 若条件复杂(如带子查询、union),建议改用 Query Builder 或专用 Repository 类
别忽略请求验证逻辑的归属
表单验证规则不该散落在控制器的 validate() 里,而应抽成独立的 FormRequest 类。它不只是校验,还能承载授权(authorize())、预处理(prepareForValidation())、错误消息定制等。
- 生成命令:
php artisan make:request StorePostRequest - 规则写在
rules()方法里,字段级错误消息写在messages() - 权限判断写在
authorize(),比如return $user->can('create', Post::class); - 控制器方法签名直接类型提示该类:
public function store(StorePostRequest $request),框架自动拦截并处理失败
最常被跳过的一步是:抽完逻辑后没删掉控制器里原来的代码。哪怕只留一行注释,也要确认旧路径彻底下线,否则等于同时维护两套逻辑。











