├── .gitignore ├── src ├── MysqlException.php ├── MysqlDriver.php ├── MysqlResult.php ├── MysqlConnection.php └── MysqlStatement.php ├── composer.json └── examples └── simple-query.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | -------------------------------------------------------------------------------- /src/MysqlException.php: -------------------------------------------------------------------------------- 1 | getMessage(), null, $exception->getCode(), $exception); 12 | } 13 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "amphp/mysql-dbal", 3 | "type": "library", 4 | "require": { 5 | "amphp/amp": "^3", 6 | "amphp/mysql": "^3", 7 | "doctrine/dbal": "^3" 8 | }, 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Niklas Keller", 13 | "email": "me@kelunik.com" 14 | } 15 | ], 16 | "minimum-stability": "dev", 17 | "autoload": { 18 | "psr-4": { 19 | "Amp\\Mysql\\DBAL\\": "src" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/simple-query.php: -------------------------------------------------------------------------------- 1 | MysqlDriver::class, 10 | 'user' => 'test', 11 | 'password' => 'test', 12 | 'dbname' => 'test', 13 | ]); 14 | 15 | $conn->executeStatement('CREATE TABLE IF NOT EXISTS articles (id int, headline varchar(100))'); 16 | 17 | $conn->insert('articles', [ 18 | 'headline' => 'Foobar' 19 | ]); 20 | 21 | $sql = "SELECT * FROM articles"; 22 | $stmt = $conn->executeQuery($sql); // Simple, but has several drawbacks 23 | 24 | while (($row = $stmt->fetchAssociative()) !== false) { 25 | echo $row['headline'] . PHP_EOL; 26 | } -------------------------------------------------------------------------------- /src/MysqlDriver.php: -------------------------------------------------------------------------------- 1 | connect($config)); 28 | } catch (\Throwable $e) { 29 | throw MysqlException::new($e); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/MysqlResult.php: -------------------------------------------------------------------------------- 1 | result = $result; 16 | } 17 | 18 | public function fetchNumeric(): array|false 19 | { 20 | $row = $this->fetchAssociative(); 21 | if ($row === false) { 22 | return false; 23 | } 24 | 25 | return \array_values($row); 26 | } 27 | 28 | public function fetchAssociative(): array|false 29 | { 30 | /** @noinspection ProperNullCoalescingOperatorUsageInspection */ 31 | return $this->result->continue() ?? false; 32 | } 33 | 34 | public function fetchOne() 35 | { 36 | return FetchUtils::fetchOne($this); 37 | } 38 | 39 | public function fetchAllNumeric(): array 40 | { 41 | return FetchUtils::fetchAllNumeric($this); 42 | } 43 | 44 | public function fetchAllAssociative(): array 45 | { 46 | return FetchUtils::fetchAllAssociative($this); 47 | } 48 | 49 | public function fetchFirstColumn(): array 50 | { 51 | return FetchUtils::fetchFirstColumn($this); 52 | } 53 | 54 | public function rowCount(): int 55 | { 56 | return $this->result->getRowCount(); 57 | } 58 | 59 | public function columnCount(): int 60 | { 61 | return \count($this->result->getFields()); 62 | } 63 | 64 | public function free(): void 65 | { 66 | $this->result->dispose(); 67 | } 68 | } -------------------------------------------------------------------------------- /src/MysqlConnection.php: -------------------------------------------------------------------------------- 1 | connection = $connection; 23 | $this->resultListener = fn(SqlResult $result) => $this->lastInsertId = $result->getLastInsertId(); 24 | } 25 | 26 | public function prepare(string $sql): Statement 27 | { 28 | try { 29 | return new MysqlStatement($this->connection->prepare($sql), $this->resultListener); 30 | } catch (\Throwable $e) { 31 | throw MysqlException::new($e); 32 | } 33 | } 34 | 35 | public function query(string $sql): Result 36 | { 37 | try { 38 | $result = $this->connection->query($sql); 39 | ($this->resultListener)($result); 40 | 41 | return new MysqlResult($result); 42 | } catch (\Throwable $e) { 43 | throw MysqlException::new($e); 44 | } 45 | } 46 | 47 | public function quote($value, $type = ParameterType::STRING) 48 | { 49 | throw new \Error("Not implemented, use prepared statements"); 50 | } 51 | 52 | public function exec(string $sql): int 53 | { 54 | try { 55 | $result = $this->connection->execute($sql); 56 | ($this->resultListener)($result); 57 | 58 | return $result->getRowCount(); 59 | } catch (\Throwable $e) { 60 | throw MysqlException::new($e); 61 | } 62 | } 63 | 64 | public function lastInsertId($name = null) 65 | { 66 | return $this->lastInsertId; 67 | } 68 | 69 | public function beginTransaction(): bool 70 | { 71 | try { 72 | await(discard($this->connection->query("START TRANSACTION"))); 73 | 74 | return true; 75 | } catch (\Throwable $e) { 76 | throw MysqlException::new($e); 77 | } 78 | } 79 | 80 | public function commit(): bool 81 | { 82 | try { 83 | await(discard($this->connection->query("COMMIT"))); 84 | 85 | return true; 86 | } catch (\Throwable $e) { 87 | throw MysqlException::new($e); 88 | } 89 | } 90 | 91 | public function rollBack(): bool 92 | { 93 | try { 94 | await(discard($this->connection->query("ROLLBACK"))); 95 | 96 | return true; 97 | } catch (\Throwable $e) { 98 | throw MysqlException::new($e); 99 | } 100 | } 101 | 102 | public function getServerVersion(): string 103 | { 104 | return $this->query("SELECT @@version")->fetchOne(); 105 | } 106 | } -------------------------------------------------------------------------------- /src/MysqlStatement.php: -------------------------------------------------------------------------------- 1 | null, 15 | ParameterType::INTEGER => null, 16 | ParameterType::STRING => null, 17 | ParameterType::ASCII => null, 18 | ParameterType::BINARY => null, 19 | ParameterType::LARGE_OBJECT => null, 20 | ParameterType::BOOLEAN => null, 21 | ]; 22 | 23 | private SqlStatement $statement; 24 | private \Closure $resultListener; 25 | 26 | private array $values = []; 27 | private array $types = []; 28 | 29 | public function __construct(SqlStatement $statement, callable $resultListener) 30 | { 31 | $this->statement = $statement; 32 | $this->resultListener = $resultListener instanceof \Closure 33 | ? $resultListener 34 | : \Closure::fromCallable($resultListener); 35 | } 36 | 37 | public function bindValue($param, $value, $type = ParameterType::STRING): bool 38 | { 39 | if (!isset(self::PARAM_TYPES[$type])) { 40 | throw Exception\UnknownParameterType::new($type); 41 | } 42 | 43 | $key = \is_int($param) ? $param - 1 : $param; 44 | 45 | $this->values[$key] = $this->convertValue($value, $type); 46 | 47 | return true; 48 | } 49 | 50 | public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null): bool 51 | { 52 | if (!isset(self::PARAM_TYPES[$type])) { 53 | throw Exception\UnknownParameterType::new($type); 54 | } 55 | 56 | $key = \is_int($param) ? $param - 1 : $param; 57 | 58 | $this->values[$key] = &$variable; 59 | $this->types[$key] = $type; 60 | 61 | return true; 62 | } 63 | 64 | public function execute($params = null): Result 65 | { 66 | $values = $this->values; 67 | 68 | if ($params !== null) { 69 | foreach ($params as $param) { 70 | $values[] = $param; 71 | } 72 | } 73 | 74 | // Convert references to correct types 75 | foreach ($this->types as $param => $type) { 76 | $values[$param] = $this->convertValue($values[$param], $type); 77 | } 78 | 79 | try { 80 | $result = $this->statement->execute($values); 81 | ($this->resultListener)($result); 82 | 83 | return new MysqlResult($result); 84 | } catch (\Throwable $e) { 85 | throw MysqlException::new($e); 86 | } 87 | } 88 | 89 | private function convertValue($value, int $type): null|bool|int|string 90 | { 91 | return match ($type) { 92 | ParameterType::NULL => null, 93 | ParameterType::INTEGER => (int) $value, 94 | ParameterType::ASCII, ParameterType::LARGE_OBJECT, ParameterType::BINARY, ParameterType::STRING => (string) $value, 95 | ParameterType::BOOLEAN => (bool) $value, 96 | default => throw Exception\UnknownParameterType::new($type), 97 | }; 98 | } 99 | } --------------------------------------------------------------------------------