Skip to content

Commit f241871

Browse files
committed
Fix bug with doubled up free results. Improved readme, updated example.
1 parent 18990b9 commit f241871

File tree

9 files changed

+171
-114
lines changed

9 files changed

+171
-114
lines changed

README.md

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,59 @@
33
Non-blocking MySQLi database access with PHP.
44
Designed to work with [reactphp/react](https://github.com/reactphp/react).
55

6+
[![Build Status](https://travis-ci.org/dustingraham/react-mysql.svg?branch=master)](https://travis-ci.org/dustingraham/react-mysql)
7+
8+
## Quickstart
9+
10+
$db = new \DustinGraham\ReactMysql\Database(
11+
['localhost', 'apache', 'apache', 'react_mysql_test']
12+
);
13+
14+
$db->statement('SELECT * FROM simple_table WHERE id = :test', [':test' => 2])
15+
->then(function(\mysqli_result $result)
16+
{
17+
$rows = $result->fetch_all(MYSQLI_ASSOC);
18+
});
19+
20+
$db->shuttingDown = true;
21+
$db->loop->run();
22+
23+
Setting `shuttingDown` to true will allow the loop to exit once the query has resolved.
624

725
## Working
826

9-
This __is__ working. But it is nowhere near complete.
27+
This __is__ working. But it is nowhere near complete. Check out the example file
28+
as well as the unit tests for more examples.
1029

11-
$ ./run
12-
Starting loop...
13-
DB Created.
30+
$ ./example
31+
Creating database....done!
1432
Run Query: 0
1533
Found rows: 0
1634
Run Query: 1
1735
Found rows: 1
18-
Current memory usage: 735.117K
36+
Current memory usage: 868.164K
1937
Run Query: 2
20-
Found rows: 0
38+
Found rows: 1
2139
Run Query: 3
2240
Found rows: 1
2341
Run Query: 4
24-
Found rows: 1
25-
Current memory usage: 735.117K
42+
Found rows: 0
43+
Current memory usage: 868.164K
2644
Run Query: 5
2745
Found rows: 0
28-
Current memory usage: 733.602K
29-
Current memory usage: 733.602K
30-
Current memory usage: 733.602K
46+
Current memory usage: 865.719K
47+
Current memory usage: 865.719K
48+
Current memory usage: 865.719K
3149
Loop finished, all timers halted.
3250

3351
This won't work out of the box without the database configured.
34-
As of this point, database configuration is hard coded.
35-
Still need to pull out the configs. You will also need to
36-
set up a database with some data to query. Check back later
37-
for more!
52+
You will also need to set up a database with some data to query.
53+
54+
## Unit Tests
55+
56+
The example and unit tests expect a database called `react_mysql_test` which it
57+
will populate with the proper tables each time it runs. It also expects `localhost`
58+
and a user `apache` with password `apache`.
3859

3960
## TODO
4061

@@ -52,23 +73,20 @@ These are just plans for now. It may change wildly as we develop.
5273

5374
Here is an example of what is currently working for the most part.
5475

55-
$loop = React\EventLoop\Factory::create();
56-
57-
ConnectionFactory::init($loop, ['db_host', 'db_user', 'db_pass', 'db_name']);
58-
59-
$db = new \DustinGraham\ReactMysql\Database();
76+
$db = new \DustinGraham\ReactMysql\Database(
77+
['localhost', 'apache', 'apache', 'react_mysql_test']
78+
);
6079

61-
$db->createCommand("SELECT * FROM `table` WHERE id = :id;", [':id' => $id])
62-
->execute()->then(
63-
function($result)
80+
$db->statement('SELECT * FROM simple_table WHERE id = :test', [':test' => 2])
81+
->then(function(\mysqli_result $result)
6482
{
6583
$rows = $result->fetch_all(MYSQLI_ASSOC);
66-
$result->close();
6784
6885
// Do something with $rows.
69-
}
70-
);
86+
});
7187

88+
$db->shuttingDown = true;
89+
$db->loop->run();
7290

7391
### Original Big Picture Plans
7492

example

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/usr/bin/env php
2+
<?php
3+
error_reporting(-1);
4+
ini_set("display_errors", 1);
5+
6+
require __DIR__.'/vendor/autoload.php';
7+
8+
echo 'Creating database..';
9+
10+
$db = new \DustinGraham\ReactMysql\Database(
11+
['localhost', 'apache', 'apache', 'react_mysql_test']
12+
);
13+
14+
echo '..done!'.PHP_EOL;
15+
16+
$j = 0;
17+
$db->loop->addPeriodicTimer(0.3, function (\React\EventLoop\Timer\TimerInterface $timer) use (&$j)
18+
{
19+
$memory = memory_get_usage() / 1024;
20+
$formatted = number_format($memory, 3).'K';
21+
echo "Current memory usage: {$formatted}\n";
22+
23+
if ($j++ > 3) $timer->cancel();
24+
});
25+
26+
$i = 0;
27+
$db->loop->addPeriodicTimer(0.1, function (\React\EventLoop\Timer\TimerInterface $timer) use (&$i, $db)
28+
{
29+
echo "Run Query: $i\n";
30+
31+
$db->statement(
32+
'SELECT * FROM `simple_table` WHERE id = :test',
33+
[':test' => $i]
34+
)->then(function(\mysqli_result $result)
35+
{
36+
$rows = $result->fetch_all(MYSQLI_ASSOC);
37+
echo 'Found rows: '.count($rows).PHP_EOL;
38+
})->done();
39+
40+
if ($i++ >= 5)
41+
{
42+
// All queries added.
43+
$db->shuttingDown = true;
44+
$timer->cancel();
45+
}
46+
});
47+
48+
$db->loop->run();
49+
50+
echo 'Loop finished, all timers halted.'.PHP_EOL;

run

Lines changed: 0 additions & 59 deletions
This file was deleted.

src/Command.php

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ class Command
2424
'NOW()',
2525
];
2626

27-
public function __construct(Database $database, $sql = null)
27+
public function __construct($sql = null, $params = null)
2828
{
29-
$this->db = $database;
3029
$this->sql = $sql;
30+
$this->bind($params);
3131
}
3232

3333
/**
@@ -39,10 +39,12 @@ public function bind($key, $value = null)
3939
{
4040
if (is_array($key))
4141
{
42-
// TODO: Is this cludgy?
43-
$this->bindValues($key);
42+
foreach ($key as $k => $v)
43+
{
44+
$this->params[$k] = $v;
45+
}
4446
}
45-
else
47+
else if (!is_null($key))
4648
{
4749
$this->params[$key] = $value;
4850
}
@@ -51,17 +53,14 @@ public function bind($key, $value = null)
5153
}
5254

5355
/**
56+
* @deprecated
57+
*
5458
* @param $params
5559
* @return $this
5660
*/
5761
public function bindValues($params)
5862
{
59-
foreach ($params as $k => $v)
60-
{
61-
$this->params[$k] = $v;
62-
}
63-
64-
return $this;
63+
return $this->bind($params);
6564
}
6665

6766
/**
@@ -113,6 +112,8 @@ protected function quoteIntoSql(Connection $connection)
113112
}
114113

115114
/**
115+
* @deprecated
116+
*
116117
* @return \React\Promise\Promise
117118
*/
118119
public function execute()

src/Connection.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,12 @@ public function __construct($host = null, $username = null, $passwd = null, $dbn
3636
*/
3737
public function escape($string)
3838
{
39-
return $this->mysqli->real_escape_string($string);
39+
return $this->real_escape_string($string);
4040
}
4141

4242
/**
43+
* @deprecated
44+
*
4345
* Close the mysqli connection.
4446
*/
4547
public function close()

src/ConnectionFactory.php

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,23 @@
44

55
class ConnectionFactory
66
{
7-
/**
8-
* @var LoopInterface
9-
*/
10-
//public static $loop;
11-
127
/**
138
* @var array
149
*/
1510
protected static $credentials;
1611

17-
public static function init(LoopInterface $loop, $credentials)
12+
public static function init($credentials)
1813
{
19-
//self::$loop = $loop;
2014
self::$credentials = $credentials;
2115
}
2216

2317
public static function createConnection()
2418
{
19+
if (is_null(self::$credentials))
20+
{
21+
throw new \Exception('Database credentials not set.');
22+
}
23+
2524
$connection = new Connection(
2625
self::$credentials[0],
2726
self::$credentials[1],

src/Database.php

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,13 @@ class Database
2222
*/
2323
protected $pollInterval = 0.01;
2424

25-
26-
public function __construct()
25+
public function __construct($credentials = null)
2726
{
27+
if (!is_null($credentials))
28+
{
29+
ConnectionFactory::init($credentials);
30+
}
31+
2832
$this->loop = Factory::create();
2933
$this->initLoop();
3034

@@ -90,12 +94,16 @@ public function getPool()
9094
return $this->pool;
9195
}
9296

93-
public function statement($sql)
97+
public function statement($sql, $params = null)
9498
{
99+
$command = new Command($sql, $params);
100+
95101
$deferred = new Deferred();
96102

97-
$this->pool->withConnection(function($connection) use ($sql, $deferred)
103+
$this->pool->withConnection(function(Connection $connection) use ($command, $deferred)
98104
{
105+
$sql = $command->getPreparedQuery($connection);
106+
99107
$connection->query($sql, MYSQLI_ASYNC);
100108

101109
$this->conns[$connection->id] = [
@@ -126,6 +134,8 @@ public function loopTick(TimerInterface $timer)
126134
// If we are shutting down, and have nothing to check, kill the timer.
127135
if ($this->shuttingDown)
128136
{
137+
// TODO: Possible race condition if shutdown also queues queries, such as a final save.
138+
// This could be prematurely cancelled.
129139
$timer->cancel();
130140
}
131141

@@ -151,7 +161,11 @@ public function loopTick(TimerInterface $timer)
151161
if ($result !== false)
152162
{
153163
$deferred->resolve($result);
154-
$result->free();
164+
165+
// If userland code has already freed the result, this will throw a warning.
166+
// No need to throw a warning here...
167+
// If you know how to check if the result has already been freed, please PR!
168+
@$result->free();
155169
}
156170
else
157171
{

0 commit comments

Comments
 (0)