From 9a3976b18620f07ae1a003b943c297451603cc40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Fri, 10 Nov 2023 22:55:07 +0100 Subject: [PATCH 1/2] Simplify API, add new `MysqlClient` and remove `Factory` --- README.md | 195 ++------ examples/01-query.php | 8 +- examples/02-query-stream.php | 7 +- examples/11-interactive.php | 126 +++-- examples/12-slow-stream.php | 72 +-- src/{ => Io}/Factory.php | 139 +----- .../LazyConnection.php => MysqlClient.php} | 22 +- tests/BaseTestCase.php | 2 +- tests/{ => Io}/FactoryTest.php | 95 +--- ...ConnectionTest.php => MysqlClientTest.php} | 472 ++++++++++++++---- tests/NoResultQueryTest.php | 80 +++ tests/ResultQueryTest.php | 24 +- 12 files changed, 620 insertions(+), 622 deletions(-) rename src/{ => Io}/Factory.php (61%) rename src/{Io/LazyConnection.php => MysqlClient.php} (94%) rename tests/{ => Io}/FactoryTest.php (90%) rename tests/{Io/LazyConnectionTest.php => MysqlClientTest.php} (64%) diff --git a/README.md b/README.md index 8b0e027..7c77dec 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,8 @@ It is written in pure PHP and does not require any extensions. * [Quickstart example](#quickstart-example) * [Usage](#usage) - * [Factory](#factory) - * [createConnection()](#createconnection) - * [createLazyConnection()](#createlazyconnection) + * [MysqlClient](#mysqlclient) + * [__construct()](#__construct) * [ConnectionInterface](#connectioninterface) * [query()](#query) * [queryStream()](#querystream) @@ -45,11 +44,10 @@ This example runs a simple `SELECT` query and dumps all the records from a `book require __DIR__ . '/vendor/autoload.php'; -$factory = new React\MySQL\Factory(); -$connection = $factory->createLazyConnection('user:pass@localhost/bookstore'); +$mysql = new React\MySQL\MysqlClient('user:pass@localhost/bookstore'); -$connection->query('SELECT * FROM book')->then( - function (QueryResult $command) { +$mysql->query('SELECT * FROM book')->then( + function (React\MySQL\QueryResult $command) { print_r($command->resultFields); print_r($command->resultRows); echo count($command->resultRows) . ' row(s) in set' . PHP_EOL; @@ -64,137 +62,13 @@ See also the [examples](examples). ## Usage -### Factory +### MysqlClient -The `Factory` is responsible for creating your [`ConnectionInterface`](#connectioninterface) instance. +The `MysqlClient` is responsible for exchanging messages with your MySQL server +and keeps track of pending queries. ```php -$factory = new React\MySQL\Factory(); -``` - -This class takes an optional `LoopInterface|null $loop` parameter that can be used to -pass the event loop instance to use for this object. You can use a `null` value -here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). -This value SHOULD NOT be given unless you're sure you want to explicitly use a -given event loop instance. - -If you need custom connector settings (DNS resolution, TLS parameters, timeouts, -proxy servers etc.), you can explicitly pass a custom instance of the -[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface): - -```php -$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 - ) -]); - -$factory = new React\MySQL\Factory(null, $connector); -``` - -#### createConnection() - -The `createConnection(string $url): PromiseInterface` method can be used to -create a new [`ConnectionInterface`](#connectioninterface). - -It helps with establishing a TCP/IP connection to your MySQL database -and issuing the initial authentication handshake. - -```php -$factory->createConnection($url)->then( - function (ConnectionInterface $connection) { - // client connection established (and authenticated) - }, - function (Exception $e) { - // an error occurred while trying to connect or authorize client - } -); -``` - -The method returns a [Promise](https://github.com/reactphp/promise) that -will resolve with a [`ConnectionInterface`](#connectioninterface) -instance on success or will reject with an `Exception` if the URL is -invalid or the connection or authentication fails. - -The returned Promise is implemented in such a way that it can be -cancelled when it is still pending. Cancelling a pending promise will -reject its value with an Exception and will cancel the underlying TCP/IP -connection attempt and/or MySQL authentication. - -```php -$promise = $factory->createConnection($url); - -Loop::addTimer(3.0, function () use ($promise) { - $promise->cancel(); -}); -``` - -The `$url` parameter must contain the database host, optional -authentication, port and database to connect to: - -```php -$factory->createConnection('user:secret@localhost:3306/database'); -``` - -Note that both the username and password must be URL-encoded (percent-encoded) -if they contain special characters: - -```php -$user = 'he:llo'; -$pass = 'p@ss'; - -$promise = $factory->createConnection( - rawurlencode($user) . ':' . rawurlencode($pass) . '@localhost:3306/db' -); -``` - -You can omit the port if you're connecting to default port `3306`: - -```php -$factory->createConnection('user:secret@localhost/database'); -``` - -If you do not include authentication and/or database, then this method -will default to trying to connect as user `root` with an empty password -and no database selected. This may be useful when initially setting up a -database, but likely to yield an authentication error in a production system: - -```php -$factory->createConnection('localhost'); -``` - -This method respects PHP's `default_socket_timeout` setting (default 60s) -as a timeout for establishing the connection and waiting for successful -authentication. You can explicitly pass a custom timeout value in seconds -(or use a negative number to not apply a timeout) like this: - -```php -$factory->createConnection('localhost?timeout=0.5'); -``` - -By default, the connection provides full UTF-8 support (using the -`utf8mb4` charset encoding). This should usually not be changed for most -applications nowadays, but for legacy reasons you can change this to use -a different ASCII-compatible charset encoding like this: - -```php -$factory->createConnection('localhost?charset=utf8mb4'); -``` - -#### createLazyConnection() - -Creates a new connection. - -It helps with establishing a TCP/IP connection to your MySQL database -and issuing the initial authentication handshake. - -```php -$connection = $factory->createLazyConnection($url); +$connection = new React\MySQL\MysqlClient($uri); $connection->query(…); ``` @@ -215,9 +89,6 @@ database right away while the underlying connection may still be outstanding. Because creating this underlying connection may take some time, it will enqueue all outstanding commands and will ensure that all commands will be executed in correct order once the connection is ready. -In other words, this "virtual" connection behaves just like a "real" -connection as described in the `ConnectionInterface` and frees you from -having to deal with its async resolution. If the underlying database connection fails, it will reject all outstanding commands and will return to the initial "idle" state. This @@ -234,15 +105,16 @@ and no further commands can be enqueued. Similarly, calling `quit()` on this instance when not currently connected will succeed immediately and will not have to wait for an actual underlying connection. -Depending on your particular use case, you may prefer this method or the -underlying `createConnection()` which resolves with a promise. For many -simple use cases it may be easier to create a lazy connection. +#### __construct() + +The `new MysqlClient(string $uri, ConnectorInterface $connector = null, LoopInterface $loop = null)` constructor can be used to +create a new `MysqlClient` instance. -The `$url` parameter must contain the database host, optional +The `$uri` parameter must contain the database host, optional authentication, port and database to connect to: ```php -$factory->createLazyConnection('user:secret@localhost:3306/database'); +$mysql = new React\MySQL\MysqlClient('user:secret@localhost:3306/database'); ``` Note that both the username and password must be URL-encoded (percent-encoded) @@ -252,7 +124,7 @@ if they contain special characters: $user = 'he:llo'; $pass = 'p@ss'; -$connection = $factory->createLazyConnection( +$mysql = new React\MySQL\MysqlClient( rawurlencode($user) . ':' . rawurlencode($pass) . '@localhost:3306/db' ); ``` @@ -260,7 +132,7 @@ $connection = $factory->createLazyConnection( You can omit the port if you're connecting to default port `3306`: ```php -$factory->createLazyConnection('user:secret@localhost/database'); +$mysql = new React\MySQL\MysqlClient('user:secret@localhost/database'); ``` If you do not include authentication and/or database, then this method @@ -269,7 +141,7 @@ and no database selected. This may be useful when initially setting up a database, but likely to yield an authentication error in a production system: ```php -$factory->createLazyConnection('localhost'); +$mysql = new React\MySQL\MysqlClient('localhost'); ``` This method respects PHP's `default_socket_timeout` setting (default 60s) @@ -278,7 +150,7 @@ successful authentication. You can explicitly pass a custom timeout value in seconds (or use a negative number to not apply a timeout) like this: ```php -$factory->createLazyConnection('localhost?timeout=0.5'); +$mysql = new React\MySQL\MysqlClient('localhost?timeout=0.5'); ``` By default, idle connections will be held open for 1ms (0.001s) when not @@ -291,7 +163,7 @@ pass a custom idle timeout value in seconds (or use a negative number to not apply a timeout) like this: ```php -$factory->createLazyConnection('localhost?idle=10.0'); +$mysql = new React\MySQL\MysqlClient('localhost?idle=10.0'); ``` By default, the connection provides full UTF-8 support (using the @@ -300,9 +172,34 @@ applications nowadays, but for legacy reasons you can change this to use a different ASCII-compatible charset encoding like this: ```php -$factory->createLazyConnection('localhost?charset=utf8mb4'); +$mysql = new React\MySQL\MysqlClient('localhost?charset=utf8mb4'); +``` + +If you need custom connector settings (DNS resolution, TLS parameters, timeouts, +proxy servers etc.), you can explicitly pass a custom instance of the +[`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface): + +```php +$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); ``` +This class takes an optional `LoopInterface|null $loop` parameter that can be used to +pass the event loop instance to use for this object. You can use a `null` value +here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). +This value SHOULD NOT be given unless you're sure you want to explicitly use a +given event loop instance. + ### ConnectionInterface The `ConnectionInterface` represents a connection that is responsible for diff --git a/examples/01-query.php b/examples/01-query.php index 776e1f5..849bdb5 100644 --- a/examples/01-query.php +++ b/examples/01-query.php @@ -3,16 +3,12 @@ // $ php examples/01-query.php // $ MYSQL_URI=test:test@localhost/test php examples/01-query.php "SELECT * FROM book" -use React\MySQL\Factory; -use React\MySQL\QueryResult; - require __DIR__ . '/../vendor/autoload.php'; -$factory = new Factory(); -$connection = $factory->createLazyConnection(getenv('MYSQL_URI') ?: 'test:test@localhost/test'); +$mysql = new React\MySQL\MysqlClient(getenv('MYSQL_URI') ?: 'test:test@localhost/test'); $query = isset($argv[1]) ? $argv[1] : 'select * from book'; -$connection->query($query)->then(function (QueryResult $command) { +$mysql->query($query)->then(function (React\MySQL\QueryResult $command) { if (isset($command->resultRows)) { // this is a response to a SELECT etc. with some rows (0+) print_r($command->resultFields); diff --git a/examples/02-query-stream.php b/examples/02-query-stream.php index c4e69b7..1562603 100644 --- a/examples/02-query-stream.php +++ b/examples/02-query-stream.php @@ -3,15 +3,12 @@ // $ php examples/02-query-stream.php "SHOW VARIABLES" // $ MYSQL_URI=test:test@localhost/test php examples/02-query-stream.php "SELECT * FROM book" -use React\MySQL\Factory; - require __DIR__ . '/../vendor/autoload.php'; -$factory = new Factory(); -$connection = $factory->createLazyConnection(getenv('MYSQL_URI') ?: 'test:test@localhost/test'); +$mysql = new React\MySQL\MysqlClient(getenv('MYSQL_URI') ?: 'test:test@localhost/test'); $query = isset($argv[1]) ? $argv[1] : 'select * from book'; -$stream = $connection->queryStream($query); +$stream = $mysql->queryStream($query); $stream->on('data', function ($row) { echo json_encode($row, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL; diff --git a/examples/11-interactive.php b/examples/11-interactive.php index 10ee6ea..2e92f4c 100644 --- a/examples/11-interactive.php +++ b/examples/11-interactive.php @@ -3,87 +3,73 @@ // $ php examples/11-interactive.php // $ MYSQL_URI=test:test@localhost/test php examples/11-interactive.php -use React\MySQL\ConnectionInterface; -use React\MySQL\QueryResult; -use React\MySQL\Factory; -use React\Stream\ReadableResourceStream; - require __DIR__ . '/../vendor/autoload.php'; -$factory = new Factory(); -$uri = getenv('MYSQL_URI') ?: 'test:test@localhost/test'; +$mysql = new React\MySQL\MysqlClient(getenv('MYSQL_URI') ?: 'test:test@localhost/test'); // open a STDIN stream to read keyboard input (not supported on Windows) -$stdin = new ReadableResourceStream(STDIN); -$stdin->pause(); - -//create a mysql connection for executing queries -$factory->createConnection($uri)->then(function (ConnectionInterface $connection) use ($stdin) { - echo 'Connection success.' . PHP_EOL; - $stdin->resume(); +$stdin = new React\Stream\ReadableResourceStream(STDIN); - $stdin->on('data', function ($line) use ($connection) { - $query = trim($line); +$stdin->on('data', function ($line) use ($mysql) { + $query = trim($line); - if ($query === '') { - // skip empty commands - return; - } - if ($query === 'exit') { - // exit command should close the connection - echo 'bye.' . PHP_EOL; - $connection->quit(); - return; - } + if ($query === '') { + // skip empty commands + return; + } + if ($query === 'exit') { + // exit command should close the connection + echo 'bye.' . PHP_EOL; + $mysql->quit(); + return; + } - $time = microtime(true); - $connection->query($query)->then(function (QueryResult $command) use ($time) { - if (isset($command->resultRows)) { - // this is a response to a SELECT etc. with some rows (0+) - echo implode("\t", array_column($command->resultFields, 'name')) . PHP_EOL; - foreach ($command->resultRows as $row) { - echo implode("\t", $row) . PHP_EOL; - } - - printf( - '%d row%s in set (%.03f sec)%s', - count($command->resultRows), - count($command->resultRows) === 1 ? '' : 's', - microtime(true) - $time, - PHP_EOL - ); - } else { - // this is an OK message in response to an UPDATE etc. - // the insertId will only be set if this is - if ($command->insertId !== 0) { - var_dump('last insert ID', $command->insertId); - } + $time = microtime(true); + $mysql->query($query)->then(function (React\MySQL\QueryResult $command) use ($time) { + if (isset($command->resultRows)) { + // this is a response to a SELECT etc. with some rows (0+) + echo implode("\t", array_column($command->resultFields, 'name')) . PHP_EOL; + foreach ($command->resultRows as $row) { + echo implode("\t", $row) . PHP_EOL; + } - printf( - 'Query OK, %d row%s affected (%.03f sec)%s', - $command->affectedRows, - $command->affectedRows === 1 ? '' : 's', - microtime(true) - $time, - PHP_EOL - ); + printf( + '%d row%s in set (%.03f sec)%s', + count($command->resultRows), + count($command->resultRows) === 1 ? '' : 's', + microtime(true) - $time, + PHP_EOL + ); + } else { + // this is an OK message in response to an UPDATE etc. + // the insertId will only be set if this is + if ($command->insertId !== 0) { + var_dump('last insert ID', $command->insertId); } - }, function (Exception $error) { - // the query was not executed successfully - echo 'Error: ' . $error->getMessage() . PHP_EOL; - }); - }); - // close connection when STDIN closes (EOF or CTRL+D) - $stdin->on('close', function () use ($connection) { - $connection->quit(); + printf( + 'Query OK, %d row%s affected (%.03f sec)%s', + $command->affectedRows, + $command->affectedRows === 1 ? '' : 's', + microtime(true) - $time, + PHP_EOL + ); + } + }, function (Exception $error) { + // the query was not executed successfully + echo 'Error: ' . $error->getMessage() . PHP_EOL; }); +}); - // close STDIN (stop reading) when connection closes - $connection->on('close', function () use ($stdin) { - $stdin->close(); - echo 'Disconnected.' . PHP_EOL; - }); -}, function (Exception $e) use ($stdin) { - echo 'Connection error: ' . $e->getMessage() . PHP_EOL; +// close connection when STDIN closes (EOF or CTRL+D) +$stdin->on('close', function () use ($mysql) { + $mysql->quit(); +}); + +// close STDIN (stop reading) when connection closes +$mysql->on('close', function () use ($stdin) { $stdin->close(); + echo 'Disconnected.' . PHP_EOL; }); + +echo '# Entering interactive mode ready, hit CTRL-D to quit' . PHP_EOL; diff --git a/examples/12-slow-stream.php b/examples/12-slow-stream.php index bb1af49..b61c6f8 100644 --- a/examples/12-slow-stream.php +++ b/examples/12-slow-stream.php @@ -4,19 +4,21 @@ // $ MYSQL_URI=test:test@localhost/test php examples/12-slow-stream.php "SELECT * FROM book" use React\EventLoop\Loop; -use React\MySQL\ConnectionInterface; -use React\MySQL\Factory; require __DIR__ . '/../vendor/autoload.php'; -$factory = new Factory(); -$uri = getenv('MYSQL_URI') ?: 'test:test@localhost/test'; +$mysql = new React\MySQL\MysqlClient(getenv('MYSQL_URI') ?: 'test:test@localhost/test'); $query = isset($argv[1]) ? $argv[1] : 'select * from book'; +$stream = $mysql->queryStream($query); -//create a mysql connection for executing query -$factory->createConnection($uri)->then(function (ConnectionInterface $connection) use ($query) { - // The protocol parser reads rather large chunked from the underlying connection +$ref = new ReflectionProperty($mysql, 'connecting'); +$ref->setAccessible(true); +$promise = $ref->getValue($mysql); +assert($promise instanceof React\Promise\PromiseInterface); + +$promise->then(function (React\MySQL\Io\Connection $connection) { + // The protocol parser reads rather large chunks from the underlying connection // and as such can yield multiple (dozens to hundreds) rows from a single data // chunk. We try to artificially limit the stream chunk size here to try to // only ever read a single row so we can demonstrate throttling this stream. @@ -28,11 +30,13 @@ $ref = new ReflectionProperty($connection, 'stream'); $ref->setAccessible(true); $conn = $ref->getValue($connection); + assert($conn instanceof React\Socket\ConnectionInterface); // access private "input" (instanceof React\Stream\DuplexStreamInterface) $ref = new ReflectionProperty($conn, 'input'); $ref->setAccessible(true); $stream = $ref->getValue($conn); + assert($stream instanceof React\Stream\DuplexStreamInterface); // reduce private bufferSize to just a few bytes to slow things down $ref = new ReflectionProperty($stream, 'bufferSize'); @@ -41,38 +45,34 @@ } catch (Exception $e) { echo 'Warning: Unable to reduce buffer size: ' . $e->getMessage() . PHP_EOL; } +}); - $stream = $connection->queryStream($query); - - $throttle = null; - $stream->on('data', function ($row) use (&$throttle, $stream) { - echo json_encode($row, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL; - - // simple throttle mechanism: explicitly pause the result stream and - // resume it again after some time. - if ($throttle === null) { - $throttle = Loop::addTimer(1.0, function () use ($stream, &$throttle) { - $throttle = null; - $stream->resume(); - }); - $stream->pause(); - } - }); - - $stream->on('error', function (Exception $e) { - echo 'Error: ' . $e->getMessage() . PHP_EOL; - }); - - $stream->on('close', function () use (&$throttle) { - echo 'CLOSED' . PHP_EOL; +$throttle = null; +$stream->on('data', function ($row) use (&$throttle, $stream) { + echo json_encode($row, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL; - if ($throttle) { - Loop::cancelTimer($throttle); + // simple throttle mechanism: explicitly pause the result stream and + // resume it again after some time. + if ($throttle === null) { + $throttle = Loop::addTimer(1.0, function () use ($stream, &$throttle) { $throttle = null; - } - }); + $stream->resume(); + }); + $stream->pause(); + } +}); - $connection->quit(); -}, function (Exception $e) { +$stream->on('error', function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); + +$stream->on('close', function () use (&$throttle) { + echo 'CLOSED' . PHP_EOL; + + if ($throttle) { + Loop::cancelTimer($throttle); + $throttle = null; + } +}); + +$mysql->quit(); diff --git a/src/Factory.php b/src/Io/Factory.php similarity index 61% rename from src/Factory.php rename to src/Io/Factory.php index 25911e7..9aa27d1 100644 --- a/src/Factory.php +++ b/src/Io/Factory.php @@ -1,21 +1,22 @@ createLazyConnection($url); - * - * $connection->query(…); - * ``` - * - * This method immediately returns a "virtual" connection implementing the - * [`ConnectionInterface`](#connectioninterface) that can be used to - * interface with your MySQL database. Internally, it lazily creates the - * underlying database connection only on demand once the first request is - * invoked on this instance and will queue all outstanding requests until - * the underlying connection is ready. This underlying connection will be - * reused for all requests until it is closed. By default, idle connections - * will be held open for 1ms (0.001s) when not used. The next request will - * either reuse the existing connection or will automatically create a new - * underlying connection if this idle time is expired. - * - * From a consumer side this means that you can start sending queries to the - * database right away while the underlying connection may still be - * outstanding. Because creating this underlying connection may take some - * time, it will enqueue all outstanding commands and will ensure that all - * commands will be executed in correct order once the connection is ready. - * In other words, this "virtual" connection behaves just like a "real" - * connection as described in the `ConnectionInterface` and frees you from - * having to deal with its async resolution. - * - * If the underlying database connection fails, it will reject all - * outstanding commands and will return to the initial "idle" state. This - * means that you can keep sending additional commands at a later time which - * will again try to open a new underlying connection. Note that this may - * require special care if you're using transactions that are kept open for - * longer than the idle period. - * - * Note that creating the underlying connection will be deferred until the - * first request is invoked. Accordingly, any eventual connection issues - * will be detected once this instance is first used. You can use the - * `quit()` method to ensure that the "virtual" connection will be soft-closed - * and no further commands can be enqueued. Similarly, calling `quit()` on - * this instance when not currently connected will succeed immediately and - * will not have to wait for an actual underlying connection. - * - * Depending on your particular use case, you may prefer this method or the - * underlying `createConnection()` which resolves with a promise. For many - * simple use cases it may be easier to create a lazy connection. - * - * The `$url` parameter must contain the database host, optional - * authentication, port and database to connect to: - * - * ```php - * $factory->createLazyConnection('user:secret@localhost:3306/database'); - * ``` - * - * Note that both the username and password must be URL-encoded (percent-encoded) - * if they contain special characters: - * - * ```php - * $user = 'he:llo'; - * $pass = 'p@ss'; - * - * $connection = $factory->createLazyConnection( - * rawurlencode($user) . ':' . rawurlencode($pass) . '@localhost:3306/db' - * ); - * ``` - * - * You can omit the port if you're connecting to default port `3306`: - * - * ```php - * $factory->createLazyConnection('user:secret@localhost/database'); - * ``` - * - * If you do not include authentication and/or database, then this method - * will default to trying to connect as user `root` with an empty password - * and no database selected. This may be useful when initially setting up a - * database, but likely to yield an authentication error in a production system: - * - * ```php - * $factory->createLazyConnection('localhost'); - * ``` - * - * This method respects PHP's `default_socket_timeout` setting (default 60s) - * as a timeout for establishing the underlying connection and waiting for - * successful authentication. You can explicitly pass a custom timeout value - * in seconds (or use a negative number to not apply a timeout) like this: - * - * ```php - * $factory->createLazyConnection('localhost?timeout=0.5'); - * ``` - * - * By default, idle connections will be held open for 1ms (0.001s) when not - * used. The next request will either reuse the existing connection or will - * automatically create a new underlying connection if this idle time is - * expired. This ensures you always get a "fresh" connection and as such - * should not be confused with a "keepalive" or "heartbeat" mechanism, as - * this will not actively try to probe the connection. You can explicitly - * pass a custom idle timeout value in seconds (or use a negative number to - * not apply a timeout) like this: - * - * ```php - * $factory->createLazyConnection('localhost?idle=10.0'); - * ``` - * - * By default, the connection provides full UTF-8 support (using the - * `utf8mb4` charset encoding). This should usually not be changed for most - * applications nowadays, but for legacy reasons you can change this to use - * a different ASCII-compatible charset encoding like this: - * - * ```php - * $factory->createLazyConnection('localhost?charset=utf8mb4'); - * ``` - * - * @param string $uri - * @return ConnectionInterface - */ - public function createLazyConnection( - #[\SensitiveParameter] - $uri - ) { - return new LazyConnection($this, $uri, $this->loop); - } } diff --git a/src/Io/LazyConnection.php b/src/MysqlClient.php similarity index 94% rename from src/Io/LazyConnection.php rename to src/MysqlClient.php index d825dbd..a88892c 100644 --- a/src/Io/LazyConnection.php +++ b/src/MysqlClient.php @@ -1,19 +1,17 @@ idlePeriod = (float)$args['idle']; } - $this->factory = $factory; + $this->factory = new Factory($loop, $connector); $this->uri = $uri; - $this->loop = $loop; + $this->loop = $loop ?: Loop::get(); } private function connecting() diff --git a/tests/BaseTestCase.php b/tests/BaseTestCase.php index be67f93..ecd19b8 100644 --- a/tests/BaseTestCase.php +++ b/tests/BaseTestCase.php @@ -5,7 +5,7 @@ use PHPUnit\Framework\TestCase; use React\EventLoop\LoopInterface; use React\MySQL\ConnectionInterface; -use React\MySQL\Factory; +use React\MySQL\Io\Factory; class BaseTestCase extends TestCase { diff --git a/tests/FactoryTest.php b/tests/Io/FactoryTest.php similarity index 90% rename from tests/FactoryTest.php rename to tests/Io/FactoryTest.php index 2e73bed..bbedaa3 100644 --- a/tests/FactoryTest.php +++ b/tests/Io/FactoryTest.php @@ -1,12 +1,13 @@ expectOutputString('closed.'); - - $factory = new Factory(); - - $uri = 'mysql://random:pass@host'; - $connection = $factory->createLazyConnection($uri); - - $connection->quit()->then(function () { - echo 'closed.'; - }); - } - - public function testConnectLazyWithValidAuthWillRunUntilQuitAfterPing() - { - $this->expectOutputString('closed.'); - - $factory = new Factory(); - - $uri = $this->getConnectionString(); - $connection = $factory->createLazyConnection($uri); - - $connection->ping(); - - $connection->quit()->then(function () { - echo 'closed.'; - }); - - Loop::run(); - } - - /** - * @doesNotPerformAssertions - */ - public function testConnectLazyWithValidAuthWillRunUntilIdleTimerAfterPingEvenWithoutQuit() - { - $factory = new Factory(); - - $uri = $this->getConnectionString(); - $connection = $factory->createLazyConnection($uri); - - $connection->ping(); - - Loop::run(); - } - - public function testConnectLazyWithInvalidAuthWillRejectPingButWillNotEmitErrorOrClose() - { - $factory = new Factory(); - - $uri = $this->getConnectionString(['passwd' => 'invalidpass']); - $connection = $factory->createLazyConnection($uri); - - $connection->on('error', $this->expectCallableNever()); - $connection->on('close', $this->expectCallableNever()); - - $connection->ping()->then(null, $this->expectCallableOnce()); - - Loop::run(); - } - - public function testConnectLazyWithValidAuthWillPingBeforeQuitButNotAfter() - { - $this->expectOutputString('rejected.ping.closed.'); - - $factory = new Factory(); - - $uri = $this->getConnectionString(); - $connection = $factory->createLazyConnection($uri); - - $connection->ping()->then(function () { - echo 'ping.'; - }); - - $connection->quit()->then(function () { - echo 'closed.'; - }); - - $connection->ping()->then(function () { - echo 'never reached'; - }, function () { - echo 'rejected.'; - }); - - Loop::run(); - } } diff --git a/tests/Io/LazyConnectionTest.php b/tests/MysqlClientTest.php similarity index 64% rename from tests/Io/LazyConnectionTest.php rename to tests/MysqlClientTest.php index 89e75e8..df7d021 100644 --- a/tests/Io/LazyConnectionTest.php +++ b/tests/MysqlClientTest.php @@ -2,24 +2,84 @@ namespace React\Tests\MySQL\Io; -use React\MySQL\Io\LazyConnection; +use React\MySQL\Io\Connection; +use React\MySQL\MysqlClient; +use React\MySQL\QueryResult; use React\Promise\Deferred; use React\Promise\Promise; use React\Promise\PromiseInterface; -use React\Tests\MySQL\BaseTestCase; use React\Stream\ReadableStreamInterface; use React\Stream\ThroughStream; -use React\MySQL\QueryResult; +use React\Tests\MySQL\BaseTestCase; -class LazyConnectionTest extends BaseTestCase +class MysqlClientTest extends BaseTestCase { + public function testConstructWithoutConnectorAndLoopAssignsConnectorAndLoopAutomatically() + { + $mysql = new MysqlClient('localhost'); + + $ref = new \ReflectionProperty($mysql, 'factory'); + $ref->setAccessible(true); + $factory = $ref->getValue($mysql); + + $ref = new \ReflectionProperty($factory, 'connector'); + $ref->setAccessible(true); + $connector = $ref->getValue($factory); + + $this->assertInstanceOf('React\Socket\ConnectorInterface', $connector); + + $ref = new \ReflectionProperty($mysql, 'loop'); + $ref->setAccessible(true); + $loop = $ref->getValue($mysql); + + $this->assertInstanceOf('React\EventLoop\LoopInterface', $loop); + + $ref = new \ReflectionProperty($factory, 'loop'); + $ref->setAccessible(true); + $loop = $ref->getValue($factory); + + $this->assertInstanceOf('React\EventLoop\LoopInterface', $loop); + } + + public function testConstructWithConnectorAndLoopAssignsGivenConnectorAndLoop() + { + $connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock(); + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $mysql = new MysqlClient('localhost', $connector, $loop); + + $ref = new \ReflectionProperty($mysql, 'factory'); + $ref->setAccessible(true); + $factory = $ref->getValue($mysql); + + $ref = new \ReflectionProperty($factory, 'connector'); + $ref->setAccessible(true); + + $this->assertSame($connector, $ref->getValue($factory)); + + $ref = new \ReflectionProperty($mysql, 'loop'); + $ref->setAccessible(true); + + $this->assertSame($loop, $ref->getValue($mysql)); + + $ref = new \ReflectionProperty($factory, 'loop'); + $ref->setAccessible(true); + + $this->assertSame($loop, $ref->getValue($factory)); + } + public function testPingWillNotCloseConnectionWhenPendingConnectionFails() { $deferred = new Deferred(); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn($deferred->promise()); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $connection = new LazyConnection($factory, '', $loop); + + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->on('error', $this->expectCallableNever()); $connection->on('close', $this->expectCallableNever()); @@ -33,27 +93,34 @@ public function testPingWillNotCloseConnectionWhenPendingConnectionFails() public function testPingWillNotCloseConnectionWhenUnderlyingConnectionCloses() { - $base = $this->getMockBuilder('React\MySQL\Io\LazyConnection')->setMethods(['ping'])->disableOriginalConstructor()->getMock(); + $base = $this->getMockBuilder('React\MySQL\Io\Connection')->setMethods(['ping', 'close'])->disableOriginalConstructor()->getMock(); $base->expects($this->once())->method('ping')->willReturn(\React\Promise\resolve(null)); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn(\React\Promise\resolve($base)); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $connection = new LazyConnection($factory, '', $loop); + + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->on('error', $this->expectCallableNever()); $connection->on('close', $this->expectCallableNever()); $connection->ping(); - $base->close(); + + assert($base instanceof Connection); + $base->emit('close'); } public function testPingWillCancelTimerWithoutClosingConnectionWhenUnderlyingConnectionCloses() { - $base = $this->getMockBuilder('React\MySQL\Io\LazyConnection')->setMethods(['ping'])->disableOriginalConstructor()->getMock(); + $base = $this->getMockBuilder('React\MySQL\Io\Connection')->setMethods(['ping', 'close'])->disableOriginalConstructor()->getMock(); $base->expects($this->once())->method('ping')->willReturn(\React\Promise\resolve(null)); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn(\React\Promise\resolve($base)); $timer = $this->getMockBuilder('React\EventLoop\TimerInterface')->getMock(); @@ -61,23 +128,34 @@ public function testPingWillCancelTimerWithoutClosingConnectionWhenUnderlyingCon $loop->expects($this->once())->method('addTimer')->willReturn($timer); $loop->expects($this->once())->method('cancelTimer')->with($timer); - $connection = new LazyConnection($factory, '', $loop); + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->on('close', $this->expectCallableNever()); $connection->ping(); - $base->close(); + + assert($base instanceof Connection); + $base->emit('close'); } public function testPingWillNotForwardErrorFromUnderlyingConnection() { - $base = $this->getMockBuilder('React\MySQL\Io\LazyConnection')->setMethods(['ping'])->disableOriginalConstructor()->getMock(); + $base = $this->getMockBuilder('React\MySQL\Io\Connection')->setMethods(['ping'])->disableOriginalConstructor()->getMock(); $base->expects($this->once())->method('ping')->willReturn(\React\Promise\resolve(null)); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn(\React\Promise\resolve($base)); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $connection = new LazyConnection($factory, '', $loop); + + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->on('error', $this->expectCallableNever()); $connection->on('close', $this->expectCallableNever()); @@ -89,12 +167,12 @@ public function testPingWillNotForwardErrorFromUnderlyingConnection() public function testPingFollowedByIdleTimerWillQuitUnderlyingConnection() { - $base = $this->getMockBuilder('React\MySQL\Io\LazyConnection')->setMethods(['ping', 'quit', 'close'])->disableOriginalConstructor()->getMock(); + $base = $this->getMockBuilder('React\MySQL\Io\Connection')->setMethods(['ping', 'quit', 'close'])->disableOriginalConstructor()->getMock(); $base->expects($this->once())->method('ping')->willReturn(\React\Promise\resolve(null)); $base->expects($this->once())->method('quit')->willReturn(\React\Promise\resolve(null)); $base->expects($this->never())->method('close'); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn(\React\Promise\resolve($base)); $timeout = null; @@ -105,7 +183,11 @@ public function testPingFollowedByIdleTimerWillQuitUnderlyingConnection() return true; }))->willReturn($timer); - $connection = new LazyConnection($factory, '', $loop); + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->on('close', $this->expectCallableNever()); @@ -117,12 +199,12 @@ public function testPingFollowedByIdleTimerWillQuitUnderlyingConnection() public function testPingFollowedByIdleTimerWillCloseUnderlyingConnectionWhenQuitFails() { - $base = $this->getMockBuilder('React\MySQL\Io\LazyConnection')->setMethods(['ping', 'quit', 'close'])->disableOriginalConstructor()->getMock(); + $base = $this->getMockBuilder('React\MySQL\Io\Connection')->setMethods(['ping', 'quit', 'close'])->disableOriginalConstructor()->getMock(); $base->expects($this->once())->method('ping')->willReturn(\React\Promise\resolve(null)); $base->expects($this->once())->method('quit')->willReturn(\React\Promise\reject(new \RuntimeException())); $base->expects($this->once())->method('close'); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn(\React\Promise\resolve($base)); $timeout = null; @@ -133,7 +215,11 @@ public function testPingFollowedByIdleTimerWillCloseUnderlyingConnectionWhenQuit return true; }))->willReturn($timer); - $connection = new LazyConnection($factory, '', $loop); + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->on('close', $this->expectCallableNever()); @@ -145,12 +231,12 @@ public function testPingFollowedByIdleTimerWillCloseUnderlyingConnectionWhenQuit public function testPingAfterIdleTimerWillCloseUnderlyingConnectionBeforeCreatingSecondConnection() { - $base = $this->getMockBuilder('React\MySQL\Io\LazyConnection')->setMethods(['ping', 'quit', 'close'])->disableOriginalConstructor()->getMock(); + $base = $this->getMockBuilder('React\MySQL\Io\Connection')->setMethods(['ping', 'quit', 'close'])->disableOriginalConstructor()->getMock(); $base->expects($this->once())->method('ping')->willReturn(\React\Promise\resolve(null)); $base->expects($this->once())->method('quit')->willReturn(new Promise(function () { })); $base->expects($this->once())->method('close'); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->exactly(2))->method('createConnection')->willReturnOnConsecutiveCalls( \React\Promise\resolve($base), new Promise(function () { }) @@ -164,7 +250,11 @@ public function testPingAfterIdleTimerWillCloseUnderlyingConnectionBeforeCreatin return true; }))->willReturn($timer); - $connection = new LazyConnection($factory, '', $loop); + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->on('close', $this->expectCallableNever()); @@ -180,13 +270,17 @@ public function testPingAfterIdleTimerWillCloseUnderlyingConnectionBeforeCreatin public function testQueryReturnsPendingPromiseAndWillNotStartTimerWhenConnectionIsPending() { $deferred = new Deferred(); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn($deferred->promise()); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $loop->expects($this->never())->method('addTimer'); - $connection = new LazyConnection($factory, '', $loop); + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $ret = $connection->query('SELECT 1'); @@ -199,10 +293,15 @@ public function testQueryWillQueryUnderlyingConnectionWhenResolved() $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); $base->expects($this->once())->method('query')->with('SELECT 1')->willReturn(new Promise(function () { })); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn(\React\Promise\resolve($base)); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $connection = new LazyConnection($factory, '', $loop); + + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->query('SELECT 1'); } @@ -214,13 +313,17 @@ public function testQueryWillResolveAndStartTimerWithDefaultIntervalWhenQueryFro $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); $base->expects($this->once())->method('query')->with('SELECT 1')->willReturn(\React\Promise\resolve($result)); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn(\React\Promise\resolve($base)); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $loop->expects($this->once())->method('addTimer')->with(0.001, $this->anything()); - $connection = new LazyConnection($factory, '', $loop); + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $ret = $connection->query('SELECT 1'); $ret->then($this->expectCallableOnceWith($result), $this->expectCallableNever()); @@ -233,13 +336,17 @@ public function testQueryWillResolveAndStartTimerWithIntervalFromIdleParameterWh $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); $base->expects($this->once())->method('query')->with('SELECT 1')->willReturn(\React\Promise\resolve($result)); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn(\React\Promise\resolve($base)); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $loop->expects($this->once())->method('addTimer')->with(2.5, $this->anything()); - $connection = new LazyConnection($factory, 'mysql://localhost?idle=2.5', $loop); + $connection = new MysqlClient('mysql://localhost?idle=2.5', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $ret = $connection->query('SELECT 1'); $ret->then($this->expectCallableOnceWith($result), $this->expectCallableNever()); @@ -252,13 +359,17 @@ public function testQueryWillResolveWithoutStartingTimerWhenQueryFromUnderlyingC $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); $base->expects($this->once())->method('query')->with('SELECT 1')->willReturn(\React\Promise\resolve($result)); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn(\React\Promise\resolve($base)); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $loop->expects($this->never())->method('addTimer'); - $connection = new LazyConnection($factory, 'mysql://localhost?idle=-1', $loop); + $connection = new MysqlClient('mysql://localhost?idle=-1', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $ret = $connection->query('SELECT 1'); $ret->then($this->expectCallableOnceWith($result), $this->expectCallableNever()); @@ -273,13 +384,17 @@ public function testQueryBeforePingWillResolveWithoutStartingTimerWhenQueryFromU $base->expects($this->once())->method('query')->with('SELECT 1')->willReturn($deferred->promise()); $base->expects($this->once())->method('ping')->willReturn(new Promise(function () { })); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn(\React\Promise\resolve($base)); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $loop->expects($this->never())->method('addTimer'); - $connection = new LazyConnection($factory, '', $loop); + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $ret = $connection->query('SELECT 1'); $connection->ping(); @@ -295,7 +410,7 @@ public function testQueryAfterPingWillCancelTimerAgainWhenPingFromUnderlyingConn $base->expects($this->once())->method('ping')->willReturn(\React\Promise\resolve(null)); $base->expects($this->once())->method('query')->with('SELECT 1')->willReturn(new Promise(function () { })); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn(\React\Promise\resolve($base)); $timer = $this->getMockBuilder('React\EventLoop\TimerInterface')->getMock(); @@ -303,7 +418,11 @@ public function testQueryAfterPingWillCancelTimerAgainWhenPingFromUnderlyingConn $loop->expects($this->once())->method('addTimer')->willReturn($timer); $loop->expects($this->once())->method('cancelTimer')->with($timer); - $connection = new LazyConnection($factory, '', $loop); + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->ping(); $connection->query('SELECT 1'); @@ -316,13 +435,17 @@ public function testQueryWillRejectAndStartTimerWhenQueryFromUnderlyingConnectio $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); $base->expects($this->once())->method('query')->with('SELECT 1')->willReturn(\React\Promise\reject($error)); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn(\React\Promise\resolve($base)); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $loop->expects($this->once())->method('addTimer'); - $connection = new LazyConnection($factory, '', $loop); + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $ret = $connection->query('SELECT 1'); $ret->then($this->expectCallableNever(), $this->expectCallableOnceWith($error)); @@ -331,13 +454,17 @@ public function testQueryWillRejectAndStartTimerWhenQueryFromUnderlyingConnectio public function testQueryWillRejectWithoutStartingTimerWhenUnderlyingConnectionRejects() { $deferred = new Deferred(); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn($deferred->promise()); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $loop->expects($this->never())->method('addTimer'); - $connection = new LazyConnection($factory, '', $loop); + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $ret = $connection->query('SELECT 1'); $ret->then($this->expectCallableNever(), $this->expectCallableOnce()); @@ -348,10 +475,15 @@ public function testQueryWillRejectWithoutStartingTimerWhenUnderlyingConnectionR public function testQueryStreamReturnsReadableStreamWhenConnectionIsPending() { $promise = new Promise(function () { }); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn($promise); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $connection = new LazyConnection($factory, '', $loop); + + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $ret = $connection->queryStream('SELECT 1'); @@ -365,13 +497,17 @@ public function testQueryStreamWillReturnStreamFromUnderlyingConnectionWithoutSt $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); $base->expects($this->once())->method('queryStream')->with('SELECT 1')->willReturn($stream); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn(\React\Promise\resolve($base)); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $loop->expects($this->never())->method('addTimer'); - $connection = new LazyConnection($factory, '', $loop); + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $ret = $connection->queryStream('SELECT 1'); @@ -387,13 +523,17 @@ public function testQueryStreamWillReturnStreamFromUnderlyingConnectionAndStartT $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); $base->expects($this->once())->method('queryStream')->with('SELECT 1')->willReturn($stream); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn(\React\Promise\resolve($base)); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $loop->expects($this->once())->method('addTimer'); - $connection = new LazyConnection($factory, '', $loop); + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $ret = $connection->queryStream('SELECT 1'); @@ -409,10 +549,15 @@ public function testQueryStreamWillReturnStreamFromUnderlyingConnectionAndStartT public function testQueryStreamWillCloseStreamWithErrorWhenUnderlyingConnectionRejects() { $deferred = new Deferred(); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn($deferred->promise()); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $connection = new LazyConnection($factory, '', $loop); + + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $ret = $connection->queryStream('SELECT 1'); @@ -427,10 +572,15 @@ public function testQueryStreamWillCloseStreamWithErrorWhenUnderlyingConnectionR public function testPingReturnsPendingPromiseWhenConnectionIsPending() { $promise = new Promise(function () { }); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn($promise); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $connection = new LazyConnection($factory, '', $loop); + + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $ret = $connection->ping(); @@ -443,10 +593,15 @@ public function testPingWillPingUnderlyingConnectionWhenResolved() $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); $base->expects($this->once())->method('ping')->willReturn(new Promise(function () { })); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn(\React\Promise\resolve($base)); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $connection = new LazyConnection($factory, '', $loop); + + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->ping(); } @@ -456,10 +611,15 @@ public function testPingTwiceWillBothRejectWithSameErrorWhenUnderlyingConnection $error = new \RuntimeException(); $deferred = new Deferred(); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn($deferred->promise()); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $connection = new LazyConnection($factory, '', $loop); + + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->ping()->then($this->expectCallableNever(), $this->expectCallableOnceWith($error)); $connection->ping()->then($this->expectCallableNever(), $this->expectCallableOnceWith($error)); @@ -471,10 +631,15 @@ public function testPingWillTryToCreateNewUnderlyingConnectionAfterPreviousPingF { $error = new \RuntimeException(); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->exactly(2))->method('createConnection')->willReturn(\React\Promise\reject($error)); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $connection = new LazyConnection($factory, '', $loop); + + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->ping()->then($this->expectCallableNever(), $this->expectCallableOnceWith($error)); $connection->ping()->then($this->expectCallableNever(), $this->expectCallableOnceWith($error)); @@ -485,13 +650,17 @@ public function testPingWillResolveAndStartTimerWhenPingFromUnderlyingConnection $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); $base->expects($this->once())->method('ping')->willReturn(\React\Promise\resolve(null)); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn(\React\Promise\resolve($base)); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $loop->expects($this->once())->method('addTimer'); - $connection = new LazyConnection($factory, '', $loop); + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $ret = $connection->ping(); $ret->then($this->expectCallableOnce(), $this->expectCallableNever()); @@ -504,13 +673,17 @@ public function testPingWillRejectAndStartTimerWhenPingFromUnderlyingConnectionR $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); $base->expects($this->once())->method('ping')->willReturn(\React\Promise\reject($error)); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn(\React\Promise\resolve($base)); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $loop->expects($this->once())->method('addTimer'); - $connection = new LazyConnection($factory, '', $loop); + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $ret = $connection->ping(); $ret->then($this->expectCallableNever(), $this->expectCallableOnceWith($error)); @@ -520,20 +693,24 @@ public function testPingWillRejectAndNotStartIdleTimerWhenPingFromUnderlyingConn { $error = new \RuntimeException(); - $base = $this->getMockBuilder('React\MySQL\Io\LazyConnection')->setMethods(['ping', 'close'])->disableOriginalConstructor()->getMock(); + $base = $this->getMockBuilder('React\MySQL\Io\Connection')->setMethods(['ping', 'close'])->disableOriginalConstructor()->getMock(); $base->expects($this->once())->method('ping')->willReturnCallback(function () use ($base, $error) { $base->emit('close'); return \React\Promise\reject($error); }); $base->expects($this->never())->method('close'); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn(\React\Promise\resolve($base)); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $loop->expects($this->never())->method('addTimer'); - $connection = new LazyConnection($factory, '', $loop); + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $ret = $connection->ping(); $ret->then($this->expectCallableNever(), $this->expectCallableOnceWith($error)); @@ -541,10 +718,15 @@ public function testPingWillRejectAndNotStartIdleTimerWhenPingFromUnderlyingConn public function testQuitResolvesAndEmitsCloseImmediatelyWhenConnectionIsNotAlreadyPending() { - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->never())->method('createConnection'); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $connection = new LazyConnection($factory, '', $loop); + + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->on('error', $this->expectCallableNever()); $connection->on('close', $this->expectCallableOnce()); @@ -558,10 +740,15 @@ public function testQuitResolvesAndEmitsCloseImmediatelyWhenConnectionIsNotAlrea public function testQuitAfterPingReturnsPendingPromiseWhenConnectionIsPending() { $promise = new Promise(function () { }); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn($promise); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $connection = new LazyConnection($factory, '', $loop); + + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->ping(); $ret = $connection->quit(); @@ -576,10 +763,15 @@ public function testQuitAfterPingWillQuitUnderlyingConnectionWhenResolved() $base->expects($this->once())->method('ping')->willReturn(\React\Promise\resolve(null)); $base->expects($this->once())->method('quit')->willReturn(new Promise(function () { })); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn(\React\Promise\resolve($base)); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $connection = new LazyConnection($factory, '', $loop); + + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->ping(); $connection->quit(); @@ -591,10 +783,15 @@ public function testQuitAfterPingResolvesAndEmitsCloseWhenUnderlyingConnectionQu $base->expects($this->once())->method('ping')->willReturn(\React\Promise\resolve(null)); $base->expects($this->once())->method('quit')->willReturn(\React\Promise\resolve(null)); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn(\React\Promise\resolve($base)); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $connection = new LazyConnection($factory, '', $loop); + + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->on('close', $this->expectCallableOnce()); @@ -612,10 +809,15 @@ public function testQuitAfterPingRejectsAndEmitsCloseWhenUnderlyingConnectionFai $base->expects($this->once())->method('ping')->willReturn(\React\Promise\resolve(null)); $base->expects($this->once())->method('quit')->willReturn(\React\Promise\reject($error)); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn(\React\Promise\resolve($base)); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $connection = new LazyConnection($factory, '', $loop); + + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->on('close', $this->expectCallableOnce()); @@ -628,10 +830,15 @@ public function testQuitAfterPingRejectsAndEmitsCloseWhenUnderlyingConnectionFai public function testCloseEmitsCloseImmediatelyWhenConnectionIsNotAlreadyPending() { - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->never())->method('createConnection'); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $connection = new LazyConnection($factory, '', $loop); + + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->on('error', $this->expectCallableNever()); $connection->on('close', $this->expectCallableOnce()); @@ -642,10 +849,15 @@ public function testCloseEmitsCloseImmediatelyWhenConnectionIsNotAlreadyPending( public function testCloseAfterPingCancelsPendingConnection() { $deferred = new Deferred($this->expectCallableOnce()); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn($deferred->promise()); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $connection = new LazyConnection($factory, '', $loop); + + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->ping(); $connection->close(); @@ -657,10 +869,15 @@ public function testCloseTwiceAfterPingWillCloseUnderlyingConnectionWhenResolved $base->expects($this->once())->method('ping')->willReturn(\React\Promise\resolve(null)); $base->expects($this->once())->method('close'); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn(\React\Promise\resolve($base)); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $connection = new LazyConnection($factory, '', $loop); + + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->ping(); $connection->close(); @@ -673,10 +890,15 @@ public function testCloseAfterPingDoesNotEmitConnectionErrorFromAbortedConnectio throw new \RuntimeException(); }); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn($promise); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $connection = new LazyConnection($factory, '', $loop); + + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->on('error', $this->expectCallableNever()); $connection->on('close', $this->expectCallableOnce()); @@ -693,7 +915,7 @@ public function testCloseAfterPingWillCancelTimerWhenPingFromUnderlyingConnectio $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); $base->expects($this->once())->method('ping')->willReturn(\React\Promise\resolve(null)); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn(\React\Promise\resolve($base)); $timer = $this->getMockBuilder('React\EventLoop\TimerInterface')->getMock(); @@ -701,7 +923,11 @@ public function testCloseAfterPingWillCancelTimerWhenPingFromUnderlyingConnectio $loop->expects($this->once())->method('addTimer')->willReturn($timer); $loop->expects($this->once())->method('cancelTimer')->with($timer); - $connection = new LazyConnection($factory, '', $loop); + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->ping()->then($this->expectCallableOnce(), $this->expectCallableNever()); $connection->close(); @@ -709,18 +935,22 @@ public function testCloseAfterPingWillCancelTimerWhenPingFromUnderlyingConnectio public function testCloseAfterPingHasResolvedWillCloseUnderlyingConnectionWithoutTryingToCancelConnection() { - $base = $this->getMockBuilder('React\MySQL\Io\LazyConnection')->setMethods(['ping', 'close'])->disableOriginalConstructor()->getMock(); + $base = $this->getMockBuilder('React\MySQL\Io\Connection')->setMethods(['ping', 'close'])->disableOriginalConstructor()->getMock(); $base->expects($this->once())->method('ping')->willReturn(\React\Promise\resolve(null)); $base->expects($this->once())->method('close')->willReturnCallback(function () use ($base) { $base->emit('close'); }); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn(\React\Promise\resolve($base)); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $connection = new LazyConnection($factory, '', $loop); + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->ping(); $connection->close(); @@ -733,10 +963,15 @@ public function testCloseAfterQuitAfterPingWillCloseUnderlyingConnectionWhenQuit $base->expects($this->once())->method('quit')->willReturn(new Promise(function () { })); $base->expects($this->once())->method('close'); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn(\React\Promise\resolve($base)); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $connection = new LazyConnection($factory, '', $loop); + + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->ping(); $connection->quit(); @@ -750,7 +985,7 @@ public function testCloseAfterPingAfterIdleTimeoutWillCloseUnderlyingConnectionW $base->expects($this->once())->method('quit')->willReturn(new Promise(function () { })); $base->expects($this->once())->method('close'); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn(\React\Promise\resolve($base)); $timeout = null; @@ -761,7 +996,11 @@ public function testCloseAfterPingAfterIdleTimeoutWillCloseUnderlyingConnectionW return true; }))->willReturn($timer); - $connection = new LazyConnection($factory, '', $loop); + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->ping(); @@ -774,10 +1013,15 @@ public function testCloseAfterPingAfterIdleTimeoutWillCloseUnderlyingConnectionW public function testCloseTwiceAfterPingEmitsCloseEventOnceWhenConnectionIsPending() { $promise = new Promise(function () { }); - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->once())->method('createConnection')->willReturn($promise); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $connection = new LazyConnection($factory, '', $loop); + + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->on('error', $this->expectCallableNever()); $connection->on('close', $this->expectCallableOnce()); @@ -789,10 +1033,15 @@ public function testCloseTwiceAfterPingEmitsCloseEventOnceWhenConnectionIsPendin public function testQueryReturnsRejectedPromiseAfterConnectionIsClosed() { - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->never())->method('createConnection'); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $connection = new LazyConnection($factory, '', $loop); + + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->close(); $ret = $connection->query('SELECT 1'); @@ -803,10 +1052,15 @@ public function testQueryReturnsRejectedPromiseAfterConnectionIsClosed() public function testQueryStreamThrowsAfterConnectionIsClosed() { - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->never())->method('createConnection'); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $connection = new LazyConnection($factory, '', $loop); + + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->close(); @@ -816,10 +1070,15 @@ public function testQueryStreamThrowsAfterConnectionIsClosed() public function testPingReturnsRejectedPromiseAfterConnectionIsClosed() { - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->never())->method('createConnection'); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $connection = new LazyConnection($factory, '', $loop); + + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->close(); $ret = $connection->ping(); @@ -830,10 +1089,15 @@ public function testPingReturnsRejectedPromiseAfterConnectionIsClosed() public function testQuitReturnsRejectedPromiseAfterConnectionIsClosed() { - $factory = $this->getMockBuilder('React\MySQL\Factory')->disableOriginalConstructor()->getMock(); + $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); $factory->expects($this->never())->method('createConnection'); $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $connection = new LazyConnection($factory, '', $loop); + + $connection = new MysqlClient('', null, $loop); + + $ref = new \ReflectionProperty($connection, 'factory'); + $ref->setAccessible(true); + $ref->setValue($connection, $factory); $connection->close(); $ret = $connection->quit(); diff --git a/tests/NoResultQueryTest.php b/tests/NoResultQueryTest.php index 1f48380..75c8f98 100644 --- a/tests/NoResultQueryTest.php +++ b/tests/NoResultQueryTest.php @@ -3,6 +3,7 @@ namespace React\Tests\MySQL; use React\EventLoop\Loop; +use React\MySQL\MysqlClient; use React\MySQL\QueryResult; class NoResultQueryTest extends BaseTestCase @@ -102,4 +103,83 @@ public function testPingMultipleWillBeExecutedInSameOrderTheyAreEnqueuedFromHand Loop::run(); } + + + public function testQuitWithAnyAuthWillQuitWithoutRunning() + { + $this->expectOutputString('closed.'); + + $uri = 'mysql://random:pass@host'; + $connection = new MysqlClient($uri); + + $connection->quit()->then(function () { + echo 'closed.'; + }); + } + + public function testPingWithValidAuthWillRunUntilQuitAfterPing() + { + $this->expectOutputString('closed.'); + + $uri = $this->getConnectionString(); + $connection = new MysqlClient($uri); + + $connection->ping(); + + $connection->quit()->then(function () { + echo 'closed.'; + }); + + Loop::run(); + } + + /** + * @doesNotPerformAssertions + */ + public function testPingWithValidAuthWillRunUntilIdleTimerAfterPingEvenWithoutQuit() + { + $uri = $this->getConnectionString(); + $connection = new MysqlClient($uri); + + $connection->ping(); + + Loop::run(); + } + + public function testPingWithInvalidAuthWillRejectPingButWillNotEmitErrorOrClose() + { + $uri = $this->getConnectionString(['passwd' => 'invalidpass']); + $connection = new MysqlClient($uri); + + $connection->on('error', $this->expectCallableNever()); + $connection->on('close', $this->expectCallableNever()); + + $connection->ping()->then(null, $this->expectCallableOnce()); + + Loop::run(); + } + + public function testPingWithValidAuthWillPingBeforeQuitButNotAfter() + { + $this->expectOutputString('rejected.ping.closed.'); + + $uri = $this->getConnectionString(); + $connection = new MysqlClient($uri); + + $connection->ping()->then(function () { + echo 'ping.'; + }); + + $connection->quit()->then(function () { + echo 'closed.'; + }); + + $connection->ping()->then(function () { + echo 'never reached'; + }, function () { + echo 'rejected.'; + }); + + Loop::run(); + } } diff --git a/tests/ResultQueryTest.php b/tests/ResultQueryTest.php index e38fe71..768a9d5 100644 --- a/tests/ResultQueryTest.php +++ b/tests/ResultQueryTest.php @@ -4,8 +4,8 @@ use React\EventLoop\Loop; use React\MySQL\Io\Constants; +use React\MySQL\MysqlClient; use React\MySQL\QueryResult; -use React\MySQL\Factory; class ResultQueryTest extends BaseTestCase { @@ -368,10 +368,8 @@ public function testSelectCharsetDefaultsToUtf8() public function testSelectWithExplicitCharsetReturnsCharset() { - $factory = new Factory(); - $uri = $this->getConnectionString() . '?charset=latin1'; - $connection = $factory->createLazyConnection($uri); + $connection = new MysqlClient($uri); $connection->query('SELECT @@character_set_client')->then(function (QueryResult $command) { $this->assertCount(1, $command->resultRows); @@ -404,12 +402,10 @@ public function testSimpleSelect() /** * @depends testSimpleSelect */ - public function testSimpleSelectFromLazyConnectionWithoutDatabaseNameReturnsSameData() + public function testSimpleSelectFromMysqlClientWithoutDatabaseNameReturnsSameData() { - $factory = new Factory(); - $uri = $this->getConnectionString(['dbname' => '']); - $connection = $factory->createLazyConnection($uri); + $connection = new MysqlClient($uri); $connection->query('select * from test.book')->then(function (QueryResult $command) { $this->assertCount(2, $command->resultRows); @@ -560,12 +556,10 @@ public function testQueryStreamExplicitCloseEmitsCloseEventWithoutData() Loop::run(); } - public function testQueryStreamFromLazyConnectionEmitsSingleRow() + public function testQueryStreamFromMysqlClientEmitsSingleRow() { - $factory = new Factory(); - $uri = $this->getConnectionString(); - $connection = $factory->createLazyConnection($uri); + $connection = new MysqlClient($uri); $stream = $connection->queryStream('SELECT 1'); @@ -577,12 +571,10 @@ public function testQueryStreamFromLazyConnectionEmitsSingleRow() Loop::run(); } - public function testQueryStreamFromLazyConnectionWillErrorWhenConnectionIsClosed() + public function testQueryStreamFromMysqlClientWillErrorWhenConnectionIsClosed() { - $factory = new Factory(); - $uri = $this->getConnectionString(); - $connection = $factory->createLazyConnection($uri); + $connection = new MysqlClient($uri); $stream = $connection->queryStream('SELECT 1'); From c4dc4153e3d7945f9c094bb03adb6070bffb7d58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Tue, 14 Nov 2023 15:26:12 +0100 Subject: [PATCH 2/2] Simplify API, remove `ConnectionInterface` --- README.md | 71 +++++------- src/ConnectionInterface.php | 225 ------------------------------------ src/Io/Connection.php | 5 +- src/Io/Factory.php | 10 +- src/MysqlClient.php | 224 +++++++++++++++++++++++++++++++++-- tests/BaseTestCase.php | 4 +- tests/Io/FactoryTest.php | 18 +-- tests/MysqlClientTest.php | 38 +++--- 8 files changed, 282 insertions(+), 313 deletions(-) delete mode 100644 src/ConnectionInterface.php diff --git a/README.md b/README.md index 7c77dec..90a6a7b 100644 --- a/README.md +++ b/README.md @@ -24,13 +24,13 @@ It is written in pure PHP and does not require any extensions. * [Usage](#usage) * [MysqlClient](#mysqlclient) * [__construct()](#__construct) - * [ConnectionInterface](#connectioninterface) * [query()](#query) * [queryStream()](#querystream) * [ping()](#ping) * [quit()](#quit) * [close()](#close) - * [Events](#events) + * [error event](#error-event) + * [close event](#close-event) * [Install](#install) * [Tests](#tests) * [License](#license) @@ -68,21 +68,21 @@ The `MysqlClient` is responsible for exchanging messages with your MySQL server and keeps track of pending queries. ```php -$connection = new React\MySQL\MysqlClient($uri); +$mysql = new React\MySQL\MysqlClient($uri); -$connection->query(…); +$mysql->query(…); ``` -This method immediately returns a "virtual" connection implementing the -[`ConnectionInterface`](#connectioninterface) that can be used to -interface with your MySQL database. Internally, it lazily creates the -underlying database connection only on demand once the first request is -invoked on this instance and will queue all outstanding requests until -the underlying connection is ready. This underlying connection will be -reused for all requests until it is closed. By default, idle connections -will be held open for 1ms (0.001s) when not used. The next request will -either reuse the existing connection or will automatically create a new -underlying connection if this idle time is expired. +This class represents a connection that is responsible for communicating +with your MySQL server instance, managing the connection state and sending +your database queries. Internally, it creates the underlying database +connection only on demand once the first request is invoked on this +instance and will queue all outstanding requests until the underlying +connection is ready. This underlying connection will be reused for all +requests until it is closed. By default, idle connections will be held +open for 1ms (0.001s) when not used. The next request will either reuse +the existing connection or will automatically create a new underlying +connection if this idle time is expired. From a consumer side this means that you can start sending queries to the database right away while the underlying connection may still be @@ -100,7 +100,7 @@ longer than the idle period. Note that creating the underlying connection will be deferred until the first request is invoked. Accordingly, any eventual connection issues will be detected once this instance is first used. You can use the -`quit()` method to ensure that the "virtual" connection will be soft-closed +`quit()` method to ensure that the connection will be soft-closed and no further commands can be enqueued. Similarly, calling `quit()` on this instance when not currently connected will succeed immediately and will not have to wait for an actual underlying connection. @@ -200,12 +200,6 @@ here in order to use the [default loop](https://github.com/reactphp/event-loop#l This value SHOULD NOT be given unless you're sure you want to explicitly use a given event loop instance. -### ConnectionInterface - -The `ConnectionInterface` represents a connection that is responsible for -communicating with your MySQL server instance, managing the connection state -and sending your database queries. - #### query() The `query(string $query, array $params = []): PromiseInterface` method can be used to @@ -218,8 +212,8 @@ and outstanding queries will be put into a queue to be executed once the previous queries are completed. ```php -$connection->query('CREATE TABLE test ...'); -$connection->query('INSERT INTO test (id) VALUES (1)'); +$mysql->query('CREATE TABLE test ...'); +$mysql->query('INSERT INTO test (id) VALUES (1)'); ``` If this SQL statement returns a result set (such as from a `SELECT` @@ -231,7 +225,7 @@ unknown or known to be too large to fit into memory, you should use the [`queryStream()`](#querystream) method instead. ```php -$connection->query($query)->then(function (QueryResult $command) { +$mysql->query($query)->then(function (QueryResult $command) { if (isset($command->resultRows)) { // this is a response to a SELECT etc. with some rows (0+) print_r($command->resultFields); @@ -254,7 +248,7 @@ You can optionally pass an array of `$params` that will be bound to the query like this: ```php -$connection->query('SELECT * FROM user WHERE id > ?', [$id]); +$mysql->query('SELECT * FROM user WHERE id > ?', [$id]); ``` The given `$sql` parameter MUST contain a single statement. Support @@ -275,7 +269,7 @@ into memory. If you know your result set to not exceed a few dozens or hundreds of rows, you may want to use the [`query()`](#query) method instead. ```php -$stream = $connection->queryStream('SELECT * FROM user'); +$stream = $mysql->queryStream('SELECT * FROM user'); $stream->on('data', function ($row) { echo $row['name'] . PHP_EOL; }); @@ -288,7 +282,7 @@ You can optionally pass an array of `$params` that will be bound to the query like this: ```php -$stream = $connection->queryStream('SELECT * FROM user WHERE id > ?', [$id]); +$stream = $mysql->queryStream('SELECT * FROM user WHERE id > ?', [$id]); ``` This method is specifically designed for queries that return a result set @@ -303,7 +297,7 @@ rows to a [`WritableStreamInterface`](https://github.com/reactphp/stream#writabl like this: ```php -$connection->queryStream('SELECT * FROM user')->pipe($formatter)->pipe($logger); +$mysql->queryStream('SELECT * FROM user')->pipe($formatter)->pipe($logger); ``` Note that as per the underlying stream definition, calling `pause()` and @@ -331,7 +325,7 @@ and outstanding command will be put into a queue to be executed once the previous queries are completed. ```php -$connection->ping()->then(function () { +$mysql->ping()->then(function () { echo 'OK' . PHP_EOL; }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; @@ -350,8 +344,8 @@ and outstanding commands will be put into a queue to be executed once the previous commands are completed. ```php -$connection->query('CREATE TABLE test ...'); -$connection->quit(); +$mysql->query('CREATE TABLE test ...'); +$mysql->quit(); ``` #### close() @@ -363,26 +357,21 @@ Unlike the `quit()` method, this method will immediately force-close the connection and reject all outstanding commands. ```php -$connection->close(); +$mysql->close(); ``` Forcefully closing the connection will yield a warning in the server logs and should generally only be used as a last resort. See also [`quit()`](#quit) as a safe alternative. -#### Events - -Besides defining a few methods, this interface also implements the -`EventEmitterInterface` which allows you to react to certain events: - -##### error event +#### error event The `error` event will be emitted once a fatal error occurs, such as when the connection is lost or is invalid. The event receives a single `Exception` argument for the error instance. ```php -$connection->on('error', function (Exception $e) { +$mysql->on('error', function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); ``` @@ -391,12 +380,12 @@ This event will only be triggered for fatal errors and will be followed by closing the connection. It is not to be confused with "soft" errors caused by invalid SQL queries. -##### close event +#### close event The `close` event will be emitted once the connection closes (terminates). ```php -$connection->on('close', function () { +$mysql->on('close', function () { echo 'Connection closed' . PHP_EOL; }); ``` diff --git a/src/ConnectionInterface.php b/src/ConnectionInterface.php deleted file mode 100644 index e379dbc..0000000 --- a/src/ConnectionInterface.php +++ /dev/null @@ -1,225 +0,0 @@ -on('error', function (Exception $e) { - * echo 'Error: ' . $e->getMessage() . PHP_EOL; - * }); - * ``` - * - * This event will only be triggered for fatal errors and will be followed - * by closing the connection. It is not to be confused with "soft" errors - * caused by invalid SQL queries. - * - * close event: - * The `close` event will be emitted once the connection closes (terminates). - * - * ```php - * $connection->on('close', function () { - * echo 'Connection closed' . PHP_EOL; - * }); - * ``` - * - * See also the [`close()`](#close) method. - */ -interface ConnectionInterface extends EventEmitterInterface -{ - /** - * Performs an async query. - * - * This method returns a promise that will resolve with a `QueryResult` on - * success or will reject with an `Exception` on error. The MySQL protocol - * is inherently sequential, so that all queries will be performed in order - * and outstanding queries will be put into a queue to be executed once the - * previous queries are completed. - * - * ```php - * $connection->query('CREATE TABLE test ...'); - * $connection->query('INSERT INTO test (id) VALUES (1)'); - * ``` - * - * If this SQL statement returns a result set (such as from a `SELECT` - * statement), this method will buffer everything in memory until the result - * set is completed and will then resolve the resulting promise. This is - * the preferred method if you know your result set to not exceed a few - * dozens or hundreds of rows. If the size of your result set is either - * unknown or known to be too large to fit into memory, you should use the - * [`queryStream()`](#querystream) method instead. - * - * ```php - * $connection->query($query)->then(function (QueryResult $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; - * }); - * ``` - * - * You can optionally pass an array of `$params` that will be bound to the - * query like this: - * - * ```php - * $connection->query('SELECT * FROM user WHERE id > ?', [$id]); - * ``` - * - * The given `$sql` parameter MUST contain a single statement. Support - * for multiple statements is disabled for security reasons because it - * could allow for possible SQL injection attacks and this API is not - * suited for exposing multiple possible results. - * - * @param string $sql SQL statement - * @param array $params Parameters which should be bound to query - * @return PromiseInterface - * Resolves with a `QueryResult` on success or rejects with an `Exception` on error. - */ - public function query($sql, array $params = []); - - /** - * Performs an async query and streams the rows of the result set. - * - * This method returns a readable stream that will emit each row of the - * result set as a `data` event. It will only buffer data to complete a - * single row in memory and will not store the whole result set. This allows - * you to process result sets of unlimited size that would not otherwise fit - * into memory. If you know your result set to not exceed a few dozens or - * hundreds of rows, you may want to use the [`query()`](#query) method instead. - * - * ```php - * $stream = $connection->queryStream('SELECT * FROM user'); - * $stream->on('data', function ($row) { - * echo $row['name'] . PHP_EOL; - * }); - * $stream->on('end', function () { - * echo 'Completed.'; - * }); - * ``` - * - * You can optionally pass an array of `$params` that will be bound to the - * query like this: - * - * ```php - * $stream = $connection->queryStream('SELECT * FROM user WHERE id > ?', [$id]); - * ``` - * - * This method is specifically designed for queries that return a result set - * (such as from a `SELECT` or `EXPLAIN` statement). Queries that do not - * return a result set (such as a `UPDATE` or `INSERT` statement) will not - * emit any `data` events. - * - * See also [`ReadableStreamInterface`](https://github.com/reactphp/stream#readablestreaminterface) - * for more details about how readable streams can be used in ReactPHP. For - * example, you can also use its `pipe()` method to forward the result set - * rows to a [`WritableStreamInterface`](https://github.com/reactphp/stream#writablestreaminterface) - * like this: - * - * ```php - * $connection->queryStream('SELECT * FROM user')->pipe($formatter)->pipe($logger); - * ``` - * - * Note that as per the underlying stream definition, calling `pause()` and - * `resume()` on this stream is advisory-only, i.e. the stream MAY continue - * emitting some data until the underlying network buffer is drained. Also - * notice that the server side limits how long a connection is allowed to be - * in a state that has outgoing data. Special care should be taken to ensure - * the stream is resumed in time. This implies that using `pipe()` with a - * slow destination stream may cause the connection to abort after a while. - * - * The given `$sql` parameter MUST contain a single statement. Support - * for multiple statements is disabled for security reasons because it - * could allow for possible SQL injection attacks and this API is not - * suited for exposing multiple possible results. - * - * @param string $sql SQL statement - * @param array $params Parameters which should be bound to query - * @return ReadableStreamInterface - */ - public function queryStream($sql, $params = []); - - /** - * Checks that the connection is alive. - * - * This method returns a promise that will resolve (with a void value) on - * success or will reject with an `Exception` on error. The MySQL protocol - * is inherently sequential, so that all commands will be performed in order - * and outstanding command will be put into a queue to be executed once the - * previous queries are completed. - * - * ```php - * $connection->ping()->then(function () { - * echo 'OK' . PHP_EOL; - * }, function (Exception $e) { - * echo 'Error: ' . $e->getMessage() . PHP_EOL; - * }); - * ``` - * - * @return PromiseInterface - * Resolves with a `void` value on success or rejects with an `Exception` on error. - */ - public function ping(); - - /** - * Quits (soft-close) the connection. - * - * This method returns a promise that will resolve (with a void value) on - * success or will reject with an `Exception` on error. The MySQL protocol - * is inherently sequential, so that all commands will be performed in order - * and outstanding commands will be put into a queue to be executed once the - * previous commands are completed. - * - * ```php - * $connection->query('CREATE TABLE test ...'); - * $connection->quit(); - * ``` - * - * @return PromiseInterface - * Resolves with a `void` value on success or rejects with an `Exception` on error. - */ - public function quit(); - - /** - * Force-close the connection. - * - * Unlike the `quit()` method, this method will immediately force-close the - * connection and reject all outstanding commands. - * - * ```php - * $connection->close(); - * ``` - * - * Forcefully closing the connection will yield a warning in the server logs - * and should generally only be used as a last resort. See also - * [`quit()`](#quit) as a safe alternative. - * - * @return void - */ - public function close(); -} diff --git a/src/Io/Connection.php b/src/Io/Connection.php index 73313ba..fc71c6c 100644 --- a/src/Io/Connection.php +++ b/src/Io/Connection.php @@ -7,7 +7,6 @@ use React\MySQL\Commands\PingCommand; use React\MySQL\Commands\QueryCommand; use React\MySQL\Commands\QuitCommand; -use React\MySQL\ConnectionInterface; use React\MySQL\Exception; use React\MySQL\QueryResult; use React\Promise\Deferred; @@ -16,9 +15,9 @@ /** * @internal - * @see ConnectionInterface + * @see \React\MySQL\MysqlClient */ -class Connection extends EventEmitter implements ConnectionInterface +class Connection extends EventEmitter { const STATE_AUTHENTICATED = 5; const STATE_CLOSING = 6; diff --git a/src/Io/Factory.php b/src/Io/Factory.php index 9aa27d1..f50eb5a 100644 --- a/src/Io/Factory.php +++ b/src/Io/Factory.php @@ -26,7 +26,7 @@ class Factory private $connector; /** - * The `Factory` is responsible for creating your [`ConnectionInterface`](#connectioninterface) instance. + * The `Factory` is responsible for creating an internal `Connection` instance. * * ```php * $factory = new React\MySQL\Io\Factory(); @@ -74,7 +74,7 @@ public function __construct(LoopInterface $loop = null, ConnectorInterface $conn * * ```php * $factory->createConnection($url)->then( - * function (ConnectionInterface $connection) { + * function (Connection $connection) { * // client connection established (and authenticated) * }, * function (Exception $e) { @@ -84,7 +84,7 @@ public function __construct(LoopInterface $loop = null, ConnectorInterface $conn * ``` * * The method returns a [Promise](https://github.com/reactphp/promise) that - * will resolve with a [`ConnectionInterface`](#connectioninterface) + * will resolve with an internal `Connection` * instance on success or will reject with an `Exception` if the URL is * invalid or the connection or authentication fails. * @@ -154,8 +154,8 @@ public function __construct(LoopInterface $loop = null, ConnectorInterface $conn * ``` * * @param string $uri - * @return PromiseInterface - * Resolves with a `ConnectionInterface` on success or rejects with an `Exception` on error. + * @return PromiseInterface + * Resolves with a `Connection` on success or rejects with an `Exception` on error. */ public function createConnection( #[\SensitiveParameter] diff --git a/src/MysqlClient.php b/src/MysqlClient.php index a88892c..c96d300 100644 --- a/src/MysqlClient.php +++ b/src/MysqlClient.php @@ -5,13 +5,48 @@ use Evenement\EventEmitter; use React\EventLoop\Loop; use React\EventLoop\LoopInterface; +use React\MySQL\Io\Connection; use React\MySQL\Io\Factory; +use React\Stream\ReadableStreamInterface; use React\Socket\ConnectorInterface; /** + * This class represents a connection that is responsible for communicating + * with your MySQL server instance, managing the connection state and sending + * your database queries. + * + * Besides defining a few methods, this class also implements the + * `EventEmitterInterface` which allows you to react to certain events: + * + * error event: + * The `error` event will be emitted once a fatal error occurs, such as + * when the connection is lost or is invalid. + * The event receives a single `Exception` argument for the error instance. + * + * ```php + * $mysql->on('error', function (Exception $e) { + * echo 'Error: ' . $e->getMessage() . PHP_EOL; + * }); + * ``` + * + * This event will only be triggered for fatal errors and will be followed + * by closing the connection. It is not to be confused with "soft" errors + * caused by invalid SQL queries. + * + * close event: + * The `close` event will be emitted once the connection closes (terminates). + * + * ```php + * $mysql->on('close', function () { + * echo 'Connection closed' . PHP_EOL; + * }); + * ``` + * + * See also the [`close()`](#close) method. + * * @final */ -class MysqlClient extends EventEmitter implements ConnectionInterface +class MysqlClient extends EventEmitter { private $factory; private $uri; @@ -20,7 +55,7 @@ class MysqlClient extends EventEmitter implements ConnectionInterface private $busy = false; /** - * @var ConnectionInterface|null + * @var Connection|null */ private $disconnecting; @@ -59,7 +94,7 @@ private function connecting() } $this->connecting = $connecting = $this->factory->createConnection($this->uri); - $this->connecting->then(function (ConnectionInterface $connection) { + $this->connecting->then(function (Connection $connection) { // connection completed => remember only until closed $connection->on('close', function () { $this->connecting = null; @@ -93,7 +128,7 @@ private function idle() if ($this->pending < 1 && $this->idlePeriod >= 0 && $this->connecting !== null) { $this->idleTimer = $this->loop->addTimer($this->idlePeriod, function () { - $this->connecting->then(function (ConnectionInterface $connection) { + $this->connecting->then(function (Connection $connection) { $this->disconnecting = $connection; $connection->quit()->then( function () { @@ -113,13 +148,72 @@ function () use ($connection) { } } + /** + * Performs an async query. + * + * This method returns a promise that will resolve with a `QueryResult` on + * success or will reject with an `Exception` on error. The MySQL protocol + * is inherently sequential, so that all queries will be performed in order + * and outstanding queries will be put into a queue to be executed once the + * previous queries are completed. + * + * ```php + * $mysql->query('CREATE TABLE test ...'); + * $mysql->query('INSERT INTO test (id) VALUES (1)'); + * ``` + * + * If this SQL statement returns a result set (such as from a `SELECT` + * statement), this method will buffer everything in memory until the result + * set is completed and will then resolve the resulting promise. This is + * the preferred method if you know your result set to not exceed a few + * dozens or hundreds of rows. If the size of your result set is either + * unknown or known to be too large to fit into memory, you should use the + * [`queryStream()`](#querystream) method instead. + * + * ```php + * $mysql->query($query)->then(function (QueryResult $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; + * }); + * ``` + * + * You can optionally pass an array of `$params` that will be bound to the + * query like this: + * + * ```php + * $mysql->query('SELECT * FROM user WHERE id > ?', [$id]); + * ``` + * + * The given `$sql` parameter MUST contain a single statement. Support + * for multiple statements is disabled for security reasons because it + * could allow for possible SQL injection attacks and this API is not + * suited for exposing multiple possible results. + * + * @param string $sql SQL statement + * @param array $params Parameters which should be bound to query + * @return PromiseInterface + * Resolves with a `QueryResult` on success or rejects with an `Exception` on error. + */ public function query($sql, array $params = []) { if ($this->closed) { return \React\Promise\reject(new Exception('Connection closed')); } - return $this->connecting()->then(function (ConnectionInterface $connection) use ($sql, $params) { + return $this->connecting()->then(function (Connection $connection) use ($sql, $params) { $this->awake(); return $connection->query($sql, $params)->then( function (QueryResult $result) { @@ -134,6 +228,65 @@ function (\Exception $e) { }); } + /** + * Performs an async query and streams the rows of the result set. + * + * This method returns a readable stream that will emit each row of the + * result set as a `data` event. It will only buffer data to complete a + * single row in memory and will not store the whole result set. This allows + * you to process result sets of unlimited size that would not otherwise fit + * into memory. If you know your result set to not exceed a few dozens or + * hundreds of rows, you may want to use the [`query()`](#query) method instead. + * + * ```php + * $stream = $mysql->queryStream('SELECT * FROM user'); + * $stream->on('data', function ($row) { + * echo $row['name'] . PHP_EOL; + * }); + * $stream->on('end', function () { + * echo 'Completed.'; + * }); + * ``` + * + * You can optionally pass an array of `$params` that will be bound to the + * query like this: + * + * ```php + * $stream = $mysql->queryStream('SELECT * FROM user WHERE id > ?', [$id]); + * ``` + * + * This method is specifically designed for queries that return a result set + * (such as from a `SELECT` or `EXPLAIN` statement). Queries that do not + * return a result set (such as a `UPDATE` or `INSERT` statement) will not + * emit any `data` events. + * + * See also [`ReadableStreamInterface`](https://github.com/reactphp/stream#readablestreaminterface) + * for more details about how readable streams can be used in ReactPHP. For + * example, you can also use its `pipe()` method to forward the result set + * rows to a [`WritableStreamInterface`](https://github.com/reactphp/stream#writablestreaminterface) + * like this: + * + * ```php + * $mysql->queryStream('SELECT * FROM user')->pipe($formatter)->pipe($logger); + * ``` + * + * Note that as per the underlying stream definition, calling `pause()` and + * `resume()` on this stream is advisory-only, i.e. the stream MAY continue + * emitting some data until the underlying network buffer is drained. Also + * notice that the server side limits how long a connection is allowed to be + * in a state that has outgoing data. Special care should be taken to ensure + * the stream is resumed in time. This implies that using `pipe()` with a + * slow destination stream may cause the connection to abort after a while. + * + * The given `$sql` parameter MUST contain a single statement. Support + * for multiple statements is disabled for security reasons because it + * could allow for possible SQL injection attacks and this API is not + * suited for exposing multiple possible results. + * + * @param string $sql SQL statement + * @param array $params Parameters which should be bound to query + * @return ReadableStreamInterface + */ public function queryStream($sql, $params = []) { if ($this->closed) { @@ -141,7 +294,7 @@ public function queryStream($sql, $params = []) } return \React\Promise\Stream\unwrapReadable( - $this->connecting()->then(function (ConnectionInterface $connection) use ($sql, $params) { + $this->connecting()->then(function (Connection $connection) use ($sql, $params) { $stream = $connection->queryStream($sql, $params); $this->awake(); @@ -154,13 +307,33 @@ public function queryStream($sql, $params = []) ); } + /** + * Checks that the connection is alive. + * + * This method returns a promise that will resolve (with a void value) on + * success or will reject with an `Exception` on error. The MySQL protocol + * is inherently sequential, so that all commands will be performed in order + * and outstanding command will be put into a queue to be executed once the + * previous queries are completed. + * + * ```php + * $mysql->ping()->then(function () { + * echo 'OK' . PHP_EOL; + * }, function (Exception $e) { + * echo 'Error: ' . $e->getMessage() . PHP_EOL; + * }); + * ``` + * + * @return PromiseInterface + * Resolves with a `void` value on success or rejects with an `Exception` on error. + */ public function ping() { if ($this->closed) { return \React\Promise\reject(new Exception('Connection closed')); } - return $this->connecting()->then(function (ConnectionInterface $connection) { + return $this->connecting()->then(function (Connection $connection) { $this->awake(); return $connection->ping()->then( function () { @@ -174,6 +347,23 @@ function (\Exception $e) { }); } + /** + * Quits (soft-close) the connection. + * + * This method returns a promise that will resolve (with a void value) on + * success or will reject with an `Exception` on error. The MySQL protocol + * is inherently sequential, so that all commands will be performed in order + * and outstanding commands will be put into a queue to be executed once the + * previous commands are completed. + * + * ```php + * $mysql->query('CREATE TABLE test ...'); + * $mysql->quit(); + * ``` + * + * @return PromiseInterface + * Resolves with a `void` value on success or rejects with an `Exception` on error. + */ public function quit() { if ($this->closed) { @@ -186,7 +376,7 @@ public function quit() return \React\Promise\resolve(null); } - return $this->connecting()->then(function (ConnectionInterface $connection) { + return $this->connecting()->then(function (Connection $connection) { $this->awake(); return $connection->quit()->then( function () { @@ -200,6 +390,22 @@ function (\Exception $e) { }); } + /** + * Force-close the connection. + * + * Unlike the `quit()` method, this method will immediately force-close the + * connection and reject all outstanding commands. + * + * ```php + * $mysql->close(); + * ``` + * + * Forcefully closing the connection will yield a warning in the server logs + * and should generally only be used as a last resort. See also + * [`quit()`](#quit) as a safe alternative. + * + * @return void + */ public function close() { if ($this->closed) { @@ -216,7 +422,7 @@ public function close() // either close active connection or cancel pending connection attempt if ($this->connecting !== null) { - $this->connecting->then(function (ConnectionInterface $connection) { + $this->connecting->then(function (Connection $connection) { $connection->close(); }, function () { // ignore to avoid reporting unhandled rejection diff --git a/tests/BaseTestCase.php b/tests/BaseTestCase.php index ecd19b8..d584c7b 100644 --- a/tests/BaseTestCase.php +++ b/tests/BaseTestCase.php @@ -4,7 +4,7 @@ use PHPUnit\Framework\TestCase; use React\EventLoop\LoopInterface; -use React\MySQL\ConnectionInterface; +use React\MySQL\Io\Connection; use React\MySQL\Io\Factory; class BaseTestCase extends TestCase @@ -30,7 +30,7 @@ protected function getConnectionString($params = []) /** * @param LoopInterface $loop - * @return ConnectionInterface + * @return Connection */ protected function createConnection(LoopInterface $loop) { diff --git a/tests/Io/FactoryTest.php b/tests/Io/FactoryTest.php index bbedaa3..8757592 100644 --- a/tests/Io/FactoryTest.php +++ b/tests/Io/FactoryTest.php @@ -3,7 +3,7 @@ namespace React\Tests\MySQL\Io; use React\EventLoop\Loop; -use React\MySQL\ConnectionInterface; +use React\MySQL\Io\Connection; use React\MySQL\Io\Factory; use React\Promise\Promise; use React\Socket\SocketServer; @@ -275,7 +275,7 @@ public function testConnectWithValidAuthWillRunUntilQuit() $factory = new Factory(); $uri = $this->getConnectionString(); - $factory->createConnection($uri)->then(function (ConnectionInterface $connection) { + $factory->createConnection($uri)->then(function (Connection $connection) { echo 'connected.'; $connection->quit()->then(function () { echo 'closed.'; @@ -292,7 +292,7 @@ public function testConnectWithValidAuthAndWithoutDbNameWillRunUntilQuit() $factory = new Factory(); $uri = $this->getConnectionString(['dbname' => '']); - $factory->createConnection($uri)->then(function (ConnectionInterface $connection) { + $factory->createConnection($uri)->then(function (Connection $connection) { echo 'connected.'; $connection->quit()->then(function () { echo 'closed.'; @@ -309,7 +309,7 @@ public function testConnectWithValidAuthWillIgnoreNegativeTimeoutAndRunUntilQuit $factory = new Factory(); $uri = $this->getConnectionString() . '?timeout=-1'; - $factory->createConnection($uri)->then(function (ConnectionInterface $connection) { + $factory->createConnection($uri)->then(function (Connection $connection) { echo 'connected.'; $connection->quit()->then(function () { echo 'closed.'; @@ -326,7 +326,7 @@ public function testConnectWithValidAuthCanPingAndThenQuit() $factory = new Factory(); $uri = $this->getConnectionString(); - $factory->createConnection($uri)->then(function (ConnectionInterface $connection) { + $factory->createConnection($uri)->then(function (Connection $connection) { echo 'connected.'; $connection->ping()->then(function () use ($connection) { echo 'ping.'; @@ -346,7 +346,7 @@ public function testConnectWithValidAuthCanQueuePingAndQuit() $factory = new Factory(); $uri = $this->getConnectionString(); - $factory->createConnection($uri)->then(function (ConnectionInterface $connection) { + $factory->createConnection($uri)->then(function (Connection $connection) { echo 'connected.'; $connection->ping()->then(function () { echo 'ping.'; @@ -366,7 +366,7 @@ public function testConnectWithValidAuthQuitOnlyOnce() $factory = new Factory(); $uri = $this->getConnectionString(); - $factory->createConnection($uri)->then(function (ConnectionInterface $connection) { + $factory->createConnection($uri)->then(function (Connection $connection) { echo 'connected.'; $connection->quit()->then(function () { echo 'closed.'; @@ -388,7 +388,7 @@ public function testConnectWithValidAuthCanCloseOnlyOnce() $factory = new Factory(); $uri = $this->getConnectionString(); - $factory->createConnection($uri)->then(function (ConnectionInterface $connection) { + $factory->createConnection($uri)->then(function (Connection $connection) { echo 'connected.'; $connection->on('close', function () { echo 'closed.'; @@ -411,7 +411,7 @@ public function testConnectWithValidAuthCanCloseAndAbortPing() $factory = new Factory(); $uri = $this->getConnectionString(); - $factory->createConnection($uri)->then(function (ConnectionInterface $connection) { + $factory->createConnection($uri)->then(function (Connection $connection) { echo 'connected.'; $connection->on('close', function () { echo 'closed.'; diff --git a/tests/MysqlClientTest.php b/tests/MysqlClientTest.php index df7d021..c3923ea 100644 --- a/tests/MysqlClientTest.php +++ b/tests/MysqlClientTest.php @@ -290,7 +290,7 @@ public function testQueryReturnsPendingPromiseAndWillNotStartTimerWhenConnection public function testQueryWillQueryUnderlyingConnectionWhenResolved() { - $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); + $base = $this->getMockBuilder('React\MySQL\Io\Connection')->disableOriginalConstructor()->getMock(); $base->expects($this->once())->method('query')->with('SELECT 1')->willReturn(new Promise(function () { })); $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); @@ -310,7 +310,7 @@ public function testQueryWillResolveAndStartTimerWithDefaultIntervalWhenQueryFro { $result = new QueryResult(); - $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); + $base = $this->getMockBuilder('React\MySQL\Io\Connection')->disableOriginalConstructor()->getMock(); $base->expects($this->once())->method('query')->with('SELECT 1')->willReturn(\React\Promise\resolve($result)); $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); @@ -333,7 +333,7 @@ public function testQueryWillResolveAndStartTimerWithIntervalFromIdleParameterWh { $result = new QueryResult(); - $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); + $base = $this->getMockBuilder('React\MySQL\Io\Connection')->disableOriginalConstructor()->getMock(); $base->expects($this->once())->method('query')->with('SELECT 1')->willReturn(\React\Promise\resolve($result)); $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); @@ -356,7 +356,7 @@ public function testQueryWillResolveWithoutStartingTimerWhenQueryFromUnderlyingC { $result = new QueryResult(); - $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); + $base = $this->getMockBuilder('React\MySQL\Io\Connection')->disableOriginalConstructor()->getMock(); $base->expects($this->once())->method('query')->with('SELECT 1')->willReturn(\React\Promise\resolve($result)); $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); @@ -380,7 +380,7 @@ public function testQueryBeforePingWillResolveWithoutStartingTimerWhenQueryFromU $result = new QueryResult(); $deferred = new Deferred(); - $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); + $base = $this->getMockBuilder('React\MySQL\Io\Connection')->disableOriginalConstructor()->getMock(); $base->expects($this->once())->method('query')->with('SELECT 1')->willReturn($deferred->promise()); $base->expects($this->once())->method('ping')->willReturn(new Promise(function () { })); @@ -406,7 +406,7 @@ public function testQueryBeforePingWillResolveWithoutStartingTimerWhenQueryFromU public function testQueryAfterPingWillCancelTimerAgainWhenPingFromUnderlyingConnectionResolved() { - $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); + $base = $this->getMockBuilder('React\MySQL\Io\Connection')->disableOriginalConstructor()->getMock(); $base->expects($this->once())->method('ping')->willReturn(\React\Promise\resolve(null)); $base->expects($this->once())->method('query')->with('SELECT 1')->willReturn(new Promise(function () { })); @@ -432,7 +432,7 @@ public function testQueryWillRejectAndStartTimerWhenQueryFromUnderlyingConnectio { $error = new \RuntimeException(); - $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); + $base = $this->getMockBuilder('React\MySQL\Io\Connection')->disableOriginalConstructor()->getMock(); $base->expects($this->once())->method('query')->with('SELECT 1')->willReturn(\React\Promise\reject($error)); $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); @@ -494,7 +494,7 @@ public function testQueryStreamReturnsReadableStreamWhenConnectionIsPending() public function testQueryStreamWillReturnStreamFromUnderlyingConnectionWithoutStartingTimerWhenResolved() { $stream = new ThroughStream(); - $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); + $base = $this->getMockBuilder('React\MySQL\Io\Connection')->disableOriginalConstructor()->getMock(); $base->expects($this->once())->method('queryStream')->with('SELECT 1')->willReturn($stream); $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); @@ -520,7 +520,7 @@ public function testQueryStreamWillReturnStreamFromUnderlyingConnectionWithoutSt public function testQueryStreamWillReturnStreamFromUnderlyingConnectionAndStartTimerWhenResolvedAndClosed() { $stream = new ThroughStream(); - $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); + $base = $this->getMockBuilder('React\MySQL\Io\Connection')->disableOriginalConstructor()->getMock(); $base->expects($this->once())->method('queryStream')->with('SELECT 1')->willReturn($stream); $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); @@ -590,7 +590,7 @@ public function testPingReturnsPendingPromiseWhenConnectionIsPending() public function testPingWillPingUnderlyingConnectionWhenResolved() { - $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); + $base = $this->getMockBuilder('React\MySQL\Io\Connection')->disableOriginalConstructor()->getMock(); $base->expects($this->once())->method('ping')->willReturn(new Promise(function () { })); $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); @@ -647,7 +647,7 @@ public function testPingWillTryToCreateNewUnderlyingConnectionAfterPreviousPingF public function testPingWillResolveAndStartTimerWhenPingFromUnderlyingConnectionResolves() { - $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); + $base = $this->getMockBuilder('React\MySQL\Io\Connection')->disableOriginalConstructor()->getMock(); $base->expects($this->once())->method('ping')->willReturn(\React\Promise\resolve(null)); $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); @@ -670,7 +670,7 @@ public function testPingWillRejectAndStartTimerWhenPingFromUnderlyingConnectionR { $error = new \RuntimeException(); - $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); + $base = $this->getMockBuilder('React\MySQL\Io\Connection')->disableOriginalConstructor()->getMock(); $base->expects($this->once())->method('ping')->willReturn(\React\Promise\reject($error)); $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); @@ -759,7 +759,7 @@ public function testQuitAfterPingReturnsPendingPromiseWhenConnectionIsPending() public function testQuitAfterPingWillQuitUnderlyingConnectionWhenResolved() { - $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); + $base = $this->getMockBuilder('React\MySQL\Io\Connection')->disableOriginalConstructor()->getMock(); $base->expects($this->once())->method('ping')->willReturn(\React\Promise\resolve(null)); $base->expects($this->once())->method('quit')->willReturn(new Promise(function () { })); @@ -779,7 +779,7 @@ public function testQuitAfterPingWillQuitUnderlyingConnectionWhenResolved() public function testQuitAfterPingResolvesAndEmitsCloseWhenUnderlyingConnectionQuits() { - $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); + $base = $this->getMockBuilder('React\MySQL\Io\Connection')->disableOriginalConstructor()->getMock(); $base->expects($this->once())->method('ping')->willReturn(\React\Promise\resolve(null)); $base->expects($this->once())->method('quit')->willReturn(\React\Promise\resolve(null)); @@ -805,7 +805,7 @@ public function testQuitAfterPingResolvesAndEmitsCloseWhenUnderlyingConnectionQu public function testQuitAfterPingRejectsAndEmitsCloseWhenUnderlyingConnectionFailsToQuit() { $error = new \RuntimeException(); - $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); + $base = $this->getMockBuilder('React\MySQL\Io\Connection')->disableOriginalConstructor()->getMock(); $base->expects($this->once())->method('ping')->willReturn(\React\Promise\resolve(null)); $base->expects($this->once())->method('quit')->willReturn(\React\Promise\reject($error)); @@ -865,7 +865,7 @@ public function testCloseAfterPingCancelsPendingConnection() public function testCloseTwiceAfterPingWillCloseUnderlyingConnectionWhenResolved() { - $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); + $base = $this->getMockBuilder('React\MySQL\Io\Connection')->disableOriginalConstructor()->getMock(); $base->expects($this->once())->method('ping')->willReturn(\React\Promise\resolve(null)); $base->expects($this->once())->method('close'); @@ -912,7 +912,7 @@ public function testCloseAfterPingDoesNotEmitConnectionErrorFromAbortedConnectio public function testCloseAfterPingWillCancelTimerWhenPingFromUnderlyingConnectionResolves() { - $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); + $base = $this->getMockBuilder('React\MySQL\Io\Connection')->disableOriginalConstructor()->getMock(); $base->expects($this->once())->method('ping')->willReturn(\React\Promise\resolve(null)); $factory = $this->getMockBuilder('React\MySQL\Io\Factory')->disableOriginalConstructor()->getMock(); @@ -958,7 +958,7 @@ public function testCloseAfterPingHasResolvedWillCloseUnderlyingConnectionWithou public function testCloseAfterQuitAfterPingWillCloseUnderlyingConnectionWhenQuitIsStillPending() { - $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); + $base = $this->getMockBuilder('React\MySQL\Io\Connection')->disableOriginalConstructor()->getMock(); $base->expects($this->once())->method('ping')->willReturn(\React\Promise\resolve(null)); $base->expects($this->once())->method('quit')->willReturn(new Promise(function () { })); $base->expects($this->once())->method('close'); @@ -980,7 +980,7 @@ public function testCloseAfterQuitAfterPingWillCloseUnderlyingConnectionWhenQuit public function testCloseAfterPingAfterIdleTimeoutWillCloseUnderlyingConnectionWhenQuitIsStillPending() { - $base = $this->getMockBuilder('React\MySQL\ConnectionInterface')->getMock(); + $base = $this->getMockBuilder('React\MySQL\Io\Connection')->disableOriginalConstructor()->getMock(); $base->expects($this->once())->method('ping')->willReturn(\React\Promise\resolve(null)); $base->expects($this->once())->method('quit')->willReturn(new Promise(function () { })); $base->expects($this->once())->method('close');