🙆
PHPでSQLアダプターを作りたい
初めに
PHPでSQLとかのコードを書いてると、ふと思いませんか?
「クラス化したSQLのやつ使いたいなぁ」って。
ただMySQLとSQLiteの書き方って微妙に違うし、PDOを使うとしても「"mysql:host=...;dbname=...;charset=..."」とかで汚くなりますよね...
なので、今回はそのアダプターを作っていきます!
どういうものにするのか
- 書き方はPDOとほぼ同等にする
- 接続設定は配列で
- MySQLとSQLiteの2つ対応したい
これらを踏まえて、まず接続部分を作っていきましょう!
DbFactory Class
class DbFactory {
public static function createAdapter(array $config): DbAdapterInterface {
if(!isset($config["type"])) {
throw new InvalidArgumentException("Database type not specified in config.");
}
switch($config["type"]) {
case "mysql":
return new MySqlAdapter([
"host" => $config["host"] ?? "",
"name" => $config["name"] ?? "",
"user" => $config["user"] ?? "",
"pass" => $config["pass"] ?? "",
]);
case "sqlite":
return new SqliteAdapter([
"path" => $config["path"] ?? "",
]);
default:
throw new Exception("Unsupported database type: ".$config["type"]);
}
}
}
- typeがなかったらエラーを吐く
- typeがmysql | sqlite以外だったらエラーを吐く
- ??を使って初期値設定
- 型定義を使用して厳密にする
SQLiteAdapter
class SqliteAdapter implements DbAdapterInterface {
private PDO $pdo;
public function __construct(array $config) {
$path = $config["path"];
$dsn = "sqlite:{$path}";
try {
$this->pdo = new PDO($dsn);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
$this->pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE,PDO::FETCH_ASSOC);
} catch(PDOException $e) {
error_log("SQLite Connection Failed: ".$e->getMessage());
throw $e;
}
}
public function prepare(string $sql): DbStatementInterface {
$stmt = $this->pdo->prepare($sql);
return new PDOStatementWrapper($stmt);
}
public function query(string $sql): DbStatementInterface {
$stmt = $this->pdo->query($sql);
return new PDOStatementWrapper($stmt);
}
public function exec(string $sql): int {
return $this->pdo->exec($sql);
}
public function beginTransaction(): bool {
return $this->pdo->beginTransaction();
}
public function commit(): bool {
return $this->pdo->commit();
}
public function rollBack(): bool {
return $this->pdo->rollBack();
}
public function lastInsertId(?string $name = null): string|false {
return $this->pdo->lastInsertId($name);
}
public function getError(): string {
$errorInfo = $this->pdo->errorInfo();
return $errorInfo[2] ?? "";
}
}
MySqlAdapter
class MySqlAdapter implements DbAdapterInterface {
private PDO $pdo;
public function __construct(array $config) {
$host = $config["host"];
$dbname = $config["name"];
$user = $config["user"];
$pass = $config["pass"];
$charset = "utf8mb4";
$dsn = "mysql:host={$host};dbname={$dbname};charset={$charset}";
try {
$this->pdo = new PDO($dsn,$user,$pass);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
$this->pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE,PDO::FETCH_ASSOC);
} catch(PDOException $e) {
error_log("MySQL Connection Failed: ".$e->getMessage());
throw $e;
}
}
public function prepare(string $sql): DbStatementInterface {
$stmt = $this->pdo->prepare($sql);
return new PDOStatementWrapper($stmt);
}
public function query(string $sql): DbStatementInterface {
$stmt = $this->pdo->query($sql);
return new PDOStatementWrapper($stmt);
}
public function exec(string $sql): int {
return $this->pdo->exec($sql);
}
public function beginTransaction(): bool {
return $this->pdo->beginTransaction();
}
public function commit(): bool {
return $this->pdo->commit();
}
public function rollBack(): bool {
return $this->pdo->rollBack();
}
public function lastInsertId(?string $name = null): string|false {
return $this->pdo->lastInsertId($name);
}
public function getError(): string {
$errorInfo = $this->pdo->errorInfo();
return $errorInfo[2] ?? "";
}
}
Interface / PDOStatementWrapper
// データベースアダプターインターフェース
interface DbAdapterInterface {
public function prepare(string $sql): DbStatementInterface;
public function query(string $sql): DbStatementInterface;
public function exec(string $sql): int;
public function beginTransaction(): bool;
public function commit(): bool;
public function rollBack(): bool;
public function lastInsertId(?string $name = null): string|false;
public function getError(): string;
}
// プリペアドステートメントインターフェース
interface DbStatementInterface {
public function execute(?array $params = null): bool;
public function fetch(?int $fetchMode = null): array|false;
public function fetchAll(?int $fetchMode = null): array;
public function bindValue(string $param,mixed $value,int $type = PDO::PARAM_STR): bool;
public function rowCount(): int;
public function closeCursor(): bool;
}
// 共通のPDOステートメントラッパー
class PDOStatementWrapper implements DbStatementInterface {
private PDOStatement $stmt;
public function __construct(PDOStatement $stmt) {
$this->stmt = $stmt;
}
public function execute(?array $params = null): bool {
return $this->stmt->execute($params);
}
public function fetch(?int $fetchMode = null): array|false {
return $this->stmt->fetch($fetchMode ?? PDO::FETCH_ASSOC);
}
public function fetchAll(?int $fetchMode = null): array {
return $this->stmt->fetchAll($fetchMode ?? PDO::FETCH_DEFAULT);
}
public function bindValue(string|int $param,mixed $value,int $type = PDO::PARAM_STR): bool {
return $this->stmt->bindValue($param,$value,$type);
}
public function rowCount(): int {
return $this->stmt->rowCount();
}
public function closeCursor(): bool {
return $this->stmt->closeCursor();
}
}
最後に
結構説明端折っていますが、MySQL / SQLiteは同じインターフェースを使用しています。
そのため、設定部分(DbFactory)をMySQL用・SQLite用とかに分けるだけで、
既存のコード変更がいらないのが特徴です。
GitHubにソースコードを載せてあります。気になる方はぜひご確認ください。
Discussion