メインメニュー
XAMPP アレンジ
IED
WSL2
-
道具箱
リポジトリ編
フレームワーク編
公開ソフトウェア
メタ
リンク
- PHP ライブラリ
- PHP 言語
apricot:app:transactionApricot トランザクション
— y2sunlight 2020-05-15
関連記事
- Apricot アプリ
- 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画面を開きます:
- 画面A
- 画面B
前もって 画面A,B共にrootユーザの編集画面を表示しておきます。
画面A
■ 備考を変更して[保存]ボタンを押します。
■ 正常に保存できます。
画面B
■ 備考を変更して[保存]ボタンを押します。
■ 画面表示時点のデータが変更されているので楽観的ロック例外が発生します。
apricot/app/transaction.txt · 最終更新: 2020/05/21 10:41 by y2sunlight
コメント