PHP/データベースとの連動
PHPは、Webアプリケーションを開発する上で欠かせないデータベースの扱いに非常に優れたプログラミング言語です。本章では、PHPと様々なRDBMSを連携させる方法について解説します。
PHPは、複数のRDBMSと連携することができます。そのための方法として、ベンダー固有モジュールと抽象化レイヤーの2つが存在します。
ベンダー固有モジュールは、ベンダーが提供する機能やデータ構造を直接PHPにマッピングすることで、RDBMSの機能を十分に活用できるという特徴があります。ただし、この方法では特定のRDBMSに依存してしまうため、別のRDBMSに移行する場合に手間がかかる可能性があります。
一方、抽象化レイヤーにはDBA、ODBC、PDOの3種類があります。これらは、RDBMSの共通機能を抽出し、機能やデータ構造を抽象化することで、移行時の手直しを最小限に抑えることができます。さらに、新しいRDBMSに対しても容易に対応できるというメリットがあります。
SQLite
[編集]SQLiteは、データベースエンジンであり、サーバーを必要とせずに利用できます。また、PHPとの相互運用性が高いため、広く利用されています。本書では、PHPとSQLiteを連携させる方法について説明します。なお、PHP5.4以降のバージョンには、SQLiteへのインタフェースが標準で付属しています。
SQLiteは、学習用途に適しており、以下のような特徴を持っています。
- MySQLのようなサーバークライアントモデルを採用していないため、アプリケーションプログラムにライブラリとしてリンクされます。
- MySQLのように、サーバーのセットアップが不要であり、ポートの開放や認証情報のセットアップ、パーミッションの整合性の維持などの手間が省けます。
- SQLiteは弱い型付けであり、MySQLのように強い型付けではありませんが、個々のフィールドに制約をかけることは可能です。
- sqlite3は、SQLite3のデータベースの保守管理を行うコマンドラインインターフェースであり、アプリケーションプログラムがリンクされるわけではありません。また、sqlite3自身も、SQLite3がリンクされたアプリケーションプログラムの一部と見なすことができます。
ベンダー固有モジュール
[編集]SQLite3
[編集]PHPは、SQLite3クラスでSQLite をサポートしています。
次のコードは、SQLite3クラスを使用して仮想メモリ上にSQLite3データベースを作成し、そのデータベースにテーブルを作成し、データを挿入し、そのデータを取得して表示するものです。
- 例
<?php // SQLite3クラスを使用したデータベースハンドリング header("Content-Type: text/plain"); $prefs = [ [01, "北海道", "Hokkaido"], [02, "青森県", "Aomori"], [03, "岩手県", "Iwate"], ]; $db = new SQLite3(":memory:"); $db->exec( "CREATE TABLE 都道府県コード一覧表 (都道府県コード INTEGER, 都道府県 STRING, prefectures STRING)" ); try { $db->enableExceptions(true); $insert = $db->prepare( "INSERT INTO 都道府県コード一覧表 VALUES (:n,:ja,:en)" ); foreach ($prefs as $pref) { $insert->bindParam(":n", $pref[0]); $insert->bindParam(":ja", $pref[1]); $insert->bindParam(":en", $pref[2]); $insert->execute(); } } catch (Exception $e) { echo sprintf("%s(%d): %s", $e->getFile(), $e->getLine(), $e->getMessage()), PHP_EOL; } $insert = null; $query = "SELECT * FROM 都道府県コード一覧表;"; echo "$query", PHP_EOL; $result = $db->query($query); while ($assoc = $result->fetchArray(SQLITE3_ASSOC)) { array_walk($assoc, function (&$v, $k) { $v = "$k:$v"; }); $v = null; echo implode(",", $assoc), PHP_EOL; } echo PHP_EOL;
- 実行結果
SELECT * FROM 都道府県コード一覧表; 都道府県コード:1, 都道府県:北海道, prefectures:Hokkaido 都道府県コード:2, 都道府県:青森県, prefectures:Aomori 都道府県コード:3, 都道府県:岩手県, prefectures:Iwate
$prefs
という都道府県の配列を定義します。それぞれの都道府県は、都道府県コード、日本語名、英語名の3つの要素を持っています。new SQLite3(":memory:")
という記述により、仮想メモリ上にSQLite3データベースを作成します。$db->exec()
メソッドを使用して、上記のデータベース内に都道府県コード一覧表
というテーブルを作成します。このテーブルは、都道府県コード、日本語名、英語名の3つのカラムを持っています。try-catch
文を使用して、例外処理を有効にします。これにより、後続の処理で発生した例外をキャッチし、エラーメッセージを出力することができます。$db->prepare()
メソッドを使用して、INSERT文を準備します。ここで、:n
、:ja
、:en
というプレースホルダを使用しています。プレースホルダは、後続のbindParam()
メソッドで値をバインドすることができます。foreach
文を使用して、$prefs
配列の要素を1つずつ取り出し、bindParam()
メソッドを使用して、プレースホルダに値をバインドします。そして、execute()
メソッドを使用して、SQL文を実行してテーブルにデータを挿入します。$query
という文字列変数に、SELECT * FROM 都道府県コード一覧表;
というSQL文を格納します。$db->query()
メソッドを使用して、上記のSQL文を実行し、その結果を$result
変数に格納します。while
ループを使用して、$result
から1行ずつデータを取り出し、$assoc
変数に連想配列形式で格納します。その後、array_walk()
関数を使用して、各要素の値の前にキーを付加します。implode()
関数を使用して、連想配列をカンマ区切りの文字列に変換し、それを表示します。最後に、改行文字を出力します。
- 機能的には不足はないのですが、次に示すPDOバージョンの方が、それを元に他のRDBMSにも対応できるので、より汎用的です。
- SQLite3クラスとPDOを比較してみてください。
抽象化レイヤー
[編集]PDO
[編集]PDO(PHP Data Objects)は、PHPの標準的なデータベースアクセスレイヤーであり、データベースサーバーに対して安全な、拡張性のある接続を提供します。 PDOは、MySQL、PostgreSQL、SQLite、Microsoft SQL Serverなどの主要なデータベースサーバーと互換性があります。 PDOは、オブジェクト指向のAPIを提供します(手続き指向のAPIはありません)。
- PDOのインストール
- PDOは、PHP 5.1以降で標準的にインストールされています。ただし、PDOを使用するには、適切なデータベースドライバーがインストールされている必要があります。たとえば、MySQLを使用する場合は、mysqlドライバーが必要です。通常、これらのドライバーはPHP拡張機能として提供されています。
次のコードは、PHPのPDO(PHP Data Objects)を使用してSQLiteデータベースを操作する方法を示しています。 具体的には、都道府県のコードと名前をSQLiteデータベースに格納し、格納されたデータを表示するためのものです。
- 例
<?php // PDOを使用したデータベースハンドリング header('Content-Type: text/plain'); $prefs = [ [01, "北海道", "Hokkaido"], [02, "青森県", "Aomori"], [03, "岩手県", "Iwate"], ]; $db = new PDO("sqlite::memory:"); $db->exec( "CREATE TABLE 都道府県コード一覧表 (都道府県コード INTEGER, 都道府県 STRING, prefectures STRING)" ); $insert = $db->prepare("INSERT INTO 都道府県コード一覧表 VALUES (:n,:ja,:en)"); $db->beginTransaction(); try { foreach ($prefs as $pref) { $insert->bindParam(":n", $pref[0]); $insert->bindParam(":ja", $pref[1]); $insert->bindParam(":en", $pref[2]); $insert->execute(); } $db->commit(); } catch (PDOException $e) { echo $e; $db->rollback(); throw $e; } $insert = null; foreach ( $db->query("SELECT * FROM 都道府県コード一覧表")->fetchAll(PDO::FETCH_ASSOC) as $assoc ) { echo implode( ", ", array_map( fn($k, $v) => "$k:$v", array_keys($assoc), array_values($assoc) ) ); echo PHP_EOL; } ?>
- 実行結果
都道府県コード:1, 都道府県:北海道, prefectures:Hokkaido 都道府県コード:2, 都道府県:青森県, prefectures:Aomori 都道府県コード:3, 都道府県:岩手県, prefectures:Iwate
$prefs
という配列を作成して、都道府県コード、日本語名、英語名の3つの要素を持つ都道府県のリストを格納しています。$db
というPDOインスタンスを作成して、sqlite::memory:
を引数に渡すことで、メモリ上にSQLiteデータベースを作成します。$db->exec()
メソッドを使用して、CREATE TABLE
ステートメントを実行して、都道府県情報を格納するためのテーブルを作成します。$insert
というPDOステートメントを作成して、INSERT INTO
ステートメントを準備します。このステートメントは、都道府県情報をテーブルに挿入するために使用されます。$db->beginTransaction()
メソッドを呼び出して、トランザクションを開始します。foreach
ループを使用して、配列$prefs
の各要素を順に取り出し、$insert
ステートメントを実行します。これにより、都道府県情報がテーブルに挿入されます。$db->commit()
メソッドを呼び出して、トランザクションをコミットして、変更を確定します。もしエラーが発生した場合は、$db->rollback()
メソッドを呼び出して、トランザクションをロールバックします。$insert
ステートメントを null に設定して、PDOステートメントを解放します。foreach
ループを使用して、SELECT
ステートメントを実行して、テーブルに格納された都道府県情報を取得します。取得した情報は、PDO::FETCH_ASSOC
モードで連想配列に変換されます。array_map()
関数を使用して、各連想配列の要素について、キーと値をペアにした文字列を生成します。implode()
関数を使用して、各連想配列の要素をカンマで区切って、1行に表示します。PHP_EOL
定数を使用して、各行の最後に改行を追加します。
- PDOを使い抽象化することで、SQLiteとMySQLの違いを吸収しています。
- queryメソッドではなく、prepareメソッドを使っているのは名前付きプレースホルダーを使いたかったためで、これはSQLインジェクションへの配慮です。
ウェブレンダリング
[編集]同内容で、HTMLに出力してみましょう。
- 例
<?php // PDOを使用したデータベースハンドリング // phpinfo(); declare(strict_types=1); header('Content-Type: text/html'); $prefs = [ [01, "北海道", "Hokkaido"], [02, "青森県", "Aomori"], [03, "岩手県", "Iwate"], ]; $db = new PDO("sqlite::memory:"); $db->exec( "CREATE TABLE 都道府県コード一覧表 (都道府県コード INTEGER, 都道府県 STRING, prefectures STRING)" ); $insert = $db->prepare("INSERT INTO 都道府県コード一覧表 VALUES (:n,:ja,:en)"); $db->beginTransaction(); try { foreach ($prefs as $pref) { $insert->bindParam(":n", $pref[0]); $insert->bindParam(":ja", $pref[1]); $insert->bindParam(":en", $pref[2]); $insert->execute(); } $db->commit(); } catch (PDOException $e) { echo $e; $db->rollback(); throw $e; } $insert = null; ?> <table class=wikitable> <caption>都道府県コード一覧表</caption> <tr><?php foreach ($db->query("PRAGMA table_info('都道府県コード一覧表')")->fetchAll(PDO::FETCH_ASSOC) as $assoc) : ?><th><?= $assoc['name'] ?><?php endforeach ?></tr> <?php foreach ($db->query("SELECT * FROM 都道府県コード一覧表")->fetchAll(PDO::FETCH_ASSOC) as $assoc) : ?> <tr><?php foreach (array_values($assoc) as $value) { ?><td><?= $value ?><?php } ?></tr> <?php endforeach ?> </table>
- 実行結果
<table class=wikitable> <caption>都道府県コード一覧表</caption> <tr><th>都道府県コード<th>都道府県<th>prefectures</tr> <tr><td>1<td>北海道<td>Hokkaido</tr> <tr><td>2<td>青森県<td>Aomori</tr> <tr><td>3<td>岩手県<td>Iwate</tr> </table>
- レンダリング結果
都道府県コード | 都道府県 | prefectures |
---|---|---|
1 | 北海道 | Hokkaido |
2 | 青森県 | Aomori |
3 | 岩手県 | Iwate |
- この例では、見出し語をそのままテーブルのフィールド名にしているのでスキーマーの情報をそのままレンダリングに使っていますが、フィールド名を英数字に限定している場合は、フィールド名⇒見出し語のテーブルを作り、その情報でイテレーションすると良いでしょう。
MySQL
[編集]PHPとMySQLを連携するには、MySQLのインストールとセットアップが必要です。
ベンダー固有モジュール
[編集]MySQLi
[編集]MySQLiは、PHPからMySQLデータベースにアクセスするための拡張モジュールです。MySQLiは、以前のMySQLに比べて、より高度なセキュリティ機能、トランザクションサポート、ストアドプロシージャのサポート、エラー処理機能などを提供しています。MySQLiは、MySQL 4.1以上で提供される機能を利用できるようにするために開発されました。
MySQLiは、PHP本体とは別にインストールする必要があるため、オペレーティングシステムやディストリビューションのパッケージ管理システムでパッケージの検索・インストールを行った後にセットアップを行います。
MySQLiには、オブジェクト指向のAPIと手続き指向のAPIの2つのAPIがあります。オブジェクト指向のAPIは、MySQLiオブジェクトを使用してデータベースにアクセスするための方法を提供します。一方、手続き指向のAPIは、MySQLi関数を使用してデータベースにアクセスするための方法を提供します。どちらのAPIを使用するかは、開発者の好みやプロジェクトのニーズに応じて決定されます。
MySQLデータベースからデータを取得して表示(オブジェクト指向)
[編集]- 例
<?php $mysqli = new mysqli("localhost", "root", "", "testmysql"); foreach ( $mysqli->query("SELECT * FROM testTable")->fetch_array(MYSQLI_ASSOC) as $key => $value ) { echo "$key:$value", PHP_EOL; } $mysqli->close(); $mysqli = null; ?>
- 実行結果
原子番号:1 元素名:Hydrogen 元素記号:H
- このPHPコードは、MySQLデータベースからデータを取得して、取得したデータをキーと値の形式で表示するものです。
- まず、mysqliオブジェクトを作成し、"localhost"というホスト名、"root"というユーザ名、""というパスワード、そして"testmysql"というデータベース名でMySQLデータベースに接続しています。
- 次に、query()メソッドを使用して、"SELECT * FROM testTable"というSQL文を実行し、その結果をfetch_array()メソッドで取得しています。fetch_array()メソッドは、取得したデータを配列で返します。また、MYSQLI_ASSOCを引数として渡すことで、連想配列として取得できます。
- その後、foreachループを使用して、取得した連想配列の各要素を順番に取り出し、キーと値を表示しています。最後に、mysqliオブジェクトをclose()メソッドでクローズし、nullを代入してメモリリークを防止しています。
- 全レコードの読取り
<?php $mysqli = new mysqli("localhost", "root", "", "testmysql"); foreach ( $mysqli->query("SELECT * FROM testTable")->fetch_all(MYSQLI_ASSOC) as $assoc ) { foreach ($assoc as $key => $value) { echo "$key:$value", PHP_EOL; } } $mysqli->close(); $mysqli = null; ?>
- 実行結果
原子番号:1 元素名:Hydrogen 元素記号:H 原子番号:2 元素名:Helium 元素記号:
MySQLデータベースからデータを取得して表示(手続き指向)
[編集]- 例
<?php $connect = mysqli_connect("localhost", "root", "", "testmysql"); $result = mysqli_query($connect, "SELECT * FROM testTable"); foreach (mysqli_fetch_array($result, MYSQLI_ASSOC) as $key => $value) { echo "$key:$value", PHP_EOL; } ?>
- 実行結果
原子番号:1 元素名:Hydrogen 元素記号:H
- オブジェクト指向版に比べて、手続き指向版のほうがメソッドチェインを使えない関係で中間変数が増えコードが長くなる傾向にあります。
- 全レコードの読取り
<?php $connect = mysqli_connect("localhost", "root", "", "testmysql"); $result = mysqli_query($connect, "SELECT * FROM testTable"); foreach (mysqli_fetch_all($result, MYSQLI_ASSOC) as $assoc) { foreach ($assoc as $key => $value) { echo "$key:$value", PHP_EOL; } } ?>
- 実行結果
原子番号:1 元素名:Hydrogen 元素記号:H 原子番号:2 元素名:Helium 元素記号:
抽象化レイヤー
[編集]PDO
[編集]- 例
<?php $pdo = new PDO( "mysql:host=localhost;dbname=testmysql;charset=utf8", "root", "" ); foreach ( $pdo->query("SELECT * FROM testTable")->fetchAll(PDO::FETCH_ASSOC) as $assoc ) { foreach ($assoc as $key => $value) { echo "$key:$value", PHP_EOL; } }
- 実行結果
原子番号:1 元素名:Hydrogen 元素記号:H 原子番号:2 元素名:Helium 元素記号:
- ベンダー固有モジュール版のMySQLiには、手続き指向版のAPIがありましたが、PDOにはオブジェクト指向版しかありません。
手続き指向APIは、関数と手続きの集合で構成され、一般的にグローバルスコープで使用されます。
一方、オブジェクト指向APIは、クラスとオブジェクトの概念に基づいて構築され、複数のインスタンスを持つことができます。
以下は、手続き指向APIとオブジェクト指向APIの主な比較です。
- 再利用性
- オブジェクト指向APIは再利用性が高いです。同じクラスの複数のインスタンスを作成できます。
- 一方、手続き指向APIは再利用性が低く、同じコードを複数回書く必要がある場合があります。
- 可読性
- オブジェクト指向APIは通常、より読みやすく、理解しやすいコードを生成します。クラスとメソッドの名前は意味を持ち、コードの構造は明確であるため、開発者がコードをより迅速に理解できます。
- 一方、手続き指向APIは、複数のグローバル関数を組み合わせた複雑なコードを生成することがあり、可読性が低い場合があります。
- 拡張性
- オブジェクト指向APIは、クラスを継承して新しいクラスを作成することができ、継承、多重継承、ポリモーフィズムなどの機能があります。これにより、コードをより簡単に拡張できます。
- 一方、手続き指向APIは、コードを機能ごとに分割して再利用することができますが、新しい関数を作成するためには手順を実行する必要がある場合があり、拡張性が低い場合があります。
- メンテナンス性
- オブジェクト指向APIは、変更を加える場合にも影響を受けないコードの部分が多く、メンテナンス性が高いと言えます。
- 一方、手続き指向APIは、グローバル変数を多用するため、変更によって予期しないエラーが発生する場合があります。