質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.37%

  • PHP

    25087questions

    PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

  • CodeIgniter

    292questions

    CodeIgniterは、PHP向けオープンソースのWebアプリケーションフレームワークです。CodeIgniterは覚える構文が少なく、自由度も高いため、PHPを理解していれば構築が簡単です。

CodeIgniterでのトランザクション管理について

解決済

回答 2

投稿

  • 評価
  • クリップ 1
  • VIEW 2,112

hukinotou

score 5

CodeIgniter、MySQLのトランザクション管理について教えてください。

実現したいこと

コントローラーの開始でトランザクションを開始し、コントローラーの終わりでトランザクションをコミット / ロールバック。
チームで開発をする際にメンバーのスキルによって、漏れが発生してしまうことを防ぐために、上位で制御を行いたいです。

回答いただきたいこと

下記に示すコード(__destruct()でのトランザクション処理)で問題はないか。

__destruct()のタイミングでコネクションがなくなる(closeされる)ことがあり得るのかが気になっています。

該当のソースコード

class MY_Controller extends CI_Controller {
    public $trans_failure = FALSE;
    private $_trans_status = FALSE;

    public function __construct()
    {
        parent::__construct();
        $this->_trans_start();
    }

    public function __destruct()
    {
        parent::__destruct();
        $last_error = error_get_last();
        if (isset($last_error) &&
            ($last_error['type'] & (E_ERROR | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING)))
        {
            // Exceptionのためロールバック
            $this->trans_failure = TRUE;
        }

        $this->_trans_complete();
    }

    private function _trans_start()
    {
        if ($this->db->trans_begin())
        {
            $this->_trans_status = TRUE;
        }
    }

    private function _trans_complete()
    {
        if ($this->_trans_status === FALSE)
        {
            return;
        }

        if ($this->trans_failure === TRUE)
        {
            $this->db->trans_rollback();
            $this->_trans_status = FALSE;
            return;
        }

        $this->db->trans_commit();
        $this->_trans_status = FALSE;
    }
}

基本的にはコントローラーの最後でコミットとし、排他チェックなどによる意図的なロールバックは$this->trans_failure = FALSE;をすることを想定しています。

試したこと

hookの機能を利用し、post_controller_constructorでトランザクションの開始、post_controllerでトランザクションの完了処理を試しました。
url_helperのredirect()などでexitした場合に、トランザクションの完了処理が呼ばれず、結果としてロールバックされてしまいました。

補足情報(言語/FW/ツール等のバージョンなど)

CodeIgniter 3.1.4
MySQL

よろしくお願いします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

0

がると申します。
CodeIgniterに長けているわけではないので、「PHPの一般論として」程度のお話、になるのですが。

デストラクタは「特定のオブジェクトを参照するリファレンスがひとつもなくなったとき」または「スクリプトの終了時」にcallされますが。
特にスクリプトの終了時には「順不同で」callされるので。

また、「$this->db->」とあるので、「$this->db」には何がしかのDB接続系インスタンス(PDOとかですかねぇ?)が入っている、と予見されるので。

上述から

  • 順不同なので「$this->dbのデストラクタ」が先にcallされる可能性

が十分に予見されるので、そうすると

  • $this->dbのデストラクタが動く(=DB接続が切られる:トランザクション中であれば、rollbackされる)
  • MY_Controllerクラスのデストラクタがcallされる時には「DB接続が切れている」ので、commitはできない

といった流れが、可能性として予見されるように思われます。

少々斜めにぐぐってみたのですが……ほかのフレームワークだと定期的にみられる、例えば「afterFilter」的なメソッドが、ざっくり検索した限りだと、見当たらないようなので。
そのあたりを自作するか、或いはサポートされている機能として実はあるのであれば、その辺を使うか、といった感じの箇所にcommit系の処理を書くと、比較的穏当に動くのではないか、と思うのですが如何でしょうか?

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/07/19 21:22

    がるさん回答ありがとうございます。
    デストラクタではやはり、正常にトランザクションのコントロールができる保証がないということですね。
    私もこのあたりが気になっておりました。

    「試したこと」に記載しておりますが、hookの機能ではexit時に意図したようにできませんでした。

    `register_shutdown_function()`を利用したら制御できるかどうかなどの知見をお持ちでしたら教示ください。

    キャンセル

  • 2017/07/21 00:21

    そのあたり、大分と「予想込み」になってしまうのですが。

    確か、先日斜めに見た限りですと、CI_Controller、シングルトンのような作りになっていたように見受けられます。
    で、もし「シングルトン」だとしますと、register_shutdown_function()関数の中で、先に明示的にMY_Controllerのインスタンスを削除してやると、ある程度、意図通りに動く、かも、しれません(元々、デストラクタって「適当なタイミングでcallされる」ので、どこまで期待できるか、は微妙ですが)。

    もし自分が実装するのであれば…を想定してみたのですが。
    自分であればなんとなく
    ・内部に「トランザクションの完了処理」用のフラグを持っておく(初手はfalse)
    ・一度デストラクタが走ったら「トランザクションの完了処理フラグをtrue」にして、二重には走らないようにしておく
    ・exitする前に、出来るだけ「自力でデストラクタ(相当の、トラン完了処理)はcallするようにしておく」
    ・register_shutdown_function()で、デストラクタを走らせるか、或いはインスタンスを明示的にunsetして、GCに送り込む
    といった作りにするか……とか、妄想しました。

    上述、結構アバウトだったり雑だったりする箇所が含まれているので「話半分」程度に見ていただければ、と思いますが、参考の足しにでもなれば幸いです。

    キャンセル

  • 2017/07/22 17:53

    回答ありがとうございます。

    がるさんの回答を踏まえ、考えたのですが、デストラクタだけでなく、register_shutdown_function() でも_trans_complete()を実行するようにして試してみたいと思います。

    キャンセル

0

下記の方法で試してみることにしました。

  1. _trans_complete() のアクセス修飾子をpublicへ変更
  2. register_shutdown_function([&$this, '_trans_complete']); を追加
  3. Exceptionの発生判定を_trans_complete()へ移動
class MY_Controller extends CI_Controller {
    public $trans_failure = FALSE;
    private $_trans_status = FALSE;

    public function __construct()
    {
        parent::__construct();
        $this->_trans_start();
    }

    public function __destruct()
    {
        parent::__destruct();

        $this->_trans_complete();
    }

    private function _trans_start()
    {
        register_shutdown_function([&$this, '_trans_complete']);
        if ($this->db->trans_begin())
        {
            $this->_trans_status = TRUE;
        }
    }

    public function _trans_complete()
    {
        $last_error = error_get_last();
        if (isset($last_error) &&
            ($last_error['type'] & (E_ERROR | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING)))
        {
            // Exceptionのためロールバック
            $this->trans_failure = TRUE;
        }

        if ($this->_trans_status === FALSE)
        {
            return;
        }

        if ($this->trans_failure === TRUE)
        {
            $this->db->trans_rollback();
            $this->_trans_status = FALSE;
            return;
        }

        $this->db->trans_commit();
        $this->_trans_status = FALSE;
    }
}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.37%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • PHP

    25087questions

    PHPは、Webサイト構築に特化して開発されたプログラミング言語です。大きな特徴のひとつは、HTMLに直接プログラムを埋め込むことができるという点です。PHPを用いることで、HTMLを動的コンテンツとして出力できます。HTMLがそのままブラウザに表示されるのに対し、PHPプログラムはサーバ側で実行された結果がブラウザに表示されるため、PHPスクリプトは「サーバサイドスクリプト」と呼ばれています。

  • CodeIgniter

    292questions

    CodeIgniterは、PHP向けオープンソースのWebアプリケーションフレームワークです。CodeIgniterは覚える構文が少なく、自由度も高いため、PHPを理解していれば構築が簡単です。