SQLiteをPHPのPDOで使う
PHPでPDOを使ってSQLite3データベースを操作してみました。
SQLite3を使用する環境
以前使用したSQLite3クラスはSQLite3データベース専用で、それ以外に使用することが出来ません。PDO(PHP Data Objects)はデータアクセスを抽象化し、他の複数のデータベースを共通で操作することが出来ます。
今回は「Ubuntu 20.04.2 LTS」上のDockerコンテナで操作しました。 使用したイメージは"php:7.4.21-apache"です。
- PHP Version 7.4.21
- SQLite Library 3.27.2
- Apache Version Apache/2.4.38 (Debian)
PHPのDockerコンテナはコマンドラインで以下のように実行しています。
$ docker run -dit --name コンテナ名 -p 8080:80 -v "$PWD":/var/www/html/ php:7.4.21-apache
ディレクトリ構成は以下のようにし、“sqlite3pdo.php"ファイルをブラウザから呼び出して実行しました。
sqlite3pdo
├ db/
│ └ database.db ◀ PHPで作成するので初期状態ではファイルは存在しません
└ web/
└ sqlite3pdo.php
データベースを作成する
データベース操作のためのPDOインスタンスを作成します。
SQLite3
クラスのコンストラクタにデータベースファイルを指定することでSQLite3オブジェクトを作成し、SQLite3データベースをオープンします。
SQLite3データベースファイルがない場合、新規作成されます。
// PDOコンストラクタ
public PDO::__construct(
string $dsn,
?string $username = null,
?string $password = null,
?array $options = null
)
// SQLite3データベースファイル名
$dbname = '../db/database.db';
try {
// データベースを操作するオプションを設定
// PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
// エラー時に例外を投げる
// これによってif文で異常を判定する必要がなくなります
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
];
// データベースを開く
// データベースファイルが存在しない場合は新規に作成されます
// PDO::__construct データベースを操作する PDO インスタンスを生成する
$pdo = new PDO('sqlite:'.$dbname, null, null, $options);
echo 'データベース'.$dbname.'を作成・オープンしました。';
} catch (Exception $e) {
echo 'データベース'.$dbname.'の作成・オープンに失敗しました。';
echo $e->getMessage();
}
ブラウザのアドレスバーに"http://localhost:8080/sqlite3pdo/web/sqlite3pdo.php"を入力するとPHPプログラムが実行され、データベースファイルが作成されます。
sqlite3pdo
├ db/
│ └ database.db ◀ 作成されたファイル
└ web/
└ sqlite3pdo.php
0バイトの"database.db"ファイルが作成されました。
テーブルを作成する
テーブルを作成するにはSQLを使用します。
PDO::exec SQLを実行し、変更があった行数が返ります
public PDO::exec(string $statement): int|false
・・・略・・・
// テーブルを作成するSQL
$createTable = 'CREATE TABLE IF NOT EXISTS test_table (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
memo TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
)';
try {
// PDO::exec SQLを実行し、変更があった行数が返ります
$createRet = $pdo->exec($createTable);
echo 'テーブルを作成しました。';
} catch (Exception $e) {
echo 'テーブル作成に失敗しました。';
echo $e->getMessage();
// PDO::errorCode 直近の操作に関連する SQLSTATE を取得します
echo "<br>PDO::errorCode(): ", $pdo->errorCode();
// PDO::errorInfo 直近で操作に失敗した拡張エラー情報を取得します
echo '<pre>';
print_r($pdo->errorInfo());
echo '</pre>';
}
・・・略・・・
データを追加する
SQLのINSERT文でデータを追加します。SQLを文字列として動的に組み立てるのは面倒なので、変更項目をパラメーター化したプリペアドステートメントを使用することが出来ます。
実行するSQL文を設定し、PDOStatementオブジェクトを受け取ります。
public PDO::prepare(string $query, array $options = []): PDOStatement|false
PDOStatementオブジェクトで変数にSQLパラメータをバインドします。
public PDOStatement::bindParam(
string|int $param,
mixed &$var,
int $type = PDO::PARAM_STR,
int $maxLength = 0,
mixed $driverOptions = null
): bool
プリペアドステートメントを実行します。
public PDOStatement::execute(?array $params = null): bool
・・・略・・・
// 直接SQLを実行する場合
$insSQL1 = "INSERT INTO test_table(id, name, memo, created_at, updated_at) VALUES (1, 'Apple', '赤いりんご🍎とgreen apple🍏', DATETIME('now'), DATETIME('now'));";
// insert SQLの実行
$insRet1 = $pdo->exec($insSQL1);
if ($insRet1) {
// PDO::exec 関数は直近のSQL文で変更された行の数を返します
echo '追加された行の数は '.$insRet1.' です。<br>';
}
// プリペアドステートメントを使用する場合
$insSQL2 = "INSERT INTO test_table(id, name, memo, created_at, updated_at) VALUES (:id, :name, :memo, DATETIME('now'), DATETIME('now'));";
// PDO::prepare 実行するSQL文を準備します
if ($insStmt = $pdo->prepare($insSQL2)) {
// SQLパラメータに変数をバインド
// PDOStatement::bindParam パラメータを変数にバインドします
$insStmt->bindParam(':id', $id, PDO::PARAM_INT);
$insStmt->bindParam(':name', $name, PDO::PARAM_STR);
$insStmt->bindParam(':memo', $memo, PDO::PARAM_STR);
// 変数に登録値をセット
$id = 2;
$name = 'Banana';
$memo = 'バナナ、<b>🍌</b>';
// PDOStatement::execute プリペアドステートメントを実行します
// 成功した場合 true が返ります
if ($insStmt->execute()) {
echo 'id : '.$id.' を追加しました。<br>';
}
// 各変数に別の値をセットして同じSQLを実行
$id = 3;
$name = 'Cherry';
$memo = 'さくらんぼ、<i>🍒</i>';
$insRet3 = $insStmt->execute();
// 各変数に別の値をセットして同じSQLを実行
$id = 4;
$name = 'Durian';
$memo = null;
$insRet4 = $insStmt->execute();
}
$insStmt = null;
・・・略・・・
データを取得する
SQLクエリを実行し、PDOStatementオブジェクトで結果を受け取ります。
public PDO::query(string $query, ?int $fetchMode = null): PDOStatement|false
public PDO::query(string $query, ?int $fetchMode = PDO::FETCH_COLUMN, int $colno): PDOStatement|false
public PDO::query(
string $query,
?int $fetchMode = PDO::FETCH_CLASS,
string $classname,
array $constructorArgs
): PDOStatement|false
public PDO::query(string $query, ?int $fetchMode = PDO::FETCH_INTO, object $object): PDOStatement|false
結果から一つの項目を取得するにはfetchColumnが簡単です。
public PDOStatement::fetchColumn(int $column = 0): mixed
結果のPDOStatementオブジェクトから全体をarrayで取得することが出来ます。
public PDOStatement::fetchAll(int $mode = PDO::FETCH_DEFAULT): array
public PDOStatement::fetchAll(int $mode = PDO::FETCH_COLUMN, int $column): array
public PDOStatement::fetchAll(int $mode = PDO::FETCH_CLASS, string $class, ?array $constructorArgs): array
public PDOStatement::fetchAll(int $mode = PDO::FETCH_FUNC, callable $callback): array
取得結果を1行ずつ受け取ります。
public PDOStatement::fetch(int $mode = PDO::FETCH_DEFAULT, int $cursorOrientation = PDO::FETCH_ORI_NEXT, int $cursorOffset = 0): mixed
・・・略・・・
// 取得結果が1つの場合
// PDO::query クエリを実行し、結果(PDOStatement|false)を返します
$countRet = $pdo->query('SELECT count(id) FROM test_table');
if ($countRet) {
// PDOStatement::fetchColumn 結果セットから一つの結果を返します
$result = $countRet->fetchColumn();
echo 'テーブルのレコード数は '.$result.' です。';
}
$countRet = null;
// 複数件のデータ取得 (SQLをそのまま実行する場合)
$selSql = 'SELECT id, name, memo, created_at, updated_at FROM test_table';
// PDO::query SQLを実行して結果をPDOStatementオブジェクトで受け取ります
$selRet = $pdo->query($selSql);
// 結果のPDOStatementオブジェクトから全体を配列で受け取ります
$rows = $selRet->fetchAll();
foreach ($rows as $row) {
// 結果を行毎に処理
$selRow = '<tr>';
$selRow .= '<td>'.htmlspecialchars($row['id']).'</td>';
$selRow .= '<td>'.htmlspecialchars($row['name']).'</td>';
$selRow .= '<td>'.htmlspecialchars($row['memo']).'</td>';
$selRow .= '<td>'.htmlspecialchars($row['created_at']).'</td>';
$selRow .= '<td>'.htmlspecialchars($row['updated_at']).'</td></tr>';
echo $selRow;
}
$selRet = null;
// プリペアドステートメントを使用する場合
if($selStmt = $pdo->prepare('SELECT id, name FROM test_table WHERE id <= :id'))
{
// 検索条件の変数:idに値をバインド
// PDOStatement::bindValue 値をパラメータにバインドします
$selStmt->bindValue(':id', 2, PDO::PARAM_INT);
// プリペアドステートメントを実行し、結果をPDOStatementオブジェクトで受け取ります
$sel2Ret = $selStmt->execute();
// カラム名をPHP変数にバインドします
$selStmt->bindColumn('id', $sel2_id);
$selStmt->bindColumn('name', $sel2_name);
// 取得データを表示します
echo '<ul>';
// PDO::FETCH_BOUND 結果セットのカラムの値を PDOStatement::bindColumn()メソッドで
// バインドされたPHP変数に代入します。
while ($selStmt->fetch(PDO::FETCH_BOUND)) {
echo '<li>';
// バインドした変数にベースから取得した値がセットされます
echo 'id : '.$sel2_id.' - name : '.$sel2_name;
echo '</li>';
}
echo '</ul>';
}
・・・略・・・
データベースから切断する
PDO オブジェクトが存在する間、接続し続けるのでnullを代入することで破棄します。
// 接続
$pdo = new PDO('sqlite:test.db');
// 切断
$pdo = null;
例外処理の有効化
エラー発生時に例外を有効化するには、データベース接続の属性を使用します。
// 接続時に設定する場合
$pdo = new PDO('sqlite:../db/test.db', null, null,
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
// 接続後に設定する場合
$pdo = new PDO('sqlite:../db/test.db');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
<!DOCTYPE html>
<html>
<head>
<title>SQLite3の例外</title>
</head>
<body>
<h1>SQLite3 例外(PDO)</h1>
<h2>例外を有効にしない場合</h2>
<?php
try {
$pdo = new PDO('sqlite::memory:');
// エラーのあるSQLを実行する
$pdo->exec('CREATE TABLE disable');
} catch(Exception $e) {
// 例外内容の表示
echo $e->getMessage();
} finally {
// データベースを閉じる
$pdo = null;
}
?>
<h2>例外を有効にした場合</h2>
<?php
try {
$pdoe = new PDO('sqlite::memory:', null, null,
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
// エラーのあるSQLを実行する
$pdoe->exec('CREATE TABLE enable');
} catch(Exception $e) {
// 例外内容の表示
echo $e->getMessage();
} finally {
// データベースを閉じる
$pdoe = null;
}
?>
</body>
</html>
上の例をブラウザから実行した結果は以下のようになります。
トランザクション
PDOにトランザクション処理を行うメソッドがあるので、それを使用します。
トランザクションを開始します。
public PDO::beginTransaction(): bool
トランザクションをコミットします。
public PDO::commit(): bool
トランザクションをロールバックします。
public PDO::rollBack(): bool
try {
$tpdo = new PDO('sqlite:../db/transaction.db');
} catch(Exception $e) {
echo $e->getMessage();
goto owari;
}
try {
// 例外のスローを有効にします
$tpdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// PDO::beginTransaction トランザクションを開始します
$tpdo->beginTransaction();
// テーブルを作成します
$tpdo->exec('CREATE TABLE temptb (name TEXT PRIMARY KEY)');
// データを追加します
$stmt = $tpdo->prepare('INSERT INTO temptb(name) VALUES (:name)');
$stmt->bindValue(':name', 'firstname', PDO::PARAM_STR);
$stmt->execute();
$stmt->bindValue(':name', 'secondname', PDO::PARAM_STR);
$stmt->execute();
// コミット 処理を確定します
$tpdo->commit();
echo 'トランザクションをコミットしました。';
} catch(Exception $e) {
// ロールバック 処理の取り消しをします
$tpdo->rollBack();
// 例外内容を表示します
echo '<pre>';
print_r($e);
echo '</pre>';
} finally {
// データベースを閉じます
$stmt = null;
$tpdo = null;
}
owari: