Skip to content

Commit

Permalink
Merge pull request #87 from clue-labs/lazy-connection
Browse files Browse the repository at this point in the history
Add new createLazyConnection() method to only connect only on demand (on first command)
  • Loading branch information
WyriHaximus authored Oct 19, 2018
2 parents f235ab8 + 83ac29b commit d0b0c0b
Show file tree
Hide file tree
Showing 9 changed files with 782 additions and 50 deletions.
99 changes: 85 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ It is written in pure PHP and does not require any extensions.
* [Usage](#usage)
* [Factory](#factory)
* [createConnection()](#createconnection)
* [createLazyConnection()](#createlazyconnection)
* [ConnectionInterface](#connectioninterface)
* [query()](#query)
* [queryStream()](#querystream)
Expand All @@ -35,20 +36,20 @@ $loop = React\EventLoop\Factory::create();
$factory = new Factory($loop);

$uri = 'test:test@localhost/test';
$factory->createConnection($uri)->then(function (ConnectionInterface $connection) {
$connection->query('SELECT * FROM book')->then(
function (QueryResult $command) {
print_r($command->resultFields);
print_r($command->resultRows);
echo count($command->resultRows) . ' row(s) in set' . PHP_EOL;
},
function (Exception $error) {
echo 'Error: ' . $error->getMessage() . PHP_EOL;
}
);
$connection->quit();
});
$connection = $factory->createLazyConnection($uri);

$connection->query('SELECT * FROM book')->then(
function (QueryResult $command) {
print_r($command->resultFields);
print_r($command->resultRows);
echo count($command->resultRows) . ' row(s) in set' . PHP_EOL;
},
function (Exception $error) {
echo 'Error: ' . $error->getMessage() . PHP_EOL;
}
);

$connection->quit();

$loop->run();
```
Expand Down Expand Up @@ -154,6 +155,76 @@ authentication. You can explicitly pass a custom timeout value in seconds
$factory->createConnection('localhost?timeout=0.5');
```

#### 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->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 (which may take some time) only once the
first request is invoked on this instance and will queue all outstanding
requests until the underlying connection is ready.

From a consumer side this means that you can start sending queries to the
database right away while the actual connection may still be outstanding.
It will ensure that all commands will be executed in the order they are
enqueued once the connection is ready. If the database connection fails,
it will emit an `error` event, reject all outstanding commands and `close`
the connection as described in the `ConnectionInterface`. In other words,
it behaves just like a real connection and frees you from having to deal
with its async resolution.

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. Similarly, calling
`quit()` on this instance before invoking any requests will succeed
immediately and will not 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');
```

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

### ConnectionInterface

The `ConnectionInterface` represents a connection that is responsible for
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"evenement/evenement": "^3.0 || ^2.1 || ^1.1",
"react/event-loop": "^1.0 || ^0.5 || ^0.4",
"react/promise": "^2.7",
"react/promise-stream": "^1.1",
"react/promise-timer": "^1.5",
"react/socket": "^1.1"
},
Expand Down
41 changes: 20 additions & 21 deletions examples/01-query.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<?php

use React\MySQL\ConnectionInterface;
use React\MySQL\Factory;
use React\MySQL\QueryResult;

Expand All @@ -12,27 +11,27 @@
$uri = 'test:test@localhost/test';
$query = isset($argv[1]) ? $argv[1] : 'select * from book';

//create a mysql connection for executing query
$factory->createConnection($uri)->then(function (ConnectionInterface $connection) use ($query) {
$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;
//create a lazy mysql connection for executing query
$connection = $factory->createLazyConnection($uri);

$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);
}
}, function (Exception $error) {
// the query was not executed successfully
echo 'Error: ' . $error->getMessage() . PHP_EOL;
});
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;
});

$connection->quit();
}, 'printf');
$connection->quit();

$loop->run();
29 changes: 14 additions & 15 deletions examples/02-query-stream.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

// $ php examples/02-query-stream.php "SHOW VARIABLES"

use React\MySQL\ConnectionInterface;
use React\MySQL\Factory;

require __DIR__ . '/../vendor/autoload.php';
Expand All @@ -13,23 +12,23 @@
$uri = 'test:test@localhost/test';
$query = isset($argv[1]) ? $argv[1] : 'select * from book';

//create a mysql connection for executing query
$factory->createConnection($uri)->then(function (ConnectionInterface $connection) use ($query) {
$stream = $connection->queryStream($query);
//create a lazy mysql connection for executing query
$connection = $factory->createLazyConnection($uri);

$stream->on('data', function ($row) {
echo json_encode($row, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
});
$stream = $connection->queryStream($query);

$stream->on('error', function (Exception $e) {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
$stream->on('data', function ($row) {
echo json_encode($row, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
});

$stream->on('close', function () {
echo 'CLOSED' . PHP_EOL;
});
$stream->on('error', function (Exception $e) {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
});

$connection->quit();
}, 'printf');
$stream->on('close', function () {
echo 'CLOSED' . PHP_EOL;
});

$connection->quit();

$loop->run();
78 changes: 78 additions & 0 deletions src/Factory.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use React\Socket\Connector;
use React\Socket\ConnectorInterface;
use React\Socket\ConnectionInterface;
use React\MySQL\Io\LazyConnection;

class Factory
{
Expand Down Expand Up @@ -194,4 +195,81 @@ public function createConnection($uri)
throw $e;
});
}

/**
* 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->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 (which may take some time) only once the
* first request is invoked on this instance and will queue all outstanding
* requests until the underlying connection is ready.
*
* From a consumer side this means that you can start sending queries to the
* database right away while the actual connection may still be outstanding.
* It will ensure that all commands will be executed in the order they are
* enqueued once the connection is ready. If the database connection fails,
* it will emit an `error` event, reject all outstanding commands and `close`
* the connection as described in the `ConnectionInterface`. In other words,
* it behaves just like a real connection and frees you from having to deal
* with its async resolution.
*
* 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. Similarly, calling
* `quit()` on this instance before invoking any requests will succeed
* immediately and will not 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');
* ```
*
* 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');
* ```
*
* @param string $uri
* @return ConnectionInterface
*/
public function createLazyConnection($uri)
{
return new LazyConnection($this, $uri);
}
}
Loading

0 comments on commit d0b0c0b

Please sign in to comment.