CodeCamp Webマスターコース PHP 提出課題 自動販売機

#制作環境
Windows 10
XAMPP
PHP : 7.4.3
Visual Studio Code

#はじめに
この記事はプログラミングをはじめたばかりの素人が、メモするのに利用しています。
内容には誤りがあるかもしれません。

当方は2月から2ヶ月間CodeCampのWebマスターコースを受講しました。
CodeCampは、意向により模範解答などを見ることができません。
※理由は、コードの記述に正解がないからだそうです。

他の人のコードを見る事も参考になると思いますので、つたないコード記述ではありますが、合格をもらったコードを掲載します。

良くも悪くも参考になればと思います。

###【追記】
SQLインジェクションの脆弱性があると指摘をいただきました。
講座にない内容ではありますが、重要な内容なのでレッスン等で確認してみて下さい。



#構成
管理ページ tool.php
購入ページ index.php
購入結果ページ result.php
オリジナル関数 functions.php
スタイルシート drink.css

#オリジナル関数 functions.phpについて
エンティティ変換と空白削除の関数を別ファイルで作成し、読み込むようになっていますが、これは後から習うやり方なのでここでは無視してもかまいません。
自分は色々調べる過程でこのやり方を見つけた為、先行してこの方法を使いました。

#課題の内容
####内容
商品の管理・購入ページを作成します。

####合格ライン
・指定要件が全て満たされていること。
・エラーがなく、分かりやすいソースコードになっていること。

####要件
管理ページ tool.php

|No.|内容|
|:---:|:---|
|1|「ドリンク名」「値段」「在庫数」「公開ステータス」を入力し、商品を追加できる。|
|2|商品を追加する場合、「商品画像」を指定してアップロードできる。|
|3|追加した商品の一覧情報として、「商品画像」、「商品名」、「値段」、「在庫数」、「公開ステータス」のデータを一覧で表示する。|
|4|商品一覧から指定ドリンクの在庫数を入力し、在庫数の変更ができる。|
|5|商品一覧から指定ドリンクの公開ステータス「公開」あるいは「非公開」の変更ができる。|
|6|商品の追加あるいは指定ドリンク情報（「在庫数」、「公開ステータス」）の変更が正常に完了した場合、完了のメッセージを表示する。|
|7|商品を追加する場合、「商品名」「値段」、「在庫数」、「公開ステータス」「商品画像」のいずれかを指定していない場合、エラーメッセージを表示して、商品を追加できない。|
|8|商品を追加する場合、「値段」、「在庫数」は、0以上の整数のみ可能とする。0以上の整数以外はエラーメッセージを表示して、商品を追加できない。|
|9|商品を追加する場合、公開ステータスは「公開」あるいは「非公開」のみ可能とする。「公開」あるいは「非公開」以外はエラーメッセージを表示して、商品を追加できない。|
|10|アップロードできる「商品画像」のファイル形式は「JPEG」、「PNG」のみ可能とする。「JPEG」、「PNG」以外はエラーメッセージを表示して、商品を追加できない。|
|11|商品一覧から指定ドリンクの在庫数を変更する場合、0以上の整数のみ可能とする。0以上の整数以外はエラーメッセージを表示して、変更できない。|

購入ページ index.php

|No.|内容|
|:---:|:---|
|12|ステータスが「公開」のドリンク情報（「商品画像」「商品名」「値段」）を一覧で表示する。|
|13|金額を投入するテキストボックスを作成する。|
|14|ドリンクを選択するラジオボタンを作成する。|
|15|ドリンクの在庫が0の場合、ドリンクを選択するラジオボタンは表示せず、「売り切れ」と表示する。|
|16|購入ボタンを押すと購入結果ページへ遷移する。|

購入結果ページ result.php

|No.|内容|
|:---:|:---|
|17|ドリンクの購入が正常に完了した場合、指定ドリンクの「画像」「商品名」「お釣りの情報」を表示する|
|18|ドリンクの購入が正常に完了した場合、指定ドリンクの在庫を減らす（管理ページの在庫数が減っていること）。|
|19|購入ページへ戻るリンクを作成する。|
|20|購入するドリンクを指定していない場合、エラーメッセージを表示して、ドリンクを購入することはできない。|
|21|投入金額と指定ドリンクを購入するときに必要な金額を比べ、もし投入金額が足りない場合はエラーメッセージを表示して、ドリンクを購入することはできない。|
|22|投入金額は0以上の整数のみ可能である。0以上の整数以外の場合は、エラーメッセージを表示して、ドリンクを購入することはできない。|
|23|指定ドリンクの在庫を確認し、もし在庫がなかった場合はエラーメッセージを表示して、ドリンクを購入することはできない（確認手順の例は以下になります）。1. 購入ページで、在庫がある商品を表示する。2. もう１つのブラウザで管理ページを表示させて、購入予定の在庫がある商品の在庫数を0に変更する（在庫なしにする）。3. 別のブラウザで開いていた購入ページで、在庫数を0に変更した商品を選択して、購入するボタンを押した場合、エラーメッセージが表示されること|
|24|指定ドリンクのステータスを確認し、もしステータスが非公開の場合はエラーメッセージを表示して、ドリンクを購入することはできない（確認手順の例は以下になります）。1. 購入ページで、ステータスが公開の商品を表示する。2. もう１つのブラウザで管理ページを表示さて、購入予定のステータスが公開である商品について、ステータスを非公開に変更する（ステータスを非公開にする）。3. 別のブラウザで開いていた購入ページで、ステータスを非公開に変更した商品を選択して、購入するボタンを押した場合、エラーメッセージが表示されること。|
|25|ドリンクの購入が正常に完了した場合、指定ドリンクと購入日時の情報をデータベースに保存する（※この要件は必須ではなく任意です）。|

全体

|No.|内容|
|:---:|:---|
|26|比較は「===」や「!==」を利用していること|

#ソースコード

```PHP:tool.php
<?php
// 関連ファイル･フォルダ･DBテーブル
/*
index.php                   商品購入ページ
result.php                  購入後ページ
functions.php               関数定義ファイル

drink.css                   ページデザイン定義ファイル
drink_pictureフォルダ       画像ファイル保存フォルダ

drink_info_table            商品情報テーブル
stock_table                 在庫情報テーブル
drink_history_table         購入履歴テーブル
*/

$uploaddir = './drink_picture/';                                    // ファイルのアップ先(フォルダ)の指定
$err_msg = [];                                                      // エラーメッセージの格納先
$complete_msg = [];                                                 // 操作正常終了時のメッセージ格納先

$host = 'localhost';                                                // ホスト名
$username = 'xxxxxxxxxxxxx';                                        // ユーザー名
$passwd = 'xxxxxxxxxxxxx';                                          // パスワード
$dbname = 'xxxxxxxxxxxxx';                                          // データベース名

require_once('functions.php');                                      // 関数呼び出し
date_default_timezone_set('Asia/Tokyo');                            // タイムゾーン設定


// DB接続開始
if ($link = mysqli_connect($host, $username, $passwd, $dbname)) {

    // 文字化け防止
    mysqli_set_charset($link, 'utf8');

    // トランザクション開始(オートコミットをオフ）
    mysqli_autocommit($link, false);

    // ここから新規登録の動作=============================

    // メソッドとクエリ内容の確認
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_POST['sql_kind'] === 'insert') {

        // 名前の登録確認と入力情報の格納
        if (isset($_POST['new_name']) === TRUE) {

            switch (TRUE) {
                case (cut($_POST['new_name']) === ''):
                    $err_msg[] = '商品名を入力してください';
                    break;
                case (cut($_POST['new_name']) === NULL);
                    $err_msg[] = '商品名を入力してください';
                    break;
                default:
                    $new_name = cut($_POST['new_name']);
                    break;
            }
        }

        // 値段の登録確認と入力情報の整数判定と格納
        if (isset($_POST['new_price']) === TRUE) {

            switch (TRUE) {
                case (cut($_POST['new_price']) === ''):
                    $err_msg[] = '値段を入力してください';
                    break;
                case (cut($_POST['new_price']) === NULL):
                    $err_msg[] = '値段を入力してください';
                    break;
                case (preg_match('/^[0-9]+$/', cut($_POST['new_price'])) !== 1):
                    $err_msg[] = '値段は0以上の半角整数を入力してください';
                    break;
                default:
                    $new_price = (int) cut($_POST['new_price']);
                    break;
            }
        }

        // 在庫の登録確認と入力情報の整数判定と格納
        if (isset($_POST['new_stock']) === TRUE) {

            switch (TRUE) {
                case (cut($_POST['new_stock']) === ''):
                    $err_msg[] = '個数を入力してください';
                    break;
                case (cut($_POST['new_stock']) === NULL):
                    $err_msg[] = '個数を入力してください';
                    break;
                case (preg_match('/^[0-9]+$/', cut($_POST['new_stock'])) !== 1):
                    $err_msg[] = '在庫は0以上の半角整数を入力してください';
                    break;
                default:
                    $new_stock = (int) cut($_POST['new_stock']);
                    break;
            }
        }

        // ファイルの登録確認とファイル情報の判定と格納
        if ($_FILES['new_img']['error'] === UPLOAD_ERR_OK) {

            if (count($err_msg) === 0) {

                // ファイル情報の取得
                $chk_picture = getimagesize($_FILES['new_img']['tmp_name']);

                // ファイル形式の確認及び拡張子偽装の確認
                if ($chk_picture['mime'] === 'image/png' || $chk_picture['mime'] === 'image/jpeg') {

                    // ファイルサイズの確認
                    if ($chk_picture[0] <= 500 && ($chk_picture[1] <= 500)) {

                        // 拡張子の取得
                        $mime = $chk_picture['mime'];
                        switch ($mime) {
                            case 'image/png':
                                $type = '.png';
                                break;
                            case 'image/jpeg':
                                $type = '.jpg';
                                break;
                        }

                        // ファイル情報の格納 ファイル名は新規作成(セキュリティ対策)
                        $upload = $uploaddir . date('YmdHis') . rand(0, 10000) . $type;
                        // 一時フォルダのファイルをアップ先に移動
                        move_uploaded_file($_FILES['new_img']['tmp_name'], $upload);
                    } else {
                        $err_msg[] = 'ファイルは縦と横500px以内にしてください';
                    }
                } else {
                    $err_msg[] = 'PNGかJPEG形式のファイルをアップロードしてください';
                }
            }
        } else {
            $err_msg[] = 'ファイルを選択してください';
        }


        // ステータスの登録確認と格納
        if (isset($_POST['new_status']) === TRUE) {
            if ((int) $_POST['new_status'] === 0 || (int) $_POST['new_status'] === 1) {
                $new_status = (int) $_POST['new_status'];
            } else {
                $err_msg[] = 'ステータスは公開か非公開を選択してください';
            }
        } else {
            $err_msg[] = 'ステータスを選択してください';
        }

        // 現状のエラー数確認
        if (count($err_msg) === 0) {

            // 作成日時の取得
            $new_time = date('Y-m-d H:i:s');

            // テーブルに登録する情報を集約し格納
            $insert_data_info = [
                'drink_name' => $new_name,
                'price' => $new_price,
                'created_at' => $new_time,
                'updated_at' => $new_time,
                'status' => $new_status,
                'path' => $upload
            ];

            //  drink_info_tableへ情報を登録するクエリ
            $sql = 'INSERT INTO drink_info_table(drink_name, price, created_at, updated_at, status, path) VALUES (\'' . implode('\',\'', $insert_data_info) . '\')';

            // クエリの結果確認
            if ($result = mysqli_query($link, $sql) === TRUE) {

                // A_Iを取得
                $drink_id = mysqli_insert_id($link);

                // テーブルに登録する情報を集約し格納
                $insert_data_stock = [
                    'drink_id' => $drink_id,
                    'stock' => $new_stock,
                    'created_at' => $new_time,
                    'updated_at' => $new_time
                ];

                // stock_tableへ情報を登録するクエリ
                $sql = 'INSERT INTO stock_table(drink_id, stock, created_at, updated_at) VALUES (\'' . implode('\',\'', $insert_data_stock) . '\')';

                // クエリの結果確認
                if ($result = mysqli_query($link, $sql) !== TRUE) {
                    $err_msg[] = 'stock_tableへのデータの登録に失敗しました';
                }
            } else {
                $err_msg[] = 'drink_info_tableへのデータの登録に失敗しました';
            }
            if (count($err_msg) === 0) {
                $complete_msg[] = '追加登録完了!';
            }
        }
    }


    // ここから在庫数更新の動作===========================

    // メソッドとクエリ内容の確認
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_POST['sql_kind'] === 'update') {

        // 在庫の登録確認と入力情報の整数判定と格納
        if (isset($_POST['update_stock']) === TRUE) {
            if (preg_match('/^[0-9]+$/', cut($_POST['update_stock'])) === 1) {
                $update_stock = (int) cut($_POST['update_stock']);

                // 更新日時の取得
                $update_time = date('Y-m-d H:i:s');

                // 対象商品idの取得
                $update_id = $_POST['drink_id'];

                // stock_tableの情報を更新するクエリ
                $sql = 'UPDATE stock_table SET stock = ' . $update_stock . ', updated_at = \'' . $update_time . '\' WHERE drink_id = ' . $update_id;

                if ($result = mysqli_query($link, $sql) === TRUE) {
                    $complete_msg[] = '在庫数更新完了!';
                } else {
                    $err_msg[] = '在庫数の更新に失敗しました';
                }
            } else {
                $err_msg[] = '0以上の半角整数を入力してください';
            }
        }
    }

    // ここからステータス変更の動作=======================

    // メソッドとクエリ内容の確認
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && $_POST['sql_kind'] === 'change') {

        // ステータスの確認と入力情報の格納
        if (isset($_POST['change_status']) === TRUE) {
            if ((int) $_POST['change_status'] === 0 || (int) $_POST['change_status'] === 1) {
                $change_id = $_POST['drink_id'];
                $change_status = (int) $_POST['change_status'];

                // 変更日時の取得
                $change_time = date('Y-m-d H:i:s');

                // drink_info_tableの情報を更新するクエリ
                $sql = 'UPDATE drink_info_table SET updated_at = \'' . $change_time . '\', status = ' . $change_status . ' WHERE drink_id = ' . $change_id;


                if ($result = mysqli_query($link, $sql) === TRUE) {
                    $complete_msg[] = 'ステータス変更完了!';
                } else {
                    $err_msg[] = 'ステータスの変更に失敗しました';
                }
            } else {
                $err_msg[] = 'ステータスは公開か非公開を選択してください';
            }
        }
    }

    // トランザクション成否判定
    if (count($err_msg) === 0) {
        // 処理確定
        mysqli_commit($link);
    } else {
        // 処理取消
        mysqli_rollback($link);
    }

    // ここから一覧表示の動作=============================

    // 登録情報を取得するクエリ
    $sql = 'SELECT drink_info_table.drink_id, drink_info_table.drink_name, drink_info_table.price, stock_table.stock, drink_info_table.status, drink_info_table.path FROM drink_info_table LEFT JOIN stock_table ON drink_info_table.drink_id = stock_table.drink_id';

    // クエリの結果確認
    if ($result = mysqli_query($link, $sql)) {

        // 取得した情報の格納
        while ($row = mysqli_fetch_array($result)) {
            $data[] = $row;
        }
    } else {
        $err_msg[] = 'データの抽出に失敗しました';
    }

    // DB接続停止
    mysqli_free_result($result);
    mysqli_close($link);
} else {
    $err_msg[] = 'DB接続失敗';
}
?>

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <title>自動販売機商品管理</title>
    <link rel="stylesheet" href="drink.css">
</head>

<body>

    <?php if (count($complete_msg) !== 0) {
        foreach ($complete_msg as $complete) { ?>
            <p><?php print $complete; ?></p>
    <?php }
    } ?>

    <?php if (count($err_msg) !== 0) {
        foreach ($err_msg as $err) { ?>
            <p><?php print $err; ?></p>
    <?php }
    } ?>

    <h1>自動販売機管理ツール</h1>

    <section>
        <h2>新規商品追加</h2>

        <form method="post" enctype="multipart/form-data">
            <label>名前: <input type="text" name="new_name" size="30" /></label><br>
            <label>値段: <input type="text" name="new_price" size="30" /></label><br>
            <label>個数: <input type="text" name="new_stock" size="30" /></label><br>
            <input type="file" name="new_img" accept="image/jpeg, image/png" /><br>
            <select name="new_status"><br>
                <option value="0">非公開</option>
                <option value="1">公開</option>
            </select><br>
            <input type="hidden" name="sql_kind" value="insert">
            <input type="submit" value="■□■□商品追加■□■□" />
        </form>

    </section>

    <section>
        <h2>商品情報変更</h2>
        <table>
            <caption>商品一覧</caption>
            <tbody>
                <tr>
                    <th>商品画像</th>
                    <th>商品名</th>
                    <th>価格</th>
                    <th>在庫数</th>
                    <th>ステータス</th>
                </tr>

                <?php if (empty($data) !== TRUE) {
                    foreach ($data as $list) {
                        if ((int) $list['status'] === 0) { ?>
                            <tr class="status_0">
                            <?php } else { ?>
                            <tr>
                            <?php } ?>
                            <td><img class="image" src="<?PHP print $list['path']; ?>"></td>
                            <td class="d_name"><?php print html_enc($list['drink_name']); ?></td>
                            <td class="d_price"><?php print $list['price']; ?></td>
                            <td>
                                <form method="post">
                                    <input type="text" class="input_text_width text_align_right" name="update_stock" value="<?php print $list['stock']; ?>">個
                                    <br>
                                    <input type="submit" value="変更">
                                    <input type="hidden" name="drink_id" value="<?php print $list['drink_id']; ?>">
                                    <input type="hidden" name="sql_kind" value="update">
                                </form>
                            </td>

                            <?php if ((int) $list['status'] === 0) { ?>
                                <td class="d_status">
                                    <form method="post">
                                        <input type="submit" value="非公開 → 公開">
                                        <input type="hidden" name="change_status" value="1">
                                        <input type="hidden" name="drink_id" value="<?php print $list['drink_id']; ?>">
                                        <input type="hidden" name="sql_kind" value="change">
                                    </form>
                                </td>
                            </tr>
                        <?php } else { ?>
                            <td class="d_status">
                                <form method="post">
                                    <input type="submit" value="公開 → 非公開">
                                    <input type="hidden" name="change_status" value="0">
                                    <input type="hidden" name="drink_id" value="<?php print $list['drink_id']; ?>">
                                    <input type="hidden" name="sql_kind" value="change">
                                </form>
                            </td>
                            </tr>
                <?php }
                        }
                    } ?>

            </tbody>
        </table>
    </section>
</body>

</html>
```

```PHP:index.php
<?php
// 関連ファイル･フォルダ･DBテーブル
/*
tool.php                    商品管理ページ
result.php                  購入後ページ
functions.php               関数定義ファイル

drink.css                   ページデザイン定義ファイル
drink_pictureフォルダ       画像ファイル保存フォルダ

drink_info_table            商品情報テーブル
stock_table                 在庫情報テーブル
drink_history_table         購入履歴テーブル
*/

$err_msg = [];                                                      // エラーメッセージの格納先

$host = 'localhost';                                                // ホスト名
$username = 'xxxxxxxxxxxxx';                                        // ユーザー名
$passwd = 'xxxxxxxxxxxxx';                                          // パスワード
$dbname = 'xxxxxxxxxxxxx';                                          // データベース名

require_once('functions.php');                                      // 関数呼び出し


// DB接続開始
if ($link = mysqli_connect($host, $username, $passwd, $dbname)) {

    // 文字化け防止
    mysqli_set_charset($link, 'utf8');

    $sql = 'SELECT drink_info_table.drink_id, drink_info_table.drink_name, drink_info_table.price, drink_info_table.path, stock_table.stock
    FROM drink_info_table
    JOIN stock_table
    ON drink_info_table.drink_id = stock_table.drink_id
    WHERE drink_info_table.status = 1';

    // クエリの結果確認
    if ($result = mysqli_query($link, $sql)) {

        // 取得した情報の格納
        while ($row = mysqli_fetch_array($result)) {
            $data[] = $row;
        }
    } else {
        $err_msg[] = 'データの抽出に失敗しました';
    }

    // DB接続停止
    mysqli_free_result($result);
    mysqli_close($link);
} else {
    $err_msg[] = 'DB接続失敗';
}
?>

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <title>自動販売機</title>
    <link rel="stylesheet" href="drink.css">
</head>

<body id="index_body">

    <?php foreach ($err_msg as $err) { ?>
        <P><?php print $err; ?></P>
    <?php } ?>

    <h1>自動販売機</h1>

    <form class="form" method="post" action="./result.php">

        <div>
            金額 <input type="text" name="money" />
        </div>
        <div class="parent">
            <?php foreach ($data as $product) { ?>

                <div class="product">

                    <div class="product_picture">
                        <img class="product_img" src="<?php print $product['path']; ?>"></img>
                    </div>

                    <div class="product_name">
                        <p class="margin"><?php print html_enc($product['drink_name']); ?></p>
                    </div>

                    <div class="product_price">
                        <p class="margin"><?php print $product['price']; ?>円</p>
                    </div>

                    <div class="product_buy">
                        <?php if ((int) $product['stock'] === 0) { ?>
                            <div class="sold_out"><?php print '売り切れ'; ?></div>
                        <?php } else { ?>

                            <input type="radio" name="drink_id" value="<?php print $product['drink_id']; ?>" />

                        <?php } ?>
                    </div>

                </div>

            <?php } ?>
        </div>

        <div id="submit">
            <input type="submit" value="■□■□購入■□■□" />
        </div>

    </form>
</body>

</html>
```

```PHP:result.php
<?php
// 関連ファイル･フォルダ･DBテーブル
/*
tool.php                    商品管理ページ
index.php                   諸品購入ページ
functions.php               関数定義ファイル

drink.css                   ページデザイン定義ファイル
drink_pictureフォルダ       画像ファイル保存フォルダ

drink_info_table            商品情報テーブル
stock_table                 在庫情報テーブル
drink_history_table         購入履歴テーブル
*/

$err_msg = [];                                                      // エラーメッセージの格納先

$host = 'localhost';                                                // ホスト名
$username = 'xxxxxxxxxxxxx';                                        // ユーザー名
$passwd = 'xxxxxxxxxxxxx';                                          // パスワード
$dbname = 'xxxxxxxxxxxxx';                                          // データベース名

require_once('functions.php');                                      // 関数呼び出し
date_default_timezone_set('Asia/Tokyo');                            // タイムゾーン設定

// DB接続開始
if ($link = mysqli_connect($host, $username, $passwd, $dbname)) {

    // 文字化け防止
    mysqli_set_charset($link, 'utf8');

    // トランザクション開始(オートコミットをオフ）
    mysqli_autocommit($link, false);

    // メソッドとクエリ内容の確認
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {

        // 購入日時の習得
        $purchase_time = date('Y-m-d H:i:s');

        // drink_idの確認と入力情報の格納
        if (isset($_POST['drink_id']) === TRUE) {

            switch (TRUE) {
                case ($_POST['drink_id'] === ''):
                    $err_msg[] = 'index.phpからdrink_idを受信できませんでした';
                    break;
                case ($_POST['drink_id'] === NULL):
                    $err_msg[] = 'index.phpからdrink_idを受信できませんでした';
                    break;
                default:
                    $drink_id = (int) $_POST['drink_id'];
                    break;
            }
        } else {
            $err_msg[] = '商品を選択してください';
        }

        // moneyの確認と入力情報の格納
        if (isset($_POST['money']) === TRUE) {

            switch (TRUE) {
                case ($_POST['money'] === ''):
                    $err_msg[] = '金額を入力してください';
                    break;
                case ($_POST['money'] === NULL):
                    $err_msg[] = 'index.phpからmoneyを受信できませんでした';
                    break;
                case (preg_match('/^[0-9]+$/', cut($_POST['money'])) !== 1):
                    $err_msg[] = '金額は0以上の半角整数を入力してください';
                    break;
                default:
                    $money = (int) cut($_POST['money']);
                    break;
            }
        }

        // 現状のエラー数確認
        if (count($err_msg) === 0) {

            // 商品情報取得のクエリ
            $sql = 'SELECT drink_info_table.drink_name, drink_info_table.price, drink_info_table.path, drink_info_table.status, stock_table.stock
            FROM drink_info_table
            JOIN stock_table
            ON drink_info_table.drink_id = stock_table.drink_id
            WHERE drink_info_table.drink_id = ' . $drink_id;

            if ($result = mysqli_query($link, $sql)) {

                // 取得した情報の格納
                while ($row = mysqli_fetch_array($result)) {
                    $data[] = $row;
                }
            } else {
                $err_msg[] = '情報の取得に失敗しました';
            }

            // 取得した情報の格納
            foreach ($data as $info) {

                $drink_name = $info['drink_name'];
                $price = (int) $info['price'];
                $stock = (int) $info['stock'];
                $path = $info['path'];
                $status = (int) $info['status'];

                $remaining_stock = $stock - 1;
                $return = $money - $price;
            }

            // 各種チェック
            switch (TRUE) {
                case ($return < 0):
                    $err_msg[] = 'お金が足りません';
                    break;
                case ($remaining_stock < 0):
                    $err_msg[] = 'この商品は品切れです';
                    break;
                case ($status === 0):
                    $err_msg[] = 'この商品は選択できません';
                    break;
            }

            // stock_tableの更新クエリ
            $sql = 'UPDATE stock_table SET stock = ' . $remaining_stock . ', updated_at = \'' . $purchase_time . '\' WHERE drink_id = ' . $drink_id;

            if ($result = mysqli_query($link, $sql)) {

                // drink_history_tableの追加クエリ
                $sql = 'INSERT INTO drink_history_table(drink_id, purchased_at) VALUES (' . $drink_id . ', \'' . $purchase_time . '\')';

                if ($result = mysqli_query($link, $sql) !== TRUE) {
                    $err_msg[] = 'drink_history_tableへの追加に失敗しました';
                }
            } else {
                $err_msg[] = 'stock_tableの更新に失敗しました';
            }
        }
    }

    // トランザクション成否判定
    if (count($err_msg) === 0) {
        // 処理確定
        mysqli_commit($link);
    } else {
        // 処理取消
        mysqli_rollback($link);
    }

    // DB接続停止
    mysqli_close($link);
} else {
    $err_msg[] = 'DB接続失敗';
}
?>

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <title>自動販売機結果</title>
    <link rel="stylesheet" href="drink.css">
</head>

<body>

    <h1>自動販売機結果</h1>

    <?php foreach ($err_msg as $err) { ?>
        <p><?php print $err; ?></p>
    <?php } ?>

    <?php if (count($err_msg) === 0) { ?>

        <div class="result">
            <img class="result_img" src="<?php print $path; ?>">
            <p><?php print 'がしゃん!【' . html_enc($drink_name) . '】が買えました!'; ?></p>
            <p><?php print 'おつりは【' . $return . '円】です!'; ?></p>
        </div>

    <?php } ?>
    <a href="index.php">戻る</a>

    </form>
</body>

</html>
```

```CSS:drink.css
@charset "UTF-8";

/* tool.php */

table,
tr,
th,
td {
    border: solid 1px #000000;
    text-align: center;
}

table {
    border-collapse: collapse;
}

td {
    height: 200px;
}

.image {
    width: 200px;
    height: 200px;
}

.d_name {
    width: 300px;
}

.d_price {
    width: 80px;
}

.input_text_width {
    width: 60px;
}

.text_align_right {
    text-align: right;
}

.d_status {
    width: 180px;
}

.status_0 {
    background-color: #c0c0c0;
}

/* index.php */

.product {
    width: 230px;
    text-align: center;
    flex: 1;
}

.product_picture,
.product_img {
    width: 230px;
    height: 230px;
}

.margin {
    margin: 0px;
}

.form {
    width: 9600px;
}

.parent {
    width: 960px;
    display: flex;
    padding: 0;
    flex-wrap: wrap;
}

.product_name,
.product_price,
.product_buy {
    width: 230px;
}

.sold_out {
    color: #ff0000;
    font-weight: bold;
}

.product_price {
    color: #0000ff;
    font-weight: bold;
}

/* result.php */

.result_img,
.result {
    width: 400px;
}

.result_img {
    height: 400px;
}

```

```PHP:functions.php
<?php
// 関数を定義

// html変換
function html_enc($text)
{
    return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
}

// 不要スペースの削除
function cut($space)
{
    return preg_replace('/\A[\p{C}\p{Z}]++|[\p{C}\p{Z}]++\z/u', '', $space);
}
```