query('SELECT * FROM users WHERE role > ?', [0])['rows']; * $id = $db->query('INSERT INTO users (name) VALUES (?)', ['諏訪子'])['insertId']; // MySQL・SQLite(PostgreSQLはRETURNINGを使う) * $db->transaction(function(Db $db): void { * $db->query('UPDATE users SET points = points - ? WHERE id = ?', [100, 1]); * $db->query('UPDATE users SET points = points + ? WHERE id = ?', [100, 2]); * }); */ class Db { private const SUPPORTED = [ 'mysql' => Mysql::class, 'mariadb' => Mysql::class, 'pgsql' => Pgsql::class, 'postgres' => Pgsql::class, 'postgresql' => Pgsql::class, 'sqlite' => Sqlite::class, 'sqlite3' => Sqlite::class, ]; public string $driverName; private DbInterface $driver; /** * コンストラクタ * * @param array|null $info 設定(省略するとDBINFO)。driver(mysql等)、その他はドライバ毎 * @throws \Exception $infoを省略し、DB_ENABLEDが無効な場合 * @throws \RuntimeException 未対応のドライバの場合 */ public function __construct(?array $info = null) { if ($info === null && !(defined('DB_ENABLED') && DB_ENABLED)) { throw new \Exception('データベースは無効です。'); } $info ??= DBINFO; $this->driverName = strtolower((string)($info['driver'] ?? 'mysql')); $class = self::SUPPORTED[$this->driverName] ?? null; if (!$class) throw new \RuntimeException("未対応のデータベース: {$this->driverName}"); $this->driver = new $class($info); } /** * ドライバに対応しているかどうか * * @param string $driver ドライバ名 * @return bool */ public static function isSupported(string $driver): bool { return isset(self::SUPPORTED[strtolower($driver)]); } /** * ドライバ(ドライバ毎の機能用、例:MysqlのsetDebug()) * * @return DbInterface */ public function driver(): DbInterface { return $this->driver; } public function connect(): bool { return $this->driver->connect(); } public function close(): void { $this->driver->close(); } public function isConnected(): bool { return $this->driver->isConnected(); } public function ping(): bool { return $this->driver->ping(); } /** * SQLを実行する * * パラメータがある場合はプリペアドステートメント(プレースホルダは「?」)で実行するので、 * 値をエスケープする必要はない。 * * @param string $sql SQL * @param array $params パラメータ(順番通り) * @return array 結果(fields、rows、affectedRows、insertId、warningCount等、DbInterfaceを参照) * @throws Db\DbException 失敗した場合 */ public function query(string $sql, array $params = []): array { $this->driver->connect(); if (empty($params)) return $this->driver->query($sql); $id = $this->driver->prepare($sql); try { return $this->driver->execute($id, $params); } finally { $this->driver->demolish($id); } } /** * 何回も実行するSQLを準備する(終わったらdemolish()) * * @param string $sql SQL * @return int ステートメントID * @throws Db\DbException */ public function prepare(string $sql): int { $this->driver->connect(); return $this->driver->prepare($sql); } public function execute(int $statementId, array $params = []): array { return $this->driver->execute($statementId, $params); } public function demolish(int $statementId): bool { return $this->driver->demolish($statementId); } /** * トランザクションの中で実行する * * 例外が出たらロールバックして、その例外を投げ直す。それ以外はコミットする。 * * @param callable $fn function(Db $db): mixed * @return mixed $fnの戻り値 * @throws \Throwable $fnの例外 */ public function transaction(callable $fn): mixed { $this->beginTransaction(); try { $res = $fn($this); } catch (\Throwable $e) { if ($this->driver->isConnected()) { try { $this->driver->rollback(); } catch (\Throwable $ignore) { // 接続が切れた場合等:元の例外を優先する } } throw $e; } $this->driver->commit(); return $res; } public function beginTransaction(): bool { $this->driver->connect(); return $this->driver->beginTransaction(); } public function commit(): bool { return $this->driver->commit(); } public function rollback(): bool { return $this->driver->rollback(); } public function inTransaction(): bool { return $this->driver->inTransaction(); } public function quoteIdentifier(string $name): string { return $this->driver->quoteIdentifier($name); } }