Repository模式能解耦业务逻辑,最小化接口应只声明必要方法如createProduct(),明确返回类型,用数组传参;实现类需注入模型、做轻量预处理;须通过RepositoryServiceProvider绑定接口,并在控制器中严格依赖注入。

直接在控制器里写 Product::create() 是最省事的写法,但只要项目开始迭代、加权限、加事务、换数据源或写单元测试,就会卡住——不是不能改,而是改起来牵一发而动全身。Repository 模式不是银弹,但它能让你在这些节点上少掉头发。
怎么定义一个最小可用的 ProductRepositoryInterface
接口不是摆设,它得真正约束实现、支撑替换。别一上来就抄 BaseRepository 或搞泛型 CRUD;先聚焦「这个模型要对外提供什么能力」。
- 只声明业务真正需要的方法,比如
createProduct(array $data): Product,而不是堆满all()、find()、update()等通用方法 - 返回类型必须明确:用
Product而不是Model或mixed,否则 IDE 提示失效,调用方也难推断结构 - 参数尽量用数组传入(而非 DTO 类),避免早期引入额外抽象;字段名和控制器层保持一致,减少映射损耗
ProductRepository 实现里最容易漏掉的三件事
实现类看似只是把 Product::create() 包一层,但实际是逻辑扩展的第一道门。很多团队跳过这步,等加日志/校验/默认值时才回头补,结果发现控制器已到处散落重复逻辑。
- 构造函数注入
Product模型实例,而不是在方法里 new 或静态调用——这样才能被 Mock,也方便后续换模型或加作用域 - 在
createProduct()里做轻量预处理:比如过滤掉前端不该传的字段(unset($data['id']))、填充created_by、统一转小写en_name,别让这些逻辑流到控制器 - 不直接 throw 异常,而是让 Eloquent 自己抛;你只负责传参、接收结果;事务、重试、通知等交给上层 Service 处理,仓储只管「存」
绑定接口时为什么必须用 RepositoryServiceProvider
把 $this->app->bind(ProductRepositoryInterface::class, ProductRepository::class) 写在 AppServiceProvider 里也能跑,但会污染核心服务提供者,且无法按需加载。RepositoryServiceProvider 是解耦的物理边界。
- 注册位置必须加到
config/app.php的providers数组末尾,确保它在框架基础服务之后加载,否则依赖的模型可能还没注册 - 不要在
boot()里 bind,必须在register();因为boot()阶段容器已冻结,某些延迟解析会失败 - 如果项目有多个仓储,别全塞进一个 bind;每个接口单独 bind,未来可按需替换某一个实现(比如测试时用
MockProductRepository)
控制器里注入后,别再手动 new 或静态调用
一旦用了依赖注入,控制器里出现 new ProductRepository 或 Product::create() 就等于前功尽弃——这不是解耦,是“假装解耦”。
- 构造函数参数类型必须是接口:
public function __construct(ProductRepositoryInterface $productRepo),不是具体类 - 调用时直接用
$this->productRepo->createProduct($request->all()),别再拆开字段、别再二次验证;验证逻辑应放在 Form Request 或 Service 层 - 如果某次请求需要绕过仓储(比如导入脚本),那就另起一个命令类,不要在控制器里加 if 分支混用两种模式
真正的复杂点不在写法,而在判断:哪些逻辑该留在仓储,哪些该上提到 Service。比如「检查 SKU 是否重复」是仓储职责,「创建成功后发消息+更新库存」就是 Service 的事。分界线模糊时,优先让仓储保持哑,越薄越好。











