fiberphp / database
FiberPHP database: PDO-based connection pool, query builder, model ORM with MySQL/PostgreSQL/SQLite drivers.
dev-master
2026-08-22 13:29 UTC
Requires
- php: >=8.3
- ext-pdo: *
- fiberphp/framework: dev-master
Requires (Dev)
- phpunit/phpunit: ^11.0
This package is auto-updated.
Last update: 2026-08-22 13:30:03 UTC
README
支持 MySQL、PostgreSQL、SQLite 三种驱动的数据库访问层,采用 Model → Query → Builder → Connection 架构,通过 DriverInterface 抽象数据库方言差异。
环境要求
- PHP >= 8.3
- MySQL >= 8.0 / PostgreSQL >= 10 / SQLite >= 3
- Redis >= 6.0(连接池/缓存依赖)
架构
Model (Table Gateway)
└─ Query (查询构造/状态持有)
└─ Builder (SQL 生成, 驱动感知)
└─ Connection (执行/连接池/事务)
└─ DriverInterface (方言抽象)
├─ MysqlDriver
├─ PgsqlDriver (uses SqlStandardDialect)
└─ SqliteDriver (uses SqlStandardDialect)
- SQL 生成唯一出口为 Builder 层,上层不编写裸 SQL
- 方言差异通过
DriverInterface方法抽象,Builder 和 Schema 层根据驱动自动切换 - 无 Repository 层,Model 直接封装单表 CRUD
配置
配置文件 config/database/app.php:
return [
'default' => 'mysql',
'connections' => [
// MySQL
'mysql' => [
'driver' => 'mysql',
'host' => '127.0.0.1',
'port' => 3306,
'database' => 'demo',
'username' => 'root',
'password' => '',
'charset' => 'utf8mb4',
'prefix' => '',
'schema_cache' => false,
'pool' => [
'max_connections' => 5,
'min_connections' => 1,
'wait_timeout' => 3,
'idle_timeout' => 60,
'heartbeat_interval' => 50,
],
'slow_sql_threshold' => 1000,
],
// PostgreSQL
'pgsql' => [
'driver' => 'pgsql',
'host' => '127.0.0.1',
'port' => 5432,
'database' => 'demo',
'username' => 'postgres',
'password' => '',
'charset' => 'utf8',
'prefix' => '',
],
// SQLite
'sqlite' => [
'driver' => 'sqlite',
'database' => '/path/to/db.sqlite', // 或 ':memory:'
'prefix' => '',
],
],
'log_sql' => true,
'log_channel' => 'db',
];
driver 字段
| 值 | 驱动类 | 适用场景 |
|---|---|---|
mysql | MysqlDriver | 生产环境(默认) |
pgsql | PgsqlDriver | 需要 JSONB/GIS/全文检索等 |
sqlite | SqliteDriver | 单元测试、本地开发、嵌入式 |
基本用法
全局函数
db() // 获取 Db 管理器实例
raw($sql, $bind) // 创建原始 SQL 表达式
inc($step) // 字段自增表达式
dec($step) // 字段自减表达式
查询构造器(Query)
use function db;
// 插入
$id = db()->table('users')->insertGetId([
'name' => 'Alice',
'email' => 'alice@test.com',
'age' => 30,
]);
// 查询单条
$row = db()->table('users')->where('id', $id)->find();
// 查询多条(有序)
$rows = db()->table('users')->order('age', 'asc')->limit(10)->select();
// 更新
$affected = db()->table('users')->where('id', $id)->update(['age' => 31]);
// 删除
$affected = db()->table('users')->where('id', $id)->delete();
// 聚合
$count = db()->table('users')->where('age', '>=', 18)->count();
$exists = db()->table('users')->where('email', 'alice@test.com')->exists();
Model(Table Gateway)
use FiberPHP\Db\Model;
class User extends Model
{
protected const TABLE = 'users';
protected const PK = 'id';
protected const CONNECTION = null; // null=默认连接, 或指定连接名如 'pgsql'
}
// 创建
$id = User::create(['name' => 'Alice', 'email' => 'alice@test.com']);
// 查找
$user = User::find($id);
$user = User::findBy(['email' => 'alice@test.com']);
// 列表
$users = User::select(['age' => 30], limit: 10);
$users = User::all();
// 更新
User::update($id, ['age' => 31]);
User::updateWhere(['age' => 30], ['status' => 'active']);
// 删除
User::delete($id);
User::deleteWhere(['status' => 'deleted']);
// 统计
$count = User::count(['age' => 30]);
$exists = User::exists(['email' => 'alice@test.com']);
驱动感知特性
随机排序
// 自动根据驱动生成 rand() 或 random()
$rows = db()->table('users')->orderRand()->limit(5)->select();
| 驱动 | 生成的 SQL |
|---|---|
| MySQL | ORDER BY rand() |
| PG/SQLite | ORDER BY random() |
UPSERT(插入或更新)
// 字符串形式:冲突时更新指定列(使用插入值)
db()->table('users')
->duplicate('name,age')
->insert(['id' => 1, 'name' => 'Alice', 'age' => 30]);
// 数组形式:冲突时更新为指定值,并声明冲突目标列
db()->table('users')
->duplicate(['name' => 'Alice2', 'age' => 31], ['id'])
->insert(['id' => 1, 'name' => 'Alice2', 'age' => 31]);
// 通过 Model::upsert
User::upsert(
['id' => 1, 'name' => 'Alice', 'age' => 30],
['name' => 'Alice', 'age' => 30],
['id']
);
| 驱动 | 生成的 SQL |
|---|---|
| MySQL | ON DUPLICATE KEY UPDATE name = VALUES(name), age = VALUES(age) |
| PG/SQLite | ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, age = EXCLUDED.age |
注意:MySQL 的
conflictTarget参数被忽略(依赖唯一索引判定冲突);PostgreSQL/SQLite 必须指定冲突目标列。
标识符引用
| 驱动 | 表名/字段名引用 |
|---|---|
| MySQL | `name` |
| PG/SQLite | "name" |
Builder 自动根据驱动选择引用符号,无需手动处理。
Schema 查询
// 获取字段元数据
$fields = $conn->getFields('users');
// 返回: ['id' => ['name' => 'id', 'type' => 'int', 'primary' => true, ...], ...]
// 获取表列表
// MySQL 传数据库名,PG/SQLite 传空字符串
$tables = $conn->getTables('demo'); // MySQL
$tables = $conn->getTables(''); // PG/SQLite
| 驱动 | getFields 实现 | getTables 实现 |
|---|---|---|
| MySQL | SHOW FULL COLUMNS FROM | SHOW TABLES [FROM db] |
| PG | pg_attribute 系统视图 | pg_tables WHERE schemaname='public' |
| SQLite | PRAGMA table_info() | sqlite_master WHERE type='table' |
事务
use function db;
// 方式一:手动控制
$conn = db()->connect();
$conn->startTrans();
try {
db()->table('users')->where('id', 1)->update(['age' => 31]);
db()->table('logs')->insert(['action' => 'update']);
$conn->commit();
} catch (\Throwable $e) {
$conn->rollback();
throw $e;
}
// 方式二:回调(自动提交/回滚)
db()->connect()->transaction(function ($conn) {
db()->table('users')->where('id', 1)->update(['age' => 31]);
db()->table('logs')->insert(['action' => 'update']);
});
// 方式三:通过 Query 代理
$query = db()->table('users');
$query->startTrans();
// ... 操作 ...
$query->commit();
事务方法在
Connection和Query上均可调用。Query代理到Connection。
批量插入
// 批量插入(自动分批)
db()->table('users')->insertAll([
['name' => 'Alice', 'age' => 30],
['name' => 'Bob', 'age' => 25],
['name' => 'Carol', 'age' => 35],
]);
// 按键值对批量插入
db()->table('users')->insertAllByKeys(
['name', 'age'],
[
['Alice', 30],
['Bob', 25],
]
);
SQL 监听
db()->listen('query', function (array $context) {
// $context = ['sql' => ..., 'duration_ms' => ..., 'bindings' => ..., 'connection' => ...]
logger()->info('SQL executed', $context);
});
db()->listen('slow', function (array $context) {
// 仅当执行时间超过 slow_sql_threshold 时触发
logger()->warning('Slow SQL', $context);
});
db()->listen('error', function (array $context) {
logger()->error('SQL error', $context);
});
获取生成的 SQL(不执行)
$sql = db()->table('users')
->where('age', '>=', 18)
->order('age', 'desc')
->fetchSql(true)
->select();
// 返回 SQL 字符串,不执行查询
自定义驱动
内置驱动映射在 Db 类的 $driverMap 属性(mysql/pgsql/sqlite)。新增驱动需扩展该默认映射:
use FiberPHP\Db\Contract\DriverInterface;
use FiberPHP\Db\Driver\AbstractDriver;
class MyDriver extends AbstractDriver implements DriverInterface
{
public function parseDsn(array $config): string
{
return "my:host={$config['host']};dbname={$config['database']}";
}
// 实现其他抽象方法...
}
// 在 Database 包的 Provider 注册流程中,向 Db::$driverMap 追加 'my' => MyDriver::class,
// 或直接在 database/src/Db.php 的 $driverMap 默认值里加一行。
运行测试
测试依赖 Docker 启动的 MySQL 和 PostgreSQL 容器:
# 启动容器
docker run -d --name fiberphp-mysql-test -p 3307:3306 \
-e MYSQL_ROOT_PASSWORD=test123 -e MYSQL_DATABASE=test_db \
mysql:8.0
docker run -d --name fiberphp-pgsql-test -p 5432:5432 \
-e POSTGRES_USER=test -e POSTGRES_PASSWORD=test123 -e POSTGRES_DB=test_db \
postgres:16
# 运行测试
cd http && php ../fiberphp/db/tests/DriverIntegrationTest.php
测试覆盖:连接、Schema 查询、CRUD、聚合、随机排序、UPSERT、事务,共 115 项断言,三种驱动全部通过。