【PHP】フォーム送信後にチェックボックスの選択を保持する

## はじめに
実装に苦戦したので、備忘録として記事にしました。
まだまだ勉強中の身なので、知らないことがたくさんあります。
参考になると嬉しいです。

## 実装したいこと
問題1で「織田信長」と「ショパン」を選択し、問題2を選択せずに送信ボタンを押したとき、選択を保持したままエラーメッセージを表示する。
複数の入力画面があってエラーがでたとき、全て入力しなおすなんてことにならないように。
![スクリーンショット 2022-12-26 182555.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3082297/da44796b-58e3-8b12-83ff-24ae05728097.png)

## 環境
* XAMPP Version 8.1.10
* Visual Studio Code Version 1.74.2

## データベースの構造(phpMyadmin)
![スクリーンショット 2022-12-26 181211.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3082297/2b223b2d-f97d-04e1-fd88-3e2143818106.png)

## phpファイル
```php
<?php
    //セッション開始
    session_start();
    try {
        // データベースに接続
        $pdo = new PDO(
            'mysql:dbname=xxx;host=localhost;charset=utf8',
            'xxx',
            'xxx',
            [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::ATTR_EMULATE_PREPARES =>false
            ]
        );
        //変数の定義とシリアライズ
        if(isset($_POST['question1'])){
            $question1 = $_POST['question1'];
            $question1_db = serialize($question1);
        }
        if(isset($_POST['question2'])){
            $question2 = $_POST['question2'];
            $question2_db = serialize($question2);
        }
        //form送信したら
        if( !empty($_POST['btn_submit']) ) {
            // 選択されているかチェック
            if(empty($question1)) {
                $error_message[] = '問題1を選択してください。';
            }
            if(empty($question2)) {
                $error_message[] = '問題2を選択してください。';
            }
            //未選択がないとき
            if( empty($error_message) ) {
                //データベースの挿入とトランザクションの開始
                $sql = "INSERT INTO xxx (question1,question2) VALUES (:question1,:question2)";
                $stmt = $pdo->prepare($sql);
                $pdo->beginTransaction();
                try{
                    $stmt->bindvalue( ':question1', $question1_db);
                    $stmt->bindvalue( ':question2', $question2_db);
                    $stmt->execute();
                    $pdo->commit();
                    $result = $stmt->fetchAll();
                }catch (PDOException $e) {
                    $pdo->rollBack();
                    throw $e;
                }
            header('Location: xxx');
            exit;
            }
        }
    }catch(PDOException $e) {
        $error_message[] = $e->getMessage();
        header('Content-Type: text/plain; charset=UTF-8', true, 500);
        exit($e->getMessage()); 
    }
    header('Content-Type: text/html; charset=utf-8');
?>
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>example1</title>
            <!-- リンク -->
            <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css">
            <link rel="stylesheet" href="css/reset.css">
        </head>
    <body>
        <main>
            <?php if( !empty($error_message) ){ ?>
                <p class="alert alert-danger col-3" role="alert">
                    <?php foreach( $error_message as $value ){?>
                        <?php echo $value; ?><br>
                    <?php } ?>
                </p>
            <?php } ?>
            <form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post" class="p-4">
                <div class="mb-3">
                        <label class="form-label">問題1:三英傑は誰？(複数回答可)</label><br>
                        <input type="checkbox" name="question1[]" value="織田信長" <?php if(isset($question1) && in_array('織田信長',$question1)) { echo "checked";} ?>>織田信長
                        <input type="checkbox" name="question1[]" value="ショパン" <?php if(isset($question1) && in_array('ショパン',$question1)) { echo "checked";} ?>>ショパン
                        <input type="checkbox" name="question1[]" value="毛沢東" <?php if(isset($question1) && in_array('毛沢東',$question1)) { echo "checked";} ?>>毛沢東
                </div>
                <div class="mb-3">
                        <label class="form-label">問題2:日本国民の三大義務は?(複数回答可)</label><br>
                        <input type="checkbox" name="question2[]" value="休息の義務" <?php if(isset($question2) && in_array('休息の義務',$question2)) { echo "checked";} ?>>休息の義務
                        <input type="checkbox" name="question2[]" value="勤労の義務" <?php if(isset($question2) && in_array('勤労の義務',$question2)) { echo "checked";} ?>>勤労の義務
                        <input type="checkbox" name="question2[]" value="納税の義務" <?php if(isset($question2) && in_array('納税の義務',$question2)) { echo "checked";} ?>>納税の義務
                </div>
                <div class="form-group">
                    <input type="submit" name="btn_submit" class="btn btn-primary" value="送信">
                </div>
            </form>
        </main>
    </body>
</html>
```
## 解説
```php
<?php
    //セッション開始
    session_start();
    try {
        // データベースに接続
        $pdo = new PDO(
            'mysql:dbname=xxx;host=localhost;charset=utf8',
            'xxx',
            'xxx',
            [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::ATTR_EMULATE_PREPARES =>false
            ]
        );
```
セッションを開始して、データベースに接続する。
`xxx`部分は順に、データベース名、ユーザー名、パスワードを入力する。
```php
//変数の定義とシリアライズ
        if(isset($_POST['question1'])){
            $question1 = $_POST['question1'];
            $question1_db = serialize($question1);
        }
        if(isset($_POST['question2'])){
            $question2 = $_POST['question2'];
            $question2_db = serialize($question2);
        }
```

送信されてきた`question1`と`question2`に値があるとき、`$question1`と`$question2`にPOST変数を代入する。
また、この2つは配列なのでデータベースに格納できないため、<a href="https://www.php.net/manual/ja/function.serialize.php">シリアライズ</a>をして格納できる形にする。

```php
        //form送信したら
        if( !empty($_POST['btn_submit']) ) {
            // 選択されているかチェック
            if(empty($question1)) {
                $error_message[] = '問題1を選択してください。';
            }
            if(empty($question2)) {
                $error_message[] = '問題2を選択してください。';
            }
```
送信ボタンがおされたとき`$question1`と`$question2`が空であったら、`$error_message[]`に各エラーメッセージを入れる。

```php
            //未選択がないとき
            if( empty($error_message) ) {
                //データベースの挿入とトランザクションの開始
                $sql = "INSERT INTO xxx (question1,question2) VALUES (:question1,:question2)";
                $stmt = $pdo->prepare($sql);
                $pdo->beginTransaction();
                try{
                    $stmt->bindvalue( ':question1', $question1_db);
                    $stmt->bindvalue( ':question2', $question2_db);
                    $stmt->execute();
                    $pdo->commit();
                    $result = $stmt->fetchAll();
                }catch (PDOException $e) {
                    $pdo->rollBack();
                    throw $e;
                }
            header('Location: xxx');
            exit;
            }
        }
    }catch(PDOException $e) {
        $error_message[] = $e->getMessage();
        header('Content-Type: text/plain; charset=UTF-8', true, 500);
        exit($e->getMessage()); 
    }
    header('Content-Type: text/html; charset=utf-8');
?>
```
エラーメッセージが空、つまり2つの選択肢が選択されているときデータベースにデータを挿入する。
`xxx`には順に、テーブル名、リダイレクトするファイル名を入力する。今回はファイル自身を設定する。
接続エラー処理の中にトランザクション処理を行ってデータの整合性を保つ。
```php
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>example1</title>
            <!-- リンク -->
            <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css">
            <link rel="stylesheet" href="css/reset.css">
        </head>
```
デザインテンプレートとしてBootstrapを使用している。
```php
        <main>
            <?php if( !empty($error_message) ){ ?>
                <p class="alert alert-danger col-3" role="alert">
                    <?php foreach( $error_message as $value ){?>
                        <?php echo $value; ?><br>
                    <?php } ?>
                </p>
            <?php } ?>
```
`$error_message`に値が入っているとき、エラーメッセージを表示する。
```php
            <form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post" class="p-4">
                <div class="mb-3">
                        <label class="form-label">問題1:三英傑は誰？(複数回答可)</label><br>
                        <input type="checkbox" name="question1[]" value="織田信長" <?php if(isset($question1) && in_array('織田信長',$question1)) { echo "checked";} ?>>織田信長
                        <input type="checkbox" name="question1[]" value="ショパン" <?php if(isset($question1) && in_array('ショパン',$question1)) { echo "checked";} ?>>ショパン
                        <input type="checkbox" name="question1[]" value="毛沢東" <?php if(isset($question1) && in_array('毛沢東',$question1)) { echo "checked";} ?>>毛沢東
                </div>
                <div class="mb-3">
                        <label class="form-label">問題2:日本国民の三大義務は?(複数回答可)</label><br>
                        <input type="checkbox" name="question2[]" value="休息の義務" <?php if(isset($question2) && in_array('休息の義務',$question2)) { echo "checked";} ?>>休息の義務
                        <input type="checkbox" name="question2[]" value="勤労の義務" <?php if(isset($question2) && in_array('勤労の義務',$question2)) { echo "checked";} ?>>勤労の義務
                        <input type="checkbox" name="question2[]" value="納税の義務" <?php if(isset($question2) && in_array('納税の義務',$question2)) { echo "checked";} ?>>納税の義務
                </div>
                <div class="form-group">
                    <input type="submit" name="btn_submit" class="btn btn-primary" value="送信">
                </div>
            </form>
        </main>
    </body>
</html>
```
自分自身にデータを送信するformを作成する。
inputタグのname属性は配列にするため、最後に`[]`をつけるのを忘れずにする。
なお、`[]`はphpでは記述しなくてよい。
`$question1`が空の配列ではなくてかつ、`$question1`に「織田信長」が入っているとき、チェックボックスを選択済みにする。
## 送信成功したときのデータベース
![スクリーンショット 2022-12-26 202226.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3082297/822ec759-56c4-4039-12bb-4b1d5ec02a95.png)


## 参考文献
- [チェックボックスで複数選択されたデータを受け取る方法(PHP) - Qiita](https://qiita.com/Yorinton/items/3c662f1d3e5aea0ee541)
- [PHP: in_array - Manual](https://www.php.net/manual/ja/function.is-array)
- [PHP: serialize - Manual](https://www.php.net/manual/ja/function.serialize.php)
- [PHPのシリアライズを知ろう！便利な使い方徹底解説 | 侍エンジニアブログ](https://www.sejuku.net/blog/80079)
- [チェック＆ラジオ～Bootstrap5設置ガイド](https://bootstrap-guide.com/forms/checks-radios)
- [セレクトボックスの選択の保持、セッション、header関数、確認画面の作成 - Qiita](https://qiita.com/jwpks313/items/3d1801e19b25f7ee435c)