跳转到主要内容
要从包中扩展数据库连接,应将创建方式的注册与所用连接的选择分离开来。设计时要确保仅安装包不会替换应用的默认连接或所有 MySQL 连接。 本页面基于对 Laravel Framework v13.40.0 官方源码的确认。连接配置的基础请参阅数据库配置,服务提供者的加载请参阅包的自动发现。

选择扩展点

数据库的创建处理中,存在适用范围不同的扩展点。 传给 DB::extend() 的回调的返回值不是 PDO 或 Query Builder,而是 Illuminate\Database\Connection。DatabaseManager::configure() 要求这个类。
数据库的 extend() 与通用的 Illuminate\Support\Manager 调用约定不同。回调的第一个参数不是应用容器,而是连接配置数组。请不要直接照搬其他驱动扩展的示例。

连接名称的扩展优先于驱动名称

DatabaseManager::makeConnection() 在获取配置后,按以下顺序选择创建方式。
  • DB::extend('reporting', ...) 适用于 database.connections.reporting。
  • DB::extend('mysql', ...) 适用于连接名称一侧没有扩展、且 driver 为 mysql 的连接。
  • 两者使用同一个扩展注册表。考虑到连接名称与驱动名称的冲突,应使用包专属的名称。
  • extend() 不会创建配置数组。仅注册扩展而不存在指定名称的连接配置时,会抛出异常。

显式选择自定义驱动名称

下面是将自定义名称 acme-sqlite 桥接到标准 SQLite 连接创建的最小示例。它并非实现新的数据库协议或 SQL 方言的示例。它展示的是分发自定义适配器时,注册与选择之间的边界。 假设 Acme\DatabaseBridge\ 通过 Composer 的 PSR-4 映射到 src/,放置在 src/DatabaseBridgeServiceProvider.php 中。
db.factory 是 Laravel 共享的 ConnectionFactory 绑定。由于直接调用标准 Factory,不会回到 DatabaseManager 的扩展选择,从而可以复用 SQLite 的延迟 PDO 创建与 Connection 创建。 此回调在首次解析连接时执行。服务提供者的 boot() 只负责注册创建方式,不执行连接测试或迁移。应作为常规的非延迟服务提供者,在目标连接首次被解析之前加载。 使用方应用在 config/database.php 现有的 connections 数组中添加连接。不要修改默认的 default。
请事先准备好 SQLite 文件,并确认写入权限后再使用。不要设计成仅加载包就创建文件。
使用者传入的是连接名称 acme_reports,而不是驱动名称 acme-sqlite。示例中在传给 Factory 之前将 driver 转换为 sqlite,因此创建后的 getDriverName() 也是 sqlite。
如果在 resolver 内部再次调用 DB::connection($name),会回到尚未保留的同一连接的创建处理。为避免递归,请通过 Factory 或自定义适配器创建 Connection。

替换为 Connection 子类属于另一个范围

如果要替换现有驱动的 Connection 类本身,可以使用 Connection::resolverFor()。传给回调的有时并非立即连接的 PDO,而是延迟创建 PDO 的 Closure。 不过,此注册保存在 Connection 类的 static 属性中。如果为 sqlite 注册,会影响通过标准 Factory 新创建的所有 SQLite 连接,上面的 acme-sqlite 示例也在其中。它不能被视为只针对某一个连接名称的替换。
  • 回调中应返回目标数据库专用的 Connection 子类。仅使用基础 Connection 无法保留数据库专有的 Grammar 和 Processor 等。
  • Factory 将 createConnector() 与 createConnection() 分开。仅注册 resolverFor() 并不会为未知驱动提供 PDO 连接器。
  • 如果 DB::extend() 的自定义 resolver 不使用标准 Factory,也不会经过 Factory 内对 resolverFor() 的检查。
  • 对同一驱动重复注册会覆盖原有注册。请确认与其他包的冲突,以及在同一 PHP 进程内的测试之间的残留。
希望限制影响范围的包,应首先考虑使用专属连接名称或驱动名称的 DB::extend()。

区分 reconnect 与 purge

DatabaseManager 按连接名称保留已创建的 Connection。即使修改配置或 resolver,也不会自动反映到已经创建的连接。 特别是 reconnect(),它并不是整体替换已保留 Connection 的类或配置数组的 API。如果要反映扩展类或连接配置的变更,请在没有进行中事务的安全时机执行 purge(),然后获取新的连接。
purge() 不会将其他服务或变量持有的 Connection 引用替换为新的引用。此外,连接名称的 ::read、::write、::direct 是不同的保留键,不要以为仅 purge 基础名称就会将它们一并删除。不要为了切换租户等目的而轻易改写运行中的共享连接配置。对于长生命周期的 worker,除了更新配置缓存外,还要确认进程的重启以及对已保留服务的影响。

测试包的注册与创建

在 Orchestra Testbench 中加载服务提供者,并实际选择自定义驱动。下面是 tests/DatabaseBridgeTest.php 的示例。运行环境需要 PDO 和 SQLite 驱动。
:memory: 在连接断开时会丢失内容,因此此测试不期望保留表或数据。在实际的包中,还应添加以下测试。
  • 连接名称的 resolver 优先于驱动名称的 resolver。
  • 同名重复注册和 forgetExtension() 不会替换已创建的 Connection。
  • 缺少必需配置时的异常,以及连接失败时的行为。
  • 在支持的数据库上的迁移、事务、读写分离等承诺提供的功能。
  • 在长生命周期 worker 或与多个包并用时,注册和连接状态不会混杂。

维护时的检查项目

  • 是否通过自定义名称选择启用,而没有悄悄覆盖默认连接或内置驱动。
  • 是否没有将 Closure 或连接对象放入配置缓存,而是将创建处理放在服务提供者中。
  • 是否将连接目标和认证信息作为可信的应用配置进行管理。
  • 是否在受支持的 Laravel 版本中重新确认了 extend() 的参数、Factory 的创建顺序以及 reconnect() 的迁移对象。
  • 是否向使用者说明了配置变更后的重新创建步骤与 worker 重启。

相关页面

一手资料

最后修改于 2026年11月3日