Piece Frameworkによるブログアプリケーションの作成

第9回データベース設計とデータアクセスコードの実装(2)

今回は前回に引き続いてアプリケーションへのデータアクセスの組み込みを行っていきます。

データアクセスコード配置先の検討とフローの変更-エントリー一覧フロー

前回は新規エントリー入力フローに対するデータアクセスコードの実装を行いました。新規エントリー入力フローで新規エントリーを作成するとエントリー一覧画面に遷移しますが、エントリー一覧画面で表示される内容はモックオブジェクトのものであり本物ではありません。そこで次はエントリー一覧フローにデータアクセスコードを組み込み、実際のレコードからエントリーの一覧が作成されるようにします。まずは新規エントリー入力フローと同様に、コードの配置先について検討してみます。

前回書いたように、筆者はビュー関連以外のコードをアクションステートのアクティビティ(ステート更新時のイベントハンドラに対応)に配置することを推奨していますので、今回もそれに従って新規にアクションステートを用意し、そのステートのアクティビティにデータアクセスコードを配置することにします。

では、下記のようにフロー定義ファイルを変更しましょう。

/path/to/pieceblog/web/webapp/config/flows/Entry/List.yaml
firstState: ProcessFindList

viewState:

  - name: DisplayList
    view: List
    activity:
      class: Entry_ListAction
      method: doActivityOnDisplayList

actionState:

  - name: ProcessFindList
    activity:
      class: Entry_ListAction
      method: doActivityOnProcessFindList
    transition:
      - event: DisplayListFromProcessFindList
        nextState: DisplayList

データアクセスコードの実装-エントリー一覧フロー

次にアクションクラスにイベントハンドラを追加し、データアクセスコードを記述します。ここでは単にentryテーブルの全レコードを取得するために$mapper->findAll()を使うことにします。また、モックオブジェクトの設定コードが不要になったためコンストラクタを削除しておきます。

/path/to/pieceblog/web/webapp/actions/Entry/ListAction.php
<?php
require_once 'Piece/Unity/Service/FlowAction.php';
require_once 'Piece/Unity/Service/FlexyElement.php';
require_once 'Piece/ORM.php';

class Entry_ListAction extends Piece_Unity_Service_FlowAction
{
    var $_entries;

    function doActivityOnDisplayList()
    {
        $viewElement = &$this->_context->getViewElement();
        $viewElement->setElementByRef('entries', $this->_entries);
    }

    function doActivityOnProcessFindList()
    {
        $mapper = &Piece_ORM::getMapper('Entry');
        $this->_entries = $mapper->findAll();

        return 'DisplayListFromProcessFindList';
    }
}
?>

作業が完了したら、ブラウザから動作を確認してください。エラーが発生せずにデータベースの内容が反映されたエントリー一覧画面が表示されたら成功です。また、新規エントリー入力フローからエントリーを追加してみましょう。きちんと新しいレコードが画面に反映されるはずです。また、直接データベースにアクセスし、表示されているレコードがデータベース由来のものか確認しておきましょう。

新規エントリー入力確認画面
新規エントリー入力確認画面
エントリー一覧画面
エントリー一覧画面
pgbashによるレコードの確認
$ select * from entry;
 id | title     | content
----+-----------+---------
  1 | タイトル1 | 内容1\r
  2 | タイトル2 | 内容2\r
  3 | タイトル3 | 内容3\r
(3 rows)

データアクセスコード配置先の検討とフローの変更-エントリー編集フロー

これまでに新規エントリー入力フローとエントリー一覧フローの実装が完了しましたので残るはエントリー編集フローのみとなります。このフローは他のフローと比べると少々複雑ですので少しずつ実装を進めていくことにします。まずは現状のフロー定義を確認しましょう。

/path/to/pieceblog/web/webapp/config/flows/Entry/Edit.yaml
firstState: DisplayShow

lastState:
  name: DisplayDeleteFinish
  view: http://example.org/list.php

viewState:

  - name: DisplayShow
    view: Show
    activity:
      class: Entry_EditAction
      method: doActivityOnDisplayShow
    transition:
      - event: DisplayEditFromDisplayShow
        nextState: DisplayEdit
      - event: DisplayDeleteConfirmViaDisplayShowFromDisplayShow
        nextState: DisplayDeleteConfirmViaDisplayShow

  - name: DisplayEdit
    view: Edit
    activity:
      class: Entry_EditAction
      method: doActivityOnDisplayEdit
    transition:
      - event: ProcessValidateEditFromDisplayEdit
        nextState: ProcessValidateEdit
      - event: DisplayDeleteConfirmViaDisplayEditFromDisplayEdit
        nextState: DisplayDeleteConfirmViaDisplayEdit

  - name: DisplayEditConfirm
    view: EditConfirm
    activity:
      class: Entry_EditAction
      method: doActivityOnDisplayEditConfirm
    transition:
      - event: DisplayShowFromDisplayEditConfirm
        nextState: DisplayShow
      - event: DisplayEditFromDisplayEditConfirm
        nextState: DisplayEdit

  - name: DisplayDeleteConfirmViaDisplayShow
    view: DeleteConfirmViaDisplayShow
    activity:
      class: Entry_EditAction
      method: doActivityOnDisplayDeleteConfirmViaDisplayShow
    transition:
      - event: DisplayDeleteFinishFromDisplayDeleteConfirmViaDisplayShow
        nextState: DisplayDeleteFinish
      - event: DisplayShowFromDisplayDeleteConfirmViaDisplayShow
        nextState: DisplayShow

  - name: DisplayDeleteConfirmViaDisplayEdit
    view: DeleteConfirmViaDisplayEdit
    activity:
      class: Entry_EditAction
      method: doActivityOnDisplayDeleteConfirmViaDisplayEdit
    transition:
      - event: DisplayDeleteFinishFromDisplayDeleteConfirmViaDisplayEdit
        nextState: DisplayDeleteFinish
      - event: DisplayEditFromDisplayDeleteConfirmViaDisplayEdit
        nextState: DisplayEdit

actionState:

  - name: ProcessValidateEdit
    activity:
      class: Entry_EditAction
      method: doActivityOnProcessValidateEdit
    transition:
      - event: DisplayEditConfirmFromProcessValidateEdit
        nextState: DisplayEditConfirm
      - event: DisplayEditFromProcessValidateEdit
        nextState: DisplayEdit
エントリー編集フローのステートチャート図
エントリー編集フローのステートチャート図

このフローのうちデータアクセスが必要な部分は下記のようになります。

  • フロー実行開始直後のエントリー参照画面表示前(SELECT)
  • エントリー編集確認画面のUpdateボタンがクリックされた後(UPDATE)
  • 各エントリー削除確認画面のDeleteボタンがクリックされた後(DELETE)

データアクセスコードの実装:エントリーの検索-エントリー編集フロー

最初に「フロー実行開始直後のエントリー参照画面表示前(SELECT)」該当部分にデータアクセスを行うためのアクションステートを追加し、イベントハンドラの実装を行います。

ステップ1-データアクセスコードの実装

まずは新規にアクションステートを追加します。

/path/to/pieceblog/web/webapp/config/flows/Entry/Edit.yaml
firstState: ProcessFind

...

actionState:

  - name: ProcessValidateEdit
    activity:
      class: Entry_EditAction
      method: doActivityOnProcessValidateEdit
    transition:
      - event: DisplayEditConfirmFromProcessValidateEdit
        nextState: DisplayEditConfirm
      - event: DisplayEditFromProcessValidateEdit
        nextState: DisplayEdit

  - name: ProcessFind
    activity:
      class: Entry_EditAction
      method: doActivityOnProcessFind
    transition:
      - event: DisplayShowFromProcessFind
        nextState: DisplayShow

検索処理を行うためのアクションステートProcessFindを追加し、ファーストステートに設定しています。次にアクションクラスを見てみましょう。

/path/to/pieceblog/web/webapp/actions/Entry/ListAction.php
<?php
require_once 'Piece/Unity/Service/FlowAction.php';
require_once 'Piece/Unity/Service/FlexyElement.php';

class Entry_EditAction extends Piece_Unity_Service_FlowAction
{
    ...
    function doActivityOnDisplayDeleteConfirmViaDisplayEdit()
    {
        $flexyElement = &new Piece_Unity_Service_FlexyElement();
        $flexyElement->addForm($this->_flow->getView(), $this->_context->getScriptName());

        $viewElement = &$this->_context->getViewElement();
        $viewElement->setElementByRef('entry', $this->_entry);
    }

    function doActivityOnProcessFind()
    {
        $request = &$this->_context->getRequest();
        $mapper = &Piece_ORM::getMapper('Entry');
        $this->_entry = &$mapper->findById($request->getParameter('id'));

        return 'DisplayShowFromProcessFind';
    }
}
...

アクションステートProcessFindのアクティビティに設定されたイベントハンドラdoActivityOnProcessFind()を追加しています。また、モックオブジェクトの設定コードが不要になったためコンストラクタを削除しています。

作業が完了したら、ブラウザから動作を確認しましょう。エントリー一覧画面のリンクをクリックすると、対象エントリーの内容が表示されるはずです。

ステップ2-idフィールドのバリデーション

しかしまだ問題があります。現在のコードはリクエストパラメータに含まれるidフィールドの値を直接参照しており、その値はバリデーションされたものではありません。アプリケーションの入力値に対するバリデーションはセキュリティリスクを低減する上で必須といえますので、きちんとidフィールドのバリデーションを行うことにしましょう。バリデーションを加えたアクションクラスは下記のようになります。

/path/to/pieceblog/web/webapp/config/validations/Entry/ID.yaml
...
    function doActivityOnProcessFind()
    {
        $validation = &$this->_context->getValidation();
        if ($validation->validate('ID', $this->_entry)) {
            $request = &$this->_context->getRequest();
            $mapper = &Piece_ORM::getMapper('Entry');
            $this->_entry = &$mapper->findById($request->getParameter('id'));

            return 'DisplayShowFromProcessFind';
        } else {
            return 'DisplayShowFromProcessFind';
        }
    }
...

$validation->validate()に渡されたバリデーションセット"ID"に対応するバリデーション定義ファイルも用意します。

/path/to/pieceblog/web/webapp/config/validations/Entry/ID.yaml
- name: id
  required:
  validator:
    - name: Range
      rule:
        min: 1
        max: 2147483647

作業が完了したら、ブラウザから動作を確認しましょう。URLを編集しidフィールドの値を数値以外にすると、内容が表示されなくなるでしょう。

ステップ3-エラー画面の作成

バリデーションが失敗した場合の動作は確認できましたが、この画面はアプリケーションが作成したリンクから遷移することになるため、本来バリデーションが失敗することはないはずです。よって、失敗時にエントリー参照画面が表示されるのは適切ではありません。ここは新規にエラー画面を用意し、バリデーション失敗時はその画面に遷移するようにします。下記に変更内容を示します。

/path/to/pieceblog/web/webapp/config/flows/Entry/Edit.yaml
...

  - name: DisplayError
    view: Error

actionState:

  - name: ProcessValidateEdit
    activity:
      class: Entry_EditAction
      method: doActivityOnProcessValidateEdit
    transition:
      - event: DisplayEditConfirmFromProcessValidateEdit
        nextState: DisplayEditConfirm
      - event: DisplayEditFromProcessValidateEdit
        nextState: DisplayEdit

  - name: ProcessFind
    activity:
      class: Entry_EditAction
      method: doActivityOnProcessFind
    transition:
      - event: DisplayShowFromProcessFind
        nextState: DisplayShow
      - event: DisplayErrorFromProcessFind
        nextState: DisplayError
/path/to/pieceblog/web/webapp/actions/Entry/ListAction.php
...
    function doActivityOnProcessFind()
    {
        $validation = &$this->_context->getValidation();
        if ($validation->validate('ID', $this->_entry)) {
            $mapper = &Piece_ORM::getMapper('Entry');
            $this->_entry = &$mapper->findById($this->_entry);

            return 'DisplayShowFromProcessFind';
        } else {
            return 'DisplayErrorFromProcessFind';
        }
    }
...

新たに遷移イベントDisplayErrorFromProcessFindを追加し、バリデーション失敗時にそのイベントを返すようにしています。また、$mapper->findById()に渡す値をリクエストパラメータのものから、$_entryプロパティに変更しています。この場合、Piece_ORMによって$_entry->idプロパティが使用されます。また、HTMLテンプレートも新規に作成します。

/path/to/pieceblog/web/webapp/templates/Entry/Error.html
<h3>Error occured!</h3>

作業が完了したら、ブラウザから動作を確認しましょう。不正な値を与えると今度はエラー画面が表示されるはずです。

エラー画面
エラー画面

ステップ4-検索結果による分岐

これでidフィールドのバリデーションは解決しましたが、まだ問題が残っています。それは$mapper->findById()によってレコードが見つからなかった場合の対応です。idフィールドに対してデータベースに存在しない値を与えると、バリデーションの時と同じく内容が表示されなくなってしまいます。そこで、idフィールドに対してデータベースに存在しない値を受け取った場合は、先ほど作成したエラー画面に遷移するようにします。下記に変更内容を示します。

/path/to/pieceblog/web/webapp/actions/Entry/ListAction.php
...
    function doActivityOnProcessFind()
    {
        $validation = &$this->_context->getValidation();
        if ($validation->validate('ID', $this->_entry)) {
            $mapper = &Piece_ORM::getMapper('Entry');
            $entry = &$mapper->findById($this->_entry);
            if (!is_null($entry)) {
                $this->_entry = &$entry;
                return 'DisplayShowFromProcessFind';
            } else {
                return 'DisplayErrorFromProcessFind';
            }
        } else {
            return 'DisplayErrorFromProcessFind';
        }
    }
...

$mapper->findById()のようなfindByから始まるメソッドは該当するレコードが見つからない場合にnullを返しますので、それを利用したコードになっています。

作業が完了したら、ブラウザから動作を確認しましょう。これでデータベースに存在しない数値を与えるとエラー画面が表示されるようになりました。エントリーの検索はこれで完成です。

データアクセスコードの実装:エントリーの更新-エントリー編集フロー

次に「エントリー編集確認画面のUpdateボタンがクリックされた後(UPDATE)」該当部分にデータアクセスを行うためのアクションステートを追加し、イベントハンドラの実装を行います。

ステップ1-データアクセスコードの実装

検索の場合と同様にまずはデータアクセスコードの実装を行います。下記はアクションステート追加後のフロー定義ファイルの内容です。

/path/to/pieceblog/web/webapp/config/flows/Entry/Edit.yaml
...

  - name: DisplayEditConfirm
    view: EditConfirm
    activity:
      class: Entry_EditAction
      method: doActivityOnDisplayEditConfirm
    transition:
      - event: DisplayEditFromDisplayEditConfirm
        nextState: DisplayEdit
      - event: ProcessUpdateFromDisplayEditConfirm
        nextState: ProcessUpdate

actionState:
...
  - name: ProcessUpdate
    activity:
      class: Entry_EditAction
      method: doActivityOnProcessUpdate
    transition:
      - event: DisplayShowFromProcessUpdate
        nextState: DisplayShow

ビューステートのイベント名を変更した場合、合わせてHTMLテンプレート内に埋め込まれたイベント名も変更する必要があります。

/path/to/pieceblog/web/webapp/templates/Entry/EditConfirm.html
<h4 class="date-header">EditConfirm</h4>
<p>Title: {entry.title}</p>
<p>Content: {GLOBALS.displayTextArea(entry.content):h}</p>
<form name="EditConfirm" id="EditConfirm">
  <input type="hidden" name="{__flowExecutionTicketKey}" value="{__flowExecutionTicket}" />
  <p>
    <input type="submit" name="{__eventNameKey}_DisplayEditFromDisplayEditConfirm" value="Back" />
    <input type="submit" name="{__eventNameKey}_ProcessUpdateFromDisplayEditConfirm" value="Update" />
  </p>
</form>

アクションクラスにイベントハンドラを追加し、レコードの更新のためのコードを実装します。

/path/to/pieceblog/web/webapp/actions/Entry/ListAction.php
...
    function doActivityOnProcessUpdate()
    {
        $mapper = &Piece_ORM::getMapper('Entry');
        $mapper->update($this->_entry);

        return 'DisplayShowFromProcessUpdate';
    }
...

作業が完了したら、ブラウザから動作を確認してみましょう。また、データベースに直接アクセスし変更が反映されているか確認してください。

ステップ2-更新結果による分岐

次に検索の場合と同様にデータベースクエリ実行結果による分岐を組み込みます。更新された件数が0件ならエラー画面に遷移するようにしましょう。

/path/to/pieceblog/web/webapp/config/flows/Entry/Edit.yaml
...
  - name: ProcessUpdate
    activity:
      class: Entry_EditAction
      method: doActivityOnProcessUpdate
    transition:
      - event: DisplayShowFromProcessUpdate
        nextState: DisplayShow
      - event: DisplayErrorFromProcessUpdate
        nextState: DisplayError
/path/to/pieceblog/web/webapp/actions/Entry/ListAction.php
...
    function doActivityOnProcessUpdate()
    {
        $mapper = &Piece_ORM::getMapper('Entry');
        if ($mapper->update($this->_entry)) {
            return 'DisplayShowFromProcessUpdate';
        } else {
            return 'DisplayErrorFromProcessUpdate';
        }
    }
...

上記のようにエラー画面に遷移するためのイベントを追加し、アクションクラスのイベントハンドラで分岐を行うようにします。

ステップ3-更新後の再検索

これでエントリーの更新は問題なく行えるようになりましたが、ひとつ問題が残っています。それは、現在の実装ではSQLやデータベースサーバによって設定されるような値に対応できない、というものです。例えば、レコードの更新日付をSQLによって設定する場合がそれにあたります。UPDATE foo SET mdate = current_timestampのようなSQLが実行された場合、最新のmdateフィールドの値を得るには再度対象レコードを検索する必要があります。この問題を解決する前に、まずは問題の可視化を行います。具体的には一旦entryテーブルを削除し、登録日(rdate)及び更新日(mdate)フィールドを加えた形で再度テーブルを作成することにします。

/path/to/pieceblog/data/schemas/entry.ddl
CREATE TABLE entry (
  id serial,
  title varchar(255) NOT NULL,
  content text NOT NULL,
  rdate timestamp with time zone NOT NULL DEFAULT current_timestamp,
  mdate timestamp with time zone NOT NULL DEFAULT current_timestamp,
  PRIMARY KEY(id)
);

テーブルの再作成が完了したら、Piece_ORMのキャッシュ/path/to/pieceblog/web/webapp/cache/ormディレクトリ以下のファイル)を削除してください。

次にエントリー参照画面のHTMLテンプレートに登録日及び更新日を追加します。

/path/to/pieceblog/web/webapp/templates/Entry/EditConfirm.html
<h4 class="date-header">Show</h4>
<p>Title: {entry.title}</p>
<p>Content: {GLOBALS.displayTextArea(entry.content):h}</p>
<p>Rdate: {entry.rdate}</p> 
<p>Mdate: {entry.mdate}</p>
<form name="Show" id="Show">
  <input type="hidden" name="{__flowExecutionTicketKey}" value="{__flowExecutionTicket}" />
  <p>
    <input type="submit" name="{__eventNameKey}_DisplayEditFromDisplayShow" value="Edit" />
    <input type="submit" name="{__eventNameKey}_DisplayDeleteConfirmViaDisplayShowFromDisplayShow" value="Delete" />
  </p>
</form>

では、新規エントリーをいくつか追加し、エントリー参照画面を表示させてみてください。そこには下記のように登録日と更新日が反映されているでしょう。

エントリー参照画面
エントリー参照画面

次に、エントリーを更新するときに更新日が自動的に設定されるようにします。

Piece_ORMはテーブルのメタデータに基づき、いくつかのメソッドと対応するSQLを自動生成します。それらの中にはSQLのINSERT, UPDATEに対応するメソッドとしてinsert(), update()も含まれますが、それらのメソッドに対応するSQLからはデフォルト値を持つフィールドが除外されるようになっています。

さて、更新日にはデフォルト値としてcurrent_timestampが設定されていますので、INSERT, UPDATEのSQLに更新日は含まれません。INSERTの方はこれで問題ありませんが、UPDATEの方はSQLを上書きしなければ更新日が更新されないことになります。よって、UPDATEのSQLはマッパー定義ファイルを使って上書きする必要があります。下記にマッパー定義ファイルの内容を示します。

/path/to/pieceblog/web/webapp/config/orm/mappers/Entry.yaml
- name: update
  query: UPDATE entry SET title = $title, content = $content, mdate = current_timestamp WHERE id = $id

マッパー定義ファイルの準備ができたら、実際にアプリケーションからエントリーの更新を行い、対象レコードのmdateフィールドの値が変更されることを確認しましょう。また、エントリー参照画面の更新日には、更新されたmdateフィールドの値が反映されないことも確認してください。

これでようやく問題の可視化ができました。この問題はエントリー更新後に再度対象レコードを検索することで解決できます。すなわち、ProcessUpdateステートからProcessFindステートへ遷移するようにフロー定義を変更することで解決できます。

/path/to/pieceblog/web/webapp/config/flows/Entry/Edit.yaml
...
  - name: ProcessUpdate
    activity:
      class: Entry_EditAction
      method: doActivityOnProcessUpdate
    transition:
      - event: ProcessFindFromProcessUpdate 
        nextState: ProcessFind
      - event: DisplayErrorFromProcessUpdate
        nextState: DisplayError

アクションステートのイベント名を変更した場合、合わせてイベントハンドラから返すイベント名も変更する必要があります。

/path/to/pieceblog/web/webapp/actions/Entry/ListAction.php
...
    function doActivityOnProcessUpdate()
    {
        $mapper = &Piece_ORM::getMapper('Entry');
        if ($mapper->update($this->_entry)) {
            return 'ProcessFindFromProcessUpdate';
        } else {
            return 'DisplayErrorFromProcessUpdate';
        }
    }
...

ここでブラウザから動作を確認したところ、エントリー更新後にエラー画面に遷移してしまうことがわかりました。これは、ProcessFindステートのアクティビティによるidフィールドのバリデーションが原因です。エントリー更新のタイミングではリクエストにidフィールドが含まれていないためバリデーションが失敗します。そして正常な遷移としてエラー画面が表示されるというわけです。

この問題に対してはバリデーション部分の抽出による対処を行います。変更後のフロー定義ファイル及びアクションクラスを下記に示します。

/path/to/pieceblog/web/webapp/config/flows/Entry/Edit.yaml
firstState: ProcessValidateID
...
actionState:
  ...
  - name: ProcessUpdate
    activity:
      class: Entry_EditAction
      method: doActivityOnProcessUpdate
    transition:
      - event: ProcessFindFromProcessUpdate
        nextState: ProcessFind
      - event: DisplayErrorFromProcessUpdate
        nextState: DisplayError

  - name: ProcessValidateID
    activity:
      class: Entry_EditAction
      method: doActivityOnProcessValidateID
    transition:
      - event: ProcessFindFromProcessValidateID
        nextState: ProcessFind
      - event: DisplayErrorFromProcessValidateID
        nextState: DisplayError
/path/to/pieceblog/web/webapp/actions/Entry/ListAction.php
...
    function doActivityOnProcessFind()
    {
        $mapper = &Piece_ORM::getMapper('Entry');
        $entry = &$mapper->findById($this->_entry);
        if (!is_null($entry)) {
            $this->_entry = &$entry;
            return 'DisplayShowFromProcessFind';
        } else {
            return 'DisplayErrorFromProcessFind';
        }
    }

    function doActivityOnProcessUpdate()
    {
        $mapper = &Piece_ORM::getMapper('Entry');
        if ($mapper->update($this->_entry)) {
            return 'ProcessFindFromProcessUpdate';
        } else {
            return 'DisplayErrorFromProcessUpdate';
        }
    }

    function doActivityOnProcessValidateID()
    {
        $validation = &$this->_context->getValidation();
        if ($validation->validate('ID', $this->_entry)) {
            return 'ProcessFindFromProcessValidateID';
        } else {
            return 'DisplayErrorFromProcessValidateID';
        }
    }
...

作業が完了したら、ブラウザから動作を確認しましょう。これでエントリーの更新は完成です。

エントリー参照画面
エントリー参照画面

データアクセスコードの実装:エントリーの削除-エントリー編集フロー

最後に「各エントリー削除確認画面のDeleteボタンがクリックされた後(DELETE)」該当部分にデータアクセスを行うためのアクションステートを追加し、イベントハンドラの実装を行います。

検索の場合と同様にまずはデータアクセスコードの実装を行います。下記はアクションステート追加後のフロー定義ファイルの内容になります。今回はこの時点でエラー画面への遷移も組み込みます。

/path/to/pieceblog/web/webapp/config/flows/Entry/Edit.yaml
...
  - name: DisplayDeleteConfirmViaDisplayShow
    view: DeleteConfirmViaDisplayShow
    activity:
      class: Entry_EditAction
      method: doActivityOnDisplayDeleteConfirmViaDisplayShow
    transition:
      - event: ProcessDeleteFromDisplayDeleteConfirmViaDisplayShow
        nextState: ProcessDelete
      - event: DisplayShowFromDisplayDeleteConfirmViaDisplayShow
        nextState: DisplayShow

  - name: DisplayDeleteConfirmViaDisplayEdit
    view: DeleteConfirmViaDisplayEdit
    activity:
      class: Entry_EditAction
      method: doActivityOnDisplayDeleteConfirmViaDisplayEdit
    transition:
      - event: ProcessDeleteFromDisplayDeleteConfirmViaDisplayEdit
        nextState: ProcessDelete
      - event: DisplayEditFromDisplayDeleteConfirmViaDisplayEdit
        nextState: DisplayEdit

  - name: DisplayError
    view: Error

actionState:
...
  - name: ProcessDelete
    activity:
      class: Entry_EditAction
      method: doActivityOnProcessDelete
    transition:
      - event: DisplayDeleteFinishFromProcessDelete
        nextState: DisplayDeleteFinish
      - event: DisplayErrorFromProcessDelete
        nextState: DisplayError

前述のように、ビューステートのイベント名を変更した場合、合わせてHTMLテンプレート内に埋め込まれたイベント名も変更する必要があります。

/path/to/pieceblog/web/webapp/templates/Entry/DeleteConfirmViaDisplayShow.html
<h4 class="date-header">DeleteConfirmViaDisplayShow</h4>
<p>Title: {entry.title}</p>
<p>Content: {GLOBALS.displayTextArea(entry.content):h}</p>
<form name="Show" id="Show">
  <input type="hidden" name="{__flowExecutionTicketKey}" value="{__flowExecutionTicket}" />
  <p>
    <input type="submit" name="{__eventNameKey}_DisplayShowFromDisplayDeleteConfirmViaDisplayShow" value="Back" />
    <input type="submit" name="{__eventNameKey}_ProcessDeleteFromDisplayDeleteConfirmViaDisplayShow" value="Delete" />
  </p>
</form>
/path/to/pieceblog/web/webapp/templates/Entry/DeleteConfirmViaDisplayEdit.html
<h4 class="date-header">DeleteConfirmViaDisplayEdit</h4>
<p>Title: {entry.title}</p>
<p>Content: {GLOBALS.displayTextArea(entry.content):h}</p>
<form name="Show" id="Show">
  <input type="hidden" name="{__flowExecutionTicketKey}" value="{__flowExecutionTicket}" />
  <p>
    <input type="submit" name="{__eventNameKey}_DisplayEditFromDisplayDeleteConfirmViaDisplayEdit" value="Back" />
    <input type="submit" name="{__eventNameKey}_ProcessDeleteFromDisplayDeleteConfirmViaDisplayEdit" value="Delete" />
  </p>
</form>

アクションクラスにイベントハンドラを追加し、レコードの削除のためのコードを実装します。

/path/to/pieceblog/web/webapp/actions/Entry/ListAction.php
...
    function doActivityOnProcessDelete()
    {
        $mapper = &Piece_ORM::getMapper('Entry');
        if ($mapper->delete($this->_entry)) {
            return 'DisplayDeleteFinishFromProcessDelete';
        } else {
            return 'DisplayErrorFromProcessDelete';
        }
    }
...

作業が完了したら、ブラウザから動作を確認しましょう。これでエントリーの削除は完成です。

エントリー削除前のエントリー一覧画面
エントリー削除前のエントリー一覧画面
エントリー削除確認画面
エントリー削除確認画面
エントリー削除後のエントリー一覧画面
エントリー削除後のエントリー一覧画面

エントリー編集フローの最終的なステートチャート図は下記のようになります。

エントリー編集フローのステートチャート図
エントリー編集フローのステートチャート図

おわりに

今回はエントリー一覧フロー及びエントリー編集フローにデータアクセスコードを組み込みました。単にデータアクセスコードが実装されるだけでなく、バリデーションやエラーハンドリングの必要性からフローの変更が行われたことに注意してください。

エントリー編集フローの最終的なステートチャート図を見ていただくとわかるように、実装が進むとともにフローの複雑性が増加することは避けられません。しかし、今ではPiece_IDEを使うことでその複雑性に対応することができます。

Piece_IDEのフローデザイナーはGUIベースのフロー定義を可能にします。フローデザイナーを使うと複雑なフローでも混乱することなく開発を進められると思います。是非ご利用ください。

Piece_IDEによるエントリー編集フローのステートチャート図
Piece_IDEによるエントリー編集フローのステートチャート図

次回は、各フローを互いに接続し、ひとつのアプリケーションとしてスムースに動作するようにします。

おすすめ記事

記事・ニュース一覧