打开任何一个维护多年的Laravel或Symfony项目,搜索"Repository"。随便点开一个结果,大概率会看到类似这样的代码:一个叫getUsersAsArray()的方法,接收array $filters参数,返回塞满stdClass的数组。它躺在App\Repositories\目录下,在容器里注册,代码评审时大家都叫它"用户仓库"。
但它不是仓库。这是个贴着仓库标签的DAO。
这种误标是PHP里最普遍的架构谎言。表面无害——数据进进出出,测试能通过。伤害在后续显现:你想换数据库时,一个用例得引入三个供应商类型才能问业务问题;新人看到findUsersBy(['status' => 'active', 'order_by' => 'created_at DESC', 'limit' => 10]),会问领域逻辑在哪。答案是根本没写。数据库表结构就是领域。
本文用PHP 8.3的术语划清界限:真正的仓库是什么,DAO是什么,以及区分二者的测试标准。
先看那个你每次接手项目都会遇到的文件:
*/public function findAll(array $filters = [], int $limit = 50, int $offset = 0): array$query = DB::table('users');if (isset($filters['status'])) {$query->where('status', $filters['status']);if (isset($filters['email_like'])) {$query->where('email', 'like', '%' . $filters['email_like'] . '%');return $query->orderBy('created_at', 'desc')->limit($limit)->offset($offset)->get()->all();public function findById(int $id): ?\stdClassreturn DB::table('users')->where('id', $id)->first();public function insert(array $row): intreturn (int) DB::table('users')->insertGetId($row);public function updateById(int $id, array $changes): intreturn DB::table('users')->where('id', $id)->update($changes);public function deleteById(int $id): intreturn DB::table('users')->where('id', $id)->delete();}看这些签名。返回类型是array。参数是array $filters加两个分页整数。方法名是findAll、insert、updateById、deleteById——全是数据库方言,不是业务语言。
这是DAO(Data Access Object)的经典形态:直接映射表结构,暴露原始查询能力,用数组传递条件。5个公开方法,每个都在说"我怎么操作数据库",而不是"业务需要什么"。
真正的仓库长什么样?
3个方法。参数是值对象(UserId、DateRange、User),返回的是领域实体或集合。没有array $filters,没有分页偏移量,没有数据库专属的stdClass。
关键区别:仓库的接口由用例驱动,DAO的接口由表结构驱动。
测试标准很简单。问两个问题:
第一,能否在不运行数据库的情况下,用内存实现替换这个类?DAO通常做不到——它的方法签名里嵌着SQL语义(email_like、order_by)。仓库可以,因为DateRange和UserCollection与存储无关。
第二,换个数据库供应商,接口需要改吗?DAO的findAll方法在MongoDB里要重写,因为过滤数组的语法完全不同。仓库的activeWithin不需要动——它描述的是业务规则,不是查询语法。
那个11个公开方法的类(实际示例中5个,但生产代码往往膨胀到7-15个)之所以危险,是因为它让团队误以为自己在做领域驱动设计。他们在代码评审时说"这是仓库模式",在文档里写"数据访问层已抽象",但架构图上的依赖箭头全部指向Illuminate\Database。
重构路径很清晰:把array $filters换成具体的查询对象,把stdClass换成实体,把分页逻辑移到应用层或专门的查询服务。但第一步是停止叫它仓库。命名即契约,错误的命名让错误的依赖变得不可见。
下次搜索"Repository"时,数一下返回类型里有多少个array和stdClass。那就是你的技术债仪表盘。
热门跟贴