react / mysql
ReactPHP的异步MySQL数据库客户端。
Requires
- php: >=5.4.0
- evenement/evenement: ^3.0 || ^2.1 || ^1.1
- react/event-loop: ^1.2
- react/promise: ^3 || ^2.7
- react/promise-stream: ^1.6
- react/promise-timer: ^1.9
- react/socket: ^1.12
Requires (Dev)
- phpunit/phpunit: ^9.6 || ^5.7 || ^4.8.36
- react/async: ^4 || ^3 || ^2
README
ReactPHP的异步MySQL数据库客户端。
开发版本:此分支包含即将发布的0.7版本的代码。要获取当前稳定版本0.6的代码,请查看
0.6.x
分支。即将发布的0.7版本将是此包的发展方向。但是,我们仍然将积极支持版本0.6,以便那些尚未升级到最新版本的用户。有关更多详细信息,请参阅安装说明。
这是一个为ReactPHP编写的MySQL数据库驱动程序。它实现了MySQL协议,并允许您访问现有的MySQL数据库。它是用纯PHP编写的,不需要任何扩展。
目录
快速入门示例
此示例执行一个简单的SELECT
查询,并将所有记录从book
表输出。
<?php require __DIR__ . '/vendor/autoload.php'; $mysql = new React\Mysql\MysqlClient('user:pass@localhost/bookstore'); $mysql->query('SELECT * FROM book')->then( function (React\Mysql\MysqlResult $command) { print_r($command->resultFields); print_r($command->resultRows); echo count($command->resultRows) . ' row(s) in set' . PHP_EOL; }, function (Exception $error) { echo 'Error: ' . $error->getMessage() . PHP_EOL; } );
请参阅示例。
用法
MysqlClient
MysqlClient
负责与MySQL服务器交换消息,并跟踪挂起的查询。
$mysql = new React\Mysql\MysqlClient($uri); $mysql->query(…);
此类表示一个负责与MySQL服务器实例通信、管理连接状态和发送数据库查询的连接。内部,它只在实例上调用第一个请求时创建底层的数据库连接,并将所有挂起的请求排队,直到底层连接就绪。此底层连接将用于所有请求,直到它关闭。默认情况下,空闲连接在不使用时将保持开启状态1毫秒(0.001秒)。下一个请求将重用现有的连接,或者如果此空闲时间已过期,将自动创建新的底层连接。
对于消费者来说,这意味着您可以立即开始向数据库发送查询,而底层连接可能仍然未就绪。因为创建此底层连接可能需要一些时间,所以它会将所有挂起的命令排队,并确保一旦连接就绪,所有命令都将按正确顺序执行。
如果底层数据库连接失败,它将拒绝所有挂起的命令,并返回到初始的“空闲”状态。这意味着您可以在稍后时间发送更多的命令,这将再次尝试打开新的底层连接。请注意,如果您使用的是持续时间超过空闲期的交易,这可能需要特别注意。
请注意,底层连接的创建将被推迟到第一次请求被调用。因此,任何可能的连接问题将在第一次使用此实例时被发现。您可以使用 quit()
方法来确保连接将被软关闭,并且无法再入队任何其他命令。同样,在此实例未连接时调用 quit()
将立即成功,无需等待实际的底层连接。
__construct()
可以使用 new MysqlClient(string $uri, ConnectorInterface $connector = null, LoopInterface $loop = null)
构造函数来创建一个新的 MysqlClient
实例。
$uri
参数必须包含要连接的数据库主机、可选的身份验证、端口和数据库。
$mysql = new React\Mysql\MysqlClient('user:secret@localhost:3306/database');
请注意,如果用户名和密码包含特殊字符,它们必须进行 URL 编码(百分号编码)。
$user = 'he:llo'; $pass = 'p@ss'; $mysql = new React\Mysql\MysqlClient( rawurlencode($user) . ':' . rawurlencode($pass) . '@localhost:3306/db' );
如果您正在连接到默认端口 3306
,则可以省略端口号。
$mysql = new React\Mysql\MysqlClient('user:secret@localhost/database');
如果您不包括身份验证和/或数据库,则此方法将默认尝试以用户 root
身份连接,密码为空,不选择任何数据库。这可能在初始设置数据库时很有用,但在生产系统中可能产生认证错误。
$mysql = new React\Mysql\MysqlClient('localhost');
此方法尊重 PHP 的 default_socket_timeout
设置(默认 60 秒)作为建立底层连接和等待成功认证的超时。您可以显式传递自定义的超时值(以秒为单位),或使用负数来不应用超时,如下所示:
$mysql = new React\Mysql\MysqlClient('localhost?timeout=0.5');
默认情况下,当未使用时,空闲连接将保持开启 1 毫秒(0.001 秒)。下一个请求将重用现有连接,或者如果此空闲时间已过,将自动创建新的底层连接。这确保您始终获得“新鲜”的连接,因此不应将其与“保持连接”或“心跳”机制混淆,因为此机制不会主动尝试探测连接。您可以显式传递自定义的空闲超时值(以秒为单位),或使用负数来不应用超时,如下所示:
$mysql = new React\Mysql\MysqlClient('localhost?idle=10.0');
默认情况下,连接提供完整的 UTF-8 支持(使用 utf8mb4
字符集编码)。对于大多数应用程序来说,通常不需要更改这一点,但出于历史原因,您可以使用以下方式将其更改为使用不同的 ASCII 兼容字符集编码:
$mysql = new React\Mysql\MysqlClient('localhost?charset=utf8mb4');
如果您需要自定义连接器设置(DNS 解析、TLS 参数、超时、代理服务器等),您可以显式传递 ConnectorInterface
的自定义实例。
$connector = new React\Socket\Connector([ 'dns' => '127.0.0.1', 'tcp' => [ 'bindto' => '192.168.10.1:0' ], 'tls' => [ 'verify_peer' => false, 'verify_peer_name' => false ) ]); $mysql = new React\Mysql\MysqlClient('user:secret@localhost:3306/database', $connector);
此类接受一个可选的 LoopInterface|null $loop
参数,可用于为此对象传递事件循环实例。您可以使用 null
值以使用 默认循环。除非您确信要显式使用给定的事件循环实例,否则不应提供此值。
query()
可以使用 query(string $query, array $params = []): PromiseInterface<MysqlResult>
方法执行异步查询。
此方法返回一个承诺,它将在成功时解析为 MysqlResult
,或者在出错时拒绝为 Exception
。MySQL 协议本质上是顺序的,因此所有查询都将按顺序执行,未完成的查询将排队,在之前的查询完成后执行。
$mysql->query('CREATE TABLE test ...'); $mysql->query('INSERT INTO test (id) VALUES (1)');
如果这个SQL语句返回一个结果集(例如从SELECT
语句返回),此方法将在内存中缓冲一切,直到结果集完成,然后解决生成的承诺。如果您知道您的结果集不超过几十或几百行,则此方法为首选方法。如果您的结果集大小未知或已知太大,无法放入内存中,则应使用queryStream()
方法。
$mysql->query($query)->then(function (React\Mysql\MysqlResult $command) { if (isset($command->resultRows)) { // this is a response to a SELECT etc. with some rows (0+) print_r($command->resultFields); print_r($command->resultRows); echo count($command->resultRows) . ' row(s) in set' . PHP_EOL; } else { // this is an OK message in response to an UPDATE etc. if ($command->insertId !== 0) { var_dump('last insert ID', $command->insertId); } echo 'Query OK, ' . $command->affectedRows . ' row(s) affected' . PHP_EOL; } }, function (Exception $error) { // the query was not executed successfully echo 'Error: ' . $error->getMessage() . PHP_EOL; });
您可以可选地传递一个包含$params
的数组,它将以这种方式绑定到查询:
$mysql->query('SELECT * FROM user WHERE id > ?', [$id]);
给定的$sql
参数必须包含一个语句。出于安全原因,禁用了对多个语句的支持,因为这可能会允许可能的SQL注入攻击,并且此API不适合公开多个可能的结果。
queryStream()
可以使用queryStream(string $sql, array $params = []): ReadableStreamInterface
方法执行异步查询并流式传输结果集的行。
此方法返回一个可读流,它将作为data
事件发射每个结果集的行。它仅将数据缓冲到内存中完成单行,并不会存储整个结果集。这允许您处理无法适应内存的无限制大小的结果集。如果知道您的结果集不超过几十或几百行,则可能希望使用query()
方法。
$stream = $mysql->queryStream('SELECT * FROM user'); $stream->on('data', function ($row) { echo $row['name'] . PHP_EOL; }); $stream->on('end', function () { echo 'Completed.'; });
您可以可选地传递一个包含$params
的数组,它将以这种方式绑定到查询:
$stream = $mysql->queryStream('SELECT * FROM user WHERE id > ?', [$id]);
此方法专门设计用于返回结果集的查询(如SELECT
或EXPLAIN
语句)。不返回结果集的查询(如UPDATE
或INSERT
语句)不会发出任何data
事件。
有关ReactPHP中可读流如何使用的更多信息,请参阅ReadableStreamInterface
。例如,您还可以使用其pipe()
方法将结果集行转发到WritableStreamInterface
,如下所示:
$mysql->queryStream('SELECT * FROM user')->pipe($formatter)->pipe($logger);
请注意,根据底层流定义,对此流调用pause()
和resume()
仅为建议性的,即流可能继续发射一些数据,直到底层网络缓冲区被清空。此外,请注意服务器端限制连接在具有传出数据的状态下允许保持的时间。应特别小心以确保及时恢复流。这意味着使用带有慢速目标流pipe()
可能导致连接一段时间后终止。
给定的$sql
参数必须包含一个语句。出于安全原因,禁用了对多个语句的支持,因为这可能会允许可能的SQL注入攻击,并且此API不适合公开多个可能的结果。
ping()
可以使用ping(): PromiseInterface<void>
方法检查连接是否存活。
此方法返回一个承诺,在成功时解决(使用空值),或在出错时拒绝带有Exception
。MySQL协议本质上具有顺序性,因此所有命令都将按顺序执行,未执行的命令将被放入队列,一旦之前的查询完成,就执行这些命令。
$mysql->ping()->then(function () { echo 'OK' . PHP_EOL; }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; });
quit()
可以使用quit(): PromiseInterface<void>
方法退出(软关闭)连接。
此方法返回一个承诺,在成功时解决(使用空值),或在出错时拒绝带有Exception
。MySQL协议本质上具有顺序性,因此所有命令都将按顺序执行,未执行的命令将被放入队列,一旦之前的命令完成,就执行这些命令。
$mysql->query('CREATE TABLE test ...'); $mysql->quit();
此方法将在所有未执行的命令完成后,优雅地关闭到MySQL数据库服务器的连接。有关在不需要等待任何命令完成的情况下强制关闭连接的信息,请参阅close()
。
close()
可以使用close(): void
方法强制关闭连接。
与quit()
方法不同,此方法将立即强制关闭连接并拒绝所有未完成的命令。
$mysql->close();
强制关闭连接将在服务器日志中产生警告,通常仅作为最后的手段。另请参阅quit()
作为安全的替代方案。
错误事件
当发生致命错误时,例如连接丢失或无效时,将发出error
事件。该事件接收一个用于错误实例的单个Exception
参数。
$mysql->on('error', function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; });
此事件仅针对致命错误触发,并将随后关闭连接。不要将其与由无效SQL查询引起的“软”错误混淆。
关闭事件
一旦连接关闭(终止),将发出close
事件。
$mysql->on('close', function () { echo 'Connection closed' . PHP_EOL; });
另请参阅close()
方法。
安装
安装此库的推荐方法是通过Composer。您是Composer的新手吗?了解Composer?
一旦发布,此项目将遵循SemVer。目前,这将安装最新的开发版本。
composer require react/mysql:^0.7@dev
有关版本升级的详细信息,请参阅变更日志。
此项目旨在在任何平台上运行,因此不需要任何PHP扩展,并支持在旧版PHP 5.4到当前PHP 8+和HHVM上运行。强烈建议使用此项目的最新支持的PHP版本。
测试
要运行测试套件,您首先需要克隆此存储库,然后通过Composer安装所有依赖项通过Composer
composer install
测试套件包含一些功能集成测试,这些测试会将实际的测试SQL查询发送到您的本地数据库,因此依赖于具有适当写入权限的本地MySQL测试数据库。测试套件将在该数据库中创建和修改测试表,因此请确保不要使用生产数据库!您可以通过传递这些ENV变量来更改您的测试数据库凭证
export DB_HOST=localhost export DB_PORT=3306 export DB_USER=test export DB_PASSWD=test export DB_DBNAME=test
例如,要创建一个空白的测试数据库,您也可以使用这样的临时mysql
Docker镜像
docker run -it --rm --net=host \ -e MYSQL_RANDOM_ROOT_PASSWORD=yes -e MYSQL_DATABASE=test \ -e MYSQL_USER=test -e MYSQL_PASSWORD=test mysql:5
要运行测试套件,请转到项目根目录并运行
vendor/bin/phpunit
许可
MIT,请参阅LICENSE文件。
这是一个由@friends-of-reactphp管理的社区项目。原始实现由@bixuehujin从2013年开始创建,并于2018年迁移到@friends-of-reactphp以帮助维护和即将推出的功能开发。
原始实现得益于以下项目
- phpdaemon:MySQL协议实现基于此项目的代码(已获得许可)。
- node-mysql:API设计受此项目的启发。