react/mysql

ReactPHP的异步MySQL数据库客户端。

v0.6.0 2023-11-10 12:08 UTC

This package is auto-updated.

Last update: 2024-08-28 22:14:14 UTC


README

CI status

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]);

此方法专门设计用于返回结果集的查询(如SELECTEXPLAIN语句)。不返回结果集的查询(如UPDATEINSERT语句)不会发出任何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设计受此项目的启发。