ja/users/piece-unity/QuickStart/SimpleWebDevelopmentWithPieceFramework

<< Piece_Unityクイックスタート

Piece FrameworkによるシンプルなWeb開発

Copyright (c) 2006 TAKESHITA Koki, SHIGETA Takuji, KUBO Atsuhiro, All rights reserved.
Copyright (c) 2007 Piece Project, All rights reserved.

このドキュメントは、クリエイティブ・コモンズ・ライセンスの下でライセンスされています。

このドキュメントは、アシアル株式会社発行のPHP技術誌、PHPプロ!マガジン 2007年冬号 Vol.2特集1 有力5大フレームワーク徹底入門Part4 Piece FrameworkによるシンプルなWeb開発として掲載された記事を基に再編集したものです。

目次

  1. はじめに
  2. アプリケーションを作ろう
  3. インストール
    1. Piece_Unityのインストール
    2. サンプルアプリケーションのインストール
  4. フロー定義
    1. フローとは?
    2. フロー定義に必要な情報
    3. ビューステートの定義
      1. (1) name - ビューステート名
      2. (2) view - ビュー(テンプレート)名
      3. (3) activity - アクティビティ
      4. (4) transition - 遷移
    4. アクションステートの定義
    5. ファーストステートの定義
    6. フロー定義のポイント
  5. ビュー
    1. (1) フロー実行チケットの埋め込み
    2. (2) イベント名の埋め込み
    3. (3) 動的な値の表示
    4. (4) エラーメッセージの表示
  6. バリデーション
    1. Piece_Unityにおけるバリデーションの特徴
    2. バリデーション定義ファイルの記述
  7. アクション
    1. Piece_Unityにおけるアクションクラス
    2. アクションクラスの実際
  8. エントリポイント
  9. 設定
  10. 動作テスト
  11. おわりに

はじめに

現在のWebアプリケーション開発において、開発者が行わなければならないことは山ほどあります。例えば、プレゼンテーション部分だけでも、リクエストの受け付け、HTMLの生成、入出力フィルタリング、ユーザへのレスポンス、アプリケーションフローの制御、ユーザ入力値の妥当性チェック、ビジネスロジックの呼び出し、テンプレート変数の設定、セッション変数の設定及び取得等があります。

Webアプリケーションフレームワーク(以下、単にフレームワークとします)を使うということは、それらの多くを開発者の代わりにフレームワークが担当することで、開発者はより本質的な部分~問題領域~へ工数を割り当てるようにすることに他なりません。

悪いことに、上記の「山ほど」に加えて頭の痛い問題が出てきました。セキュリティ対策です。中でもCSRF対策については、従来のフレームワークが提供する機能(主に同期トークン)を使うにせよ、開発者自ら対策するにせよ、本質的な対応が大変難しいことがわかってきました。(CSRF及び対策については、高木浩光氏のサイトに詳しい。1

Piece_Unityはアプリケーションフローを強制することで、根本的なCSRF対策を提供します。

このドキュメントでは、サンプルアプリケーションを通して、Piece_Unityがいかにアプリケーションフローを制御するかを示します。そしてこの記事を通して、Piece FrameworkによってWeb開発がよりシンプルなものになることを理解頂けるでしょう。従来のフレームワークと大きく異なる、Piece Frameworkの世界を体験してください。

※サンプルアプリケーションの動作は、デモサイト2で確認することができます。

アプリケーションを作ろう

新たなフレームワークを理解しようとする場合、最もシンプルな形のサンプルアプリケーションを作成してみるのが一番の近道です。ここからは、典型的なパターンのWebアプリケーションを、Piece_Unityを使って表現してみたいと思います。

作成するのは、下記のように3ページに渡って回答していく形式のアンケートアプリケーションです。

サンプルアプリケーションの画面遷移

通常であれば1ページで盛り込める程度の内容ですが、Piece_Unityの特徴であるステートフルな動きをイメージしやすいように、あえて複数ページに分けています。また、このようなアプリケーションでは通常はデータベース処理が発生しますが、フレームワークの骨格部分のみを見ていただくため、今回はビジネスロジックは省きました。

入力->確認->完了という流れがあり、必要に応じて前のページに戻って再入力、かつ入力中の値は維持する、というのはWebアプリケーションではおなじみの仕様ですので、このサンプルアプリケーションを通して、Piece_Unityを使ったWebアプリケーションの構造や、他のフレームワークとの違いもイメージできるはずです。

インストール

Piece_Unityのインストール

Piece Frameworkは、フレームワークのフロントエンドとなるプレゼンテーションフレームワークPiece_Unity、フローエンジンPiece_Flow、バリデーションフレームワークPiece_Right, その他いくつかのプロダクトで構成されます。Piece Frameworkのすべてのプロダクトは、下記のようにpearコマンドでインストールできます。(Spyc3のみ手動でインストールする必要があります。)

pearコマンドによるインストール:

>pear.bat channel-discover pear.piece-framework.com
>pear.bat install piece/piece_unity-beta

PEARチャネルからのインストールでは、依存関係が自動的に解決されるため、今回であればPiece_Unityをインストールするだけで、必要なパッケージが自動的にインストールされます。ただし、上記の例ではオプションパッケージはインストールされません。

サンプルアプリケーションは、テンプレートシステムとしてHTML_Template_Flexy4を使っていますので、そちらもインストールしておきます。

>pear.bat install html_template_flexy

Piece_Unityのインストールについての詳細は、ユーザーズマニュアルインストールの章を参照ください。

サンプルアプリケーションのインストール

サンプルアプリケーションもPiece_Unityと同様にpearコマンドでインストールできます。

>pear.bat install piece/piece_examples_questionnaire-beta

下記はサンプルアプリケーションのファイル一覧です。

>pear.bat list-files piece/piece_examples_questionnaire
INSTALLED FILES FOR PIECE/PIECE_EXAMPLES_QUESTIONNAIRE
======================================================
TYPE INSTALL PATH
doc  C:\path\to\pear\docs\Piece_Examples_Questionnaire\docs\LICENSE
doc  C:\path\to\pear\docs\Piece_Examples_Questionnaire\docs\questionnaire.jude
data C:\path\to\pear\data\Piece_Examples_Questionnaire\web\htdocs\css\style.css
data C:\path\to\pear\data\Piece_Examples_Questionnaire\web\htdocs\questionnaire.php
data C:\path\to\pear\data\Piece_Examples_Questionnaire\web\webapp\actions\QuestionnaireAction.php
data C:\path\to\pear\data\Piece_Examples_Questionnaire\web\webapp\cache\flows\README
data C:\path\to\pear\data\Piece_Examples_Questionnaire\web\webapp\cache\validations\README
data C:\path\to\pear\data\Piece_Examples_Questionnaire\web\webapp\cache\README
data C:\path\to\pear\data\Piece_Examples_Questionnaire\web\webapp\compiled-templates\README
data C:\path\to\pear\data\Piece_Examples_Questionnaire\web\webapp\config\flows\Questionnaire.yaml
data C:\path\to\pear\data\Piece_Examples_Questionnaire\web\webapp\config\validations\Answer1.yaml
data C:\path\to\pear\data\Piece_Examples_Questionnaire\web\webapp\config\validations\Answer2.yaml
data C:\path\to\pear\data\Piece_Examples_Questionnaire\web\webapp\config\validations\Answer3.yaml
data C:\path\to\pear\data\Piece_Examples_Questionnaire\web\webapp\config\piece-unity-config.yaml
data C:\path\to\pear\data\Piece_Examples_Questionnaire\web\webapp\sessions\README
data C:\path\to\pear\data\Piece_Examples_Questionnaire\web\webapp\templates\Layout\Layout.html
data C:\path\to\pear\data\Piece_Examples_Questionnaire\web\webapp\templates\Questionnaire\Confirmation.html
data C:\path\to\pear\data\Piece_Examples_Questionnaire\web\webapp\templates\Questionnaire\Finish.html
data C:\path\to\pear\data\Piece_Examples_Questionnaire\web\webapp\templates\Questionnaire\Form1.html
data C:\path\to\pear\data\Piece_Examples_Questionnaire\web\webapp\templates\Questionnaire\Form2.html
data C:\path\to\pear\data\Piece_Examples_Questionnaire\web\webapp\templates\Questionnaire\Form3.html

ディレクトリ構成は下記のようになります。

[TOP (C:\path\to\pear\data\Piece_Examples_Questionnaire)]
  |
  +- [web]
       |
       +- [htdocs]
       |    |
       |    +- [css]
       |    |    |
       |    |    +- style.css
       |    |
       |    +-questionnaire.php
       |
       +- [webapp]
            |
            +- [actions]
            |    |
            |    +- QuestionnaireAction.php
            |
            +- [cache]
            |    |
            |    +- [flows]
            |    |    |
            |    |    +- README
            |    |
            |    +- [validations]
            |    |    |
            |    |    +- README
            |    |
            |    +- README
            |
            +- [compiled-templates]
            |    |
            |    +- README
            |
            +- [config]
            |    |
            |    +- [flows]
            |    |    |
            |    |    +- Questionnaire.yaml
            |    |
            |    +- [validations]
            |    |    |
            |    |    +- Answer1.yaml
            |    |    |
            |    |    +- Answer2.yaml
            |    |    |
            |    |    +- Answer3.yaml
            |    |
            |    +- piece-unity-config.yaml
            |
            +- [sessions]
            |    |
            |    +- README
            |
            +- [templates]
                 |
                 +- [Layout]
                 |    |
                 |    +- Layout.html
                 |
                 +- [Questionnaire]
                      |
                      +- Confirmation.html
                      |
                      +- Finish.html
                      |
                      +- Form1.html
                      |
                      +- Form2.html
                      |
                      +- Form3.html

これはPiece_Unityを使ったアプリケーションの典型的なディレクトリ構成になります。htdocs以下にブラウザで直接アクセスされるエントリポイントが配置され、webapp以下にアプリケーションの本体が配置されています。アクションクラスやテンプレート、各種設定ファイルを配置するディレクトリに加えて、キャッシュ用のディレクトリを作成しておきます。アプリケーションはサーバ上のどこに配置してもかまいませんが、htdocs以下はWebサーバ経由で参照できる必要があります。多くの場合、htdocsをバーチャルホストのドキュメントルートとして設定することになるでしょう。また、webapp/compiled-templates, webapp/sessions, webapp/cache, webapp/cache/flows, webapp/cache/validationsについては、Webサーバの実行ユーザが書き込めるようパーミッションを適宜変更してください。

フロー定義

従来のWebアプリケーションにおける画面遷移は、それぞれが独立したコードを単にテンプレート上のリンクによって結びつけ、一連のコードが関連しているかのように振る舞っているに過ぎません。そのため、それぞれのコード間でお互いの関連性や前後関係をチェックするには、それ相応の労力が必要です。リクエスト->レスポンスの繰り返しとなる、HTTP上のアプリケーションならではの悩みですが、昨今CSRFという言葉が取り沙汰されていることからもわかるように、これがアプリケーションの深刻な脆弱性となり得る可能性は高まっています。Piece_Unityではあらかじめフロー定義を用意することによって、予期せぬアクションへのアクセスや不正な遷移に頭を悩ませることなく、セキュアなWebアプリケーションを実現できます。

フローとは?

フローの説明の前にもう一度サンプルアプリケーションの画面遷移を確認してみましょう。通常こういった仕様書レベルでの画面遷移図は、ビュー同士の関連性が示されるだけで、各遷移の合間に実際に行わなければならない処理(アクション)が示されていません。具体的には、入力画面(1)から入力画面(2)に遷移する際には、必須チェックなど送信データのバリデーションを行うアクション、また、確認画面から完了画面に遷移する際には、データベースなどへのデータ登録を行うアクションが存在します。

Piece_Unityではフローを表現する単位としてステートと呼ばれるものを使います。ステートには2つの種類があります。ひとつは、画面遷移図で使われるような画面そのものと、その画面に対するクライアントからのイベントを受け取り、適切な遷移先を決定するためのビューステート、もうひとつは、これらのサーバ側アクションからのイベントを受け取り、次の遷移先を決定するためのアクションステートです。これらシーケンシャルに遷移するすべてのビューステートとアクションステート、またそれらの遷移のトリガとなるイベントを1つのファイルに定義し、一連のステートの関連性をフローとして内部的に管理しているのです。HTMLのリンクだけでなく内部的にそれぞれのステート間の関連性が保たれるため、フローの途中へ不正にアクセスするようなリクエストは一切排除されることになります。

フロー定義に必要な情報

では、概念的な画面遷移図からPiece_Unityのフロー定義ファイルを作成するまでの過程として、まずは最初の画面となる入力画面(1)から入力画面(2)へステートが遷移する流れを考えてみましょう。

フロー定義ファイルで基本となるのは、ビューステート、アクションステート、イベントの3つの要素です。「あるビューステートにおいてあるユーザイベントが発生すると、あるアクションステートに遷移する」といった遷移ルールを洗い出し、それぞれの要素に名前を付けて定義するのが手順です。

今回のサンプルアプリケーションでは、フローの実行が開始されると入力画面(1)のビューステートに遷移し、フォーム画面が出力されます。次にユーザが「次へ」ボタンをクリックする事でイベントが発生しアクションステートに遷移します。このアクションステートではバリデーションを行い、その結果によって、入力画面(2)のビューステート、あるいは入力画面(1)のビューステートに遷移するイベントを発生させることになります。

ビューステートの定義

では、ここまでの情報を元にビューステートを定義しましょう。今回は設定ファイルの可読性を高めるため下記の命名規約を適用します。

  • ビューステート名はDisplayを接頭辞とする
  • アクションステート名はProcessを接頭辞とする
  • ビューステートへの遷移イベント名はgoDisplay + 遷移先ステート名 + From + 遷移元ステート名とする

ビューステートの定義で必要な要素は下記の4項目となります。

(1) name - ビューステート名

name要素はビューステートの名称となります。フロー定義ファイル内で複数のビューステートを定義するため、画面毎に重複しない文字列を定義する必要があります。今回はDisplayForm1と命名します。

(2) view - ビュー(テンプレート)名

view要素はテンプレート名として使用されるため、拡張子を除いた文字列Form1を定義します。

(3) activity - アクティビティ

activity要素はそのステートの活動を示します。具体的には、ビューが表示される前に実行したいクラス名/メソッド名を記述します。今回は後述のアクションクラスとなるQuestionnaireActionクラスのsetupForm1()メソッドをactivityとして定義し、テンプレートに埋め込まれる変数の設定が行われるようにします。

(4) transition - 遷移

transition要素はそのステートが受け付けるイベントの情報を子要素として定義します。transitionの子要素には下記の定義が必要です。

  • event - イベント名
  • nextState - 遷移先のステート名
  • action - イベントにともなうアクションとなるメソッド名

下記の図は、サンプルアプリケーションのステートチャート図から、入力画面(1)から入力画面(2)への遷移部分を抜き出したものです。

入力画面(1)から入力画面(2)へのステートチャート図

ビューステートDisplayForm1では、ユーザの操作によってイベントanswer1が発生し、アクションステートProcessAnswer1へ遷移する事が確認できます。イベントanswer1にともなうバリデーションはアクションクラスQuestionnaireActionのvalidateAnswer1()メソッド内に記述する事とします。

では、この内容をフロー定義ファイルの形式で記述してみましょう。Piece_Unityではフロー定義ファイルをYAML5もしくはXML形式で定義できますが、今回は可読性の高いYAML形式で記述します。下記のようにviewStateの子要素に、配列要素(-はYAMLにおける配列要素です)としてビューステート定義をしていきます。下記は最も基本的なビューステート定義といえます。慣れてくれば、コピー&ペーストからの一括置換、といった再利用も可能となるでしょう。

web/webapp/config/flows/Questionnaire.yaml:

viewState:

  - name: DisplayForm1
    view: Form1
    activity:
      method: setupForm1
    transition:
      - event: answer1
        nextState: ProcessAnswer1
        action:
          method: validateAnswer1


  1. 5. データ表現に適したフォーマット。詳細については、プログラマーのための YAML 入門 (初級編)を参照ください。

アクションステートの定義

アクションステートも同様に同じファイル内に記述します。アクションステートでは主に、ステート名(name)と遷移イベント情報(transition)のみ定義します。入力画面(1)から入力画面(2)へのステートチャート図でも示している通り、アクションステートProcessAnswer1は2つのイベント、goDisplayForm2FromProcessAnswer1とgoDisplayForm1FromProcessAnswer1を受け付けます。QuestionnaireAction::validateAnswer1()の戻り値が、アクションステートで受け取るイベントとなりますので、今回はメソッド内でバリデーションを行い、その結果によって、次の画面に遷移するためのイベントを返すようにします。下記のように、transitionの子要素に入力画面(1)から入力画面(2)へのステートチャート図のProcessAnswer1からの2つの遷移を定義しましょう。

web/webapp/config/flows/Questionnaire.yaml:

actionState:

  - name: ProcessAnswer1
    transition:
      - event: goDisplayForm2FromProcessAnswer1
        nextState: DisplayForm2
      - event: goDisplayForm1FromProcessAnswer1
        nextState: DisplayForm1

ファーストステートの定義

サンプルアプリケーションは5つのビューステートと4つのアクションステートで構成されます。それぞれのステートの前後関係はnextState要素によって結び付けられていますが、フロー実行後に最初に遷移するステートは別途指定する必要があります。それが、firstState要素になります。firstState要素はフロー定義ファイル内のどこに記述してもかまいませんが、ファイルの先頭に記述することを推奨します。

フロー定義のポイント

ビューステート、アクションステートで重要となるのは、transitionで定義されたイベントによってのみ遷移が行われるという点です。未定義のイベントが発生した場合は不正なものとして扱われ、ステートが遷移することはありません

また、今回のサンプルアプリケーションでは使用していませんが、activityのようにコールバックを表す要素が他にもいくつかあります。6

実際にアプリケーションを作成する際には、いきなりフロー定義ファイルを作るのではなく、JUDE7などのツールを使いステートチャート図を作成することを推奨します。これは単にフロー定義ファイル作成のためだけでなく、アプリケーションの流れを視覚的に捉える為の有用なプロセスとなるでしょう。下記にサンプルアプリケーションのステートチャート図を示します。

サンプルアプリケーションのステートチャート図


  1. 6. 詳細はPiece_Flowユーザーズマニュアルを参照のこと。
  2. 7. UMLモデリングツール。詳細は、http://jude.change-vision.com/jude-web/を参照のこと

ビュー

Webアプリケーションの画面(HTML)作成において、テンプレートエンジンはもはや欠かせないものとなっています。Piece_Unityでは、テンプレートエンジンとしてPEARの8とSmarty9, そして純粋なPHPによるテンプレートがレンダラとして用意されています。

今回のアプリケーションではHTML_Template_Flexy1を使用し、画面遷移に沿ってForm1.html, Form2.html, Form3.html, Confirmation.html, Finish.htmlの5つのテンプレートを作成します。それぞれのテンプレートの記述で注目して頂きたいのは、下記の4点です。


  1. 8. PHPのテンプレートエンジンの1つで、HTMLタグの属性に制御構造を記述できる点や、HTMLの要素をDOM的にオブジェクトとして扱える点が特徴です。詳細は、http://pear.php.net/package/HTML_Template_Flexyを参照のこと
  2. 9. http://smarty.php.net

(1) フロー実行チケットの埋め込み

フロー実行チケットとは、現在実行中のフローを継続するためのキーとなる、フローの実行毎に1つの、一意の文字列です。フロー実行の保存/復元に常に必要とされるため、イベントの発生元となるフォームやリンクに埋め込まれて使用されます。

フロー実行チケットは__flowExecutionTicketという変数(ビューエレメント)としてテンプレートに渡されます。下記は、フロー実行チケットをフォームのhidden項目として埋め込んだ例です。

web/webapp/templates/Questionnaire/Form1.html:

<input type="hidden" name="{__flowExecutionTicketKey}" value="{__flowExecutionTicket}" />

(2) イベント名の埋め込み

Piece_Unityはイベントドリブンなフレームワークです。ボタンやリンクにイベントを対応付けることによって、それらがクリックされたときのWebアプリケーションの動作が定まります。画面の構成要素にイベントを仕込むという発想は、Visual Basicなどでおなじみのものであり、慣れてくると非常に判りやすく、1つの画面から複数の処理を走らせたい場合に便利な仕組みです。下記は、フォームのsubmitボタンのname属性値としてイベント名を埋め込んだ例です。

web/webapp/templates/Questionnaire/Form1.html:

<input type="submit" name="{__eventNameKey}_answer1" value="次へ" />

(3) 動的な値の表示

従来のWebアプリケーションでもおなじみの処理で、例えば、データベースの検索結果を一覧表示していく場合などがこれに当たります。今回のサンプルではデータベース処理は行いませんが、確認画面では、前画面までに入力されたすべての値を保持するオブジェクトを受け取り、まとめて表示しています。

web/webapp/templates/Questionnaire/Confirmation.html:

<tr><td>お名前:</td><td>{questionnaireAnswer.name}</td></tr>
<tr><td>E-Mail:</td><td>{questionnaireAnswer.email}</td></tr>
<tr><td>性別:</td><td>{questionnaireAnswer.sex}</td></tr>
<tr><td>職業:</td><td>{questionnaireAnswer.job}</td></tr>

(4) エラーメッセージの表示

これもやはりWebアプリケーションではおなじみの処理で、バリデーションエラーなどの場合にメッセージを表示します。詳しくは後述しますが、Piece_Unityではバリデーションセットという単位(通常は画面と対で定義する)でバリデーションが実行され、その実行結果を保持するオブジェクトが自動的に、

__ + バリデーションセット名 + Results

または、バリデーションセット名が与えられていない場合は、

__results

という名称でフロー変数及びテンプレート変数に保存されます。HTML側ではそのオブジェクトからメッセージを取り出して表示するだけです。下記では、Form1.htmlの画面に対応するバリデーションセットAnswer1の実行結果オブジェクト__Answer1Resultsから、メッセージを取り出して表示しています。

<div class="err_msg" flexy:if="__Answer1Results.isError(#name#)">{__Answer1Results.getErrorMessage(#name#)}</div>
<div class="err_msg" flexy:if="__Answer1Results.isError(#email#)">{__Answer1Results.getErrorMessage(#email#)}</div>

バリデーション

Piece_Unityにおけるバリデーションの特徴

前の章で少し触れましたが、Piece_Unityではバリデーションセットという単位でバリデーション処理が実行されます。バリデーションセットの実体は、フォームの項目名やバリデーション、フィルタ、エラーメッセージの定義などをすべて含む設定ファイル(バリデーションセット名.yaml)で、通常は画面と対で作成します。今回のサンプルで見ると、Form1.html->Answer1.yaml, Form2.html->Answer2.yaml, Form3.html->Answer3.yamlといった具合です。多くのフレームワークのように、フォームを表現するクラスを別途作成する必要はありません。

バリデーションの実行結果は、エラーメッセージを含めすべて現在のフロー実行に保存されますので、随所でその内容にアクセスできます。また、Piece_Unityではバリデーション機構としてPiece_Rightという汎用的なフレームワークを使っており、Piece_Rightが提供する多くのバリデータやフィルタを使うことができます。

バリデーション定義ファイルの記述

下記は、今回のサンプルの入力画面(1)(Form1.html)に対応するバリデーション定義ファイルです。

web/webapp/config/validations/Answer1.yaml:

- name: name
  description: お名前
  required:
    message: %_description%を入力してください
  filter:
    - trim

- name: email
  description: E-Mail
  required:
    message: %_description%を入力してください
  filter:
    - trim
  validator:
    - name: Email
      message: %_description%を正しく入力してください

nameにはフォーム項目のname属性値を、descriptionにはその項目の表示用文字列を記述します。必須チェックを行う場合にはrequiredを指定し、その下にエラーメッセージを定義します。フィルタ処理が必要な場合には、filter要素を記述し、その下にフィルタの種類(trim, Empty2NULL?, JapaneseAlphaNumeric?など)を記述します。trimのように単なる関数もフィルタとして使うことができます。

各フィールドに対するバリデーションは、そのフィールドに適用したいバリデータ?をvalidator要素に定義することで有効になります。上記の例では、emailフィールドにはEmailバリデータ?のみが適用されます。組み込みバリデータで必要なバリデーションがカバーできない場合は、容易に独自のバリデータ?を作成し使用することもできます。独自のバリデータ?を作成する前に、より簡単な方法としてWithMethodバリデータ?を使用した、任意のメソッドによるバリデーションを試すことができます。

- name: userCode
  description: ユーザコード
  validator:
    - name: WithMethod
      rule:
        class: MyDomain_Validator_Foo
        method: validateUserCode
        isStatic: false
      message: %_description%が正しくありません


    アクション

    Piece_Unityにおけるアクションクラス

    何らかのWebアプリケーションフレームワークを使ったことのある開発者なら、アクションクラスという呼び名には馴染みがあるかも知れません。従来のフレームワークでは、アクションクラスは処理目的毎に作成されるため、大きなアプリケーションではファイル数の爆発を招きます。また、アプリケーションフローの制御はアクションクラス内で行われることが多く、セキュリティを考慮すればするほど複雑で重複が多いコードになってしまう、といった問題点もあります。

    Piece_Unityの場合、アクションクラスの仕事は大変シンプルです。アクションクラスは、通常はフロー毎に1つ作成することになります。そして、アクションクラスに各イベントに対応するメソッド(イベントハンドラ?)を定義していくことになります。フローやイベントは定義ファイルという形で作成するので、サンプルの例で見ると、Questionnaire.yamlとして定義したフローに対しQuestionnaireActionというアクションクラスが対応しています。そして、フロー定義中のanswer1というイベントに対し、validateAnswer1というイベントハンドラ?が対応する、といった具合になります。Piece_Unityではアプリケーションフローの制御はフレームワーク側で行うので、アクションクラスでそれを実装する必要はありません。

    アクションクラスの実際

    では、サンプルのアクションクラスを見てみましょう。

    <?php
    class QuestionnaireAction extends Piece_Flow_Action
    {
        var $_questionnaireAnswer;
        function QuestionnaireAction() {}
        function setupForm1() {}
        function setupForm2() {}
        function setupForm3() {}
        function validateAnswer1() {}
        function validateAnswer2() {}
        function validateAnswer3() {}
        function setupConfirmation() {}
        function registerAnswer() {}
        function _setupForm($validationSetName) {}
        function _validate($validationSetName) {}
        function _setupFormAttributes() {}
        function _getFormElements() {}
    }
    

    アクションクラスはPiece_Flow_Actionのサブクラスとして作成する必要があります。これは、フレームワーク側が保持している値や各種機能にアクセスするために必須となります。10

    また、コンストラクタ及びprivateなメソッド(_で始まるもの)を除いたメソッドが、フロー定義ファイル内で指定したメソッド(イベントハンドラ?)であることが判ります。

    それでは、アクションクラスの主な仕事を見ていきましょう。まずは、各画面を表示する際に必要な値を、HTML_Template_Flexyのオブジェクトに設定する処理です。

    web/webapp/actions/QuestionnaireAction.php:

    <?php
    function _setupForm($validationSetName)
    {
        $this->_setupFormAttributes();
        $results = &$this->_flow->getAttribute("__{$validationSetName}Results");
        if ($results) {
            $viewElement = &$this->_payload->getViewElement();
            $elements = $this->_getFormElements();
            foreach ($results->getFieldNames() as $field) {
                $fieldValue = $this->_questionnaireAnswer->$field;
                if (is_array($fieldValue)) {
                    $field .= '[]';
                }
                $elements[$field]['_value'] = $fieldValue;
            }
    
            $viewElement->setElement('_elements', $elements);
        }
    }
    

    上記のメソッドではフレームワークを介し、HTML_Template_FlexyのDOM的な特徴を活かして値を設定しています。ここでの値は、ユーザが3画面に渡って入力しているアンケートの回答データになります。その値は常にフロー実行の内部に保持されるため、遷移の度に値の維持に腐心する必要はありません。

    次に、バリデーションの実行処理です。

    web/webapp/actions/QuestionnaireAction.php:

    <?php
    function _validate($validationSetName)
    {
        $validation = $this->_payload->getValidation();
        return $validation->validate($validationSetName, $this->_questionnaireAnswer);
    }
    

    バリデーションの章で既に述べたように、Piece_Unityではフォームを表現するクラスを別途定義することはないため、リクエスト値の保持やバリデーションの対象としては、PHPの汎用的なクラスであるstdClassを利用する方法が一般的となります。このオブジェクトをアクションのプロパティとしてフロー実行の内部に保持させることで、サンプルのように複数ページに渡る入力内容を常に維持することができるわけです。11

    コード中の$validationオブジェクトの実体は、Piece_Unity_Validationクラスです。Piece_UnityはPiece FrameworkのMVCフレームワーク部分を担当しており、この仕組みを介してPiece_Rightのバリデーションを実行することで、その結果はフロー実行の内部に保持されることになります。それはPiece Frameworkの世界にある、2つの独立したフレームワークの接点といえます。

    次は、各メソッドの処理結果による遷移先の決定です。

    web/webapp/actions/QuestionnaireAction.php:

    <?php
    function validateAnswer1()
    {
        if ($this->_validate('Answer1')) {
            return 'goDisplayForm2FromProcessAnswer1';
        } else {
            return 'goDisplayForm1FromProcessAnswer1';
        }
    }
    

    上記は、入力画面(1)(Form1.html)からPOSTされた入力値に対するバリデーション結果によって、遷移先を分岐するメソッドです。この分岐もPiece_Unityの特徴であるイベントドリブンで行われるので、メソッドの戻り値はフロー定義ファイルに定義したイベントになります。つまり、画面でユーザがボタンやリンクをクリックしたときと同じように、今度はアクションクラスのメソッドがイベントを発生させているわけです。


    1. 10. この仕様は近い将来に変更される予定です。詳細は、Piece_Flowのチケットフローアクションに任意のユーザ定義クラスを使えるようにするを参照のこと。
    2. 11. アクションのプロパティが保存される仕組みについては、ユーザーズマニュアルアクション継続の項を参照のこと。

    エントリポイント

    エントリポイントは文字通りWebアプリケーションの入口であり、ブラウザで直接アクセスされることになるスクリプトです。サンプルでは、htdocsというディレクトリに配置しているquestionnaire.phpがこれに当たります。

    エントリポイントとなるスクリプトでは、最初に、フレームワークの処理を実行する前に必要な準備を行います。サンプルでは、エラー発生時に実行させたいコールバック関数の定義、クッキーを有効にするパスの指定などを行っています。その他、必要に応じてPHPの振る舞いに関わる基本的な設定をここで行います。

    準備が出来たら、いよいよPiece_Unityの処理の開始です。実行対象となるフロー名やアプリケーションのディレクトリ構成などをPiece_Unity_Configクラスのオブジェクトとして用意し、それをPiece_Unityクラスの新規インスタンスにセット、最後にdispatchメソッドを呼ぶことでフレームワークの処理を開始します。

    サンプルでは必要最低限の設定のみで処理を開始させていますが、Piece_Unity_Configオブジェクトを通して、フレームワークの挙動に関わる様々な設定を行うことが可能です。バリデーションセットの設定ファイルを、フローと同名のディレクトリ以下に配置する場合の例を下記に示します。

    web/htdocs/questionnaire.php:

    <?php
    $flowName = 'Questionnaire';
    $config = &new Piece_Unity_Config();
    $config->setConfiguration('Dispatcher_Continuation', 'flowName', $flowName);
    $config->setConfiguration('Renderer_Flexy', 'templateDir', "$base/templates/$flowName");
    $config->setConfiguration('Renderer_Flexy', 'compileDir', "$base/compiled-templates/$flowName");
    $unity = &new Piece_Unity("$base/config", "$base/cache", $config);
    

    設定

    Piece_Unityの設定には、設定ファイル(piece-unity-config.yaml)?を使う方法と、先の例のようにPiece_Unity_Configオブジェクトを使う方法12があります。アプリケーション全体に適用可能なものは設定ファイルを使い、エントリポイント毎に変更する必要があるものはPiece_Unity_Configオブジェクトを使うようにしましょう。主要な設定ポイントを持つプラグインを下記に示します。

    Dispatcher_Continuation
    Piece_Unityをステートフルに動作させる場合のディスパッチャです。各ディレクトリ設定の他、フロー動作モードやフロー定義などをここで定義します。
    Dispatcher_Simple
    Piece_Unityをステートレスに動作させる場合のディスパッチャです。アクションディレクトリのみを設定ポイントに持ちます。
    View
    設定ポイントrendererにおいて使用したいレンダラ(基本的にテンプレートエンジン)を指定します。
    Renderer_Flexy
    テンプレートエンジンHTML_Template_Flexy用のレンダラです。主にテンプレートディレクトリやコンパイルディレクトリを指定します。
    Configurator_Env, Configurator_Event, Configurator_Plugin, Configurator_Request, Configurator_Validation
    Piece_Unityの中核となるクラスの設定に使用されるプラグインです。


    1. 12. この方法をダイナミックコンフィグレーションといいます。

    動作テスト

    ※サンプルアプリケーションの動作は、デモサイト13で確認することができます。

    では、実際にセキュアなアプリケーションとなっているか確認してみましょう。

    エントリポイントquestionnaire.phpに対し、まずは何もパラメータを付けずにアクセスします。予想通りfirstStateとなる入力画面(1)が表示されるでしょう。これだけでは確認として物足りないので、エントリポイントに対し不正なイベントを渡してみましょう。エントリポイントのURLに続き、「?_event=answer3」を付与しアクセスします。従来の動作としては、ここで入力画面(3)で実行されるバリデーションが走り入力画面(2)に遷移すると考えられますが、フローが開始されていない状態ではいかなるイベントも受け付けられないため、firstStateに設定されているビューステートへ遷移します。

    では、フローが開始されている状態で不正なイベントを渡すとどうでしょうか?開始されたフローを継続するためには前述のフロー実行チケットを渡す必要がありますので、一旦firstStateに遷移し、HTMLソースに記述されているフロー実行チケットをURLに付与します。さらにregisterイベントも追加しアクセスしてみましょう。DisplayForm1ビューステートに定義されていませんので、registerイベントは受け付けられず、画面が遷移することもありません。Piece_Unityでは開発者が何ら特別な対策を行わなくても、不正なリクエストは受け付けられず、アプリケーションフローが強制されることが理解いただけるでしょう。

    おわりに

    ここまでの説明で、Piece_Unityの最も重要な機能である、アプリケーションフローの制御について、ご理解頂けたでしょうか?また様々な部分でPiece FrameworkがWeb開発をよりシンプルにすることが理解頂けたでしょうか?

    Piece ProjectはWebサイト本ドキュメントサイトメーリングリスト、勉強会やセミナーを通じて、様々なサポートを提供していきます。

    ありがとうございました。


    << Piece_Unityクイックスタート

    Attachments

    • 1.png (24.5 KB) - added by iteman 3 years ago. サンプルアプリケーションの画面遷移
    • 2.png (7.8 KB) - added by iteman 3 years ago. 入力画面(1)から入力画面(2)へのステートチャート図
    • 3.png (15.9 KB) - added by iteman 3 years ago. サンプルアプリケーションのステートチャート図