Ground Sunlight

Windowsで作る - Webプログラミングの開発環境(PHP)

ユーザ用ツール

サイト用ツール


サイドバー

メインメニュー

道具箱

リポジトリ編

フレームワーク編

公開ソフトウェア

メタ
リンク


このページへのアクセス
今日: 1 / 昨日: 3
総計: 67

apricot:app:transaction

Apricot トランザクション

y2sunlight 2020-05-15

Apricot に戻る

関連記事

ユーザ登録画面にトランザクションの機能を追加します。トランザクションを作るか否かはアクション毎に設定できるようにします。また、トランザクション機能を追加することによりアクションでスローされる ApplicationException をキャッチして(エラー画面に遷移することなく)入力画面でエラーメッセージを表示できるようになります。アクションでスローされるApplicationException には以下のものがあります。

  • 楽観的ロック例外(OptimissticLockException)
  • 対象レコードが存在しない(レコード更新時および削除時)
  • その他のアクションで発生するException
    ( パースエラーなどの Error は対象外です )

通常、トランザクションは更新系の処理で設定するするので、参照系で発生した全ての例外は集約エラーハンドラで処理されエラー画面が表示されます。この状況を避けたい場合は、次の何れかの選択になるでしょう。

  • 参照系アクションもトランザクションを設定する
  • アクション内のcatchブロックでエラーメッセージ付きの入力画面をレンダリングする

コントローラベース

コントローラのベースクラス(Controller)に以下のprotectedメソッドを追加します。

メソッド機能
transactional
(array|string $actionName):void
トランザクションを開始するアクションの指定
callAction
(string $actionName, array $params):Response
アクションの起動

callAction() はコアのBaseControllerクラスのオーバーライドです。この関数はApplicationクラスが実際にアクションを呼びだす時に使用されます。

以下にControllerクラスのソースコードを示します。

/apricot/app/Foundation

Controller.php
<?php
namespace App\Foundation;
 
use App\Exceptions\ApplicationException;
use Core\Foundation\BaseController;
use Core\Foundation\ErrorBag;
use ORM;
 
/**
 * コントローラ
 */
class Controller extends BaseController
{
    /**
     * Transactional Actions
     * @var array
     */
    protected $transactionalActions = [];
 
    /**
     * Register transactional action on the controller.
     * @param  array|string $actionName
     */
    protected function transactional($actionName)
    {
        $actions = is_array($actionName) ? $actionName : func_get_args();
        $this->transactionalActions = array_merge($this->transactionalActions , $actions);
    }
 
    /**
     * {@inheritDoc}
     * @see \Core\Foundation\BaseController::callAction()
     */
    protected function callAction($actionName, $params)
    {
        if (!in_array($actionName, $this->transactionalActions))
        {
            // Non transactional action
            return parent::callAction($actionName, $params);
        }
 
        // Transactional action
        if (!ORM::getDb()->beginTransaction())
        {
            // Redirect
            $errorBag = new ErrorBag(__('messages.error.db.access'));
            return redirect(back())->withInputs()->withErrors($errorBag);
        }
 
        try
        {
            $response = parent::callAction($actionName, $params);
            ORM::getDb()->commit();
            return $response;
        }
        catch(ApplicationException $e)
        {
            ORM::getDb()->rollBack();
            \Core\Log::exception('error',$e);
            \Core\Debug::error($e);
 
            // Redirect
            $errorBag = new ErrorBag($e->getUserMessage());
            return redirect(back())->withInputs()->withErrors($errorBag);
        }
    }
}
  • transactional($actionName)
    • アクションをトランザクションアクション配列( $transactionalActions )に追加します
    • $actionNameは配列、文字列、文字列だけの可変長引数のいずれでも可能です
  • callAction($actionName, $params)
    • $transactionalActions に追加されていないアクションの場合
      • 普通にアクションを呼び出します
    • $transactionalActions に追加されてるアクションの場合
      • ORMのbeginTransaction()でトランザクションを開始します
      • アクションを呼び出します
      • アクションが正常終了の場合
        • ORMのcommit()でトランザクションをコミットします
        • アクションのResponseを返します
      • アクション内で例外が発生した場合
        • ORMのrollBack()でトランザクションをロールバックします
        • withInputs()で入力変数をフラッシュ変数に保存します
        • withErrors()で例外メッセージのエラーバッグをフラッシュ変数に保存します
        • redirect()で前画面にリダイレクトするResponseオブジェクトをします


ユーザコントローラ

アクションのトランザクション処理を有効にしたい場合は、コントローラのコンストラクタの中でControllerクラスのtransactional()メソッドを使って、以下のようにします。

  • transactional( 'action1', … );

/apricot/app/Controllers

UserController.php
<?php
namespace App\Controllers;
 
use App\Exceptions\ApplicationException;
use App\Foundation\Controller;
use App\Models\User;
use Core\Input;
 
/**
 * ユーザコントローラ
 */
class UserController extends Controller
{
    ...
 
    /**
     * ユーザコントローラの生成
     */
    public function __construct()
    {
        // モデル
        $this->user = new User();
 
        // トランザクションアクション登録
        $this->transactional('insert','update','delete');
    }
 
    ...
}
  • transactional()を使って3つのアクション( insert(), update(), delete() )のトランザクション処理が有効になるように設定しています。


テスト実行

楽観的ロック例外を使って、トランザクション機能をテストしてみましょう。

2つのApricot画面を開きます:

  1. 画面A
  2. 画面B

前もって 画面A,B共にrootユーザの編集画面を表示しておきます。

画面A

■ 備考を変更して[保存]ボタンを押します。

■ 正常に保存できます。


画面B

■ 備考を変更して[保存]ボタンを押します。

■ 画面表示時点のデータが変更されているので楽観的ロック例外が発生します。


コメント

コメントを入力. Wiki文法が有効です:
 
apricot/app/transaction.txt · 最終更新: 2020/05/21 10:41 by y2sunlight