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>

上の例をブラウザから実行した結果は以下のようになります。

SQLite3クラスの例外

SQLite3クラスの例外

トランザクション

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: