必须定义 UserRepositoryInterface 接口,命名空间为 App\Repository,仅声明业务契约;MySQL 实现类 MySqlUserRepository 依赖注入 PDO,使用预处理语句;通过 DI 容器绑定接口与实现;服务类构造函数类型提示接口,调用仅限接口方法。

设计一个可替换、可测试、不耦合数据库实现的PHP数据访问接口,必须让业务逻辑层完全不知道MySQL或Redis具体怎么连接、怎么查,只通过接口约定行为。
定义数据访问接口
创建 src/Repository/UserRepositoryInterface.php 文件,声明业务需要的最小契约:
这一步不能写具体实现,也不能引入任何数据库类——接口只描述“能做什么”,不描述“怎么做”。
【接口名必须带 Interface 后缀】,否则后续自动解析会失败;命名空间需与实际目录结构严格一致,比如 App\Repository 对应 src/Repository 目录。
立即学习“PHP免费学习笔记(深入)”;
编写 MySQL 实现类
在 src/Repository/MySqlUserRepository.php 中实现接口:
构造函数接收 PDO 实例而非自己 new PDO,这是依赖注入的第一道门槛——【禁止在类内部调用 new PDO()】,否则就退化为硬编码,失去替换能力。
所有 SQL 拼接必须使用预处理语句,防止注入;返回值类型严格遵循接口定义,不额外加数组键或包装对象。
注册到依赖注入容器
方法一:使用 PHP-DI 的配置数组方式
在 config/di.php 中写:
UserRepositoryInterface::class => DI\get(MySqlUserRepository::class),
MySqlUserRepository::class => DI\factory(function (Container $container) {
return new MySqlUserRepository($container->get(PDO::class));
});
方法二:Laravel 风格的 bind 绑定(适用于自建轻量容器)
第一步:在容器初始化时调用 $container->bind(UserRepositoryInterface::class, function ($c) {
第二步: return new MySqlUserRepository($c->make(PDO::class));
第三步:});
注意:绑定必须在任何 get() 调用之前完成,否则容器找不到映射关系会抛出异常。
在服务类中使用接口
构造函数直接类型提示接口,不要写具体类名:
public function __construct(private UserRepositoryInterface $userRepository) {}
这样 UserService 完全不感知底层是 MySQL 还是 SQLite,甚至可以临时换成内存数组实现用于单元测试。
调用时只用接口方法,例如 $this->userRepository->findById(123),绝不调用 $this->userRepository->getPdo() 或类似扩展方法——【一旦调用实现类特有方法,契约即被破坏】。



















