4. DB接続設定
<< 前の記事 [【③問題表示ページを作る】](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2)
[3. 問題表示ページの実装](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-問題表示ページの実装) [3-1. 無効なアクセスの拒否](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-1-無効なアクセスの拒否) [3-2. リセット回数の計測](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-2-リセット回数の計測) [3-3. 問題用文字配列の設定](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-3-問題用文字配列の設定) [3-3-1. 文字ペア配列の選択](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-3-1-文字ペア配列の選択) [3-3-2. 文字ペア配列及び文字ペアのシャッフル](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-3-2-文字ペア配列及び文字ペアのシャッフル) [3-3-3. 正解文字と不正解文字配列の設定](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-3-3-正解文字と不正解文字配列の設定) [3-3-3-1. 正解文字の設定](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-3-3-1-正解文字の設定) [3-3-3-2. 不正解文字配列の設定](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-3-3-2-不正解文字配列の設定) [3-3-4. 問題用文字配列の生成](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-3-4-問題用文字配列の生成) [3-4. 開始時刻の記録](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-4-開始時刻の記録) [3-5. 選択肢の描写](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-5-選択肢の描写)次の記事 >> [【⑤結果表示ページを作る】](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5)
[5. 結果表示ページ](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5#5-結果表示ページ) [5-1. 無効なアクセスの拒否](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5#5-1-無効なアクセスの拒否) [5-2. 回答時間の算出](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5#5-2-回答時間の算出) [5-3. 各変数への格納](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5#5-3-各変数への格納) [5-4. 保存する回答時間とカウント数の上限設定](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5#5-4-保存する回答時間とカウント数の上限設定) [5-5. 正解/不正解による表示メッセージの分岐](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5#5-5-正解不正解による表示メッセージの分岐) [5-6. ランキングへの登録](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5#5-6-ランキングへの登録) [5-6-1. ランキングテーブルの構成](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5#5-6-1-ランキングテーブルの構成) [5-6-2. ランキングへの登録](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5#5-6-2-ランキングへの登録)この記事ではDB接続設定ファイル「db_connect.php」を作成します。
必要となったところでこのファイルを呼び出すことで、いちいちPDOオブジェクトの属性などを記述しなくても済みます。
<?php
// 定数定義
const PDO_DSN = 'mysql:host=localhosts;dbname=[FILTERED];charset=utf8mb4';
const USERNAME = '[FILTERED]';
const PASSWORD = '[FILTERED]';
// DB接続
try {
$dbh = new PDO(PDO_DSN, USERNAME, PASSWORD, [
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
]);
} catch (PDOException $e) {
header('Content-Type: text/plain; charset=UTF-8', true, 500);
exit('DB接続に失敗しました' . '<br>' . PHP_EOL . $e->getMessage());
}
冒頭でも述べましたが、DB構築については本記事では扱いません。
接続にはPDOオブジェクトを使用しています。
PDOのインスタンスを作成する為にはPDO()
を用いて
- 第1引数・・・DSN(必須)
- 第2引数・・・ユーザーネーム(任意)
- 第3引数・・・パスワード(任意)
- 第4引数・・・PDOオブジェクトの属性(任意)
を渡す必要があります。
今回のコードでは第1〜第3引数を定数としてを定義してますが、変数定義や引数に直接渡す書き方でも問題ありません。
また、DBにはプレイヤーが入力した名前を保存します。このため絵文字を取り扱う可能性があるので、文字コードは**utf8
ではなくマルチバイト対応のuft8mb4
**を指定しています。
コピペする際は、**[FILTERED]
**を適切な値に置き換えて下さい。
PHPマニュアル「PHP: 接続、および接続の管理 - Manual」
PHPマニュアル「PHP: PDO::__construct - Manual」
4-1. PDOオブジェクトの属性
属性に関する公式リファレンスは以下を参照願います。
PHPマニュアル「PHP: PDO::setAttribute - Manual」
また下記Qiita記事が体系的にまとめられており、とても参考になります。ぜひ一読を!
Qiita「PHPでデータベースに接続するときのまとめ - Qiita」 by @mpyw さん
Qiita「【PHP超入門】クラス~例外処理~PDOの基礎 - Qiita」 by @7968 さん
4-1-1. フェッチ形式の指定
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
PDO::FETCH_ASSOC: は、結果セットに 返された際のカラム名で添字を付けた配列を返します。
引用元:PHPマニュアル「PHP: PDOStatement::fetch - Manual」
SQL文で得られた結果をフェッチする際の形式を指定しています。
カラム名がキーとなった配列で返ってくるので直感的に操作しやすいです。
この形式がもっともスタンダードな気がします。
4-1-2. エラーモードの設定
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
エラーモードは3種類あり、PDO::ERRMODE_EXCEPTION
はエラー発生時にPDOExceptionの**例外**を発生させます。
また、エラーコードだけでなくその関連情報も返してくれるようにもなります。
どれに設定するかは開発する物や状況によると思いますが、今回はPDO::ERRMODE_EXCEPTION
を選択しました。
4-1-2-1. エラーモードの違いによるエラー文の違い
以下は本ゲームの「result.php」の56行目において、テーブルのカラムとは異なるカラム名を指定したINSERT
文を実行した際のエラー文の比較です。
PDO::ERRMODE_SILENT
Fatal error: Uncaught Error: Call to a member function execute() on boolean in /path/to/result.php:58 Stack trace: #0 {main} thrown in /path/to/result.php on line 58
PDO::ERRMODE_WARNING
Warning: PDO::prepare(): SQLSTATE[42S22]: Column not found: 1054 Unknown column 'namae' in 'field list' in /path/to/result.php on line 57
Fatal error: Uncaught Error: Call to a member function execute() on boolean in /path/to/result.php:58 Stack trace: #0 {main} thrown in /path/to/result.php on line 58
PDO::ERRMODE_EXCEPTION
Fatal error: Uncaught PDOException: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'namae' in 'field list' in /path/to/result.php:57 Stack trace: #0 /path/to/result.php(57): PDO->prepare('INSERT INTO ran...') #1 {main} thrown in /path/to/result.php on line 57
デフォルトのPDO::ERRMODE_SILENT
では単に「SQL文を実行するexecute()
でエラーが起こった」と表示されるだけであるのに対し、PDO::ERRMODE_WARNING
とPDO::ERRMODE_EXCEPTION
では「指定されたカラムが見つからない」とより具体的なエラー文となっています。
また、PDO::ERRMODE_WARNING
ではE_WARNING
メッセージが追加されており、PDO::ERRMODE_EXCEPTION
では例外がPDOException
クラスになっているという違いもあります。
PHPマニュアル「エラーおよびエラー処理 - Manual」
4-1-3. エミュレーションの設定
PDO::ATTR_EMULATE_PREPARES => false
プリペアドステートメントのエミュレーションに関する設定です。
4-1-3-1. プリペアドステートメント
直訳すると「準備された記述」になりますが、一文で言うと
「後から値を入れる部分を別の文字・単語などで仮置きしたSQL文」
かなと思います。
プリペアドステートメントを用いる利点は以下の2つです。
- 後から入れる値のみを変更しながら、何度も使用できる
- SQLインジェクション対策になる
下記記事もご参照ください。
Qiita「【PHP超入門】クラス~例外処理~PDOの基礎 - プリペアドステートメント - Qiita」 by @7968 さん
4-1-3-2. エミュレーション
true
かfalse
(ONかOFF)かによってプリペアドステートメントの挙動が変化します。
上述のプリペアドステートメントにおいて、仮置きする文字・単語のことをプレースホルダーといい、
- 疑問符プレースホルダー
- 名前つきプレースホルダー
の2種類があり、今回の記事では前者に統一しています。
さらにプレースホルダー自体にも
- 静的プレースホルダー
- 動的プレースホルダー
の2種類のタイプがあり、エミュレーションをfalse
とすることで静的プレースホルダーが用いられるようになります。
静的プレースホルダーを選択する理由としては、よりセキュアである為です。
こちらに関しては下記記事が参考になります。
Qiita「【PHP超入門】クラス~例外処理~PDOの基礎 - 静的プレースホルダと動的プレースホルダ - Qiita」 by @7968 さん
また、エミュレーションのON/OFFによる挙動の違いに関しては下記記事が参考になります。
Qiita「PHPでデータベースに接続するときのまとめ - エミュレーションに関するまとめ - Qiita」 by @mpyw さん
更に、下記質問もご参考までに。
Teratail「`PDO::ATTR_EMULATE_PREPARES => false`は必要か?」
4-2. 例外発生時の処理
} catch (PDOException $e) {
header('Content-Type: text/plain; charset=UTF-8', true, 500);
exit('DB接続に失敗しました' . PHP_EOL . $e->getMessage() . PHP_EOL);
}
catch()
を用いることで、発生した例外を捕捉することができます。
PHPマニュアル「PHP: 例外(exceptions) - Manual」
PDOException
が発生した例外のクラス名で、$e
が発生した例外のクラスから作成したインスタンスを代入する変数となっています。
4-2-1. HTTPヘッダの送信
エラーメッセージを表示する際、webブラウザにエラーメッセージを「単なるテキストである」と解釈してもらうため、header()
を用いてMINEタイプを設定しています。
また、第3引数にはHTTPレスポンスステータスコードを指定し、
サーバー側のエラーであることを明示しています。
MDN web docs「MIME タイプ (IANA メディアタイプ) - HTTP | MDN」
MDN web docs「HTTP レスポンスステータスコード - HTTP | MDN」
4-2-2. 処理の中断とエラーメッセージの表示
続くexit()
によって、
- 自作のエラー文
- 発生した例外に関するエラーメッセージ
を出力させ、後続の処理を中断させています。
このファイルを呼び出しているということは、DBのアクセスを必要とする処理を行うはずなので、接続に失敗した場合には後続の処理も失敗する可能性が高いためです。
PHPマニュアル「PHP: exit - Manual」
例外に関するエラーメッセージは、$e->getMessage()
によってインスタンスのgetMessage()
メソッドにアクセスすることで取得しています。
PHPマニュアル「PHP: Error::getMessage - Manual」
PHP_EOL
はPHPの定義済み定数で、プラットフォームの行末文字を意味します(EOLは"End Of Line"の略です)。
この定数は、OSを自動判定して行末文字を選定してくれますので、サーバーのOSを気にすることなく改行して表示させることができます。
PHPマニュアル「PHP: 定義済みの定数 - Manual」
4-3. DBの切断について
PDOインスタンスを格納した変数にNULL
を代入することで、DBから切断させることができます(今回の場合なら$dbh = NULL
)。
接続を閉じるには、他から 参照されていないことを保障することでオブジェクトを破棄する 必要があります。それには、オブジェクトを保持している変数に対して NULL を代入します。
引用元:PHPマニュアル「PHP: 接続、および接続の管理 - Manual」
しかし、
明示的にこれを行わなかった場合は、スクリプトの終了時に自動的に 接続が閉じられます。
引用元:PHPマニュアル「PHP: 接続、および接続の管理 - Manual」
とも書かれており、あえて記述する必要はないかと思われます。
下記もご参照下さい。
Qiita「PHPでデータベースに接続するときのまとめ - データベース接続の切断 - Qiita」 by @mpyw さん
これでDBの接続設定が完了したので、次は結果ページの実装に入ります。