clue / reactphp-sqlite
异步 SQLite 数据库,基于 ReactPHP 的轻量级非阻塞文件数据库扩展(ext-sqlite3)
Requires
- php: >=5.4
- ext-sqlite3: *
- clue/ndjson-react: ^1.0
- react/child-process: ^0.6
- react/event-loop: ^1.2
- react/promise: ^3 || ^2.7 || ^1.2.1
Requires (Dev)
- phpunit/phpunit: ^9.6 || ^5.7 || ^4.8.36
README
异步 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)');
此方法专门用于不返回结果集的查询(例如UPDATE
或INSERT
语句)。返回结果集的查询(例如来自SELECT
或EXPLAIN
语句)不允许访问此数据,因此建议您使用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数据类型。此转换支持int
、float
、string
和null
。任何有效UTF-8且不带任何控制字符的string
将被映射到TEXT
,二进制字符串将被映射到BLOB
。两者都将始终映射到string
。SQLite没有原生的布尔类型,所以true
和false
将被映射到整数值1
和0
。
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)。