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