ゼロから始めるPHP講座Vol.39 トランザクション応用


ゼロから始めるPHP講座Vol.39 トランザクション応用
目次
  1. ゼロから始めるPHP講座トランザクション応用
  2. トランザクション開始(自動コミットオフ)
  3. 処理確定(コミット)
  4. 処理取消(ロールバック)
  5. 課題

ゼロから始めるPHP講座トランザクション応用

トランザクション開始(自動コミットオフ)

php_transaction2-1-639

自動コミットをオフにすることでトランザクション開始となり、後に説明するコミットを明示的に行うまで、クエリ実行しても処理が確定しません。

処理確定(コミット)

php_transaction2-2-639

自動コミットをオフにしてトランザクション開始した場合、INSERTやUPDATE、DELETEを実行しても明示的にコミットをせず接続を閉じると、データベースに反映されません。

作成したtransaction.phpにて、mysqli_commit関数を実行している行をコメントアウトしWebページから商品購入した後、phpMyAdminから新規レコードがINSERTされているか確認してみましょう。

処理取消(ロールバック)

php_transaction2-3-639

自動コミットオフでトランザクションを明示的にコミットせずに接続を閉じるとMySQLはそのトランザクションをロールバックしてくれますが、プログラム内でもしっかりとロールバックを明示するようにしましょう。php_transaction2-4-639

上記はtransaction.phpの処理の流れとなります。

order_tableとorder_detail_tableへのINSERTは両方とも成功しなければ発注情報に不整合が発生してしまうため、トランザクションを利用し、コミットorロールバックを行っています。

より正確に各機能の役割と処理の流れを理解するため、

  • トランザクション開始(mysqli_autocommit)- 処理1(INSERTINTOorder_table)- 処理2(INSERTINTOorder_detail_table)- コミット(mysqli_commit)

transaction.phpの14の正常な処理の流れにおいて、一部ソースコードを変更してWebページから商品購入し、phpMyAdminにて処理1と処理2のINSERT結果が反映されているか確認してみましょう。

transaction.phpの変更実施内容

  • 4をコメントアウト※mysqli_commit関数説明時に実行済- 1をコメントアウト、4をコメントアウト- 3を必ず失敗するSQLへ変更- 1をコメントアウト、3を必ず失敗するSQLへ変更

トランザクションに限らず、どこかに異常(エラー)が発生した場合に問題が起きないプログラムを作成することが、サービスの品質に繋がります。

そのためにプログラムの役割や処理の流れは大切ですので、しっかりと理解をしましょう。

まとめると、トランザクションの特徴は以下となります。

トランザクションとは、関連する複数の処理を一つの処理としてまとめたものトランザクション

また、トランザクションには大きく分けて3つの処理があります。

  • トランザクション開始- 処理確定(コミット)- 処理取消(ロールバック)

トランザクションはデータの不整合を防ぐための重要な処理です。

不整合が発生してはならない処理を行う場合は必ずトランザクションを利用し、全て成功したらコミット、一部でも失敗したらロールバックを行う処理を記述してください。

課題

あるサイトにて、顧客が景品購入をするページのプログラムを作成してください。

購入時の処理以外のソースコードは用意したものを利用してください。


<?php

/**
 * 必要テーブル
 *
 * point_gift_table:     ポイントで購入可能な景品
 * point_customer_table: ユーザーのポイント保有情報
 * point_history_table:  ポイントでの購入履歴
 */

// MySQL接続情報
$host   = 'localhost'; // データベースのホスト名又はIPアドレス
$user   = 'username';  // MySQLのユーザ名
$passwd = 'passwd';    // MySQLのパスワード
$dbname = 'dbname';    // データベース名

$customer_id     = 1;       // 顧客は1に固定
$message         = '';      // 購入処理完了時の表示メッセージ
$point           = 0;       // 保有ポイント情報
$err_msg         = array(); // エラーメッセージ
$point_gift_list = array(); // ポイントで購入できる景品

// コネクション取得
if ($link = mysqli_connect($host, $user, $passwd, $dbname)) {

    // 文字コードセット
    mysqli_set_charset($link, 'UTF8');

    /**
     * 保有ポイント情報を取得
     */
    // 現在のポイント保有情報を取得するためのSQL
    $sql = 'SELECT point FROM point_customer_table WHERE customer_id = ' . $customer_id;

    // クエリ実行
    if ($result = mysqli_query($link, $sql)) {

        // 1件取得
        $row = mysqli_fetch_assoc($result);

        // 変数に格納
        if (isset($row['point']) === TRUE) {
            $point = $row['point'];
        }

    } else {
        $err_msg[] = 'SQL失敗:' . $sql;
    }

    mysqli_free_result($result);

    // POSTの場合はポイントでの景品購入処理
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {

        /*
         * ここに購入時の処理を記載してください
         * 既存のソースを変更したい場合、変更が必要な理由を講師に説明し、許可をとってください。
         */

    }

    /**
     * 景品情報を取得
     */
    // SQL
    $sql = 'SELECT point_gift_id, name, point FROM point_gift_table';

    // クエリ実行
    if ($result = mysqli_query($link, $sql)) {

        $i = 0;
        while ($row = mysqli_fetch_assoc($result)) {
            $point_gift_list[$i]['point_gift_id'] = htmlspecialchars($row['point_gift_id'], ENT_QUOTES, 'UTF-8');
            $point_gift_list[$i]['name']          = htmlspecialchars($row['name'],          ENT_QUOTES, 'UTF-8');
            $point_gift_list[$i]['point']         = htmlspecialchars($row['point'],         ENT_QUOTES, 'UTF-8');
            $i++;
        }

    } else {
        $err_msg[] = 'SQL失敗:' . $sql;
    }

    mysqli_free_result($result);
    mysqli_close($link);

} else {
    $err_msg[] = 'error: ' . mysqli_connect_error();
}

//var_dump($err_msg); // エラーの確認が必要ならばコメントを外す

?>
<!DOCTYPE HTML>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>トランザクション課題</title>
</head>
<body>
<?php if (empty($message) !== TRUE) { ?><?php print $message; ?>

<?php } ?>
    <section>
<h1>保有ポイント</h1>
<?php print number_format($point); ?>ポイント

</section>
    <section>
<h1>ポイント商品購入</h1>
<form method="post">
<ul>
<?php       foreach ($point_gift_list as $point_gift) { ?>
    <li>
                    <span><?php print $point_gift['name']; ?></span>
                    <span><?php print number_format($point_gift['point']); ?>ポイント</span>
<?php           if ($point_gift['point'] <= $point) { ?>
                    <button type="submit" name="point_gift_id" value="<?php print $point_gift['point_gift_id']; ?>">購入する</button>
<?php           }else{ ?>
                    <button type="button" disabled="disabled">購入不可</button>
<?php           } ?></li>
<?php       } ?></ul>
</form>※サンプルのためポイント購入は1景品 & 1個に固定

</section>
</body>
</html>

php_transaction2-5

プログラミングにおいて他人の書いたソースを読む力というのも大切なため、用意したコードを読み解いた上で変更はせず、追記のみで課題を解いて下さい。

今回利用するテーブルは以下3つです。

ポイントで購入可能な景品point_gift_tablepoint_customer_tablepoint_history_tablephp_transaction2-6-639

インポート用ファイル

このECサイトでは、お金ではなく独自のポイントを利用し商品購入をします。

購入した際に行うトランザクション処理は以下となります。

  • point_history_tableへ購入履歴をINSERT- point_customer_tableの顧客保有ポイントをUPDTE

購入が無事完了したら、購入した景品情報を表示してください。php_transaction2-7

NEXTLESSONユーザー定義関数①ユーザー定義関数①

PREVLESSONトランザクション基礎トランザクション基礎


CodeCampus編集部
この記事を書いた人
CodeCampus編集部
まずは7日間お試し!人気プログラミング講座を無料公開中
オンライン・プログラミングレッスンNo.1のCodeCamp