Skip to content

Commit a729faa

Browse files
committed
Improve error reporting, include MySQL URI in all connection errors
1 parent 5c8d3ec commit a729faa

File tree

7 files changed

+114
-43
lines changed

7 files changed

+114
-43
lines changed

README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,7 @@ This example runs a simple `SELECT` query and dumps all the records from a `book
3333

3434
```php
3535
$factory = new React\MySQL\Factory();
36-
37-
$uri = 'test:test@localhost/test';
38-
$connection = $factory->createLazyConnection($uri);
36+
$connection = $factory->createLazyConnection('user:pass@localhost/bookstore');
3937

4038
$connection->query('SELECT * FROM book')->then(
4139
function (QueryResult $command) {

examples/01-query.php

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
<?php
22

3+
// $ php examples/01-query.php
4+
// $ MYSQL_URI=test:test@localhost/test php examples/01-query.php "SELECT * FROM book"
5+
36
use React\MySQL\Factory;
47
use React\MySQL\QueryResult;
58

69
require __DIR__ . '/../vendor/autoload.php';
710

811
$factory = new Factory();
12+
$connection = $factory->createLazyConnection(getenv('MYSQL_URI') ?: 'test:test@localhost/test');
913

10-
$uri = 'test:test@localhost/test';
1114
$query = isset($argv[1]) ? $argv[1] : 'select * from book';
12-
13-
//create a lazy mysql connection for executing query
14-
$connection = $factory->createLazyConnection($uri);
15-
1615
$connection->query($query)->then(function (QueryResult $command) {
1716
if (isset($command->resultRows)) {
1817
// this is a response to a SELECT etc. with some rows (0+)

examples/02-query-stream.php

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
<?php
22

33
// $ php examples/02-query-stream.php "SHOW VARIABLES"
4+
// $ MYSQL_URI=test:test@localhost/test php examples/02-query-stream.php "SELECT * FROM book"
45

56
use React\MySQL\Factory;
67

78
require __DIR__ . '/../vendor/autoload.php';
89

910
$factory = new Factory();
11+
$connection = $factory->createLazyConnection(getenv('MYSQL_URI') ?: 'test:test@localhost/test');
1012

11-
$uri = 'test:test@localhost/test';
1213
$query = isset($argv[1]) ? $argv[1] : 'select * from book';
13-
14-
//create a lazy mysql connection for executing query
15-
$connection = $factory->createLazyConnection($uri);
16-
1714
$stream = $connection->queryStream($query);
1815

1916
$stream->on('data', function ($row) {

examples/11-interactive.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
<?php
22

3+
// $ php examples/11-interactive.php
4+
// $ MYSQL_URI=test:test@localhost/test php examples/11-interactive.php
5+
36
use React\MySQL\ConnectionInterface;
47
use React\MySQL\QueryResult;
58
use React\MySQL\Factory;
@@ -8,8 +11,7 @@
811
require __DIR__ . '/../vendor/autoload.php';
912

1013
$factory = new Factory();
11-
12-
$uri = 'test:test@localhost/test';
14+
$uri = getenv('MYSQL_URI') ?: 'test:test@localhost/test';
1315

1416
// open a STDIN stream to read keyboard input (not supported on Windows)
1517
$stdin = new ReadableResourceStream(STDIN);

examples/12-slow-stream.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22

33
// $ php examples/12-slow-stream.php "SHOW VARIABLES"
4+
// $ MYSQL_URI=test:test@localhost/test php examples/12-slow-stream.php "SELECT * FROM book"
45

56
use React\EventLoop\Loop;
67
use React\MySQL\ConnectionInterface;
@@ -9,8 +10,8 @@
910
require __DIR__ . '/../vendor/autoload.php';
1011

1112
$factory = new Factory();
13+
$uri = getenv('MYSQL_URI') ?: 'test:test@localhost/test';
1214

13-
$uri = 'test:test@localhost/test';
1415
$query = isset($argv[1]) ? $argv[1] : 'select * from book';
1516

1617
//create a mysql connection for executing query

src/Factory.php

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,9 @@ public function createConnection($uri)
163163
}
164164

165165
$parts = parse_url($uri);
166+
$uri = preg_replace('#:[^:/]*@#', ':***@', $uri);
166167
if (!isset($parts['scheme'], $parts['host']) || $parts['scheme'] !== 'mysql') {
167-
return \React\Promise\reject(new \InvalidArgumentException('Invalid connect uri given'));
168+
return \React\Promise\reject(new \InvalidArgumentException('Invalid MySQL URI given'));
168169
}
169170

170171
$args = [];
@@ -187,9 +188,11 @@ public function createConnection($uri)
187188
$parts['host'] . ':' . (isset($parts['port']) ? $parts['port'] : 3306)
188189
);
189190

190-
$deferred = new Deferred(function ($_, $reject) use ($connecting) {
191+
$deferred = new Deferred(function ($_, $reject) use ($connecting, $uri) {
191192
// connection cancelled, start with rejecting attempt, then clean up
192-
$reject(new \RuntimeException('Connection to database server cancelled'));
193+
$reject(new \RuntimeException(
194+
'Connection to ' . $uri . ' cancelled'
195+
));
193196

194197
// either close successful connection or cancel pending connection attempt
195198
$connecting->then(function (SocketConnectionInterface $connection) {
@@ -198,7 +201,7 @@ public function createConnection($uri)
198201
$connecting->cancel();
199202
});
200203

201-
$connecting->then(function (SocketConnectionInterface $stream) use ($authCommand, $deferred) {
204+
$connecting->then(function (SocketConnectionInterface $stream) use ($authCommand, $deferred, $uri) {
202205
$executor = new Executor();
203206
$parser = new Parser($stream, $executor);
204207

@@ -209,12 +212,20 @@ public function createConnection($uri)
209212
$command->on('success', function () use ($deferred, $connection) {
210213
$deferred->resolve($connection);
211214
});
212-
$command->on('error', function ($error) use ($deferred, $stream) {
213-
$deferred->reject($error);
215+
$command->on('error', function (\Exception $error) use ($deferred, $stream, $uri) {
216+
$deferred->reject(new \RuntimeException(
217+
'Connection to ' . $uri . ' failed during authentication: ' . $error->getMessage(),
218+
$error->getCode(),
219+
$error
220+
));
214221
$stream->close();
215222
});
216-
}, function ($error) use ($deferred) {
217-
$deferred->reject(new \RuntimeException('Unable to connect to database server', 0, $error));
223+
}, function (\Exception $error) use ($deferred, $uri) {
224+
$deferred->reject(new \RuntimeException(
225+
'Connection to ' . $uri . ' failed: ' . $error->getMessage(),
226+
$error->getCode(),
227+
$error
228+
));
218229
});
219230

220231
// use timeout from explicit ?timeout=x parameter or default to PHP's default_socket_timeout (60)
@@ -223,10 +234,10 @@ public function createConnection($uri)
223234
return $deferred->promise();
224235
}
225236

226-
return \React\Promise\Timer\timeout($deferred->promise(), $timeout, $this->loop)->then(null, function ($e) {
237+
return \React\Promise\Timer\timeout($deferred->promise(), $timeout, $this->loop)->then(null, function ($e) use ($uri) {
227238
if ($e instanceof TimeoutException) {
228239
throw new \RuntimeException(
229-
'Connection to database server timed out after ' . $e->getTimeout() . ' seconds'
240+
'Connection to ' . $uri . ' timed out after ' . $e->getTimeout() . ' seconds'
230241
);
231242
}
232243
throw $e;

tests/FactoryTest.php

Lines changed: 80 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,14 @@ public function testConnectWithInvalidUriWillRejectWithoutConnecting()
109109
$factory = new Factory($loop, $connector);
110110
$promise = $factory->createConnection('///');
111111

112-
$this->assertInstanceof('React\Promise\PromiseInterface', $promise);
113-
$promise->then(null, $this->expectCallableOnce());
112+
$promise->then(null, $this->expectCallableOnceWith(
113+
$this->logicalAnd(
114+
$this->isInstanceOf('InvalidArgumentException'),
115+
$this->callback(function (\InvalidArgumentException $e) {
116+
return $e->getMessage() === 'Invalid MySQL URI given';
117+
})
118+
)
119+
));
114120
}
115121

116122
public function testConnectWithInvalidCharsetWillRejectWithoutConnecting()
@@ -149,7 +155,7 @@ public function testConnectWithInvalidPassRejectsWithAuthenticationError()
149155
$this->logicalAnd(
150156
$this->isInstanceOf('Exception'),
151157
$this->callback(function (\Exception $e) {
152-
return !!preg_match("/^Access denied for user '.*?'@'.*?' \(using password: YES\)$/", $e->getMessage());
158+
return !!preg_match("/^Connection to mysql:\/\/[^ ]* failed during authentication: Access denied for user '.*?'@'.*?' \(using password: YES\)$/", $e->getMessage());
153159
})
154160
)
155161
));
@@ -180,15 +186,17 @@ public function testConnectWillRejectOnExplicitTimeoutDespiteValidAuth()
180186
{
181187
$factory = new Factory();
182188

183-
$uri = $this->getConnectionString() . '?timeout=0';
189+
$uri = 'mysql://' . $this->getConnectionString() . '?timeout=0';
184190

185191
$promise = $factory->createConnection($uri);
186192

193+
$uri = preg_replace('/:[^:]*@/', ':***@', $uri);
194+
187195
$promise->then(null, $this->expectCallableOnceWith(
188196
$this->logicalAnd(
189197
$this->isInstanceOf('Exception'),
190-
$this->callback(function (\Exception $e) {
191-
return $e->getMessage() === 'Connection to database server timed out after 0 seconds';
198+
$this->callback(function (\Exception $e) use ($uri) {
199+
return $e->getMessage() === 'Connection to ' . $uri . ' timed out after 0 seconds';
192200
})
193201
)
194202
));
@@ -200,18 +208,20 @@ public function testConnectWillRejectOnDefaultTimeoutFromIniDespiteValidAuth()
200208
{
201209
$factory = new Factory();
202210

203-
$uri = $this->getConnectionString();
211+
$uri = 'mysql://' . $this->getConnectionString();
204212

205213
$old = ini_get('default_socket_timeout');
206214
ini_set('default_socket_timeout', '0');
207215
$promise = $factory->createConnection($uri);
208216
ini_set('default_socket_timeout', $old);
209217

218+
$uri = preg_replace('/:[^:]*@/', ':***@', $uri);
219+
210220
$promise->then(null, $this->expectCallableOnceWith(
211221
$this->logicalAnd(
212222
$this->isInstanceOf('Exception'),
213-
$this->callback(function (\Exception $e) {
214-
return $e->getMessage() === 'Connection to database server timed out after 0 seconds';
223+
$this->callback(function (\Exception $e) use ($uri) {
224+
return $e->getMessage() === 'Connection to ' . $uri . ' timed out after 0 seconds';
215225
})
216226
)
217227
));
@@ -382,22 +392,75 @@ public function testConnectWithValidAuthCanCloseAndAbortPing()
382392
Loop::run();
383393
}
384394

385-
public function testCancelConnectWillCancelPendingConnection()
395+
public function testlConnectWillRejectWhenUnderlyingConnectorRejects()
396+
{
397+
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
398+
$connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
399+
$connector->expects($this->once())->method('connect')->willReturn(\React\Promise\reject(new \RuntimeException('Failed', 123)));
400+
401+
$factory = new Factory($loop, $connector);
402+
$promise = $factory->createConnection('user:[email protected]');
403+
404+
$promise->then(null, $this->expectCallableOnceWith($this->isInstanceOf('RuntimeException')));
405+
$promise->then(null, $this->expectCallableOnceWith($this->callback(function ($e) {
406+
return ($e->getMessage() === 'Connection to mysql://user:***@127.0.0.1 failed: Failed');
407+
})));
408+
$promise->then(null, $this->expectCallableOnceWith($this->callback(function ($e) {
409+
return ($e->getCode() === 123);
410+
})));
411+
}
412+
413+
public function provideUris()
414+
{
415+
return [
416+
[
417+
'localhost',
418+
'mysql://localhost'
419+
],
420+
[
421+
'mysql://localhost',
422+
'mysql://localhost'
423+
],
424+
[
425+
'mysql://user:pass@localhost',
426+
'mysql://user:***@localhost'
427+
],
428+
[
429+
'mysql://user:@localhost',
430+
'mysql://user:***@localhost'
431+
],
432+
[
433+
'mysql://user@localhost',
434+
'mysql://user@localhost'
435+
]
436+
];
437+
}
438+
439+
/**
440+
* @dataProvider provideUris
441+
* @param string $uri
442+
* @param string $safe
443+
*/
444+
public function testCancelConnectWillCancelPendingConnection($uri, $safe)
386445
{
387446
$pending = new Promise(function () { }, $this->expectCallableOnce());
388447
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
389448
$connector = $this->getMockBuilder('React\Socket\ConnectorInterface')->getMock();
390449
$connector->expects($this->once())->method('connect')->willReturn($pending);
391450

392451
$factory = new Factory($loop, $connector);
393-
$promise = $factory->createConnection('127.0.0.1');
452+
$promise = $factory->createConnection($uri);
394453

395454
$promise->cancel();
396455

397-
$promise->then(null, $this->expectCallableOnceWith($this->isInstanceOf('RuntimeException')));
398-
$promise->then(null, $this->expectCallableOnceWith($this->callback(function ($e) {
399-
return ($e->getMessage() === 'Connection to database server cancelled');
400-
})));
456+
$promise->then(null, $this->expectCallableOnceWith(
457+
$this->logicalAnd(
458+
$this->isInstanceOf('RuntimeException'),
459+
$this->callback(function (\RuntimeException $e) use ($safe) {
460+
return $e->getMessage() === 'Connection to ' . $safe . ' cancelled';
461+
})
462+
)
463+
));
401464
}
402465

403466
public function testCancelConnectWillCancelPendingConnectionWithRuntimeException()
@@ -416,7 +479,7 @@ public function testCancelConnectWillCancelPendingConnectionWithRuntimeException
416479

417480
$promise->then(null, $this->expectCallableOnceWith($this->isInstanceOf('RuntimeException')));
418481
$promise->then(null, $this->expectCallableOnceWith($this->callback(function ($e) {
419-
return ($e->getMessage() === 'Connection to database server cancelled');
482+
return ($e->getMessage() === 'Connection to mysql://127.0.0.1 cancelled');
420483
})));
421484
}
422485

@@ -436,7 +499,7 @@ public function testCancelConnectDuringAuthenticationWillCloseConnection()
436499

437500
$promise->then(null, $this->expectCallableOnceWith($this->isInstanceOf('RuntimeException')));
438501
$promise->then(null, $this->expectCallableOnceWith($this->callback(function ($e) {
439-
return ($e->getMessage() === 'Connection to database server cancelled');
502+
return ($e->getMessage() === 'Connection to mysql://127.0.0.1 cancelled');
440503
})));
441504
}
442505

0 commit comments

Comments
 (0)