要让php数据访问层真正解耦、可替换、易测试,必须把数据库操作抽象成接口。需定义databaserepository接口(不含pdo/mysqli)、实现mysqluserrepository和redisuserrepository、通过容器注入并按环境变量动态切换实现。

要让PHP数据访问层真正解耦、可替换、易测试,必须把数据库操作抽象成接口,而不是直接写MySQLi或PDO实例。硬编码连接细节会让单元测试无法Mock,换数据库时得改遍所有DAO类。
定义数据访问接口
第一步:新建 interfaces/DatabaseRepository.php 文件,声明核心契约方法。
接口中只写方法签名,不写任何实现,也不引入具体类——【PDO或MySQLi绝对不能出现在interface里】。
方法名用动词+名词结构,如 findUserById、saveOrder,避免 get/insert/update/delete 这类底层动词,聚焦业务语义。
返回类型严格标注,比如 findUserById(int $id): ?User;null 表示未找到,不是 false 或空数组。
实现MySQL数据访问类
方法一:基于PDO预处理的实现
创建 repositories/MySqlUserRepository.php,继承 PDO 并实现上一步定义的接口。
构造函数接收 PDO 实例,而非自己 new PDO()——这保证依赖可注入,也方便在测试中传入 Mock 对象。
所有 SQL 查询必须使用命名占位符(:id)而非问号,防止参数顺序错乱引发静默失败。
执行查询后,立即调用 fetch(PDO::FETCH_CLASS, User::class),让结果自动映射为实体对象,避免手动遍历赋值。
实现Redis缓存适配器
方法一:缓存层也实现同一接口
新建 repositories/RedisUserRepository.php,同样实现 DatabaseRepository 接口。
findUserById 方法先查 Redis,命中则反序列化返回 User 对象;未命中则调用 MySQL 实现类回源,并写入缓存。
注意:缓存键必须带业务前缀和版本号,例如 user:v1:123,避免不同环境或模型复用键导致脏读。
saveUser 方法需同步失效对应 Redis key,不能只写缓存不刷库,也不能只刷库不删缓存——【双写一致性必须由实现类自行保障】。
容器注入与运行时切换
第一步:在服务容器配置中绑定接口到具体实现
使用 PHP-DI 或 Laravel Container,在 config.php 中写:ContainerBuilder::addDefinitions([DatabaseRepository::class => create(MySqlUserRepository::class)->constructorParameter('pdo', $pdo)]);
第二步:业务逻辑层只依赖接口,不感知实现
UserService 构造函数声明为 public function __construct(DatabaseRepository $repo),传入的是接口类型。
第三步:上线前通过环境变量动态切换实现
if ($_ENV['DB_ADAPTER'] === 'redis') { $container->set(DatabaseRepository::class, RedisUserRepository::class); } else { $container->set(DatabaseRepository::class, MySqlUserRepository::class); }
这一步做完,代码就完成了面向接口的数据访问层切换能力。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











