Skip to content

Commit 860f0ce

Browse files
committed
Throw InvalidArgumentException if query params are invalid
1 parent 7ce51b1 commit 860f0ce

File tree

4 files changed

+90
-9
lines changed

4 files changed

+90
-9
lines changed

src/Io/Query.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,16 @@ class Query
4444
/**
4545
* @param string $sql
4646
* @param list<string|int|float|bool|null> $params
47+
* @throws \InvalidArgumentException if given $params are invalid
4748
*/
4849
public function __construct($sql, array $params = [])
4950
{
51+
foreach ($params as $param) {
52+
if (!\is_scalar($param) && $param !== null) {
53+
throw new \InvalidArgumentException('Query param must be of type string|int|float|bool|null, ' . (\is_object($param) ? \get_class($param) : \gettype($param)) . ' given');
54+
}
55+
}
56+
5057
$this->sql = $sql;
5158
$this->builtSql = $params ? null : $sql;
5259
$this->params = $params;
@@ -77,9 +84,6 @@ protected function resolveValueForSql($value)
7784
case 'NULL':
7885
$value = 'NULL';
7986
break;
80-
default:
81-
throw new \InvalidArgumentException(sprintf('Not supported value type of %s.', $type));
82-
break;
8387
}
8488

8589
return $value;

src/MysqlClient.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,15 +157,16 @@ public function __construct(
157157
* @param list<string|int|float|bool|null> $params Parameters which should be bound to query
158158
* @return PromiseInterface<MysqlResult>
159159
* Resolves with a `MysqlResult` on success or rejects with an `Exception` on error.
160+
* @throws \InvalidArgumentException if given $params are invalid
160161
*/
161162
public function query($sql, array $params = [])
162163
{
164+
$query = new Query($sql, $params);
165+
163166
if ($this->closed || $this->quitting) {
164167
return \React\Promise\reject(new Exception('Connection closed'));
165168
}
166169

167-
$query = new Query($sql, $params);
168-
169170
return $this->getConnection()->then(function (Connection $connection) use ($query) {
170171
return $connection->query($query)->then(function (MysqlResult $result) use ($connection) {
171172
$this->handleConnectionReady($connection);
@@ -235,15 +236,17 @@ public function query($sql, array $params = [])
235236
* @param string $sql SQL statement
236237
* @param list<string|int|float|bool|null> $params Parameters which should be bound to query
237238
* @return ReadableStreamInterface
239+
* @throws \InvalidArgumentException if given $params are invalid
240+
* @throws Exception if connection is already closed/closing
238241
*/
239242
public function queryStream($sql, array $params = [])
240243
{
244+
$query = new Query($sql, $params);
245+
241246
if ($this->closed || $this->quitting) {
242247
throw new Exception('Connection closed');
243248
}
244249

245-
$query = new Query($sql, $params);
246-
247250
return \React\Promise\Stream\unwrapReadable(
248251
$this->getConnection()->then(function (Connection $connection) use ($query) {
249252
$stream = $connection->queryStream($query);

tests/Io/QueryTest.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@
22

33
namespace React\Tests\Mysql\Io;
44

5-
use PHPUnit\Framework\TestCase;
65
use React\Mysql\Io\Query;
6+
use React\Tests\Mysql\BaseTestCase;
77

8-
class QueryTest extends TestCase
8+
class QueryTest extends BaseTestCase
99
{
10+
public function testCtorThrowsForInvalidParams()
11+
{
12+
$this->setExpectedException('InvalidArgumentException', 'Query param must be of type string|int|float|bool|null, resource given');
13+
new Query('SELECT ?', [tmpfile()]);
14+
}
15+
1016
public function testBindParams()
1117
{
1218
$query = new Query('select * from test where id = ? and name = ?', [100, 'test']);

tests/MysqlClientTest.php

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1921,6 +1921,40 @@ public function testQueryReturnsRejectedPromiseAfterConnectionIsClosed()
19211921
$ret->then($this->expectCallableNever(), $this->expectCallableOnce());
19221922
}
19231923

1924+
public function testQueryThrowsForInvalidQueryParamsWithoutCreatingNewConnection()
1925+
{
1926+
$factory = $this->getMockBuilder('React\Mysql\Io\Factory')->disableOriginalConstructor()->getMock();
1927+
$factory->expects($this->never())->method('createConnection');
1928+
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
1929+
1930+
$connection = new MysqlClient('', null, $loop);
1931+
1932+
$ref = new \ReflectionProperty($connection, 'factory');
1933+
$ref->setAccessible(true);
1934+
$ref->setValue($connection, $factory);
1935+
1936+
$this->setExpectedException('InvalidArgumentException', 'Query param must be of type string|int|float|bool|null, array given');
1937+
$connection->query('SELECT ?', [[]]);
1938+
}
1939+
1940+
public function testQueryThrowsForInvalidQueryParamsWhenConnectionIsAlreadyClosed()
1941+
{
1942+
$factory = $this->getMockBuilder('React\Mysql\Io\Factory')->disableOriginalConstructor()->getMock();
1943+
$factory->expects($this->never())->method('createConnection');
1944+
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
1945+
1946+
$connection = new MysqlClient('', null, $loop);
1947+
1948+
$ref = new \ReflectionProperty($connection, 'factory');
1949+
$ref->setAccessible(true);
1950+
$ref->setValue($connection, $factory);
1951+
1952+
$connection->close();
1953+
1954+
$this->setExpectedException('InvalidArgumentException', 'Query param must be of type string|int|float|bool|null, array given');
1955+
$connection->query('SELECT ?', [[]]);
1956+
}
1957+
19241958
public function testQueryStreamThrowsAfterConnectionIsClosed()
19251959
{
19261960
$factory = $this->getMockBuilder('React\Mysql\Io\Factory')->disableOriginalConstructor()->getMock();
@@ -1939,6 +1973,40 @@ public function testQueryStreamThrowsAfterConnectionIsClosed()
19391973
$connection->queryStream('SELECT 1');
19401974
}
19411975

1976+
public function testQueryStreamThrowsForInvalidQueryParamsWithoutCreatingNewConnection()
1977+
{
1978+
$factory = $this->getMockBuilder('React\Mysql\Io\Factory')->disableOriginalConstructor()->getMock();
1979+
$factory->expects($this->never())->method('createConnection');
1980+
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
1981+
1982+
$connection = new MysqlClient('', null, $loop);
1983+
1984+
$ref = new \ReflectionProperty($connection, 'factory');
1985+
$ref->setAccessible(true);
1986+
$ref->setValue($connection, $factory);
1987+
1988+
$this->setExpectedException('InvalidArgumentException', 'Query param must be of type string|int|float|bool|null, stdClass given');
1989+
$connection->queryStream('SELECT ?', [new \stdClass()]);
1990+
}
1991+
1992+
public function testQueryStreamThrowsForInvalidQueryParamsWhenConnectionIsAlreadyClosed()
1993+
{
1994+
$factory = $this->getMockBuilder('React\Mysql\Io\Factory')->disableOriginalConstructor()->getMock();
1995+
$factory->expects($this->never())->method('createConnection');
1996+
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
1997+
1998+
$connection = new MysqlClient('', null, $loop);
1999+
2000+
$ref = new \ReflectionProperty($connection, 'factory');
2001+
$ref->setAccessible(true);
2002+
$ref->setValue($connection, $factory);
2003+
2004+
$connection->close();
2005+
2006+
$this->setExpectedException('InvalidArgumentException', 'Query param must be of type string|int|float|bool|null, stdClass given');
2007+
$connection->queryStream('SELECT ?', [new \stdClass()]);
2008+
}
2009+
19422010
public function testPingReturnsRejectedPromiseAfterConnectionIsClosed()
19432011
{
19442012
$factory = $this->getMockBuilder('React\Mysql\Io\Factory')->disableOriginalConstructor()->getMock();

0 commit comments

Comments
 (0)