fiberphp / database
🗄️ FiberPHP 数据库组件 —— 基于 PDO 的连接池、查询构造器、驱动抽象层、协程安全。
Requires
- php: >=8.3
- ext-pdo: *
- fiberphp/container: dev-master
- fiberphp/contract: dev-master
- fiberphp/discovery: dev-master
- fiberphp/event: dev-master
- fiberphp/support: dev-master
- psr/log: ^3.0
Requires (Dev)
- fiberphp/config: dev-master
- phpunit/phpunit: ^11.0
Suggests
- fiberphp/cache: 查询结果缓存 / Schema 缓存 / 写后自动失效(缺失时所有缓存逻辑优雅跳过)
- fiberphp/orm: 模型/关联/软删除等 ORM 能力(依赖本包)
- workerman/workerman: ^5.1 框架运行时的协程连接池(缺失时回退 SyncPool)
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-10 13:43:37 UTC
README
FiberPHP 数据库组件:协程连接池 + 查询构造器 + SQL 生成 + 驱动方言抽象。内置 MySQL / PostgreSQL / SQLite 三驱动。
特性
- 连接池:workerman 协程环境自动池化(挂起等待/心跳探活/上限控制);无 workerman 时回退内置
SyncPool(同步复用),可脱离框架独立使用 - 驱动方言抽象:方言差异收敛到
DriverInterface(17 方法),SQL 生成为无状态 Builder(可注册扩展解析器) - 查询缓存 / 延迟写入 / JSON 字段 / 游标流式读取 / 光标分页
- 慢查询诊断:超阈值 SELECT 自动 EXPLAIN 并写日志;
query事件可监听 - 表结构缓存:schema 元信息三层递进(运行时 → cache 包 → 数据库),支持按表清理
环境要求
- PHP >= 8.3、ext-pdo
- 可选:workerman/workerman(协程池,缺失时回退内置 SyncPool)
- fiberphp/cache 为硬依赖(查询缓存 / 延迟写入 / 表结构缓存均通过其
cache()助手读写)
安装
composer require fiberphp/database
配置(config/database.php)
return [
'default' => 'mysql',
'connections' => [
'mysql' => [
'driver' => 'mysql', // mysql | pgsql | sqlite
'host' => env('DB_HOST', '127.0.0.1'),
'database' => env('DB_DATABASE', 'demo'),
'username' => env('DB_USERNAME', 'root'),
'password' => env('DB_PASSWORD', ''),
'port' => (string)env('DB_PORT', 3306),
'charset' => 'utf8mb4',
'prefix' => '', // 表前缀
'params' => [PDO::ATTR_TIMEOUT => 3],
'schema_cache' => false, // 表结构缓存(跨进程)
'pool' => [ // 连接池
'max_connections' => 5,
'min_connections' => 1,
'wait_timeout' => 3,
'idle_timeout' => 60,
'heartbeat_interval' => 50,
],
'slow_sql_threshold' => 1000, // 慢查询阈值(ms),0=不检测
],
],
'log_sql' => true, // SQL 日志
'log_channel' => 'db', // 日志通道名
];
快速上手
use FiberPHP\Database\Db;
$q = db()->newQuery(); // 显式入口(推荐);db()->name('user') 经 __call 亦可
// 查
db()->name('user')->where('id', 1)->find(); // array|null
db()->name('user')->where('status', 1)->select(); // Collection
db()->name('user')->where('status', 1)->count(); // int
// 增删改
db()->name('user')->insert(['name' => 'tom', 'age' => 18]);
db()->name('user')->where('id', 1)->update(['age' => 19]);
db()->name('user')->delete(1); // 按 PK 删
// 多连接:db()->name('user') 走 default;显式指定连接名
db()->newQuery('log')->table('access_log')->find();
API 全集
表与别名
$q->table('user u') // 原始表名(含前缀需完整写)
$q->name('user') // 自动加前缀(推荐)
$q->name('user')->alias('u') // 别名
$q->name('user')->tableRaw('user FORCE INDEX(idx)') // 原生表表达式
$q->setTableSuffix('_2026') // 表名后缀(分表)
$q->getTable() // 当前表名
字段选择
$q->field('id,name') // 字符串
$q->field(['id', 'name', 'SUM(score)' => 'total']) // 数组(别名)
$q->field(true) // 全部字段(展开为显式列表)
$q->withoutField(['password']) // 排除字段
$q->fieldRaw('COUNT(*) AS c') // 原生字段
$q->distinct() // DISTINCT
条件(WhereQuery)
$q->where('id', 1) // =
$q->where('id', '>', 10) // 三参:运算符(=,<>,<,>,<=,>=,LIKE,IN,BETWEEN,...)
$q->where(['status' => 1, 'type' => 'a']) // 数组(AND)
$q->where(fn($q) => $q->where('a', 1)->whereOr('b', 2)) // 闭包子条件(括号分组)
$q->whereOr('status', 2) // OR
$q->whereNull('deleted_at') / whereNotNull()
$q->whereIn('id', [1,2,3]) / whereNotIn()
$q->whereBetween('age', [18, 30]) / whereNotBetween()
$q->whereLike('name', '%tom%') / whereNotLike()
$q->whereExists(fn($q) => ...) / whereNotExists()
$q->whereColumn('score', '>', 'base_score') // 字段与字段比较
$q->whereExp('age', 'age + 1 > 20', $bind) // 表达式(带绑定)
$q->whereRaw('YEAR(created_at) = ?', [2026]) // 原生 AND / whereOrRaw 原生 OR
$q->whereFieldRaw('created_at', '>', 'NOW()') // 字段侧原生值
$q->whereFindInSet('tags', 'php') // FIND_IN_SET(MySQL)
$q->whereJsonContains('ext->tags', 'php') // JSON 包含(三方言适配,OR 用 whereOr(fn) 包裹)
// 条件触发(动态查询)
$q->when($kw, fn($q) => $q->whereLike('name', "%$kw%"),
fn($q) => $q->where('status', 1))
时间条件(TimeFieldQuery)
$q->whereTime('created_at', '>', '2026-01-01')
$q->whereDay('created_at') // 今天(可传 'yesterday'、日期、步长)
$q->whereWeek('created_at') // 本周
$q->whereMonth('created_at') // 本月
$q->whereYear('created_at') // 今年
$q->whereBetweenTime('created_at', '09:00', '18:00')
$q->whereNotBetweenTime('created_at', '00:00', '06:00')
$q->whereBetweenTimeField('start_at', 'end_at') // 跨字段区间(NOW() 在区间内)
$q->whereTimeInterval('created_at', 'hour', 2) // 最近 N 小时
$q->timeRule(['today' => fn() => [...]]) // 自定义时间规则
排序 / 分组 / 技巧
$q->order('id', 'desc')
$q->order(['status' => 'asc', 'id' => 'desc'])
$q->orderRaw('FIELD(id, 3,1,2)')
$q->orderField('status', [2, 1, 3]) // 按值枚举排序
$q->orderRand()
$q->group('type')->having('COUNT(*) > 5')
$q->force('idx_user_status') // FORCE INDEX(MySQL)
$q->partition(['p2026']) // 分区表(MySQL)
$q->comment('后台导出') // SQL 注释
关联与联合
$q->join('profile p', 'p.user_id = u.id')
$q->leftJoin('profile p', 'p.user_id = u.id') // rightJoin / fullJoin(Pgsql)
$q->using('user_id') // USING 单字段
$q->via('u') // 关联字段自动加前缀
$q->union(fn($q) => $q->name('admin')->field('id,name'))
$q->unionAll(...同理)
$q->buildSql() // 生成子查询 SQL(( ... ))
聚合
$q->count() $q->count('DISTINCT uid')
$q->sum('score') $q->avg('score')
$q->min('age') $q->max('age')
$q->exists() // bool,是否命中
$q->value('name') // 单值
$q->column('name') // 一维列表
$q->column('name', 'id') // id => name 映射
终态操作(Query 层)
// 查
$q->select() // Collection(空抛错可用 selectOrFail)
$q->find(1) // 主键查单条 / find(null) 走 where
$q->findOrEmpty(1) // 空返回 [] 不报错
$q->findOrFail(1) // 空抛 DataNotFoundException
$q->allowEmpty() // 允许空结果(与 selectOrFail 配合)
$q->failException() // 空结果抛异常(调试用)
// 增
$q->insert(['name' => 'tom'])
$q->insertGetId(['name' => 'tom']) // 返回自增 ID
$q->insertAll([row1, row2, ...], 500) // 批量(自动分批 500/批 + 事务)
$q->name('tmp')->insertAll($rows) // Pgsql 走 ON CONFLICT 需要 duplicate()
$q->replace(true)->insert($row) // REPLACE INTO(MySQL)
$q->name('user')->duplicate(['age' => 30]) // UPSERT:MySQL ON DUPLICATE / Pgsql ON CONFLICT
->insert(['uid' => 1, 'age' => 30])
$q->selectInsert(['id','name'], 'user_bak') // INSERT INTO ... SELECT
// 改
$q->where('id', 1)->update(['age' => 19])
$q->inc('score', 5)->update() // score = score + 5
$q->dec('balance', 10)->update()
$q->inc('views', 1, 60)->update() // 延迟写入:60 秒窗口内合并(需 cache)
$q->save(['id' => 1, 'age' => 20]) // 有主键更新,无主键插入
// 删
$q->delete(1) // 主键
$q->where('status', 0)->delete()
// 原生
$q->execute('UPDATE user SET age = age + 1 WHERE id > ?', [100]) // int 影响行数
$q->query('SELECT * FROM user LIMIT 10') // array(连接层快捷)
// 调试
$q->fetchSql(true)->find() // 返回 SQL 字符串而不执行
$q->getLastSql() // 最后执行的 SQL
$q->buildSql() // 构造 SQL(子查询用)
JSON 字段
$q->json(['ext']) // 声明 ext 为 JSON 字段(读写自动编解码)
$q->json(['ext'], true) // 解码为关联数组
$q->whereJsonContains('ext->tags', 'php') // JSON 条件
结果处理(ResultOperation)
$q->filter(fn($row) => $row) // 逐行加工
$q->filter(fn($row) => $row, 'id') // 同上并按 id 为键
$q->readonly(['internal_note']) // 只读字段(写入时自动剥离)
$q->schema(['id' => 'int', 'score' => 'float']) // 手动声明字段类型(免 schema 探测)
$q->fieldMap(['uid' => 'id']) // 输出字段名映射
$q->strict(false) // 写入时允许非表字段(默认丢弃)
大数据量:游标 / 分块 / 惰性
foreach ($q->cursor() as $row) {} // Generator 逐行,默认 buffered
foreach ($q->cursor(true) as $row) {} // unbuffered:最省内存(独占连接至迭代完)
$q->chunk(1000, function ($rows) { ... }) // 按主键分块回调,返回 false 终止
foreach ($q->lazy(1000) as $rows) {} // LazyCollection(按主键倒序分块,惰性流)
$q->stream(function (LazyCollection $rows) { // 大表流式处理
foreach ($rows as $row) { ... }
});
LazyCollection 方法:map/filter/each/take/skip/page/first/last/count/isEmpty/reduce/when/chunk/toArray/toJson/jsonSerialize(惰性安全操作集)。
分页(Paginatable)
$paginator = $q->where('status', 1)->paginate(15); // Paginator(页码分页)
$simple = $q->simplePaginate(15); // 无总数(只算下一页)
$cursor = $q->orderBy('id')->cursorPaginate(20); // CursorPaginator(keyset,大表友好)
$paginator->items() / currentPage() / lastPage() / total() / hasMorePages()
事务
// 回调式(推荐)
db()->transaction(function () {
db()->name('user')->insert($row);
db()->name('log')->insert($log);
}); // 任一异常自动回滚并上抛
// 手动事务:经连接操作
$conn = db()->connect();
$conn->startTrans();
try {
db()->name('user')->insert($row);
$conn->commit();
} catch (Throwable $e) {
$conn->rollback();
throw $e;
}
事务事件钩子:database Transaction trait 自动调用
FiberPHP\Event\Event::beginTransaction()/commit()/rollback(),emit 派发的事件在事务内暂存,commit 后才 flush(afterCommit 语义),rollback 时丢弃。三处钩子均加了 try/catch 守卫,监听器异常不冒泡阻断数据库提交。
查询缓存(QueryCache,需 fiberphp/cache)
$q->cache('user_list', 300)->select() // 键 + TTL(秒)
$q->cacheAlways('user_list', 300)->select() // 忽略缓存直查后回写
$q->cacheForce('user_list', 300)->select() // 强制(升级场景绕过 tag 失效)
// 失效:cache()->tag('user_list')->clear() —— tag 随查询自动登记
魔术快捷(__call)
$q->getByName('tom') // where('name','=','tom')->find()
$q->getFieldById(5, 'name') // where('id','=',5)->value('name')
$q->whereName('tom')->find() // where('name','tom') 链式快捷
连接与池
use FiberPHP\Database\Db;
use FiberPHP\Database\Pool\SyncPool;
$db = new Db($config); // 独立使用(无容器)
// 自定义池
$db->setPoolFactory(function (string $name, array $poolConfig) {
return new SyncPool($poolConfig['max_connections'] ?? 10);
});
$conn = $db->connect(); // 借出连接(协程环境自动复用)
$conn->close(); // 显式关闭
$conn->isClosed(); // 池归还前由框架调用
$conn->rollbackHangingTransaction(); // 归还前回滚悬挂事务
- workerman 存在(框架运行时)→
WorkermanPool:协程挂起等待、心跳探活、空闲回收 - 无 workerman →
SyncPool:空闲队列复用 + 借出前探活 + 上限控制(同步场景) - 连接归还时自动:回滚悬挂事务 → 关闭游标 → 归还池;Context 销毁时兜底回收
事件与慢查询诊断
database 有两套事件机制:
CRUD 事件(强类型 Event 类,走 fiberphp/event):
use FiberPHP\Database\Event\BeforeFindEvent;
use FiberPHP\Database\Event\AfterInsertEvent;
use FiberPHP\Event\Event;
// 监听 CRUD 事件(继承匹配自动覆盖父类/接口)
Event::on(BeforeFindEvent::class, function (array $payload, BeforeFindEvent $event) {
// $payload['query'] 是 Query 对象
});
// 可用事件类:BeforeFindEvent / BeforeSelectEvent / AfterInsertEvent / AfterUpdateEvent / AfterDeleteEvent / AfterConnectEvent
// 目录:src/Event/*.php
SQL 运行时事件(字符串 API,走 Db::listen 内部回调):
// SQL 查询 / 慢查询 / 错误
db()->listen('query', function (array $payload) {
// payload: sql / bindings / duration_ms / connection
});
db()->listen('slow', function (array $payload) {
// payload: 同上,触发条件超 slow_sql_threshold
});
db()->listen('error', function (array $payload) {
// payload: sql / bindings / error / connection
});
// 慢查询自动 EXPLAIN:超过 slow_sql_threshold 的 SELECT/WITH
// 自动执行 EXPLAIN 并写 log_channel 通道(info 级,含执行计划)
配合 log_sql => true,SQL 全量走 log_channel => 'db' 通道。
扩展点
// 注册驱动(方言实现 DriverInterface)
Db::registerDriver('mysql', MysqlDriver::class);
// 注册 SQL 片段解析器(Builder 无状态扩展)
$db->connect()->getBuilder()->registerParser('order', [
'custom' => fn($q, $v) => '...',
]);
// 自定义时间规则
$q->timeRule(['lunar_new_year' => fn() => ['2026-02-17 00:00:00', '2026-02-17 23:59:59']]);
DriverInterface 关键方法:connect / getFields / getTables / jsonPath / jsonContains / buildUpsertPrefix / quoteExcludedColumn / lock / random / getRealDriverName 等。
目录结构
database/
├── config/database.php # 配置样例
├── src/
│ ├── Contract/
│ │ ├── DriverInterface.php # 驱动方言契约
│ │ └── PoolInterface.php # 连接池契约
│ ├── Pool/
│ │ ├── SyncPool.php # 同步池(无协程环境)
│ │ └── WorkermanPool.php # 协程池适配
│ ├── Driver/
│ │ ├── AbstractDriver.php # 驱动模板基类
│ │ ├── Mysql.php / Pgsql.php / Sqlite.php
│ │ ├── SqlStandardDialect.php
│ │ └── DriverProvider.php
│ ├── Concern/ # Query/Connection 行为拆分
│ │ ├── WhereQuery / TimeFieldQuery / JoinQuery / Paginatable
│ │ ├── ResultOperation / QueryCache / Cursor
│ │ └── Crud / Transaction / Schema / Bindings
│ ├── Exception/ # DbException / DataNotFoundException / DuplicateException / PDOException
│ ├── Pagination/ # AbstractPaginator / Paginator / CursorPaginator
│ ├── Builder.php # SQL 生成(无状态)
│ ├── Connection.php # 连接 + 执行 + schema
│ ├── Db.php # 管理器(多连接 + 池)
│ ├── DbProvider.php # 容器注册
│ ├── Facade/Db.php # Db Facade
│ ├── LazyCollection.php # 惰性集合
│ ├── Query.php # 查询构造器
│ ├── Raw.php / Expression.php # 原生表达式
│ ├── Install.php # 安装钩子(发布 config/database.php)
│ └── helpers.php # db() / raw() / inc() / dec() / paginate() 助手
└── tests/ # 单元测试
测试
composer install && php vendor/bin/phpunit
集成测试默认跳过(需真实数据库),设置环境变量后启用:DB_HOST / DB_DATABASE / DB_USERNAME / DB_PASSWORD。