HTML・PHP・MySQLだけで作る間違い探しゲーム【④DB接続設定ファイルを作る】

# 4. DB接続設定
<details>
<summary><< 前の記事 [【③問題表示ページを作る】](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2)</summary>
&emsp;[3. 問題表示ページの実装](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-問題表示ページの実装)
&emsp;&emsp;[3-1. 無効なアクセスの拒否](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-1-無効なアクセスの拒否)
&emsp;&emsp;[3-2. リセット回数の計測](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-2-リセット回数の計測)
&emsp;&emsp;[3-3. 問題用文字配列の設定](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-3-問題用文字配列の設定)
&emsp;&emsp;&emsp;[3-3-1. 文字ペア配列の選択](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-3-1-文字ペア配列の選択)
&emsp;&emsp;&emsp;[3-3-2. 文字ペア配列及び文字ペアのシャッフル](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-3-2-文字ペア配列及び文字ペアのシャッフル)
&emsp;&emsp;&emsp;[3-3-3. 正解文字と不正解文字配列の設定](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-3-3-正解文字と不正解文字配列の設定)
&emsp;&emsp;&emsp;&emsp;[3-3-3-1. 正解文字の設定](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-3-3-1-正解文字の設定)
&emsp;&emsp;&emsp;&emsp;[3-3-3-2. 不正解文字配列の設定](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-3-3-2-不正解文字配列の設定)
&emsp;&emsp;&emsp;[3-3-4. 問題用文字配列の生成](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-3-4-問題用文字配列の生成)
&emsp;&emsp;[3-4. 開始時刻の記録](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-4-開始時刻の記録)
&emsp;&emsp;[3-5. 選択肢の描写](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-5-選択肢の描写)
</details>
<details>
<summary>次の記事 >> [【⑤結果表示ページを作る】](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5)</summary>
&emsp;[5. 結果表示ページ](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5#5-結果表示ページ)
&emsp;&emsp;[5-1. 無効なアクセスの拒否](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5#5-1-無効なアクセスの拒否)
&emsp;&emsp;[5-2. 回答時間の算出](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5#5-2-回答時間の算出)
&emsp;&emsp;[5-3. 各変数への格納](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5#5-3-各変数への格納)
&emsp;&emsp;[5-4. 保存する回答時間とカウント数の上限設定](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5#5-4-保存する回答時間とカウント数の上限設定)
&emsp;&emsp;[5-5. 正解／不正解による表示メッセージの分岐](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5#5-5-正解不正解による表示メッセージの分岐)
&emsp;&emsp;[5-6. ランキングへの登録](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5#5-6-ランキングへの登録)
&emsp;&emsp;&emsp;[5-6-1. ランキングテーブルの構成](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5#5-6-1-ランキングテーブルの構成)
&emsp;&emsp;&emsp;[5-6-2. ランキングへの登録](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5#5-6-2-ランキングへの登録)
</details>

この記事では**DB接続設定ファイル**「**db_connect.php**」を作成します。

必要となったところでこのファイルを呼び出すことで、いちいちPDOオブジェクトの属性などを記述しなくても済みます。

```php:db_connect.php
<?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](https://www.php.net/manual/ja/pdo.connections.php)」
PHPマニュアル「[PHP: PDO::__construct - Manual](https://www.php.net/manual/ja/pdo.construct.php)」

## 4-1. PDOオブジェクトの属性

属性に関する公式リファレンスは以下を参照願います。
PHPマニュアル「[PHP: PDO::setAttribute - Manual](https://www.php.net/manual/ja/pdo.setattribute.php)」

また下記Qiita記事が体系的にまとめられており、とても参考になります。ぜひ一読を！

Qiita「[PHPでデータベースに接続するときのまとめ - Qiita](https://qiita.com/mpyw/items/b00b72c5c95aac573b71)」 by @mpyw さん
Qiita「[【PHP超入門】クラス～例外処理～PDOの基礎 - Qiita](https://qiita.com/7968/items/6f089fec8dde676abb5b)」 by @7968 さん

### 4-1-1. フェッチ形式の指定

```php:db_connect.php
		PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
```

>PDO::FETCH_ASSOC: は、結果セットに 返された際のカラム名で添字を付けた配列を返します。
>引用元：PHPマニュアル「[PHP: PDOStatement::fetch - Manual](https://www.php.net/manual/ja/pdostatement.fetch.php)」

SQL文で得られた結果をフェッチする際の形式を指定しています。
**カラム名がキーとなった配列**で返ってくるので直感的に操作しやすいです。
この形式がもっともスタンダードな気がします。

### 4-1-2. エラーモードの設定

```php:db_connect.php
		PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
```
エラーモードは**3種類**あり、`PDO::ERRMODE_EXCEPTION`はエラー発生時に**PDOException**の**[例外](https://www.php.net/manual/ja/language.exceptions.php)**を発生させます。

また、エラーコードだけでなくその**関連情報**も返してくれるようにもなります。

どれに設定するかは開発する物や状況によると思いますが、今回は`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](https://www.php.net/manual/ja/pdo.error-handling.php)」

### 4-1-3. エミュレーションの設定

```php:db_connect.php
		PDO::ATTR_EMULATE_PREPARES => false
```

**プリペアドステートメント**の**エミュレーション**に関する設定です。

#### 4-1-3-1. プリペアドステートメント

直訳すると「準備された記述」になりますが、一文で言うと
「**後から値を入れる部分を別の文字・単語などで仮置きしたSQL文**」
かなと思います。

プリペアドステートメントを用いる利点は以下の2つです。

- 後から入れる値**のみ**を変更しながら、何度も使用できる
- **SQLインジェクション対策**になる

下記記事もご参照ください。

Qiita「[【PHP超入門】クラス～例外処理～PDOの基礎 - プリペアドステートメント - Qiita](https://qiita.com/7968/items/6f089fec8dde676abb5b#%E3%83%97%E3%83%AA%E3%83%9A%E3%82%A2%E3%83%89%E3%82%B9%E3%83%86%E3%83%BC%E3%83%88%E3%83%A1%E3%83%B3%E3%83%88)」 by @7968 さん

#### 4-1-3-2. エミュレーション

`true`か`false`（ONかOFF）かによって**プリペアドステートメントの挙動**が変化します。

上述のプリペアドステートメントにおいて、仮置きする文字・単語のことを**プレースホルダー**といい、

- **疑問符プレースホルダー**
- **名前つきプレースホルダー**

の2種類があり、今回の記事では前者に統一しています。

さらにプレースホルダー自体にも

- **静的プレースホルダー**
- **動的プレースホルダー**

の2種類のタイプがあり、エミュレーションを`false`とすることで静的プレースホルダーが用いられるようになります。

静的プレースホルダーを選択する理由としては、**よりセキュアである為**です。

こちらに関しては下記記事が参考になります。

Qiita「[【PHP超入門】クラス～例外処理～PDOの基礎 - 静的プレースホルダと動的プレースホルダ - Qiita](https://qiita.com/7968/items/6f089fec8dde676abb5b#%E9%9D%99%E7%9A%84%E3%83%97%E3%83%AC%E3%83%BC%E3%82%B9%E3%83%9B%E3%83%AB%E3%83%80%E3%81%A8%E5%8B%95%E7%9A%84%E3%83%97%E3%83%AC%E3%83%BC%E3%82%B9%E3%83%9B%E3%83%AB%E3%83%80)」 by @7968 さん

また、エミュレーションのON／OFFによる**挙動の違い**に関しては下記記事が参考になります。

Qiita「[PHPでデータベースに接続するときのまとめ - エミュレーションに関するまとめ - Qiita](https://qiita.com/mpyw/items/b00b72c5c95aac573b71#%E3%82%A8%E3%83%9F%E3%83%A5%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8B%E3%81%BE%E3%81%A8%E3%82%81)」 by @mpyw さん

更に、下記質問もご参考までに。

Teratail「[\`PDO::ATTR_EMULATE_PREPARES => false`は必要か？](https://teratail.com/questions/233051)」

## 4-2. 例外発生時の処理

```php:db_connect.php
} 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](https://www.php.net/manual/ja/language.exceptions.php#language.exceptions.catch)」

`PDOException`が**発生した例外のクラス名**で、`$e`が**発生した例外のクラスから作成したインスタンスを代入する変数**となっています。

### 4-2-1. HTTPヘッダの送信

エラーメッセージを表示する際、webブラウザにエラーメッセージを「単なるテキストである」と解釈してもらうため、`header()`を用いて**MINEタイプ**を設定しています。

また、第3引数には**HTTPレスポンスステータスコード**を指定し、
**サーバー側のエラー**であることを明示しています。

MDN web docs「[MIME タイプ (IANA メディアタイプ) - HTTP | MDN](https://developer.mozilla.org/ja/docs/Web/HTTP/Basics_of_HTTP/MIME_types)」
MDN web docs「[HTTP レスポンスステータスコード - HTTP | MDN](https://developer.mozilla.org/ja/docs/Web/HTTP/Status)」

### 4-2-2. 処理の中断とエラーメッセージの表示

続く`exit()`によって、

- **自作のエラー文**
- **発生した例外に関するエラーメッセージ**

を出力させ、後続の処理を中断させています。

このファイルを呼び出しているということは、DBのアクセスを必要とする処理を行うはずなので、接続に失敗した場合には後続の処理も失敗する可能性が高いためです。

PHPマニュアル「[PHP: exit - Manual](https://www.php.net/manual/ja/function.exit.php)」

例外に関するエラーメッセージは、`$e->getMessage()`によってインスタンスの`getMessage()`メソッドにアクセスすることで取得しています。

PHPマニュアル「[PHP: Error::getMessage - Manual](https://www.php.net/manual/ja/error.getmessage)」

`PHP_EOL`は**PHPの定義済み定数**で、プラットフォームの**行末文字**を意味します（EOLは"End Of Line"の略です）。

この定数は、**OSを自動判定して行末文字を選定**してくれますので、サーバーのOSを気にすることなく改行して表示させることができます。

PHPマニュアル「[PHP: 定義済みの定数 - Manual](https://www.php.net/manual/ja/reserved.constants.php)」

## 4-3. DBの切断について

PDOインスタンスを格納した変数に`NULL`を代入することで、DBから切断させることができます（今回の場合なら`$dbh = NULL`）。

>接続を閉じるには、他から 参照されていないことを保障することでオブジェクトを破棄する 必要があります。それには、オブジェクトを保持している変数に対して NULL を代入します。 
>引用元：PHPマニュアル「[PHP: 接続、および接続の管理 - Manual](https://www.php.net/manual/ja/pdo.connections.php)」

しかし、

>明示的にこれを行わなかった場合は、スクリプトの終了時に自動的に 接続が閉じられます。
>引用元：PHPマニュアル「[PHP: 接続、および接続の管理 - Manual](https://www.php.net/manual/ja/pdo.connections.php)」

とも書かれており、あえて記述する必要はないかと思われます。

下記もご参照下さい。

Qiita「[PHPでデータベースに接続するときのまとめ - データベース接続の切断 - Qiita](https://qiita.com/mpyw/items/b00b72c5c95aac573b71#%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9%E6%8E%A5%E7%B6%9A%E3%81%AE%E5%88%87%E6%96%AD)」 by @mpyw さん

これでDBの接続設定が完了したので、次は結果ページの実装に入ります。

<details>
<summary><< 前の記事 [【③問題表示ページを作る】](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2)</summary>
&emsp;[3. 問題表示ページの実装](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-問題表示ページの実装)
&emsp;&emsp;[3-1. 無効なアクセスの拒否](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-1-無効なアクセスの拒否)
&emsp;&emsp;[3-2. リセット回数の計測](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-2-リセット回数の計測)
&emsp;&emsp;[3-3. 問題用文字配列の設定](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-3-問題用文字配列の設定)
&emsp;&emsp;&emsp;[3-3-1. 文字ペア配列の選択](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-3-1-文字ペア配列の選択)
&emsp;&emsp;&emsp;[3-3-2. 文字ペア配列及び文字ペアのシャッフル](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-3-2-文字ペア配列及び文字ペアのシャッフル)
&emsp;&emsp;&emsp;[3-3-3. 正解文字と不正解文字配列の設定](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-3-3-正解文字と不正解文字配列の設定)
&emsp;&emsp;&emsp;&emsp;[3-3-3-1. 正解文字の設定](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-3-3-1-正解文字の設定)
&emsp;&emsp;&emsp;&emsp;[3-3-3-2. 不正解文字配列の設定](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-3-3-2-不正解文字配列の設定)
&emsp;&emsp;&emsp;[3-3-4. 問題用文字配列の生成](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-3-4-問題用文字配列の生成)
&emsp;&emsp;[3-4. 開始時刻の記録](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-4-開始時刻の記録)
&emsp;&emsp;[3-5. 選択肢の描写](https://qiita.com/_Taturon_/items/cb1cee8a3035f476ccf2#3-5-選択肢の描写)
</details>
<details>
<summary>次の記事 >> [【⑤結果表示ページを作る】](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5)</summary>
&emsp;[5. 結果表示ページ](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5#5-結果表示ページ)
&emsp;&emsp;[5-1. 無効なアクセスの拒否](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5#5-1-無効なアクセスの拒否)
&emsp;&emsp;[5-2. 回答時間の算出](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5#5-2-回答時間の算出)
&emsp;&emsp;[5-3. 各変数への格納](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5#5-3-各変数への格納)
&emsp;&emsp;[5-4. 保存する回答時間とカウント数の上限設定](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5#5-4-保存する回答時間とカウント数の上限設定)
&emsp;&emsp;[5-5. 正解／不正解による表示メッセージの分岐](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5#5-5-正解不正解による表示メッセージの分岐)
&emsp;&emsp;[5-6. ランキングへの登録](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5#5-6-ランキングへの登録)
&emsp;&emsp;&emsp;[5-6-1. ランキングテーブルの構成](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5#5-6-1-ランキングテーブルの構成)
&emsp;&emsp;&emsp;[5-6-2. ランキングへの登録](https://qiita.com/_Taturon_/items/b3eb00e4c3ea52f0b6b5#5-6-2-ランキングへの登録)
</details>