diff --git a/README-zh.md b/README-zh.md index e10a259..c703ea9 100644 --- a/README-zh.md +++ b/README-zh.md @@ -26,7 +26,6 @@ Swoole底层实现协程调度, **业务层无需感知**, 开发者可以无感 除了构造函数有所不同, 其它使用方法完全一样 - #### query ```php @@ -56,6 +55,7 @@ var_dump($pdo_both === $swpdo_both); var_dump($pdo_assoc === $swpdo_assoc); var_dump($pdo_object == $swpdo_object); var_dump($pdo_number === $swpdo_number); +//output: true true true true ``` #### prepare @@ -64,16 +64,70 @@ var_dump($pdo_number === $swpdo_number); //PDO $pdo = new \PDO(...$options); $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); //strong type -$statement = $pdo->prepare('select * from `user`'); +$statement = $pdo->prepare($sql); $statement->execute(); $pdo_fetch = $statement->fetch(\PDO::FETCH_ASSOC); +$statement->execute(); +$pdo_fetch_all = $statement->fetchAll(); +$statement->execute(); +$pdo_fetch_all_column = $statement->fetchAll(\PDO::FETCH_COLUMN, 1); +$statement->execute(); +$pdo_fetch_column = $statement->fetchColumn(); +$statement->execute(); -//PDO +//SwPDO $swpdo = SwPDO::construct(...$options); -$statement = $swpdo->prepare('select * from `user`'); +$statement = $swpdo->prepare($sql); $statement->execute(); $swpdo_fetch = $statement->fetch(\PDO::FETCH_ASSOC); +$statement->execute(); +$swpdo_fetch_all = $statement->fetchAll(); +$statement->execute(); +$swpdo_fetch_all_column = $statement->fetchAll(\PDO::FETCH_COLUMN, 1); +$statement->execute(); +$swpdo_fetch_column = $statement->fetchColumn(); +$statement->execute(); + +var_dump($pdo_fetch === $swpdo_fetch); //true +var_dump($pdo_fetch_all === $swpdo_fetch_all); //true +var_dump($pdo_fetch_all_column === $swpdo_fetch_all_column); //true +var_dump($pdo_fetch_column === $swpdo_fetch_column); //true +``` + +### bind + +```php +//PDO +$pdo = new \PDO(...$options); +$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); //strong type +$statement = $pdo->prepare($sql); +$statement->execute(['id' => 1]); +$pdo_bind_exec = $statement->fetch(\PDO::FETCH_ASSOC); + +$statement->bindValue(':id', 1); +$statement->execute(); +$pdo_bind_val = $statement->fetch(\PDO::FETCH_ASSOC); + +$statement->bindParam(':id', $id); +$statement->execute(); +$pdo_bind_param = $statement->fetch(\PDO::FETCH_ASSOC); + +//SwPDO +$swpdo = SwPDO::construct(...$options); +$statement = $swpdo->prepare($sql); +$statement->execute(['id' => 1]); +$swpdo_bind_exec = $statement->fetch(\PDO::FETCH_ASSOC); + +$statement->bindValue(':id', 1); +$statement->execute(); +$swpdo_bind_val = $statement->fetch(\PDO::FETCH_ASSOC); + +$statement->bindParam(':id', $id); +$statement->execute(); +$swpdo_bind_param = $statement->fetch(\PDO::FETCH_ASSOC); + +var_dump($pdo_bind_exec === $swpdo_bind_exec); //true +var_dump($pdo_bind_val === $swpdo_bind_val); //true +var_dump($pdo_bind_param === $swpdo_bind_param); //true +``` -var_dump($pdo_fetch === $swpdo_fetch); -//output: true -``` \ No newline at end of file diff --git a/README.md b/README.md index d959df0..85e2c81 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ Traditional PDO to Swoole Coroutine migration plan without cost. +[中文文档](README-zh.md) +
## Coroutine @@ -64,17 +66,70 @@ var_dump($pdo_number === $swpdo_number); //PDO $pdo = new \PDO(...$options); $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); //strong type -$statement = $pdo->prepare('select * from `user`'); +$statement = $pdo->prepare($sql); $statement->execute(); $pdo_fetch = $statement->fetch(\PDO::FETCH_ASSOC); +$statement->execute(); +$pdo_fetch_all = $statement->fetchAll(); +$statement->execute(); +$pdo_fetch_all_column = $statement->fetchAll(\PDO::FETCH_COLUMN, 1); +$statement->execute(); +$pdo_fetch_column = $statement->fetchColumn(); +$statement->execute(); -//PDO +//SwPDO $swpdo = SwPDO::construct(...$options); -$statement = $swpdo->prepare('select * from `user`'); +$statement = $swpdo->prepare($sql); $statement->execute(); $swpdo_fetch = $statement->fetch(\PDO::FETCH_ASSOC); +$statement->execute(); +$swpdo_fetch_all = $statement->fetchAll(); +$statement->execute(); +$swpdo_fetch_all_column = $statement->fetchAll(\PDO::FETCH_COLUMN, 1); +$statement->execute(); +$swpdo_fetch_column = $statement->fetchColumn(); +$statement->execute(); + +var_dump($pdo_fetch === $swpdo_fetch); //true +var_dump($pdo_fetch_all === $swpdo_fetch_all); //true +var_dump($pdo_fetch_all_column === $swpdo_fetch_all_column); //true +var_dump($pdo_fetch_column === $swpdo_fetch_column); //true +``` + +### bind + +```php +//PDO +$pdo = new \PDO(...$options); +$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); //strong type +$statement = $pdo->prepare($sql); +$statement->execute(['id' => 1]); +$pdo_bind_exec = $statement->fetch(\PDO::FETCH_ASSOC); + +$statement->bindValue(':id', 1); +$statement->execute(); +$pdo_bind_val = $statement->fetch(\PDO::FETCH_ASSOC); + +$statement->bindParam(':id', $id); +$statement->execute(); +$pdo_bind_param = $statement->fetch(\PDO::FETCH_ASSOC); + +//SwPDO +$swpdo = SwPDO::construct(...$options); +$statement = $swpdo->prepare($sql); +$statement->execute(['id' => 1]); +$swpdo_bind_exec = $statement->fetch(\PDO::FETCH_ASSOC); + +$statement->bindValue(':id', 1); +$statement->execute(); +$swpdo_bind_val = $statement->fetch(\PDO::FETCH_ASSOC); + +$statement->bindParam(':id', $id); +$statement->execute(); +$swpdo_bind_param = $statement->fetch(\PDO::FETCH_ASSOC); -var_dump($pdo_fetch === $swpdo_fetch); -//output: true +var_dump($pdo_bind_exec === $swpdo_bind_exec); //true +var_dump($pdo_bind_val === $swpdo_bind_val); //true +var_dump($pdo_bind_param === $swpdo_bind_param); //true ``` diff --git a/examples/bind.php b/examples/bind.php new file mode 100644 index 0000000..4db7f47 --- /dev/null +++ b/examples/bind.php @@ -0,0 +1,53 @@ + + * Date: 2018/4/7 下午11:53 + */ + +use Swlib\SwPDO; + +require __DIR__ . '/../vendor/autoload.php'; + +go(function () { + $options = [ + 'mysql:host=127.0.0.1;dbname=test;charset=UTF8', + 'root', + 'root' + ]; + $sql = 'select * from `user` where id=:id'; + $id = 1; + + //PDO + $pdo = new \PDO(...$options); + $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); //strong type + $statement = $pdo->prepare($sql); + $statement->execute(['id' => 1]); + $pdo_bind_exec = $statement->fetch(\PDO::FETCH_ASSOC); + + $statement->bindValue(':id', 1); + $statement->execute(); + $pdo_bind_val = $statement->fetch(\PDO::FETCH_ASSOC); + + $statement->bindParam(':id', $id); + $statement->execute(); + $pdo_bind_param = $statement->fetch(\PDO::FETCH_ASSOC); + + //SwPDO + $swpdo = SwPDO::construct(...$options); + $statement = $swpdo->prepare($sql); + $statement->execute(['id' => 1]); + $swpdo_bind_exec = $statement->fetch(\PDO::FETCH_ASSOC); + + $statement->bindValue(':id', 1); + $statement->execute(); + $swpdo_bind_val = $statement->fetch(\PDO::FETCH_ASSOC); + + $statement->bindParam(':id', $id); + $statement->execute(); + $swpdo_bind_param = $statement->fetch(\PDO::FETCH_ASSOC); + + var_dump($pdo_bind_exec === $swpdo_bind_exec); //true + var_dump($pdo_bind_val === $swpdo_bind_val); //true + var_dump($pdo_bind_param === $swpdo_bind_param); //true +}); \ No newline at end of file diff --git a/examples/prepare.php b/examples/prepare.php index a196f9c..53cd1cb 100644 --- a/examples/prepare.php +++ b/examples/prepare.php @@ -11,23 +11,41 @@ go(function () { $options = [ - 'mysql:host=127.0.0.1;port=9502;dbname=custed;charset=UTF8', - 'php', - 'justfortest' + 'mysql:host=127.0.0.1;dbname=test;charset=UTF8', + 'root', + 'root' ]; + $sql = 'select * from `user`'; //PDO $pdo = new \PDO(...$options); $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); //strong type - $statement = $pdo->prepare('select * from `user`'); + $statement = $pdo->prepare($sql); $statement->execute(); $pdo_fetch = $statement->fetch(\PDO::FETCH_ASSOC); + $statement->execute(); + $pdo_fetch_all = $statement->fetchAll(); + $statement->execute(); + $pdo_fetch_all_column = $statement->fetchAll(\PDO::FETCH_COLUMN, 1); + $statement->execute(); + $pdo_fetch_column = $statement->fetchColumn(); + $statement->execute(); - //PDO + //SwPDO $swpdo = SwPDO::construct(...$options); - $statement = $swpdo->prepare('select * from `user`'); + $statement = $swpdo->prepare($sql); $statement->execute(); $swpdo_fetch = $statement->fetch(\PDO::FETCH_ASSOC); + $statement->execute(); + $swpdo_fetch_all = $statement->fetchAll(); + $statement->execute(); + $swpdo_fetch_all_column = $statement->fetchAll(\PDO::FETCH_COLUMN, 1); + $statement->execute(); + $swpdo_fetch_column = $statement->fetchColumn(); + $statement->execute(); var_dump($pdo_fetch === $swpdo_fetch); + var_dump($pdo_fetch_all === $swpdo_fetch_all); + var_dump($pdo_fetch_all_column === $swpdo_fetch_all_column); + var_dump($pdo_fetch_column === $swpdo_fetch_column); }); \ No newline at end of file diff --git a/examples/query.php b/examples/query.php index 5917f1b..9cd622d 100644 --- a/examples/query.php +++ b/examples/query.php @@ -32,8 +32,8 @@ $swpdo_object = $swpdo->query($sql)->fetch(\PDO::FETCH_OBJ); $swpdo_number = $swpdo->query($sql)->fetch(\PDO::FETCH_NUM); - var_dump($pdo_both === $swpdo_both); - var_dump($pdo_assoc === $swpdo_assoc); - var_dump($pdo_object == $swpdo_object); - var_dump($pdo_number === $swpdo_number); + var_dump($pdo_both === $swpdo_both); //true + var_dump($pdo_assoc === $swpdo_assoc); //true + var_dump($pdo_object == $swpdo_object); //true + var_dump($pdo_number === $swpdo_number); //true }); \ No newline at end of file diff --git a/src/Mysql.php b/src/Mysql.php index bb4680b..3d61943 100644 --- a/src/Mysql.php +++ b/src/Mysql.php @@ -28,9 +28,14 @@ class Mysql public static function construct(array $options) { $mysql = new self(); - if (isset($options['dbname'])) { - $options['database'] = $options['dbname']; - unset($options['dbname']); + static $keyMap = [ + 'dbname' => 'database' + ]; + foreach ($keyMap as $pdoKey => $swpdoKey) { + if (isset($options[$pdoKey])) { + $options[$swpdoKey] = $options[$pdoKey]; + unset($options[$pdoKey]); + } } $options = $options + self::$default_options; $mysql->client = new \Swoole\Coroutine\Mysql(); @@ -89,10 +94,30 @@ public function query(string $statement, float $timeout = 1.000) return new MysqlStatement($this, $statement, ['timeout' => $timeout]); } + private function rewriteToPosition(string $statement) + { + + } + public function prepare(string $statement, array $driver_options = []) { + //rewriting :name to ? style. + if (strpos($statement, ':') !== false) { + $i = 0; + $bindKeyMap = []; + $statement = preg_replace_callback( + '/:(\w+)\b/', + function ($matches) use (&$i, &$bindKeyMap) { + $bindKeyMap[$matches[1]] = $i++; + + return '?'; + }, + $statement + ); + } $stmt_obj = $this->client->prepare($statement); if ($stmt_obj) { + $stmt_obj->bindKeyMap = $bindKeyMap ?? []; return new MysqlStatement($this, $stmt_obj, $driver_options); } else { return false; diff --git a/src/MysqlStatement.php b/src/MysqlStatement.php index a317b3a..e9e5ad0 100644 --- a/src/MysqlStatement.php +++ b/src/MysqlStatement.php @@ -45,26 +45,133 @@ public function rowCount() return $this->statement->affected_rows; } + public function bindParam($parameter, &$variable) + { + if (!is_string($parameter) && !is_int($parameter)) { + return false; + } + $parameter = ltrim($parameter, ':'); + $this->bindMap[$parameter] = &$variable; + + return true; + } + + public function bindValue($parameter, $variable) + { + if (!is_string($parameter) && !is_int($parameter)) { + return false; + } + if (is_object($variable)) { + if (!method_exists($variable, '__toString')) { + return false; + } else { + $variable = (string)$variable; + } + } + $parameter = ltrim($parameter, ':'); + $this->bindMap[$parameter] = $variable; + + return true; + } + + private function afterExecute() + { + $this->cursor = -1; + $this->bindMap = []; + } + public function execute(array $input_parameters = [], ?float $timeout = null) { + if (!empty($input_parameters)) { + foreach ($input_parameters as $key => $value) { + $this->bindParam($key, $value); + } + } + $input_parameters = []; + if (!empty($this->statement->bindKeyMap)) { + foreach ($this->statement->bindKeyMap as $name_key => $num_key) { + $input_parameters[$num_key] = $this->bindMap[$name_key]; + } + } else { + $input_parameters = $this->bindMap; + } $r = $this->statement->execute($input_parameters, $timeout ?? $this->timeout); - $failed = $r === false; - $this->result_set = $failed ? [] : $r; + $this->result_set = ($ok = $r !== false) ? $r : []; + $this->afterExecute(); - return !$failed; + return $ok; } private function __executeWhenStringQueryEmpty() { if (is_string($this->statement) && empty($this->result_set)) { $this->result_set = $this->parent->client->query($this->statement); + $this->afterExecute(); } } + private static function transBoth($raw_data) + { + $temp = []; + foreach ($raw_data as $row) { + $row_set = []; + $i = 0; + foreach ($row as $key => $value) { + $row_set[$key] = $value; + $row_set[$i++] = $value; + } + $temp[] = $row_set; + } + + return $temp; + } + + private static function transStyle( + $raw_data, + int $fetch_style = \PDO::FETCH_BOTH, + $fetch_argument = null, + array $ctor_args = [] + ) { + if (!is_array($raw_data)) { + return false; + } + if (empty($raw_data)) { + return $raw_data; + } + $result_set = []; + switch ($fetch_style) { + case \PDO::FETCH_BOTH: + $result_set = self::transBoth($raw_data); + break; + case \PDO::FETCH_COLUMN: + $result_set = array_column( + is_numeric($fetch_argument) ? self::transBoth($raw_data) : $raw_data, + $fetch_argument + ); + break; + case \PDO::FETCH_OBJ: + foreach ($raw_data as $row) { + $result_set[] = (object)$row; + } + break; + case \PDO::FETCH_NUM: + foreach ($raw_data as $row) { + $result_set[] = array_values($row); + } + break; + case \PDO::FETCH_ASSOC: + default: + return $raw_data; + } + + return $result_set; + } + public function fetch( int $fetch_style = \PDO::FETCH_BOTH, int $cursor_orientation = \PDO::FETCH_ORI_NEXT, - int $cursor_offset = 0 + int $cursor_offset = 0, + $fetch_argument = null ) { $this->__executeWhenStringQueryEmpty(); switch ($cursor_orientation) { @@ -78,37 +185,43 @@ public function fetch( default: $this->cursor++; } - $temp = $this->result_set[$this->cursor] ?? false; - switch ($fetch_style) { - case \PDO::FETCH_BOTH: - $result = []; - $i = 0; - foreach ($temp as $key => $value) { - $result[$key] = $value; - $result[$i++] = $value; - } - break; - case \PDO::FETCH_OBJ: - $result = (object)$temp; - break; - case \PDO::FETCH_NUM: - $result = array_values($temp); - break; - case \PDO::FETCH_ASSOC: - default: - $result = &$temp; - break; + + if (isset($this->result_set[$this->cursor])) { + $result = $this->result_set[$this->cursor]; + unset($this->result_set[$this->cursor]); + } else { + $result = false; } - return $result; + if (empty($result)) { + return $result; + } else { + return self::transStyle([$result], $fetch_style, $fetch_argument)[0]; + } } - public function fetchAll() + /** + * Returns a single column from the next row of a result set or FALSE if there are no more rows. + * + * @param int $column_number + * 0-indexed number of the column you wish to retrieve from the row. + * If no value is supplied, PDOStatement::fetchColumn() fetches the first column. + * + * @return bool|mixed + */ + public function fetchColumn(int $column_number = 0) { $this->__executeWhenStringQueryEmpty(); - - return $this->result_set; + return $this->fetch(\PDO::FETCH_COLUMN, \PDO::FETCH_ORI_NEXT, 0, $column_number); } + public function fetchAll(int $fetch_style = \PDO::FETCH_BOTH, $fetch_argument = null, array $ctor_args = []) + { + $this->__executeWhenStringQueryEmpty(); + $result_set = self::transStyle($this->result_set, $fetch_style, $fetch_argument, $ctor_args); + $this->result_set = []; + + return $result_set; + } } \ No newline at end of file