fiberphp / database
🗄️ FiberPHP 数据库组件 —— 基于 PDO 的连接池、查询构造器、驱动抽象层、协程安全。
Requires
- php: >=8.3
- ext-pdo: *
- fiberphp/event: dev-master
- fiberphp/framework: dev-master
- fiberphp/helper: dev-master
- psr/log: ^3.0
Requires (Dev)
- fiberphp/cache: 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 not auto-updated.
Last update: 2026-10-09 02:10:08 UTC
README
FiberPHP 数据库组件:协程连接池 + 查询构造器 + SQL 生成 + 驱动方言抽象。内置 MySQL / PostgreSQL / SQLite 三驱动。
特性
- 连接池:workerman 协程环境自动池化(挂起等待/心跳探活/上限控制);无 workerman 时回退内置
SyncPool(同步复用),可脱离框架独立使用 - 驱动方言抽象:方言差异收敛到
AbstractDriver(驱动模板基类),SQL 生成为无状态 Builder(可注册扩展解析器) - 查询缓存 / 延迟写入 / JSON 字段 / 游标流式读取 / 光标分页
- 慢查询诊断:超阈值 SELECT 自动 EXPLAIN 并写日志;
query事件可监听 - 表结构缓存:schema 元信息三层递进(运行时 → cache 包 → 数据库),支持按表清理
环境要求
- PHP >= 8.3、ext-pdo
- 可选:workerman/workerman(协程池,缺失时回退内置 SyncPool)
- fiberphp/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('连接名') 返回该连接上的查询对象
db('log')->name('access_log')->find();
// 显式入口(语义同上):db()->newQuery('log')->name('access_log')->find()
API 全集
表与别名
$q->name('user') // 自动加前缀+snake(推荐)
$q->name('user')->alias('u') // 别名
$q->tableRaw('user FORCE INDEX(idx)') // 原生表表达式(不转义、不拼前后缀)
$q->setTableSuffix('_2026') // 表名后缀(分表)
$q->getTable() // 当前表名
表结构与元信息
$q->getTables() // 当前库所有表名(可传库名)
$q->getTableFields('user') // 字段名数组
$q->getFieldsType('user') // 字段 => 类型映射
$q->getPrimaryKey('user') // 主键(array|string|null,无主键返回 null)
$q->setPk(['id', 'tenant_id']) // 查询级覆盖主键(复合主键传数组)
$q->getSchemaInfo('user') // 表结构信息(含缓存,true 强制刷新)
$q->clearSchemaCache('user') // 清理单表 schema 缓存(null 清全部)
字段选择
$q->field(['id', 'name']) // 列表数组(仅接受数组)
$q->field(['name' => 'nickname']) // 别名映射 → name AS nickname
$q->field([new Raw('COUNT(*)')]) // Raw 对象元素
$q->withoutField(['password']) // 排除字段
$q->fieldRaw('COUNT(*) AS c') // 原生字段(显式字符串通道)
$q->distinct() // DISTINCT
条件(WhereQuery)
where() / whereOr() 只接受数组或闭包,数组有两种形态:
// 关联 map:仅支持等值(=)与 null(IS NULL),AND 连接
$q->where(['status' => 1, 'deleted_at' => null])
// list:每项必须是恰好三段的 [字段, 操作符, 值]
$q->where([
['id', '>', 10],
['age', '<>', null], // 值为 null:=/<>/!= 转 IS NULL / IS NOT NULL
])
$q->whereOr([['status', '=', 2]]) // OR 组,形态与 where 相同
// 闭包子条件(括号分组)
$q->where(fn($q) => $q->where([['a', '=', 1]])->whereOr([['b', '=', 2]]))
$q->where([]) // 空数组 no-op
IN / LIKE / BETWEEN / NULL 等直接在三段元组中写操作符:
$q->where([['id', 'IN', [1,2,3]]])
$q->where([['age', 'BETWEEN', [18, 30]]]) // 值必须是两元素数组
$q->where([['name', 'LIKE', '%tom%']])
$q->where([['deleted_at', '=', null]]) // IS NULL;'<>' 归一 IS NOT NULL
其余语义明确的专用方法:
$q->whereExists(fn($q) => ...) / whereNotExists()
$q->whereColumn('score', '>', 'base_score') // 字段与字段比较
$q->whereExp('age', '+ 1 > 20', $bind) // 字段 + 表达式尾部(带绑定)
$q->whereRaw('YEAR(created_at) = :year', ['year' => 2026]) // 原生 AND / whereOrRaw 原生 OR
$q->whereFieldRaw('created_at', '>', 'NOW()') // 字段侧原生值
$q->whereJsonContains('ext->tags', 'php') // JSON 包含(三方言适配,OR 用 whereOr(fn) 包裹)
// 条件触发(动态查询)
$q->when($kw, fn($q) => $q->where([['name', 'LIKE', "%$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', '2026-01-01 08:00', 'hour', 2) // 起点+步长构造 between 区间
$q->timeRule(['today' => fn() => [...]]) // 自定义时间规则
排序 / 分组 / 技巧
$q->order(['id' => 'desc'])
$q->order(['status' => 'asc', 'id' => 'desc']) // map:键=字段,值=asc/desc
$q->order(['id', 'create_time']) // list:裸字段,默认方向
$q->orderRaw('FIELD(id, 3,1,2)') // 原生表达式(可用 Raw list 元素替代)
$q->orderField('status', [2, 1, 3]) // 按值枚举排序
$q->orderRand()
$q->group(['type'])->having('COUNT(*) > 5')
$q->group([new Raw('DATE(created_at)')]) // 表达式分组(函数/JSON 提取等)
$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->delete(1) // 主键
$q->where(['status' => 0])->delete()
// 原生
$q->execute('UPDATE user SET age = age + 1 WHERE id > :min_id', ['min_id' => 100]) // int 影响行数
$q->query('SELECT * FROM user LIMIT 10') // array(连接层快捷)
$q->batchQuery(['SET NAMES utf8mb4', 'TRUNCATE TABLE tmp']) // 批量执行 SQL 脚本
// 调试
$q->fetchSql(true)->find() // 返回 SQL 字符串而不执行
$q->getLastSql() // 最后执行的 SQL
$q->buildSql() // 构造 SQL(子查询用)
$q->getPdo() // 原生 PDOStatement
$q->extra('FOR UPDATE') // 追加子句(原样拼接,勿拼用户输入)
// 参数绑定(命名占位符,不支持 ?)
$ph = $q->bindValue(1) // 返回生成的占位符名(值注册在当前查询)
$q->autoBind(false) // 关闭自动参数绑定(连接配置 auto_bind 可全局控制)
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->order(['id'])->cursorPaginate(20); # CursorPaginator(offset 游标编码,无 COUNT)
$paginator->items() / currentPage() / lastPage() / total() / hasMorePages()
$cursor->nextCursor() / previousCursor() // CursorPaginator 游标令牌(null = 到末尾/已在首页)
事务
// 回调式(推荐)
db()->transaction(function () {
db()->name('user')->insert($row);
db()->name('log')->insert($log);
}); // 任一异常自动回滚并上抛
// 手动事务:经连接操作,支持保存点嵌套与断线重连重试
// startTrans 内层自动 SAVEPOINT transN;commit 内层只释放保存点,最外层才 COMMIT
// rollback 内层回滚到保存点;断线重建后 commit/rollback 显式抛 DbException(防误判已提交)
$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 随查询自动登记
连接与池
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 类,走 framework 内置事件总线):
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
// SQL 运行时:QueryEvent / SlowQueryEvent / QueryErrorEvent(目录:src/Event/)
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' 通道。
扩展点
// 注册驱动(方言继承 AbstractDriver)
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']]);
AbstractDriver 关键方法:connect / getFields / getTables / jsonPath / jsonContains / buildUpsertPrefix / quoteExcludedColumn / lock / random / getRealDriverName 等。
目录结构
database/
├── config/database.php # 配置样例
├── src/
│ ├── Pool/
│ │ ├── AbstractPool.php # 连接池抽象基类
│ │ ├── 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 # 容器注册
│ ├── LazyCollection.php # 惰性集合
│ ├── Query.php # 查询构造器
│ ├── Raw.php / Expression.php # 原生表达式
│ └── helpers.php # db() / raw() / inc() / dec() / paginate() 助手
└── tests/ # 单元测试
测试
composer install && php vendor/bin/phpunit
集成测试默认跳过(需真实数据库),设置环境变量后启用:DB_HOST / DB_DATABASE / DB_USERNAME / DB_PASSWORD。