fiberphp/database

FiberPHP database: PDO-based connection pool, query builder, model ORM with MySQL/PostgreSQL/SQLite drivers.

Maintainers

Package info

gitee.com/FiberPHP/database

Issues

pkg:composer/fiberphp/database

Transparency log

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

dev-master 2026-08-22 13:29 UTC

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 字段

驱动类适用场景
mysqlMysqlDriver生产环境(默认)
pgsqlPgsqlDriver需要 JSONB/GIS/全文检索等
sqliteSqliteDriver单元测试、本地开发、嵌入式

基本用法

全局函数

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
MySQLORDER BY rand()
PG/SQLiteORDER 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
MySQLON DUPLICATE KEY UPDATE name = VALUES(name), age = VALUES(age)
PG/SQLiteON 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 实现
MySQLSHOW FULL COLUMNS FROMSHOW TABLES [FROM db]
PGpg_attribute 系统视图pg_tables WHERE schemaname='public'
SQLitePRAGMA 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();

事务方法在 ConnectionQuery 上均可调用。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 项断言,三种驱动全部通过。