====== Apricot トランザクション ====== --- //[[http://www.y2sunlight.com|y2sunlight]] 2020-05-15// [[apricot:top|Apricot に戻る]] 関連記事 * [[apricot:configuration|Apricot プロジェクトの作成]] * [[apricot:public|Apricot 公開フォルダ]] * [[apricot:core:top|Apricot コア]] * Apricot アプリ * [[apricot:app:top|Apricot アプリ作成の準備]] * [[apricot:app:home|Apricot ホーム画面]] * [[apricot:app:error|Apricot エラー画面]] * [[apricot:app:db-model|Apricot データベースとモデル]] * [[apricot:app:user-list|Apricot ユーザ一覧画面]] * [[apricot:app:user-edit|Apricot ユーザ登録画面]] * [[apricot:app:validation|Apricot バリデーション]] * Apricot トランザクション * [[apricot:ext:middleware|Apricot 拡張]] ユーザ登録画面にトランザクションの機能を追加します。トランザクションを作るか否かはアクション毎に設定できるようにします。また、トランザクション機能を追加することによりアクションでスローされる [[apricot:app:top#例外クラス|ApplicationException]] をキャッチして(エラー画面に遷移することなく)入力画面でエラーメッセージを表示できるようになります。アクションでスローされるApplicationException には以下のものがあります。 * 楽観的ロック例外(OptimissticLockException) * 対象レコードが存在しない(レコード更新時および削除時) * その他のアクションで発生する''Exception'' \\ ( パースエラーなどの ''Error'' は対象外です ) 通常、トランザクションは更新系の処理で設定するするので、参照系で発生した全ての例外は集約エラーハンドラで処理されエラー画面が表示されます。この状況を避けたい場合は、次の何れかの選択になるでしょう。 * 参照系アクションもトランザクションを設定する * アクション内のcatchブロックでエラーメッセージ付きの入力画面をレンダリングする ---- ===== コントローラベース ===== コントローラのベースクラス(Controller)に以下のprotectedメソッドを追加します。 ^メソッド^機能^ |transactional\\ (array|string $actionName):void|トランザクションを開始するアクションの指定| |callAction\\ (string $actionName, array $params):Response|アクションの起動| callAction() はコアの[[apricot:core:base-controller|BaseControllerクラス]]のオーバーライドです。この関数はApplicationクラスが実際にアクションを呼びだす時に使用されます。 以下にControllerクラスのソースコードを示します。 {{fa>folder-open-o}} ** /apricot/app/Foundation ** 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', ... );** {{fa>folder-open-o}} ** /apricot/app/Controllers ** user = new User(); // トランザクションアクション登録 $this->transactional('insert','update','delete'); } ... } * transactional()を使って3つのアクション( ''insert()'', ''update()'', ''delete()'' )のトランザクション処理が有効になるように設定しています。 \\ ===== テスト実行 ===== 楽観的ロック例外を使って、トランザクション機能をテストしてみましょう。 2つのApricot画面を開きます: - 画面A - 画面B 前もって 画面A,B共にrootユーザの編集画面を表示しておきます。 === 画面A === [{{apricot:app:app11.png?nolink}}] ■ 備考を変更して[保存]ボタンを押します。 [{{apricot:app:app12.png?nolink}}] ■ 正常に保存できます。 \\ === 画面B === [{{apricot:app:app13.png?nolink}}] ■ 備考を変更して[保存]ボタンを押します。 [{{apricot:app:app14.png?nolink}}] ■ 画面表示時点のデータが変更されているので楽観的ロック例外が発生します。 \\