clue/reactphp-sqlite

异步 SQLite 数据库,基于 ReactPHP 的轻量级非阻塞文件数据库扩展(ext-sqlite3)

v1.6.0 2023-05-12 12:33 UTC

This package is auto-updated.

Last update: 2024-09-10 15:17:11 UTC


README

CI status code coverage installs on Packagist

异步 SQLite 数据库,基于 ReactPHP 的轻量级非阻塞文件数据库扩展(ext-sqlite3

SQLite 是一种广泛使用且高效的进程内数据库。它提供了一个通用的 SQL 接口来处理查询,以便在内存中或持久化到简单的、可移植的数据库文件中操作其关系数据。其轻量级设计使其成为嵌入式数据库的理想选择,适用于可移植(CLI)应用程序、测试环境等。这个库提供了一个简单的 API,让您可以在 PHP 中操作 SQLite 数据库。由于操作 SQLite 和底层文件系统本身是阻塞的,该项目构建为一个轻量级非阻塞过程包装器,因此您可以在不阻塞主应用程序的情况下查询数据。

  • 查询异步执行 - 并行(自动队列)向 SQLite 发送任意数量的查询(SQL),并在结果到来时立即处理它们的响应。基于 Promise 的设计提供了一个合理的工作界面,用于处理异步结果。
  • 轻量级,SOLID 设计 - 提供了一个薄薄的抽象,它“刚好足够”且不会干扰您。未来的或自定义命令和事件无需更改即可得到支持。
  • 良好的测试覆盖率 - 配备了自动测试套件,并且定期针对野外的实际 SQLite 数据库进行测试。

目录

支持我们

我们投入了大量时间开发、维护和更新我们出色的开源项目。您可以通过 在 GitHub 上成为赞助商 来帮助我们维持工作的高质量。赞助商将获得许多回报,请参阅我们的 赞助页面 以获取详细信息。

让我们共同努力将这些项目提升到新的水平!🚀

快速入门示例

以下示例代码演示了如何使用此库打开现有的 SQLite 数据库文件(或首次运行时自动创建它),然后向数据库中插入一条新记录

<?php

require __DIR__ . '/vendor/autoload.php';

$factory = new Clue\React\SQLite\Factory();
$db = $factory->openLazy(__DIR__ . '/users.db');

$db->exec('CREATE TABLE IF NOT EXISTS user (id INTEGER PRIMARY KEY AUTOINCREMENT, name STRING)');

$name = 'Alice';
$db->query('INSERT INTO user (name) VALUES (?)', [$name])->then(
    function (Clue\React\SQLite\Result $result) use ($name) {
        echo 'New ID for ' . $name . ': ' . $result->insertId . PHP_EOL;
    },
    function (Exception $e) {
        echo 'Error: ' . $e->getMessage() . PHP_EOL;
    }
);

$db->quit();

请参阅 示例

用法

工厂

Factory 负责打开您的 DatabaseInterface 实例。

$factory = new Clue\React\SQLite\Factory();

这个类接受一个可选的 LoopInterface|null $loop 参数,可以用它来传递用于此对象的事件循环实例。您可以在这里使用 null 值以使用 默认循环。除非您确定要显式使用特定的事件循环实例,否则不应提供此值。

这个类接受一个可选的 ?string $binary 参数,可以用它来传递在启动子进程时使用的自定义PHP二进制文件。您可以在这里使用 null 值以自动检测当前PHP二进制文件。如果您自动检测失败或希望以与父进程不同的PHP版本或环境运行子进程,则可能需要传递自定义可执行文件路径。

// advanced usage: pass custom PHP binary to use when spawning child process
$factory = new Clue\React\SQLite\Factory(null, '/usr/bin/php6.0');

或者您也可以使用此参数来传递一个空的PHP二进制路径,这将导致此项目在任何数据库交互中都不会启动PHP子进程。在这种情况下,使用SQLite将阻塞主进程,但将继续提供完全相同的异步API。当不需要并发执行时,这很有用,尤其是在使用传统Web服务器(非CLI SAPI)时。

// advanced usage: empty binary path runs blocking SQLite in same process
$factory = new Clue\React\SQLite\Factory(null, '');

open()

使用 open(string $filename, int $flags = null): PromiseInterface<DatabaseInterface> 方法可以打开给定SQLite数据库文件的数据库连接。

此方法返回一个承诺,在成功时解析为 DatabaseInterface,或者在出错时拒绝 Exception。SQLite扩展本身是阻塞的,因此此方法将启动一个SQLite工作进程,在单独的进程中运行所有SQLite命令和查询,而不阻塞主进程。在Windows上,它使用临时网络套接字进行此通信,在其他所有平台上,它通过标准进程I/O管道进行通信。

$factory->open('users.db')->then(function (DatabaseInterface $db) {
    // database ready
    // $db->query('INSERT INTO users (name) VALUES ("test")');
    // $db->quit();
}, function (Exception $e) {
    echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

$filename 参数是SQLite数据库文件的路径或 :memory: 以创建一个临时的内存数据库。从PHP 7.0.10开始,可以提供一个空字符串来创建一个私有的、临时的磁盘数据库。相对路径将相对于当前工作目录解析,因此通常建议传递绝对路径以避免任何歧义。

$promise = $factory->open(__DIR__ . '/users.db');

可选的 $flags 参数用于确定如何打开SQLite数据库。默认情况下,open使用 SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE

$factory->open('users.db', SQLITE3_OPEN_READONLY)->then(function (DatabaseInterface $db) {
    // database ready (read-only)
    // $db->quit();
}, function (Exception $e) {
    echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

openLazy()

使用 openLazy(string $filename, int $flags = null, array $options = []): DatabaseInterface 方法可以打开给定SQLite数据库文件的数据库连接。

$db = $factory->openLazy('users.db');

$db->query('INSERT INTO users (name) VALUES ("test")');
$db->quit();

此方法立即返回一个实现 DatabaseInterface 的“虚拟”连接,可以用于与SQLite数据库接口。内部,它仅在实例上调用第一个请求时才懒惰地创建底层数据库进程,并将所有挂起的请求排队直到底层数据库准备就绪。此外,它将默认将此底层数据库保持在“空闲”状态60秒,并在不再需要时自动结束底层数据库。

对于消费者来说,这意味着您可以直接向数据库发送查询,而底层数据库进程可能仍然未完成。由于创建此底层进程可能需要一些时间,因此它将挂起所有挂起的命令,并确保一旦数据库就绪,所有命令都将按正确顺序执行。换句话说,这个“虚拟”数据库的行为就像一个“真实”数据库,如 DatabaseInterface 中描述的那样,并让您免于处理其异步解析。

如果底层数据库进程失败,它将拒绝所有挂起的命令,并返回到初始的“空闲”状态。这意味着您可以在稍后发送额外的命令,这些命令将再次尝试打开新的底层数据库。请注意,如果您使用的是超过空闲期的长时间保持打开的事务,可能需要特别注意。

请注意,底层数据库的创建将被推迟到第一次请求被调用。相应地,任何最终连接问题将在这个实例首次使用时被发现。您可以使用quit()方法来确保“虚拟”连接将被软关闭,且不会进一步排队命令。同样,在不连接此实例时调用quit()将立即成功,无需等待实际的底层连接。

根据您的特定用例,您可能更喜欢这种方法或底层open()方法,该方法通过承诺解决。对于许多简单用例,创建一个懒加载连接可能更容易。

$filename 参数是SQLite数据库文件的路径或 :memory: 以创建一个临时的内存数据库。从PHP 7.0.10开始,可以提供一个空字符串来创建一个私有的、临时的磁盘数据库。相对路径将相对于当前工作目录解析,因此通常建议传递绝对路径以避免任何歧义。

$$db = $factory->openLazy(__DIR__ . '/users.db');

可选的 $flags 参数用于确定如何打开SQLite数据库。默认情况下,open使用 SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE

$db = $factory->openLazy('users.db', SQLITE3_OPEN_READONLY);

默认情况下,此方法将保持“空闲”连接60秒,然后结束底层连接。在“空闲”连接结束后,下一个请求将自动创建新的底层连接。这确保您始终获得“新鲜”的连接,因此不应与“保持活动”或“心跳”机制混淆,因为这不会主动尝试探测连接。您可以显式传递自定义空闲超时值(以秒为单位),或使用负数来不应用超时,如下所示:

$db = $factory->openLazy('users.db', null, ['idle' => 0.1]);

DatabaseInterface

DatabaseInterface代表负责与您的SQLite数据库包装器通信、管理连接状态和发送您的数据库查询的连接。

exec()

可以使用exec(string $query): PromiseInterface<Result>方法来执行异步查询。

此方法返回一个承诺,在成功时将解析为Result,或者在出错时将拒绝为Exception。SQLite包装器本质上是顺序的,因此所有查询将按顺序执行,挂起的查询将被放入队列,在之前的查询完成后执行。

$db->exec('CREATE TABLE test ...');
$db->exec('INSERT INTO test (id) VALUES (1)');

此方法专门用于不返回结果集的查询(例如UPDATEINSERT语句)。返回结果集的查询(例如来自SELECTEXPLAIN语句)不允许访问此数据,因此建议您使用query()方法。

$db->exec($query)->then(function (Result $result) {
    // this is an OK message in response to an UPDATE etc.
    if ($result->insertId !== 0) {
        var_dump('last insert ID', $result->insertId);
    }
    echo 'Query OK, ' . $result->changed . ' row(s) changed' . PHP_EOL;
}, function (Exception $error) {
    // the query was not executed successfully
    echo 'Error: ' . $error->getMessage() . PHP_EOL;
});

query()方法不同,此方法不支持传递将绑定到查询的占位符参数数组。如果您想传递用户提供的数据,建议您使用query()方法。

query()

可以使用query(string $query, array $params = []): PromiseInterface<Result>方法来执行异步查询。

此方法返回一个承诺,在成功时将解析为Result,或者在出错时将拒绝为Exception。SQLite包装器本质上是顺序的,因此所有查询将按顺序执行,挂起的查询将被放入队列,在之前的查询完成后执行。

$db->query('CREATE TABLE test ...');
$db->query('INSERT INTO test (id) VALUES (1)');

如果这个SQL语句返回一个结果集(例如来自SELECT语句),此方法将缓冲内存中的所有内容,直到结果集完成,然后解析结果承诺。

$db->query($query)->then(function (Result $result) {
    if (isset($result->rows)) {
        // this is a response to a SELECT etc. with some rows (0+)
        print_r($result->columns);
        print_r($result->rows);
        echo count($result->rows) . ' row(s) in set' . PHP_EOL;
    } else {
        // this is an OK message in response to an UPDATE etc.
        if ($result->insertId !== 0) {
            var_dump('last insert ID', $result->insertId);
        }
        echo 'Query OK, ' . $result->changed . ' row(s) changed' . PHP_EOL;
    }
}, function (Exception $error) {
    // the query was not executed successfully
    echo 'Error: ' . $error->getMessage() . PHP_EOL;
});

您可以选择传递一个将绑定到查询的$params数组,如下所示:

$db->query('SELECT * FROM user WHERE id > ?', [$id]);

同样,您还可以使用命名占位符将绑定到查询,如下所示:

$db->query('SELECT * FROM user WHERE id > :id', ['id' => $id]);

所有占位符值将自动映射到原生SQLite数据类型,所有结果值将自动映射到原生PHP数据类型。此转换支持intfloatstringnull。任何有效UTF-8且不带任何控制字符的string将被映射到TEXT,二进制字符串将被映射到BLOB。两者都将始终映射到string。SQLite没有原生的布尔类型,所以truefalse将被映射到整数值10

quit()

可以使用quit(): PromiseInterface<void, Exception>方法来退出(软关闭)连接。

此方法返回一个承诺,在成功时(无返回值)解决,或者在出错时以Exception拒绝。SQLite包装器本质上是顺序的,因此所有命令都将按顺序执行,未完成的命令将放入队列中,等待前面的命令完成后执行。

$db->query('CREATE TABLE test ...');
$db->quit();

close()

可以使用close(): void方法强制关闭连接。

quit()方法不同,此方法将立即强制关闭连接并拒绝所有未完成的命令。

$db->close();

通常只应将强制关闭连接作为最后的手段。有关安全替代方案,请参阅quit()

事件

除了定义了一些方法外,该接口还实现了EventEmitterInterface,允许您响应某些事件。

错误事件

当发生致命错误,例如连接丢失或无效时,将触发error事件。该事件接收一个错误实例的单一Exception参数。

$db->on('error', function (Exception $e) {
    echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

此事件仅在发生致命错误时触发,并将随后关闭连接。不要与由无效SQL查询引起的“软”错误混淆。

关闭事件

一旦连接关闭(终止),将触发close事件。

$db->on('close', function () {
    echo 'Connection closed' . PHP_EOL;
});

有关更多信息,请参阅close()方法。

安装

推荐通过Composer安装此库。您是Composer的新手吗?了解Composer?

本项目遵循SemVer。这将安装最新的支持版本。

composer require clue/reactphp-sqlite:^1.6

有关版本升级的详细信息,请参阅CHANGELOG

本项目旨在在任何平台上运行,因此仅需要ext-sqlite3扩展,并支持从PHP 5.4到当前PHP 8+的运行。强烈建议使用本项目支持的最新PHP版本。

本项目是在ext-sqlite3 PHP扩展周围实现的轻量级进程包装器,因此您必须确保已安装合适的版本。在基于Debian/Ubuntu的系统上,您可以像这样安装它

sudo apt install php-sqlite3

测试

要运行测试套件,您首先需要克隆此仓库,然后通过Composer安装所有依赖项。

composer install

要运行测试套件,请转到项目根目录并运行

vendor/bin/phpunit

测试套件已设置,始终确保在所有受支持的环境中(除Windows上不执行的特定平台代码外)100%的代码覆盖率。如果您已安装Xdebug扩展,还可以本地生成代码覆盖率报告,如下所示

XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text

许可证

本项目在许可的MIT许可证下发布。

你知道吗?我提供定制开发服务,并为发布和贡献发放发票。如需详细信息,请联系我(@clue)。