Search by

fiberphp / database

fiberphp

🗄️ FiberPHP 数据库组件 —— 基于 PDO 的连接池、查询构造器、驱动抽象层、协程安全。

Package info

gitee.com/fiberphp/database.git

Issues

pkg:composer/fiberphp/database

Statistics

Installs: 2

Dependents: 3

Suggesters: 3

dev-master 2026-09-10 13:43 UTC

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