第17章 データベース

PHPでのデータベース接続

PHPでは、主にMySQLi(MySQL Improved)とPDO(PHP Data Objects)の2つの方法を用いてデータベースに接続します。両方とも似たような機能を提供していますが、PDOの方がより柔軟性があり、さまざまなデータベースに対応しています。


MySQLi(MySQL Improved
  1. メリット:MySQLデータベース専用の機能に対応しています。ストアドプロシージャなどの高度なMySQL機能を使用する場合、MySQLiは良い選択です。オブジェクト指向と手続き型の両方のインターフェースを提供します。これは、特定のコーディングスタイルに慣れている開発者にとって便利です。
  2. デメリット:MySQLiはMySQLデータベースのみに対応しています。異なるデータベースを使用する可能性がある場合、または複数のデータベースと連携する予定のプロジェクトでは、MySQLiは最適な選択ではありません。


PDO(PHP Data Objects)
  1. メリット:データベースに依存しないコードを書くことができます。PDOは12種類以上のデータベースに対応しています。つまり、あなたのアプリケーションがMySQL、SQLite、PostgreSQLなどの異なるデータベースで動作する必要がある場合、PDOが適しています。名前付きパラメータをサポートしています。これにより、SQLクエリがより読みやすく、保守しやすくなります。
  2. デメリット:PDOは特定のデータベースの高度な機能をサポートしていない場合があります。MySQLの高度な機能を利用したい場合は、MySQLiの方が適している可能性があります。PDOはオブジェクト指向のインターフェースのみを提供します。手続き型のスタイルに慣れている開発者にとっては、この学習曲線は急であるかもしれません。


最終的にどちらを選ぶかは、あなたのプロジェクトの要件と個人的な好みに大きく依存します。データベースの種類やプログラミングスタイルなど、さまざまな要因を考慮して選択を行ってください。


MySQLiの使用例
// データベースに接続
$db = new mysqli('localhost', 'username', 'password', 'test_db');

if ($db->connect_error) {
    die("接続失敗: " . $db->connect_error);
}

// クエリを実行
$result = $db->query("SELECT * FROM test_table");

if ($result->num_rows > 0) {
    // 結果を出力
    while($row = $result->fetch_assoc()) {
        echo "id: " . $row["id"]. " - Name: " . $row["name"]. "
"; } } else { echo "0結果"; } $db->close();


PDOの使用例
try {
    // データベースに接続
    $db = new PDO('mysql:host=localhost;dbname=test_db;charset=utf8', 'username', 'password');

    // エラーが発生した場合に例外をスロー
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // クエリを実行
    $result = $db->query("SELECT * FROM test_table");

    // 結果を出力
    foreach($result as $row) {
        echo "id: " . $row['id'] . " - Name: " . $row['name'] . "
"; } } catch (PDOException $e) { echo "接続失敗: " . $e->getMessage(); }

どちらの例も、データベースへの接続、クエリの実行、結果の取得、接続の閉じ方を示しています。ただし、エラーハンドリングの方法やクエリ結果の取得方法など、いくつかの差異が存在します。

これらは非常に基本的な例であり、実際のアプリケーションではセキュリティとパフォーマンスを考慮してコードを書く必要があります。特に、ユーザーからの入力をクエリに直接含めるのではなく、パラメータ化されたクエリやプリペアドステートメントを使用することを推奨します。


トランザクション処理

トランザクション処理は、複数の操作が一つの単一の作業単位として扱われるようにするものです。これにより、全ての操作が成功するか、いずれかの操作が失敗した場合には全ての操作がロールバック(取り消し)される、という一貫性が確保されます。

MySQLiでのトランザクションの使用例
$db = new mysqli('localhost', 'username', 'password', 'test_db');

if ($db->connect_error) {
    die("接続失敗: " . $db->connect_error);
}

// トランザクション開始
$db->begin_transaction();

try {
    $db->query("INSERT INTO test_table (id, name) VALUES (1, 'Alice')");
    $db->query("INSERT INTO test_table (id, name) VALUES (2, 'Bob')");

    // コミット(確定)
    $db->commit();
} catch (Exception $e) {
    // エラーが発生した場合はロールバック
    $db->rollback();
}


PDOでのトランザクションの使用例
try {
    $db = new PDO('mysql:host=localhost;dbname=test_db;charset=utf8', 'username', 'password');
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // トランザクション開始
    $db->beginTransaction();

    $db->exec("INSERT INTO test_table (id, name) VALUES (1, 'Alice')");
    $db->exec("INSERT INTO test_table (id, name) VALUES (2, 'Bob')");

    // コミット(確定)
    $db->commit();
} catch (Exception $e) {
    // エラーが発生した場合はロールバック
    $db->rollback();
    echo "トランザクション失敗: " . $e->getMessage();
}

これらの例では、AliceBobという名前の2つのレコードをデータベースに挿入しようとしています。これらの操作はトランザクションとしてグループ化されていますので、どちらか一方のINSERTが失敗した場合、成功したもう一方のINSERTもロールバックされます。これにより、データベースの一貫性が保たれます。


SQLインジェクションと対策

SQLインジェクションは、アプリケーションがデータベースへのクエリを構築する際に、不正なSQL文を注入しデータベースを操られるセキュリティ上の問題です。これを防ぐために、PHPではプレースホルダーとパラメーターのバインド(結び付け)を使用することが一般的です。

MySQLiでのSQLインジェクション対策の使用例
$db = new mysqli('localhost', 'username', 'password', 'test_db');

if ($db->connect_error) {
    die("接続失敗: " . $db->connect_error);
}

$stmt = $db->prepare("INSERT INTO test_table (name) VALUES (?)");
if ($stmt === false) {
    die('ステートメントの準備に失敗: ' . htmlspecialchars($db->error));
}

$user_input = "John'; DROP TABLE test_table; --"; // これがSQLインジェクションを試みる悪意のある入力だと仮定
$stmt->bind_param('s', $user_input); // 's' は文字列型を表す

if ($stmt->execute()) {
    echo "データの挿入に成功しました";
} else {
    echo "エラー: " . $stmt->error;
}


PDOでのSQLインジェクション対策の使用例
try {
    $db = new PDO('mysql:host=localhost;dbname=test_db;charset=utf8', 'username', 'password');
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    $user_input = "John'; DROP TABLE test_table; --"; // これがSQLインジェクションを試みる悪意のある入力だと仮定
    $stmt = $db->prepare("INSERT INTO test_table (name) VALUES (?)");
    $stmt->bindParam(1, $user_input, PDO::PARAM_STR);

    $stmt->execute();
    echo "データの挿入に成功しました";
} catch (PDOException $e) {
    echo "エラー: " . $e->getMessage();
}

これらの例では、bindParamメソッド(またはbind_paramメソッド)を使用して、ユーザーの入力をSQLクエリにバインドしています。これにより、ユーザーが提供したデータが直接SQLクエリに組み込まれることがなく、SQLインジェクション攻撃を防げます。


ORM

RMは"Object-Relational Mapping"の略で、オブジェクト指向プログラミングとリレーショナルデータベースの間のマッピング(対応付け)を行う手法のことを指します。

リレーショナルデータベースはテーブルとその間のリレーション(関係)によってデータを管理しますが、オブジェクト指向プログラミングではオブジェクトとそのプロパティ(属性)やメソッド(振る舞い)によってデータとロジックを管理します。これら二つは概念的に異なるので、その間をつなげるには何らかの手段が必要となります。

ORMはその手段の一つで、データベースのテーブルをクラス(オブジェクト)に、レコードをクラスのインスタンス(具体的なオブジェクト)に、カラムをプロパティにマッピングします。これにより、データベースの操作を直接SQLで書く代わりに、プログラム言語の標準的な構文(PHPならオブジェクトのメソッド呼び出しやプロパティへのアクセスなど)で書くことができます。

メリット
  • データベース操作を直感的に行える: データベースのレコードを操作するのに、SQLを直接書く代わりにオブジェクトの操作として表現できます。これによりコードが読みやすくなり、デバッグも容易になります。
  • DBMSの差異を吸収できる: ほとんどのORMは複数のデータベース管理システム(DBMS)をサポートしており、DBMS間の差異(SQLの文法など)を吸収してくれます。これにより、DBMSを変更する際の手間を軽減できます。
  • SQLインジェクション対策: 多くのORMでは、パラメータを自動的に適切にエスケープ(特殊文字を無効化)してくれます。これにより、SQLインジェクションというセキュリティリスクを軽減できます。

デメリット
  • パフォーマンス: ORMは抽象化のレイヤーを追加するため、生のSQLを直接書くよりもパフォーマンスが若干低下する可能性があります。
  • 複雑さ: ORMは簡単なクエリには便利ですが、複雑なクエリを書く場合にはSQLの知識が必要になることがあります。また、ORM自体の学習コストも考慮に入れる必要があります。
  • 隠蔽されたSQL: ORMを使うと、どのようなSQLが発行されるのかが分かりにくくなることがあります。これは、パフォーマンスチューニングやデバッグの難易度を上げる可能性があります。


ORMライブラリ

便利なライブラリはいくつかあります。以下に、PHPでよく使用されるいくつかのライブラリをご紹介します。

  • Eloquent ORM: Eloquent ORMは、Laravelフレームワークに含まれるデータベース操作のためのオブジェクトリレーショナルマッパー(ORM)です。様々な種類のデータベースに対応し、直感的で表現豊かなAPIを提供します。
  • Doctrine: DoctrineはフルスタックのORMであり、PHPのオブジェクトをデータベースにマッピングします。Doctrineは多数のデータベースエンジンをサポートしています。
  • RedBean: RedBeanは"zero-config" ORMで、データベース操作を非常にシンプルにします。テーブル、カラム、インデックスの自動生成など、開発の初期段階で特に便利です。
  • Propel: PropelはフルスタックのORMで、データベース操作のための強力なAPIを提供します。Propelはマイグレーションや逆マイグレーション、複雑なクエリ操作などをサポートしています。
  • CakePHP's ORM: CakePHPフレームワークに含まれるこのORMは、EloquentやDoctrineのように強力な機能を持っており、非常に使いやすいAPIを提供します。

これらのライブラリは、データベース操作をよりシンプルで、保守しやすく、安全にするために使われます。プロジェクトのニーズに合わせて適切なライブラリを選択することが重要です。


練習問題1.

MySQLiを使用して、MySQLデータベースに接続し、データベースからデータを取得するシンプルなPHPスクリプトを作成してください。


練習問題2.

PDOを使用して、MySQLデータベースに接続し、データベースにデータを挿入するPHPスクリプトを作成してください。また、その挿入処理をトランザクションを用いて実行してください。


練習問題3.

PHPスクリプト内でSQLインジェクション攻撃を防ぐために、どのようにクエリを作成するべきかを説明してください。さらに、その説明に従って、SQLインジェクション攻撃を防ぐための安全なクエリを作成してください。