├── .gitignore ├── LICENSE ├── README.md ├── composer.json └── src └── Atrox └── AsyncMysql.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | compose.lock 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | New BSD License 2 | --------------- 3 | 4 | Copyright © 2012 Karel Čížek (http://k47.cz) All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, 7 | are permitted provided that the following conditions are met: 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | * Neither the name of Karel Čížek nor the names of its contributors may be used to 14 | endorse or promote products derived from this software without specific prior 15 | written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 21 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AsyncMysql 2 | 3 | Asynchronous & non-blocking MySQL driver for [React.PHP](https://github.com/reactphp/react). 4 | 5 | ## Install 6 | 7 | Add this crap to your composer.json: 8 | 9 | ``` 10 | { 11 | "require": { 12 | "atrox/async-mysql": "dev-master" 13 | }, 14 | "repositories": [ 15 | { 16 | "type": "vcs", 17 | "url": "https://github.com/kaja47/async-mysql" 18 | } 19 | ] 20 | } 21 | 22 | ``` 23 | 24 | ## Usage 25 | 26 | Create instance of AsyncMysql and call method `query`. 27 | It returns [Promise](https://github.com/reactphp/promise) of [mysqli_result](http://cz2.php.net/manual/en/class.mysqli-result.php) that will be resolved imediately after query completes. 28 | 29 | ```php 30 | query('select * from ponies_and_unicorns')->then( 40 | function ($result) { writeHttpResponse(json_encode($result->fetch_all(MYSQLI_ASSOC))); $result->close(); }, 41 | function ($error) { writeHeader500(); } 42 | ); 43 | ``` 44 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "atrox/async-mysql", 3 | "description": "Asynchronous & non-blocking MySQL driver for React.PHP", 4 | "license": "BSD-3-Clause", 5 | "authors": [ 6 | {"name": "Karel Čížek", "email": "kaja47@k47.cz"} 7 | ], 8 | "require": { 9 | "php": ">=5.4.0", 10 | "react/react": "dev-master" 11 | }, 12 | "autoload": { 13 | "psr-0": {"Atrox": "src/"} 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Atrox/AsyncMysql.php: -------------------------------------------------------------------------------- 1 | loop = $loop; 22 | $this->pool = ($connectionPool instanceof ConnectionPool) ? $connectionPool : new ConnectionPool($connectionPool, $maxConnections); 23 | } 24 | 25 | 26 | /** 27 | * @param string 28 | * @return React\Promise\PromiseInterface 29 | */ 30 | function query($query) { 31 | return $this->pool->getConnection()->then(function ($conn) use ($query) { 32 | $status = $conn->query($query, MYSQLI_ASYNC); 33 | if ($status === false) { 34 | $this->pool->freeConnection($conn); 35 | throw new \Exception($conn->error); 36 | } 37 | 38 | $defered = new Deferred(); 39 | 40 | $this->loop->addPeriodicTimer(0.001, function ($timer) use ($conn, $defered) { 41 | $links = $errors = $reject = array($conn); 42 | mysqli_poll($links, $errors, $reject, 0); // don't wait, just check 43 | if (($read = in_array($conn, $links, true)) || ($err = in_array($conn, $errors, true)) || ($rej = in_array($conn, $reject, true))) { 44 | if ($read) { 45 | $result = $conn->reap_async_query(); 46 | if ($result === false) { 47 | $defered->reject(new \Exception($conn->error)); 48 | } else { 49 | $defered->resolve($result); 50 | } 51 | } elseif ($err) { 52 | $defered->reject($conn->error); 53 | } else { 54 | $defered->reject(new \Exception('Query was rejected')); 55 | } 56 | $timer->cancel(); 57 | $this->pool->freeConnection($conn); 58 | } 59 | }); 60 | 61 | return $defered->promise(); 62 | }); 63 | } 64 | } 65 | 66 | 67 | 68 | class ConnectionPool { 69 | 70 | private $makeConnection; 71 | private $maxConnections; 72 | 73 | /** pool of all connections (both idle and busy) */ 74 | private $pool; 75 | 76 | /** pool of idle connections */ 77 | private $idle; 78 | 79 | /** array of Deferred objects waiting to be resolved with connection */ 80 | private $waiting = []; 81 | 82 | 83 | function __construct($makeConnection, $maxConnections = 100) { 84 | $this->makeConnection = $makeConnection; 85 | $this->maxConnections = $maxConnections; 86 | $this->pool = new \SplObjectStorage(); 87 | $this->idle = new \SplObjectStorage(); 88 | } 89 | 90 | 91 | function getConnection() { 92 | // reuse idle connections 93 | if (count($this->idle) > 0) { 94 | $this->idle->rewind(); 95 | $conn = $this->idle->current(); 96 | $this->idle->detach($conn); 97 | return Promise\resolve($conn); 98 | } 99 | 100 | // max connections reached, must wait till one connection is freed 101 | if (count($this->pool) >= $this->maxConnections) { 102 | $deferred = new Deferred(); 103 | $this->waiting[] = $deferred; 104 | return $deferred->promise(); 105 | } 106 | 107 | $conn = call_user_func($this->makeConnection); 108 | $this->pool->attach($conn); 109 | return ($conn === false) ? Promise\reject(new \Exception(mysqli_connect_error())) : Promise\resolve($conn); 110 | } 111 | 112 | 113 | function freeConnection(\mysqli $conn) { 114 | if (!empty($this->waiting)) { 115 | $deferred = array_shift($this->waiting); 116 | $deferred->resolve($conn); 117 | } else { 118 | return $this->idle->attach($conn); 119 | } 120 | } 121 | } 122 | --------------------------------------------------------------------------------