Piece Framework で作る対話的なアプリケーション¶
Copyright (c) 2007-2008 KUMAKURA Yousuke <kumatch at gmail dot com>, All rights reserved.
Copyright (c) 2009 Piece Project Project, All rights reserved.
- このドキュメントは、クリエイティブ・コモンズ・ライセンス の下でライセンスされています。
- このドキュメントは、株式会社インプレスジャパン 発行の PHP 技術誌、まるごとPHP! Vol.2 の 特集2 どれが最適?フレームワーク対決 に Piece Frameworkで作る対話的なアプリケーション として掲載された記事を基に Piece Project が再編集したものです。
- Piece Framework で作る対話的なアプリケーション
- はじめに
- アプリケーションは対話的
- 連続したやり取りによる対話
- Piece Framework による開発
- 書いた図がそのままアプリケーションに
- 場面ごとに必要な処理を記述
- おかしなやり取りは許さない!
- 統合開発環境
- ライブラリの利用
- Piece Frameworkの頻出用語
- ページフロー
- ステート
- 遷移イベント
- Piece Framework の構成
- Web アプリケーションフレームワーク Piece_Unity
- フロー管理 Piece_Flow
- バリデーション Piece_Right
- データベースアクセス Piece_ORM
- Piece Framework によるアプリケーション開発
- 動作環境について
- 「ポテトバーガー注文アプリケーション」のサンプルのインストール
- Piece Framework のインストール
- Piece_IDE によるスタートアップ
- Piece_IDE Flow Designer
- 注文受付フローを作成する
- エントリポイントとブートストラップを作成する
- PHP 5 に適した手法を導入する
- ページを作成する
- 具体的な動作を考える
- バリデーションを行う
- イベントハンドラを記述する
- アプリケーションの動作を確認する
- 注文内容をデータベースに書き込む
- フロー設計のポイント
- ビューから考える
- ステートを細かく分ける
- 適切な名称をつける
- おわりに
はじめに¶
メールを見る、絵を描く、計算する……アプリケーションを利用して出来ることは数多くあります。これらの動作は全てアプリケーションとそれを利用するユーザによる行動や応答の連続、すなわち対話により実現されているものです。
アプリケーションは対話的¶
図 1 は、名前を使った挨拶を行うという極めて単純なアプリケーションの動作を表したものです。質問者であるアプリケーションは回答者であるユーザに対し名前を伺い、答えられた名前を使って文章を作成して回答者へ挨拶します。挨拶に利用される挨拶文は、アプリケーションの「質問」に対してユーザが「回答」するという対話によって初めて完成されるものです。
図 1 挨拶アプリケーションの動作の様子
図 2 は、上記アプリケーションに少し変化をつけたものです。同じようにアプリケーションがユーザに対して質問を投げかけ、その回答内容によってさらに応答内容を変化させるという分岐型アプリケーションとなっています。
図 2 質問アプリケーションの動作の様子
ここでは回答者であるユーザがどのように答えるかによって、アプリケーションの処理内容が変化する必要があります。同時に、応答する内容が対話的に矛盾しないものでなければなりません。話が矛盾し対話が成立しなければ、アプリケーションは正しい動作を行っているとはいえません。
連続したやり取りによる対話¶
対話による物事の達成は、一度のやり取りでいいとは限りません。場合によっては何回も連続してやり取りを行わなければ達成できないこともあります。
図 3 は、よくあるファーストフード店での注文の様子を再現したものです。そのお店で取り扱っている食べ物を購入するために商品名やセット内容などを注文し、代金の請求が行われるまで連続したやり取りによる対話がなされていることが分かります。このような対話が行われる理由は、来店者からみれば商品名を正しく伝えなければ希望する食べ物を購入することができませんし、お店側からみれば利用者の購入希望商品が何であるかを確認し、それに対する代金を計算し請求する必要があるからです。
図 3 ファーストフード店でなされる会話
これは現実で行われている出来事ですが、仮にこれがインターネットを通じて商品を注文するような Web アプリケーションであったとしても、上記のような対話が行われることになることでしょう。目的に対して行わなければならないやり取りの内容に違いはないのです。
Piece Framework による開発¶
Piece Framework はアプリケーションとユーザの一連の対話によってアプリケーションが構成されていることに注目し、対話的な考え方や捉え方をそのままアプリケーション開発に適用することができるといった特徴をもつ PHP のアプリケーションフレームワークです。
Piece Framework による開発は、前述したようなアプリケーションとユーザの相互のやり取りを定義していくことで自然とアプリケーションが組みあがっていくという、これまでのアプリケーション開発とは少し異なるものになっています。
書いた図がそのままアプリケーションに¶
Piece Framework では、アプリケーションとユーザとの間で行われる連続的なやり取りを図表化することで、アプリケーションの動作を簡単に定義することができます。ユーザによって描かれた図は Piece Framework によって実行可能形式に変換され、そのままプログラムとして動作します。
図を描くための手順はさほど難しいものではありません。とあるアプリケーションの動作としてページの遷移を A -> B -> C というように考えているのであれば、ページ A, B, C を表すパーツをそれぞれ準備して、 A から B へ、そして B から C へと伸びる矢印を引けばよいだけです。
図 4 アプリケーションの動作を図で書く
場面ごとに必要な処理を記述¶
図によってアプリケーションの動作を作ることはできますが、各場面で実際に必要な処理はアプリケーションによって異なります。そこで、図の中に存在する各パーツごとに異なる PHP コードを定義することで目的の処理を実現し、アプリケーションを作り上げていきます。図と処理は分離されるため、PHP コードは目的の処理のみが記述された簡素なものになります。
図 5 図中のパーツごとに PHP コードを記述
おかしなやり取りは許さない!¶
Piece Framework アプリケーションは、図の中で示される処理や流れ以外の挙動、情報を一切受け付けません。二者間で行われる適切な対話のみを取り扱い、唐突な出来事や流れを無視した行為を排除します。仮に 図 6 のようなやり取りが起きたとしたらどうでしょう?お店はお客様に対して注意を促したり改めて聞きなおしたりするはずです。
図 6 普通は受け付けません
Piece Framework ではこのようなおかしな行為を排除することで、不正アクセスを発端とした機能の悪用や、意図しない動作を防ぎます。
統合開発環境¶
Piece Framework では、様々なプログラミング言語の統合開発環境 (IDE) を提供する Eclipse で動作する専用の開発環境を提供しており、先に紹介した図を作成するためのグラフィカルなエディタなどを利用することができます。
ライブラリの利用¶
Piece Framework ではデータベースアクセスや入力値の検証を行うためのフレームワークが用意されており、必要に応じて自由に利用することができます。また、PEAR を始めとした数多くの外部ライブラリや自作のライブラリも、特別な設定なしで Piece Framework と共に使用することができます。
Piece Frameworkの頻出用語¶
Piece Framework の特徴を紹介したところで、これまで紹介してきたものの中から実際に Piece Framework による開発を進めていくなかでよく出てくる用語を使用例と共に紹介します。
ページフロー¶
Piece Framework では図を描くことでアプリケーションの動作を組み立てていきますが、この図および図が表現する流れ全体、または保存形式であるテキストファイルのことを ページフロー (または単に フロー ) といいます。「ファーストフード店における注文受付のフローは、まずメインの注文内容を聞いて…」といった感じで使用します。
ステート¶
フローを作成する際は、1 画面に出力するページを 1 つのパーツとして扱います。このパーツを ステート (状態) といいます。「注文内容を提示するステート」「セット内容を確認するステート」といった感じで使用します。
遷移イベント¶
Web アプリケーションはユーザ入力によって状態を変えていきますが、フローの場合も例外ではありません。フローで定義されたある状態から別の状態へと遷移する引き金となるものを 遷移イベント といいます。ユーザ入力によって何らかの遷移イベントが発生することで、結果的に次のページが表示されることになります。「○○遷移イベントによってステートが遷移して…」といった感じで使用します。
Piece Framework の構成¶
さて、ここまで PHP アプリケーションフレームワークの名称として Piece Framework を使ってきましたが、実際には「Piece Framework」というプロダクトがあるわけではなく、機能領域ごとに個別のプロダクトが提供されています。それらは全て「 Piece_○○○ 」という共通の冠名をもっており、○○○部分には提供される機能を表す名称が与えられています。
Web アプリケーションフレームワーク Piece_Unity¶
Piece_Unity は、これまでに紹介してきた Piece Framework の特徴を実際に利用するための Web アプリケーションフレームワークであり、ユーザのプログラムコードに直接関わってくるプロダクトです。「Piece Framework を使ってアプリケーションを開発する」ということは、この Piece_Unity を使うことに他なりません。フレームワークの役割としては Ethna , CakePHP , symfony とほぼ同様の位置にあるものと考えてください。
なお、Unity には「統一体、集合体」といった意味があり、以下にあげる Piece Framework のフレームワークやライブラリを利用しやすい形に取りまとめる役割も果たしています。
フロー管理 Piece_Flow¶
Piece_Flow は、前述してきたアプリケーションの動作を表現する「フロー (Flow) 」を構築・管理するためのライブラリです。実際にユーザがフローを使ってアプリケーションを作っていく際には前述の Piece_Unity を利用しますので、これ自体は Piece_Unity を縁の下で支える隠れた存在となっています。
バリデーション Piece_Right¶
Piece_Right は、アプリケーション動作中にユーザから送られてくるデータの内容を検証・調整するためのバリデーションフレームワークです。Right という名のとおり「正しい、妥当である」ことをチェックするための機能が提供されています。こちらも Piece_Unity を使ったアプリケーション開発の上で自然と利用する形になります。
データベースアクセス Piece_ORM¶
Piece_ORM は、データベースにおけるデータ操作 (入出力) を行うためのオブジェクトリレーショナルマッピングフレームワークです。データベースレコードと PHP オブジェクト間のデータの移動や連携をシンプルに行うことが可能となっています。
Piece Framework によるアプリケーション開発¶
本章では、Piece Framework を使った Web アプリケーション開発の流れを、Piece Framework のセットアップからアプリケーションの開発、そして動作確認まで順に紹介します。
今回は、前述の例で登場したファーストフード店における注文受付の流れをアプリケーションとして実装していきます。以下、このアプリケーションのことを「ポテトバーガー注文アプリケーション」といいます。
動作環境について¶
「ポテトバーガー注文アプリケーション」は以下の環境で開発および動作確認を行いました。環境の違いによる細かな情報の違いにご注意ください。
「ポテトバーガー注文アプリケーション」のサンプルのインストール¶
Piece Framework を構成するプロダクト群は、PEAR パッケージマネージャ によって管理・配布されています。PHP に付属している PEAR のセットアップが完了していれば、コマンドラインによる PEAR インストーラ を利用することができます。
なお、「ポテトバーガー注文アプリケーション」のサンプルは Piece_Examples_Conversation というプロダクトで PEAR パッケージ として配布されていますので、以下のコマンドでインストールすることができます。
pear channel-discover pear.piece-framework.com pear install piece/Piece_Examples_Conversation pear install piece/Piece_Unity_Component_ExceptionHandler-beta pear install piece/Stagehand_Autoload-beta
以上で Piece Framework の主要なプロダクトがすべてインストールされますが、それぞれのプロダクトを個別にインストールする方法も知っていただくために次節 Piece Framework のインストール も合わせてご覧になることをおすすめします。
Piece Framework のインストール¶
この節では「ポテトバーガー注文アプリケーション」が使用する PEAR パッケージ を個別にインストールする方法についてご紹介します。
最初に、お手持ちの PEAR 環境に Piece Framework 用 PEAR チャネル を登録します。登録には以下のコマンドを実行します。
pear channel-discover pear.piece-framework.com
その後、追加したチャネルから Piece_Unity と「ポテトバーガー注文アプリケーション」で使用するライブラリをインストールします。
pear install piece/Piece_Unity pear install piece/Stagehand_Autoload-beta
これで、Piece Framework の基本パッケージのインストールは完了です。
Piece_Unity には基本パッケージとは別に、付加的な機能を提供するコンポーネントが用意されています。こちらも PEAR インストーラ によって簡単にインストールすることができます。ここでは「ポテトバーガー注文アプリケーション」のために、以下のコマンドを実行し、複数のパッケージをインストールすることにします。
pear install piece/Piece_Unity_Component_Authentication pear install piece/Piece_Unity_Component_ContentLength pear install piece/Piece_Unity_Component_Flexy pear install piece/Piece_Unity_Component_JapaneseZ2H pear install piece/Piece_Unity_Component_NullByteAttackPreventation pear install piece/Piece_Unity_Component_PieceORM pear install piece/Piece_Unity_Component_ExceptionHandler-beta
Piece_Unity の主なコンポートネントは 表 1 のとおりです。
表 1 Piece_Unity が提供する主なコンポーネント| コンポーネント | 概要 |
|---|---|
| Piece_Unity_Component_Authentication | 認証機能を提供する |
| Piece_Unity_Component_ContentLength | Content-Length ヘッダを自動送出する |
| Piece_Unity_Component_Flexy | テンプレートエンジン HTML_Template_Flexy を利用する |
| Piece_Unity_Component_Smarty | テンプレートエンジン Smarty を利用する |
| Piece_Unity_Component_JSON | JSON データを出力する |
| Piece_Unity_Component_JapaneseZ2H | ページ上の全角カナを半角カナに変換する |
| Piece_Unity_Component_NullByteAttackPreventation | セキュリティのためのヌルバイト攻撃防止する |
| Piece_Unity_Component_PieceORM | Piece_ORM を利用する |
| Piece_Unity_Component_ExcpetionHandler | 例外をハンドリングする |
Spyc のインストール
Spyc は YAML フォーマットのデータを PHP 上で利用するためのライブラリです。YAML はテキストベースで可読性の高いフォーマットであり、階層化されたデータ構造を容易に表現することができます。Piece Framework では様々な構成ファイルのフォーマットに YAML を採用しており、そのデータの読み取りのために Spyc を利用しています。
Spyc は先ほどの PEAR インストーラ によるインストールが行えないため、配布サイトから別途インストールする必要があります。インストールを行うには以下のサイトにアクセスし、アーカイブをダウンロードします。
http://spyc.sourceforge.net/
ダウンロードしたアーカイブを展開し、 spyc.php というファイルをお手持ちの PHP 環境の include_path 上に spyc.php5 という名称でコピーします。たとえば PEAR 用ディレクトリにそのままコピーしてもよいでしょう。
Spyc 0.2.5 以前までは、PHP 5 用のファイルの拡張子は .php5 となっていますのでご注意ください。
Piece_IDE のインストール
Piece Framework を使ったアプリケーションの開発はどのようなエディタや開発環境でも可能ですが、今回は Eclipse 上で動作する Piece Framework の開発環境である Piece_IDE を使うことにします。
最初に Piece_IDE のインストール方法について簡単に紹介します。詳しくはインストールマニュアル をご覧ください。
では Piece_IDE のインストールを、以下のステップで進めていきます。
まず 図 7 のように [Help]->[Software Update...] を選択します。
図 7 Eclipse 上ソフトウェアのインストール開始
続いてポップアップされるダイアログ上の右側にある [Add Site...] ボタンをクリックします。そして、以下のように入力し OK ボタンを押します。
| Location | http://eclipse.piece-framework.com/piece-ide/ |
図 8 Piece_IDE の更新サイトを追加
表示される更新サイトの一覧に Piece_IDE の更新サイトが追加されますので、このエントリにチェックが入っていることを確認してから、 [Install...] ボタンをクリックします。
図 9 Piece_IDE をチェックしてインストール
依存関係の計算が行われ、 図 10 のように最終確認画面が表示されますので、 * ボタンをクリックします。インストール中に証明書を信頼するかを問い合わせるダイアログが表示されますので、 *[Select All] ボタンをクリックします。インストール終了後に Eclipse の再起動を行うことで、Eclipse 上で Piece_IDE が利用可能になります。
図 10 Piece_IDE インストールの最終確認
Piece_IDE によるスタートアップ¶
Piece_IDE が利用できるようになったところで、早速アプリケーションの開発を始めましょう。
まず、Eclipse の [File]->[New]->[Project...] で新規プロジェクト作成ウィザードを立ち上げます。 図 11 のように Piece Framework の Piece_Unity Project を選択し、 [Next >] ボタンをクリックします。
図 11 Piece Framework 用プロジェクトを作成
プロジェクトの名称と保存するフォルダを指定する画面になりますので、ここでは 図 12 のようにプロジェクト名を piece-examples-conversation 、プロジェクトが保存されるワークスペースを /home/user/WORK/piece-examples-conversation とし、 [Finish] ボタンをクリックします。
図 12 プロジェクト名とワークスペース
以上の操作で、Eclipse プロジェクトの作成およびディレクトリ構造の生成が完了します。
なお、Piece_IDE 0.5.0 では、このプロジェクト作成ウィザードが廃止され、別途ディレクトリ構造のみを作成する機能が提供される予定です。
Piece Framework のディレクトリ構造
ここで、Piece_IDE によって生成される構成のうち、Piece Framework に直接関係のあるディレクトリ構造について紹介します。
なお、Piece_IDE が生成するプロジェクトのディレクトリ構造は現在推奨されているものと多少異なります。
- トップレベルに作成されるプロジェクト名と同じディレクトリは、作成するアプリケーションのためのコードを配置するためのものです。現在は、このディレクトリを src とすることが推奨されています。
- web ディレクトリは www ディレクトリとすることが推奨されています。
Piece Framework によるアプリケーション開発では、基本的に www ディレクトリ以下に展開されている各用途ごとのディレクトリへファイルを配置していくことになります。
Piece_IDE によって生成されたディレクトリを推奨の名称に変更したディレクトリ構造は以下のとおりです。
Piece Framework を使用したアプリケーションの推奨ディレクトリ構造
+ [src] (旧 プロジェクト名)
+ [www] (旧 web)
+ [htdocs]
+ [webapp]
+ [actions]
+ [cache]
+ [compiled-templates]
+ [config]
+ [flows]
+ [validations]
+ [sessions]
+ [templates]
htdocs
Web サーバのドキュメントルートとなるディレクトリです。ここはアプリケーションの入口となる PHP スクリプトが配置される場所になります。その他、静的ページのための HTML ファイル、画像、スタイルシート、JavaScript などもこのディレクトリへ配置します。
actions
続いて、アプリケーションの動作に必要となるファイルが配置される webapp ディレクトリ以下の内容を解説します。
actions はアプリケーションの具体的な処理が記述された PHP スクリプトを配置するディレクトリです。1 つのフローに対して 1 つの PHP スクリプトを準備し、フローの各タイミングで実行される関数を 1 つのクラスにまとめて記述します。なおここに配置される PHP スクリプトは アクション あるいは アクションクラス と呼ばれます。
cache
Piece Framework が内部で利用するキャッシュデータが格納されるディレクトリです。
Apache の実行ユーザ・グループからファイルが書き込めるように適切なパーミッションが設定されている必要があります。ユーザがこのディレクトリの操作を行うことはありません。
compiled-templates
HTML_Template_Flexy や Smarty といったコンパイルを必要とするテンプレートエンジンを使用する場合に、コンパイルされたファイルが格納されるディレクトリです。
Apache の実行ユーザ・グループからファイルが書き込めるように適切なパーミッションが設定されている必要があります。 cache ディレクトリと同様、ユーザがこのディレクトリの操作を行うことはありません。
config
Piece Framework の各種構成ファイルを配置するディレクトリです。このディレクトリ以下には Piece_Unity の構成ファイル piece-unity-config.yaml 以外にも以下のファイルを配置するためのディレクトリがあります。
config/flows
Piece Framework の特徴であるフローに関する構成ファイルを配置するディレクトリです。なおこの構成ファイルのことを フロー定義ファイル といいます。
config/validations
ユーザからの入力データ (リクエスト) に対して行われる検証処理 (バリデーション) を行う際にフレームワークによって使用される検証ルールを集めた構成ファイルを配置するディレクトリです。なおこの構成ファイルのことを バリデーション定義ファイル といいます。
sessions
PHP のセッションファイルが格納されるディレクトリです。
Apache の実行ユーザ・グループからファイルが書き込めるように適切なパーミッションが設定されている必要があります。 cache ディレクトリと同様、ユーザがこのディレクトリの操作を行うことはありません。
templates
各ページのテンプレートとなる HTML を配置するディレクトリです。
サンプルアプリケーションの動作確認
プロジェクト生成ウィザードでは Piece Framework を使ったサンプルアプリケーションが作成されます。まずは、これまでのセットアップが正しく行われたか実際に動作させて確かめることにしましょう。このサンプルアプリケーションを Web サーバが参照できるよう、Eclipse プロジェクトのパスを Web サーバのドキュメントルートとして設定します。 Eclipse プロジェクトのパスは /home/user/WORK/piece-examples-conversation としたので、Apache の設定ファイルは以下のように記述します。
アプリケーションを動作させるための Apache の設定:
<VirtualHost localhost:80>
ServerName localhost
DocumentRoot "/home/user/WORK/piece-examples-conversation/www/htdocs"
DirectoryIndex index.php index.html
php_value mbstring.language Japanese
php_value mbstring.internal_encoding UTF-8
php_value mbstring.http_input auto
php_value mbstring.http_output pass
php_flag mbstring.encoding_translation On
php_value mbstring.substitute_character none
php_value mbstring.func_overload 0
php_value session.save_path "/home/user/WORK/piece-examples-conversation/www/webapp/sessions"
<Directory "/home/user/WORK/piece-examples-conversation/www/htdocs">
Options FollowSymLinks
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
なお、PEAR インストーラ インストーラを使って「ポテトバーガー注文アプリケーション」をインストールした場合、Apache2 のバーチャルホストの設定例が data/apache2/sites-available/piece-examples-conversation に用意されていますので、合わせてご参照ください。
それでは Apache を起動し、ブラウザで http://localhost/ へアクセスしてみましょう。設定に問題がなければ 図 13 のような画面が表示されます。ここでは、Piece Framework を使用したサンプルアプリケーションの動作を確認することができます。
図 13 Piece Framework のサンプルアプリケーション
Piece_IDE Flow Designer¶
Piece Framework のサンプルアプリケーションが動作することを確認できたところで、これから実際にフローを作成してアプリケーションを作っていくことにします。
Piece_IDE はフローをグラフィカルに作成、編集することができる Flow Designer という機能を備えています。この機能を使ってフローを順に作っていきます。
新しいフローはウィザードを使って作成することができます。 [File]->[New]->[Others...] でウィザードを選択するダイアログを開き、 ページフロー を選択します。
図 14 「ページフロー」を選択する
[Next >] ボタンをクリックすると、「新しいPiece_Flowページフロー」ダイアログが表示されます。
図 15 「新しいPiece_Flowページフロー」ダイアログ
ここでは作成するフォルダを www/webapp/config/flows , ファイル名を Order.flow として作成します。
ウィザードが終了すると、 図 16 のようなグラフィカルなエディタが Eclipse 上に展開されます。これが Piece_IDE の Flow Designer です。 flow という拡張子を持つファイルを読み込むと自動的に Flow Designer が起動します。なお右側に編集のためのパレットが表示されていない場合は、左向きの三角のアイコンをクリックすることで表示されます。
図 16 Flow Designer
Flow Designer では、フローの作成や編集をマウスとキーボードでとても簡単に進めていくことができます。画面上には赤 (ピンク) 色の丸が 1 つ準備されていますが、この状態から右側のパレットにある ビューステート を選択し、そのままマウスカーソルを赤丸の右側あたりにあるスペースに持ってきてクリックしてみましょう。茶色の四角枠が 1 つ表示されます。これでビューステートが 1 つ作成されます。
ステートを作成するとステートの名称を入力するためのテキストボックスが表示されます。ステートの名称は必須なので、入力しないと次の作業を行うことができないようになっています。
続けて、パレットの 遷移イベント をクリックし、今度はマウスカーソルを赤丸そのものに持ってきて一度クリックします。マウスのボタンを離さず、マウスカーソルを動かすと、赤丸からマウスカーソルまでが一本の線のようなもので繋がった状態になります。このまま、先ほどの茶色の四角枠にもってきてマウスのボタンを離すと、 図 17 のように赤丸から茶色枠へ矢印が引かれます。
図 17 フロー作成の様子
このようにしてメニューから必要なものを選択して配置していくだけで、アプリケーションのページと処理、そしてそれらの繋がりを作成していくことができます。フローとして表現される図の中には、様々な意味を持つモデルエレメントが配置されることになります。( 表 2 )
表 2 Flow Designer のモデルエレメント一覧| 名称 | 見た目 | 概要 |
|---|---|---|
| ビューステート | 茶色の四角枠 | ユーザ入力とページ表示を担当するステート。 |
| アクションステート | 灰色の四角枠 | 処理と分岐を担当するステート。 |
| 遷移イベント | 矢印 | フローのイベントと遷移の方向を示す要素。 |
| イニシャルステート | 赤 (ピンク) の丸 | フローの開始地点となるステート。フロー内には必ず 1 つだけが存在する。 |
| ファイナルステート | 緑の丸 | フローの終了地点となるステート。なおこのステートは存在しなくてもよい。 |
注文受付フローを作成する¶
これから「ポテトバーガー注文アプリケーション」のフローを作成していきますが、その前にアプリケーションの仕様を決めておくことにしましょう。なお今回はサンプルアプリケーションのためいくつかの制限を設けることにします。
- 注文の際は、メインメニューを 1 つ指定し、セットとして付随するサイドメニューを後から選択できること。
- メインメニューはいくつかの料金の異なる商品を備えること。
- サイドメニューはいくつかの異なる商品を備えること。ただし、選択内容によって料金は変化しないこと。
- ファーストフード店の例であげたような追加注文は行えないこと。
以上の仕様に基づき、Flow Designer を使って「ポテトバーガー注文アプリケーション」のフローを実際に作っていきます。
ステートの配置
フロー作成の第一ステップは、そのフローにどんなページ (画面) が存在するかを考え、ステートとして準備することです。今回のフローでは、(1) メインメニューを選択する、(2) サイドメニューを選択する、(3) 注文内容を確認する、(4) 終了(ありがとうございました)の 4 つのシーンがありますので、これらをそれぞれのページとして用意することにします。
ページ表示を担当するステートを作るには、Flow Designer のパレットより ビューステート を選択しモデルエレメントを配置します。今回は 4 つのページがありますので、計 4 つのビューステートを配置していきます。フローの開始地点を意味する赤丸の イニシャルステート の下側スペースへ縦一列に並べましょう。作成するステートの名称とビューは 表 3 のとおりです。
ビューは View: の右側の <...> をクリックして選択状態にし、F2 キーを押下することで編集することができます。
表 3 各ビューステートの設定| ページ内容 | ステート | ビュー |
|---|---|---|
| メインメニューページ | MainMenu | MainMenu |
| サイドメニューページ | SideMenu | SideMenu |
| 注文内容確認ページ | Confirmation | Confirmation |
| 注文受付完了ページ | Finish | Finish |
最後にフローの終了を示すファイナルステートも作成します。
図 18 注文受付フローのビューステートの作成
続いて、これらページを順に表示していけるよう、すなわち「ステートの遷移」が行えるように遷移イベントを準備します。パレットより 遷移イベント を選択し、赤丸のイニシャルステートから先ほど並べた 4 つのビューステートへ 図 18 のように一方向(一方通行)に矢印を引いていききます。作成する遷移イベントの名称は 表 4 のとおりです。
遷移イベントを作成すると遷移イベントの名称を入力するためのテキストボックスが表示されます。遷移イベントの名称もステートと同様必須なので、入力しないと次の作業ができないようになっています。但し、イニシャルステート、ファイナルステートが関連する遷移イベントだけは自動的に名称が付けられます。
表 4 各遷移イベントの設定| 遷移イベント | 遷移元ステート | 遷移先ステート |
|---|---|---|
| selectMainMenu | MainMenu | SideMenu |
| selectSideMenu | SideMenu | Confirmation |
| finish | Confirmation | Finish |
| Start (固定) | イニシャルステート | MainMenu |
| End (固定) | Finish | ファイナルステート |
図 19 注文受付フローの遷移イベントの作成
フローの登録
注文受付フローを作成したところで、このフローを Piece Framework に登録し、アプリケーションとして動作するよう準備を行うことにしましょう。
プロジェクトツリーの www/webapp/config ディレクトリにあるファイル piece-unity-config.yaml をエディタで開きます。このファイルは、Piece_Unity アプリケーションを動作させるための構成 (例えば、テンプレートエンジンの種類やテンプレートディレクトリなど) がまとめて記述されている重要なファイルです。なお基本的な構成は Piece_IDE によって完了しています。
この構成ファイルから flowMappings と書かれている箇所を探します。 flowMappings は Piece Framework にフローを登録するための構成であり、デフォルトではサンプルアプリケーションのフローが設定されています。 value 以下に uri , flowName , isExclusive という 3 つの項目を 1 つのまとまりとする設定が、インデント (字下げ) される形でいくつか並んでいますので、これと並列に注文受付フローを追加します。
なお uri はフローを動作させるためにブラウザからアクセスされる PHP スクリプトのアプリケーションのルート URI からの相対 URI, flowName はそのフローの名称、 isExclusive はフローの実行の排他制御の有効・無効を示すフラグです。ここでは以下のように、 uri を order.php、 flowName を Order、 isExclusive を false と設定します。
uri は以前は url として設定していたものです。Piece_Unity 1.5.0 からは uri の使用が推奨されています。
flowMappings の構成を変更した場合は、ブラウザを再起動する必要があることに注意してください。
www/webapp/config/piece-unity-config.yaml:
...
- name: flowMappings
type: configuration
value:
- url: /register-with-non-exclusive-mode.php
flowName: Registration
isExclusive: false
...
# 上と同じインデント (字下げ) レベルに以下の 3 行を追加
- uri: /order.php
flowName: Order
isExclusive: false
...
エントリポイントとブートストラップを作成する¶
続いてフローを動かすため先ほど uri として追加した order.php を実際に作成します。この PHP スクリプトはブラウザからのアクセス対象となりますので、アプリケーションのドキュメントルートである htdocs ディレクトリへ配置します。このようにアプリケーションの入口となる PHP スクリプトのことを エントリポイント といいます。エントリポイントにはアプリケーションを起動するために必要なコード (ブートストラップコード) を記述します。
通常ひとつのフローに対してひとつのエントリポイントが必要になります。従って複数のフローを持つアプリケーションではそれに対応したエントリポイントが必要になるのですが、それぞれのエントリポイントのブートストラップコードは構成ファイル piece-unity-config.yaml で定義された構成を上書きする場合など特殊な場合を除いて、ほとんど同じです。そこで共通のブートストラップコードを bootstrap.php として抽出し、これをエントリポイントから読み込むことにします。 bootstrap.php は www/webapp/config ディレクトリ以下に配置します。
www/webapp/config/bootstrap.php:
<?php
error_reporting(E_ALL);
$projectRoot = realpath(dirname(__FILE__) . '/../../..');
set_include_path("$projectRoot/src" . PATH_SEPARATOR .
get_include_path()
);
unset($projectRoot);
require 'Stagehand/Autoload/PEAR.php';
Piece_Unity_Service_ExceptionHandler::register(new Piece_Unity_Service_ExceptionHandler_DebugInfo());
// Piece_Unity_Service_ExceptionHandler::register(new Piece_Unity_Service_ExceptionHandler_InternalServerError());
Piece_Unity_Service_ExceptionHandler::register(new Piece_Unity_Service_ExceptionHandler_ErrorLog());
Piece_Unity_Service_ExceptionHandler::enable();
Piece_Unity::setConfigurationCallback('configure');
function configure(Piece_Unity $runtime)
{
$appRoot = realpath(dirname(__FILE__) . '/..');
$runtime->configure("$appRoot/config", "$appRoot/cache");
$runtime->setConfiguration('Configurator_AppRoot', 'appRoot', $appRoot);
}
実際のエントリポイントは以下のようになります。
www/htdocs/order.php
<?php require dirname(__FILE__) . '/../webapp/config/bootstrap.php'; Piece_Unity::createRuntime()->dispatch();
PHP 5 に適した手法を導入する¶
ここで PHP 5 で Piece_Unity を使用する上でポイントとなるオートローディングと致命的な例外のハンドリングについて説明します。
オートローディングの導入
最新の Piece_Unity を使用した Web アプリケーションの開発では、PHP 5 から導入されているオートローディングを利用することが推奨されています。Piece Framework ではオートローディングを効果的に使うために Stagehand_Autoload というプロダクトを提供しており、今回のアプリケーションでもこれを利用しています。
オートローディングの詳細については PHP マニュアルの オブジェクトのオートローディング をご参照ください。
致命的な例外のハンドリング
実際にアプリケーションを開発するときに重要になるのが、致命的な例外が発生した場合の処理です。開発中であればページにデバッグ情報を表示する、運用の段階では HTTPステータス 500 (Internal Server Error) を返し、例外の内容をログに出力するなどさまざまなパターンが考えられます。
Piece_Unity では例外をハンドリングするために Piece_Unity_Service_ExceptionHandler コンポーネントを用意しています。このコンポーネントを利用することで状況に合わせた例外のハンドリングを行うことができます。
今回の bootstrap.php では、デバッグ情報の表示とエラーログの出力を行うようにしています。
図 20 Piece_Unity_Service_ExceptionHandler が表示するデバッグ情報
ページを作成する¶
ここからはアプリケーションの具体的な見た目となるページを作っていきます。
PHP から HTML ページを出力する際は、テンプレートとなる HTML に対して PHP プログラムから値を流し込み HTML を出力する方法が一般的になっています。この方法をサポートする仕組みは テンプレートエンジン と呼ばれています。Piece Framework ではテンプレートエンジンとして HTML_Template_Flexy, Smarty, そして PHP 自体を使うことができます。
一般的に HTML テンプレートには、ページのデザインを行うことや、Web アプリケーションとして動作させるためにフォームやリンクを配置することなどの役割があります。Piece Framework における HTML テンプレートの役割にはそれらに加えて「フローを適切に動作させるために情報を埋め込む」というものがあります。この「情報」はフローの管理下にあるアプリーケーションを動作させるために必須となるもので、以下の 2 つで構成されます。
- 遷移イベント
- フロー実行チケット
遷移イベント
フォームやリンクのパラメータとして Flow Designer 上で設定した遷移イベントの名称を含める必要があります。遷移イベントの名称が含まれるフォームのボタンやリンクをクリックすることは、表示しているページ (ビューステート) 上で遷移イベントを発生させることを意味します。
遷移イベントの名称をパラメータとしてサーバに送信する場合、必ず __eventNameKey とセットにする必要があることに注意してください。たとえば今回の注文受付フローに対して selectMainMenu 遷移イベントを発生させるためには、以下のように記述します。
{__eventNameKey}_selectMainMenu
{__eventNameKey}=selectMainMenu
このように __eventNameKey というテンプレート変数に続けて、 _(アンダースコア) もしくは =(イコール) , 最後に遷移イベントの名称を記述します。
フロー実行チケット
何らかのフローが実行されると、そのフローの状態は他のフローの実行のものと区別するために固有の領域に保存されます。2 回目以降のリクエストでそのフローの実行を特定し状態を復元するために、Piece Framework は フロー実行チケット と呼ばれる一意の ID を使用します。フロー実行チケットはテンプレート変数 __flowExecutionTicket で参照することができます。
遷移イベントの場合と同様に、フロー実行チケットをパラメータとしてサーバに送信する場合、必ず __flowExecutionTicketKey とセットにする必要があることに注意してください。以下ように記述することで、ページ出力の際に適切な値に置換されます。
{__flowExecutionTicketKey}={__flowExecutionTicket}
以上を参考に、遷移イベントを発生させるリンクをもつテンプレートファイルを作って、それぞれのページ内容を準備していきましょう。なお今回は、Piece_IDE が生成するサンプルアプリケーションの環境をそのまま利用しますので、テンプレートエンジンもサンプルアプリケーションで使用されている HTML_Template_Flexy をそのまま使用することにします。今回作成するテンプレートでは {} で囲まれたテンプレート変数が使われている程度で、そのほとんどが普通の HTML と変わりないため、この形式がはじめての方でも簡単に読むことができるでしょう。
まず今回の注文受付フロー用のテンプレートファイルを配置するディレクトリを準備します。プロジェクトツリーの www/webapp/templates ディレクトリ内に Order (注文受付フローの名称) というディレクトリを作成します。このディレクトリ内へ、注文受付フローのためのテンプレートファイルを 1 ページ ( 1 ステート) につき 1 ファイルとして作成していきます。Flow Designer でフローを作成した際に各ステートの名称にあわせてビューを設定しましたが、テンプレートファイルの名称はこのビューとテンプレートの拡張子で構成することになります。
メインメニューページ
まずはフローの最初のページであるメインメニューページから取りかかります。メインメニューを表示するための MainMenu ステートでは、ビューを MainMenu としていますので、Order ディレクトリ内に MainMenu.html というファイルを作成します。以下のように、MainMenu.html には具体的なページデザインと遷移イベント発生のためのリンクを HTML タグで記述します。
www/webapp/templates/Order/MainMenu.html:
<h3>ご注文は何になさいますか?</h3>
<ul>
<li><a href="{__appRootPath}/order.php?{__eventNameKey}_selectMainMenu&{__flowExecutionTicketKey}={__flowExecutionTicket}&main=1">ジャーマンポテトバーガー (650円)</a></li>
<li><a href="{__appRootPath}/order.php?{__eventNameKey}_selectMainMenu&{__flowExecutionTicketKey}={__flowExecutionTicket}&main=2">ポテトコロッケバーガー (600円)</a></li>
<li><a href="{__appRootPath}/order.php?{__eventNameKey}_selectMainMenu&{__flowExecutionTicketKey}={__flowExecutionTicket}&main=3">肉じゃがバーガー (700円)</a></li>
</ul>
メインメニューとして選択できる商品は 3 種類とし、それぞれに <a> タグによるリンクを準備しています。遷移イベントを発生させるため、前述した遷移イベントおよびフロー実行チケットを記述しますが、ここでは 3 つのリンクとも同一の内容です。異なる部分は <a> タグ末尾の main=1~3 というクエリ変数部分と、具体的な商品名のみです。なお、HTML のための <header> タグや <body> タグはここでは必要ありません。メインメニューを構成するタグのみを記述します。
サイドメニューページ
続いてサイドメニューページです。SideMenu ステートではビューを SideMenu としていますので、メインメニューと同じように Order ディレクトリ内に SideMenu.html というファイルを作成します。
サイドメニューもメインメニューと同じく 3 種類とし、それぞれ <a> タグで準備します。遷移イベントを発生させるのために selectSideMenu 遷移イベントとフロー実行チケット、そしてサイドメニューの違いを表す side=1..3 という値を記述します。
www/webapp/templates/Order/SideMenu.html:
<h3>サイドメニューは何になさいますか?</h3>
<ul>
<li><a href="{__appRootPath}/order.php?{__eventNameKey}_selectSideMenu&{__flowExecutionTicketKey}={__flowExecutionTicket}&side=1">フライドポテト</a></li>
<li><a href="{__appRootPath}/order.php?{__eventNameKey}_selectSideMenu&{__flowExecutionTicketKey}={__flowExecutionTicket}&side=2">ポテトサラダ</a></li>
<li><a href="{__appRootPath}/order.php?{__eventNameKey}_selectSideMenu&{__flowExecutionTicketKey}={__flowExecutionTicket}&side=3">スイートポテト</a></li>
</ul>
注文内容確認ページ
次は注文内容確認ページです。Confirmation ステートではビューを Confirmation としていますので、Order ディレクトリ内に Confirmation.html というファイルを作成します。このページでは、先の 2 つのページで指定された注文内容を具体的に示す必要がありますが、とりあえず遷移イベント発生部分のみを準備することにします。
www/webapp/templates/Order/Confirmation.html:
<h3>注文内容は以上で宜しいですか?</h3>
<ul>
<li>メインメニュー: +++</li>
<li>サイドメニュー: ---</li>
</ul>
<div>小計: *** 円</div>
<a href="{__appRootPath}/order.php?{__eventNameKey}_finish&{__flowExecutionTicketKey}={__flowExecutionTicket}">OK</a>
注文受付完了ページ
最後は注文受付完了ページです。Finish ステートではビューを Finish としていますので、Order ディレクトリ内に Finish.html というファイルを作成します。このページの出力時には既に処理およびフローは完了しているため、遷移イベントに関する記述は特にありません。このページには注文受付が終わった際の文章などを記述しておきます。<a> タグによるリンクも準備していますが、こちらは遷移イベントを発生させるためのものではなく、単に最初のページに戻る (新たにフローを実行する) ためのものです。
www/webapp/templates/Order/Finish.html:
<h3>ご注文ありがとうございました。</h3>
<a href="{__appRootPath}/order.php">はじめに戻る</a>
動作確認
全てのページのテンプレートができたところで、再び http://localhost/order.php へアクセスしてみることにしましょう。先ほど確認したときとは違い、具体的な内容が表示されるようになりました。( 図 21 )
またリンクをクリックしてページを切り替えていくことで注文受付の流れが確認できる状態にもなっています。なおページ内リンクにカーソルを合わせると、そのリンクが遷移イベント発生用リンク (例: /order.php?_event_next&_flowExecutionTicket=<長い英数字>&main=1 ) になっていることもわかります。
図 21 ポテトバーガー注文アプリケーション
レイアウトの変更
ページレイアウトのデザインが Piece_IDE によって生成されたサンプルアプリケーションのものとなっていますので、この見た目も変更してみましょう。現在は全てのページの見た目や HTML における基本情報が同じとなるように、レイアウト用テンプレート (レイアウト) が利用されている状態になっています。(もちろんこの機能を無効にすることもできます。)
プロジェクトツリーの www/webapp/templates/Layout ディレクトリ内にある Layout.html というファイルがこのレイアウト用テンプレートファイルとなります。このファイルの内容を編集し、レイアウトの見た目や HTML の <header> タグの内容を変更することができます。なおその変更の際には、アプリケーションの実際の内容 (ビューステートのビューに対応する内容) が挿入される場所となるテンプレート変数 __content を忘れないようにしましょう。
具体的な動作を考える¶
テンプレートを追加したことでページに動きが出始め Web アプリケーションらしいものになってきました。引き続いてアプリケーションが機能するよう、具体的な動作について作り込みを進めていきます。
今回の「ポテトバーガー注文アプリケーション」における具体的な動作とはどのようなものでしょうか。「注文を受け付ける」ということが最終目標ですが、そこに至るまでにもアプリケーションが行わなければならないことがいくつかあるかもしれません。「注文を受け付ける」という目標に達するまでの一連の流れが、すでに Piece Framework のフローとして完成していますので、このフローを軸に各場面ごとに必要となる動作を考えていきます。
まずメインメニューを選択された (selectMainMenu 遷移イベント) 際には、選択された商品を記録しておく必要があります。それと同時に、そのメニューがそもそも取り扱っているものかどうかを確認する必要もあります。仮に取り扱っていないメニューであれば、改めてメインメニューを聞きなおさなければなりません。
次のサイドメニューを選択された (selectSideMenu 遷移イベント) 際もメインメニューの場合と同様に、商品を記録し、そのメニューが取り扱っているものかどうかを確認する必要があります。仮に取り扱っていないメニューであれば、改めてサイドメニューを聞きなおさなければなりません。
メイン、サイドの両メニューの注文内容が分かった際には、注文されたメニューおよびメニューに応じた料金をお客様に確認していただく (Confirmation ステート) 必要があります。また、ここでもしかすると注文内容を変更したいという申し出があるかもしれません。提示した内容を了承いただけた際には、記録しておいたメイン、サイドの両メニューを注文内容として確定させることになります。
まとめると、以下の 4 つの動作が登場することが分かります。
- 選択されたメインメニュー (A とする) を確認し記録する。問題があれば聞きなおす。
- 選択されたサイドメニュー (B とする) を確認し記録する。問題があれば聞きなおす。
- A、B をそれぞれ提示する。問題がなければ先に進む。問題があればメインメニューの選択に戻る。
- A、B を注文として確定させる。(最終目標)
ここでフローの「分岐」のようなものが出てきました。この注文受付フロー、どうやら状況に応じて動作を変える必要があるようです。早速 Flow Designer を使ってこれらの動作を作成していきます。
フローの分岐には、分岐処理のための別のステートを利用します。パレットより アクションステート を選択し、MainMenu ステートと SideMenu ステートの右側の空きスペースにアクションステートのモデルエレメントを設置します。このアクションステートを IsValidForMainMenu という名称にします。
次に関連する遷移イベントを作成しなおします。まず、既存の MainMenu ステートから SideMenu ステートへの selectMainMenu 遷移イベントを削除します。
続いて MainMenu ステートから IsValidForMainMenu ステートへ向かっての遷移イベントの矢印を引き、これを selectMainMenu という名称にします。逆に IsValidForMainMenu ステートから MainMenu ステートへ戻る遷移イベントを引き、 invalid という名称にします。
最後に、IsValidForMainMenu ステートから SideMenu ステートへの遷移イベントを引き、 valid という名称にします。
以上の変更により、フローを構成するステートとイベント、そして遷移は 図 22 のようになります。
図 22 注文受付フローの変更 (一部)
これと同じように、サイドメニューの注文確認の間にも分岐処理を 1 つ追加します。アクションステートを設置して IsValidForSideMenu と名称にします。
次に関連する遷移イベントを作成しなおします。これもメインメニューに関連する遷移イベントと同じようにします。まず、既存の SideMenu ステートから Confirmation ステートへの selectSideMenu 遷移イベントを削除します。
続いて SideMenu ステートから IsValidForSideMenu ステートへ向かっての遷移イベントを引き、この遷移イベントを selectSideMenu という名称にします。逆に IsValidForSideMenu ステートから SideMenu ステートへ戻る遷移イベントを引き、 invalid という名称にします。
最後に、IsValidForSideMenu ステートから Confirmation ステートへの遷移イベントを引き、 valid という名称にします。
また注文内容を最初から選択しなおせるように Confirmation ステートから MainMenu ステートへ遷移するための reselect 遷移イベントも作成しておきます。
以上の変更を行った上で、全体的なモデルエレメントの配置を見直したフローが 図 23 になります。パレットの Select を使い、配置済みの各モデルエレメントをドラッグすることで、モデルエレメントの配置を自由に変更することができます。
図 23 注文受付フローの変更 (全体)
バリデーションを行う¶
前述のとおり、注文としてメインメニューとサイドメニューをそれぞれ選択しますが、その選択内容によっては注文を聞きなおす必要があります。注文が正しいかどうかの検証、すなわちユーザからの情報を検証するためには、 バリデーション機能 を利用します。バリデーション機能は、検証ルールが記載された バリデーション定義ファイル を利用してユーザからの情報の妥当性を検証し、真偽値による結果を返すというものです。バリデーション定義を簡単に構成できるように、 表 5 のような バリデータ が提供されています。
表 5 主なバリデータ| バリデータ名 | 検証内容 |
|---|---|
| Numeric | 数値の大きさ |
| Length | 文字列の長さ |
| Date | 日付 |
| メールアドレス | |
| List | 値が定義済みリストに含まれているか |
| File | ファイルのサイズやメディアタイプ |
| Regex | 正規表現にマッチするか |
| Compare | 2 つの値が同一であるか |
| Unique | 指定されたフィールドの値がユニークであるか |
実際にメインメニューとサイドメニューそれぞれの検証を行うためのバリデーション定義ファイルを作成し、注文を受けた際の検証処理に利用できるようにしましょう。
メインメニューの検証
メインメニューの注文受付では、提示しているメニューの内容に応じて main=1..3 という値をテンプレート上の遷移イベント用リンクに付与していました。このクエリ変数 main の値を対象に検証を行うようバリデーション定義を作成します。バリデーション定義ファイルは www/webapp/config/validations ディレクトリに任意の名称で配置するようになっていますので、今回は MainMenu.yaml というファイルを作成し、以下のように記述します。
www/webapp/config/validations/MainMenu.yaml:
- name: main
required:
message: 注文内容を選択してください
validator:
- name: List
rule:
elements: [1, 2, 3]
message: 正しい注文内容を選択してください
検証ルールは YAML ベースの独自の文法に従って記述していきます。ここでは main という値に対し required (必須) というルールを設定し、 List バリデータを使ってその値が 1, 2, 3 のいずれかであることを検証する内容になっています。
サイドメニューの検証
メインメニューと同様に、サイドメニューの注文受付では、提示しているメニューの内容に応じて side=1..3 という値をテンプレート上の遷移イベント用リンクに付与していました。今度はバリデーション定義ファイル SideMenu.yaml を作成し、以下のように記述します。検証対象がクエリ変数 side になったこと以外はメインメニューとほぼ同じ内容です。
www/webapp/config/validations/SideMenu.yaml:
- name: side
required:
message: サイドメニューを選択してください
validator:
- name: List
rule:
elements: [1, 2, 3]
message: 正しいサイドメニューを選択してください
以上の検証ルールを利用するバリデーションは PHP コードから実行します。
イベントハンドラを記述する¶
作成したフローの各ステートに対してあらかじめ PHP 関数を設定しておくことで、イベント発生時にその関数が実行されます。この関数のことをイベントハンドラといいます。すべてのイベントとその発生タイミング、Flow Designer の関係をまとめると以下のようになります。
| イベント | タイミング | Flow Designer のコンテキスト | Flow Designer のプロパティ |
|---|---|---|---|
| activity | ステート更新時 | ステート | Activity |
| entry | ステート入場時 | ステート | Entry |
| exit | ステート退場時 | ステート | Exit |
| action | 遷移イベント発生時 | 遷移イベント | Action |
| guard | 遷移イベント発生時 | 遷移イベント | Guard |
| initial | フロー開始時 | イニシャルステート | Initialize |
| final | フロー終了時 | ファイナルステート | Finalize |
Piece Framework アプリケーションでは、これらのイベント発生時にイベントハンドラを実行することで目的の動作や機能を実現します。
フローの可視化レベルを効果的なものにするために、実際にはこれらのイベントのうち activity に対するイベントハンドラのみを使う方法が推奨されています。
フローに PHP コードを割り当てる際、1 つのフローにつき 1 つの PHP クラスを準備します。フローに対応するクラスのことを アクションクラス といいます。イベントハンドラはこのアクションクラスのメソッドとして定義します。
アクションクラスはプロジェクトツリーの www/webapp/actions ディレクトリに、1 クラス 1 PHP ファイルで設置します。今回の注文受付フロー用のアクションクラスとして、このディレクトリ内に OrderAction.php という名称のファイルを作成します。クラス名もファイル名と同じく OrderAction にします。なおアクションクラスは、 Piece_Unity_Service_FlowAction という Piece_Unity によって提供されるアクション用クラスを継承する必要があります。
www/webapp/actions/OrderAction.php:
<?php
class OrderAction extends Piece_Unity_Service_FlowAction
{
}
メインメニュー受付処理
まずはメインメニューの受付および注文内容の確認処理です。Flow Designer 上で IsValidForMainMenu ステートを右クリックし、 Show Properties View メニューを選択します。Properties ビューの Activity 欄に onIsValidForMainMenu と入力します。これでこのステートに遷移してきたタイミングで、アクションクラス内に存在する onIsValidForMainMenu() メソッドが実行されるようになります。
図 24 イベントハンドラとしてメソッド名を設定する
実際に onIsValidForMainMenu() メソッドをアクションクラス内に作成します。前述のとおりここで行う処理は、注文内容に問題がなければ値を記録して次のページへ、不備があれば再度注文を聞きなおすという分岐です。先ほどメインメニューの注文内容を バリデーション によって検証するためのバリデーション定義ファイルを作成しましたので、ここでそのバリデーションを実行するようにします。イベントハンドラ onIsValidForMainMenu() は以下のとおりです。
www/webapp/actions/OrderAction.php:
<?php
...
public function onIsValidForMainMenu()
{
if (!$this->_context->getValidation()->validate('MainMenu', $this->_order)) {
return 'invalid';
}
return 'valid';
}
...
イベントハンドラには引数はありません。バリデーションは、アクションクラスのプロパティとして準備されている $_context というコンテキストオブジェクトに対して getValidation() メソッドを実行して得られる専用のオブジェクト経由で実行することになります。
このオブジェクトに対し、バリデーション定義ファイルの名称を引数として validate() メソッドを実行することで、指定したバリデーション定義ファイルによる検証を行うことができます。この validate() メソッドによる検証結果が真偽値として返されますので、この値を元に分岐処理を行います。
また validate() メソッドの第 2 引数にオブジェクトを与えることで、バリデーションの対象になった値がそのオブジェクトのプロパティとして設定されます。この場合 $this->_order の main というプロパティに対して main=* の値が代入されます。従って、このバリデーションの実行後は $this->_order->main にアクセスすれば、選択されたメインメニューの値を参照することができます。
なお、現在のステートから遷移する先のステートを決定するには、そのステートに設定されている遷移イベントの名称をイベントハンドラの返り値とします。今回の場合 IsValidForMainMenu ステートに設定されている遷移イベントは、MainMenu ステートへ遷移するための invalid 遷移イベントと SideMenu ステートへ遷移するための valid 遷移イベントです。遷移イベントの名称からわかるようにバリデーションに成功した場合は valid 遷移イベントで SideMenu ステートへ進み、失敗した場合は invalid 遷移イベントで MainMenu ステートへ戻るようにします。
アクションステートが行った処理の結果をそのまま遷移イベントの名称にすることによって、フローもソースコードも理解しやすくなっていることがお分かりいただけると思います。
サイドメニュー受付処理
サイドメニュー受付処理も同様に進めていきます。Flow Designer 上で IsValidForSideMenu ステートを右クリックし、 Show Properties View メニューを選択します。Properties ビューの Activity 欄に onIsValidForSideMenu と入力します。これでこのステートに遷移してきたタイミングで、アクションクラス内に存在する onIsValidForSideMenu() メソッドが実行されるようになります。
イベントハンドラ onIsValidForSideMenu() の内容は以下のようになりますが、先ほどのイベントハンドラとの違いは使用するバリデーション定義のみとなっています。
www/webapp/actions/OrderAction.php:
<?php
...
public function onIsValidForSideMenu()
{
if (!$this->_context->getValidation()->validate('SideMenu', $this->_order)) {
return 'invalid';
}
return 'valid';
}
...
注文内容と金額の提示
次はメインメニューおよびサイドメニューの注文内容を確認してもらうために情報を提示する処理です。こちらは先ほどの 2 つのフロー分岐処理とは違い、画面表示のためのものです。このような処理の場合は、ビューステートが更新される (ビューステートの activity イベントが発生する) タイミングで合わせて行うようにします。対象となるステートは確認ページのための Confirmation ステートです。
Flow Designer 上で Confirmation ステートを右クリックし、 Show Properties View メニューを選択します。Properties ビューの Activity 欄に onConfirmation と入力します。次にイベントハンドラ onConfirmation() を以下のように作成します。
www/webapp/actions/OrderAction.php:
<?php
...
private static $_mainMenu array('1' => 'ジャーマンポテトバーガー',
'2' => 'ポテトコロッケバーガー',
'3' => '肉じゃがバーガー'
);
private static $_sideMenu array('1' => 'フライドポテト',
'2' => 'ポテトサラダ',
'3' => 'スイートポテト'
);
private static $_prices array('1' => 650,
'2' => 600,
'3' => 700
);
...
public function onConfirmation()
{
$viewElement $this->_context->getViewElement();
$viewElement->setElement('main', self::$_mainMenu[$this->_order->main]);
$viewElement->setElement('side', self::$_sideMenu[$this->_order->side]);
$viewElement->setElement('price', self::$_prices[$this->_order->main]);
}
...
ここでのポイントは、注文内容に応じて可変するページの内容を、$viewElement というオブジェクトの setElement() メソッドを使って設定しているところです。メインメニューの注文内容、サイドメニューの注文内容、そして金額をそれぞれ main, side, price という変数に設定しています。
なおメインメニュー、サイドメニューの際のメソッドの内容とは異なり、イベントハンドラの返り値として イベント名を返していない という点に注意してください。ビューステートにおけるイベントハンドラの実行では、アクションステートと異なりイベント名を返す必要はなく、また返すことで遷移先を切り替えるといった分岐処理も行えません。遷移先を切り替えたい場合はメイン、サイドメニューのケースのようにアクションステートを利用するようにしましょう。
ページ上の可変内容を扱うコードを記述しましたので、値が参照できるようにテンプレートを変更しましょう。 www/webapp/templates/Order 内にある Confirmation.html を開き、以下のように変更します。
www/webapp/templates/Order/Confirmation.html:
<h3>注文内容は以上で宜しいですか?</h3>
<ul>
<li>メインメニュー: {main}</li>
<li>サイドメニュー: {side}</li>
</ul>
<div>小計: {price} 円</div>
<a href="{__appRootPath}/order.php?{__eventNameKey}_finish&{__flowExecutionTicketKey}={__flowExecutionTicket}">OK</a>
<a href="{__appRootPath}/order.php?{__eventNameKey}_reselect&{__flowExecutionTicketKey}={__flowExecutionTicket}">選びなおす</a>
ここでは、イベントハンドラ onConfirmation() で準備した main, side, price の値を変数名としてそれぞれ記述するようにします。
またフロー内容の見直しの際に、注文内容を変更するための reselect 遷移イベントを追加していましたので、これを発生させるためのリンクを追加しています。リンク内の値は finish 遷移イベントを発生させるためのリンクと、遷移イベントが異なる以外の違いはありません。
アプリケーションの動作を確認する¶
再びアプリケーションへアクセスし、「ポテトバーガー注文アプリケーション」の動作を確認することにしましょう。これまでのフローの変更および PHP コードの追加によって、注文を実際に受け付けたり、確認ページによる注文内容の提示が行えるようになりました。( 図 25 )
図 25 注文受付 (確認ページ)
以上で「ポテトバーガー注文アプリケーション」としての振る舞いはほぼ実装できたといえるでしょう。最後に Piece Framework を使用した Web アプリケーションの動作そのものについて少し確認していきましょう。
複数ウィンドウによる注文
複数のブラウザを同時に起動 (もしくは 1 つのブラウザ内に複数のタブを作成) し、それぞれ http://localhost/order.php へアクセスしてみましょう。各々のウィンドウ上で注文を進めていくと、異なる注文内容が混同されることなく正しく注文を確定させることができます。
Piece Framework による Web アプリケーションには、複数ウィンドウ動作時においても各々のウィンドウのデータ管理を開発者自身が意識する必要がない、という利点があります。
イレギュラーな注文
http://localhost/order.php へアクセスし、始めに表示されるメインメニューの選択リンクをブラウザ機能の「新しいウィンドウ (タブ) で開く」を使って選択します。メインメニューページが表示されているウィンドウ (タブ) とは別にもう 1 つウィンドウ (タブ) が開き、そこへサイドメニューの内容が表示されることになります。このままサイドメニューも続けて選択すると、「メインメニューが表示されたウィンドウ」「注文内容確認が表示されたウィンドウ」の 2 つが並ぶ状態となります。ここで再びメインメニュー側のページでメニューの 1 つのリンクを選択すると、本来のフローであるサイドメニュー選択ページが表示されず、もう一方の注文内容確認と同一の内容が表示されることになります。
先ほどの複数ウィンドウで行った注文のケースとは異なり、今回のウィンドウ (タブ) は注文受付フローが始まった後に作られたものです。稼動中のフローに対し途中で新規ウィンドウ (タブ) を作成すると、その時点のページ内容が片方に残る状態になります。しかしあくまでもフロー自体ははじめに作成された 1 つのみです。ある程度フローが進んだ後に、別ウィンドウに残ったページを使って改めてイベントを発生させたとしても、フロー内ではそのイベントは既に起こったものと認識され無視されます。
なお前述した レイアウトの変更 で紹介した内容の変更を行わなかった場合は、今回のような動作をさせると「Sorry, this page does not support ...」といったメッセージが表示されます。これは、レイアウト用テンプレートに対して、イレギュラーな (現在のステートに対して設定されていない) イベントが発生した場合に出力されるようにメッセージが設定されているためです。
その他不正なリクエスト
その他、order.php の後ろのクエリ変数に手を加えて、なんの前置きもなくいきなり selectMainMenu 遷移イベントを発生させる、フロー実行のためのフロー実行チケットをでっちあげる (_flowExecutionTicket=xxxx など) といったような URI でこのアプリケーションにアクセスしてみましょう。全てのアクセスが無視され、画面にはフローの始まりであるメインメニューページが表示されます。不正なリクエストによるアクセスが排除されることを確認することができるでしょう。
注文内容をデータベースに書き込む¶
Web アプリケーションでは、データベースを利用してデータの読み書きを行うケースが数多くあります。Piece Framework 上のアプリケーションにおいても簡単にデータベース操作を行うことができますので紹介します。今回は、注文受付が確定した際にその注文内容をデータベースに書き込む処理を実現することにしましょう。
データベースについて
ここでは PostgreSQL を利用してデータ操作を行うことにしますが、もちろん PostgreSQL に限らずその他多くのデータベースでも問題ありません。現在のところ MySQL, PostgreSQL, Microsoft SQL Server が公式にサポートされていますので、それぞれ対象のデータベースに置き換えながらお読みください。
注文内容データを格納するテーブル名は、それぞれのデータベースで定められているルールに従ったものであれば何でもかまいません。ここでは 表 6 に示すようなテーブルを orders として作成しました。
表 6 orders テーブルの内容| カラム名 | 型 | 説明 | デフォルト値 |
|---|---|---|---|
| id | serial | プライマリキー | シーケンスの値 |
| main | integer | 注文されたメインメニュー | |
| side | integer | 注文されたサイドメニュー | |
| created_at | timestamp | 挿入された日時 | 現在の日時 |
| updated_at | timestamp | 更新された日時 | 現在の日時 |
Piece_Examples_Conversation パッケージをインストールされた場合、 data/schemas ディレクトリ以下に MySQL と PostgreSQL のスキーマ定義ファイルがありますので、そちらをご利用になるとよいでしょう。
また、詳細なステップは割愛しますが、データベース操作に必要な PHP エクステンションが利用できるように、php.ini ファイルなどを準備しておきましょう。
MDB2 ドライバの準備
Piece Framework でデータベース操作を行う場合は、Piece_ORM というデータベース用ライブラリを利用します。このライブラリのインストールは既に済んでいますが、Piece_ORM 内部で利用されているデータベースライブラリ MDB2 を利用する際、データベースにあわせて接続用ドライバをインストールする必要があります。ドライバのインストールには pear コマンドを利用します。
PostgreSQL 用ドライバを利用する場合は、以下のように pear コマンドを実行します。
pear install MDB2_Driver_pgsql
ドライバの PEAR パッケージ 名は pgsql のように各々のデータベース向けのものになっています。たとえば MySQL 用ドライバの場合は、MDB2_Driver_mysql というパッケージ名になります。
Piece_ORM の準備
続いて、Piece_ORM を構成します。
まず www/webapp/config ディレクトリにある piece-unity-config.yaml を開き、Piece_ORM を利用するための準備を行います。ファイルの末尾に以下の内容を記述し、Piece_ORM が利用する各種ディレクトリを設定します。
www/webapp/config/piece-unity-config.yaml:
...
- name: ConfiguratorChain
point:
- name: configurators
type: extension
value:
- Configurator_PieceORM
- name: Configurator_PieceORM
point:
- name: configDirectory
type: configuration
value: ../webapp/config/orm
- name: cacheDirectory
type: configuration
value: ../webapp/cache/orm
- name: mapperConfigDirectory
type: configuration
value: ../webapp/config/orm/mappers
次に、設定したディレクトリ自体を実際に作成します。プロジェクトツリーに対して、以下のようなパスとなるようディレクトリを新規作成します。現存する config, cache ディレクトリそれぞれの中で、フロー、バリデーションと並んで Piece_ORM 用ディレクトリを追加するようなイメージです。
- www/webapp/config/orm
- www/webapp/config/orm/mappers
- www/webapp/cache/orm
続いて、データベース接続のための情報をもつファイルを作成します。先ほど作成した www/webapp/config/orm 内に piece-orm-config.yaml という構成ファイルを作成します。このファイルの内容は以下のようになります。dsn の username や password などは各自の環境のものに置き換えてください。
www/webapp/config/piece-orm-config.yaml:
- name: production
dsn:
phptype: pgsql
hostspec: localhost
database: piece_conversation
username: piece
password: piece
charset: utf8
最後に、Piece_ORM が必要とする、テーブルに対するメソッドや SQL を定義するための マッパー定義ファイル を準備します。 www/webapp/config/orm/mappers ディレクトリへ Orders.yaml というファイルを新規作成します。ファイル名は、データ操作の対象となるテーブル名をアッパーキャメルケース (例えば、foo_bar_baz であれば FooBarBaz) で表現したものである必要があります。
このファイルは通常は空のファイルでかまいません。テーブルに対するメソッドを追加したり、SQL を上書きしたりといった Piece_ORM の機能を利用する際に初めてこのファイルに内容を記述することになります。今回はそれらの機能を利用しませんので、空のファイルを新規作成するだけで結構です。
以上のディレクトリ設定、データベース接続の設定、マッパー定義ファイルの作成で準備は完了です。
注文内容の保存
では注文内容が確定したタイミングでデータをデータベースへ格納することにしましょう。
データベース操作は PHP コードで行いますので、新たに Register というアクションステートを用意し、そのステートの activity で注文内容を保存する処理を行うことにします。activity のイベントハンドラは onRegister() とします。
次に今まで Confirmation ステートから Finish ステートへ遷移している finish 遷移イベントを削除し、Confirmation ステートから Register ステートへ遷移する register 遷移イベント、Register ステートから Finish ステートへ遷移する done 遷移イベントを作成します。
図 26 Register ステートを追加した注文受付フロー
確認ページからの遷移イベントが変更されましたので、合わせて Confrimation.html のリンクも変更します。
www/webapp/templates/Order/Confirmation.html:
...
<a href="{__appRootPath}/order.php?{__eventNameKey}_register&{__flowExecutionTicketKey}={__flowExecutionTicket}">OK</a>
<a href="{__appRootPath}/order.php?{__eventNameKey}_reselect&{__flowExecutionTicketKey}={__flowExecutionTicket}">選びなおす</a>
続いて OrderAction クラスにコンストラクタとイベントハンドラ onRegister() を追加します。
www/webapp/actions/OrderAction.php:
<?php
...
public function __construct()
{
$this->_order = Piece_ORM::createObject('Orders');
}
...
public function onRegister()
{
Piece_ORM::getMapper('Orders')->insert($this->_order);
return 'done';
}
...
まずコンストラクタで注文内容オブジェクトである $this->_order を初期化します。具体的には Piece_ORM::createObject() メソッドの戻り値をそのままセットします。Piece_ORM::createObject() メソッドは引数で渡されたテーブルのカラムをプロパティに持つオブジェクトを生成します。orders テーブルであれば、id, main, side といったプロパティを持つオブジェクトが生成されます。
次にイベントハンドラ onRegister() で orders テーブルに注文内容を挿入します。最初に Piece_ORM::getMapper() という static メソッドを利用して、orders テーブルを操作するためのマッパーオブジェクトを取得しています。
Piece_ORM を利用したデータベースレコードの操作は、すべてこのマッパーオブジェクトを経由して実行されます。マッパーオブジェクトを使用することによって、SELECT, INSERT, UPDATE といった SQL をそれに対応するメソッドで簡単に実行することが可能となっています。( 表 7 )
表 7 マッパーオブジェクトで利用できる主なメソッド| メソッド | 動作 | メソッドの引数 |
|---|---|---|
| insert | INSERT 文の実行 | テーブルのカラム名をプロパティにもつオブジェクト |
| update | UPDATE 文の実行 | テーブルのカラム名をプロパティにもつオブジェクト |
| findByXXX | SELECT 文の実行。単一レコードを取得する。XXX に相当するカラム名を WHERE 節に利用する | XXX の値、もしくは XXX プロパティを持つオブジェクト |
| findAllByXXX | SELECT 文の実行。複数レコードを取得する。XXX に相当するカラム名を WHERE 節に利用する | XXX の値、もしくは XXX プロパティを持つオブジェクト |
| delete | DELETE 文の実行 | テーブルのカラム名をプロパティにもつオブジェクト |
今回のコードでは、注文内容オブジェクトである $this->_order を引数にマッパーオブジェクトの insert() メソッドが実行され、以下のような INSERT SQL 文に相当する処理が行われます。
INSERT INTO orders (main, side) VALUES ('メインメニューを示す数値', 'サイドメニューを示す数値');
Piece_ORM では引数に与えられるオブジェクトのプロパティとテーブルのカラムが対応する仕組みになっており、このアプリケーションでは $this->_order オブジェクトの main, side プロパティの値がテーブルの main, side カラムの値として利用されることになります。
id, created_at, updated_at といったカラムには Piece_ORM によって適切な値がセットされるので、通常はユーザ側で変更する必要はありません。
動作確認
再びこの「ポテトバーガー注文アプリケーション」を最後まで動作させます。最終ページ (ご注文ありがとうございました) に到達した際に、メインメニューおよびサイドメニューとして選択した項目の値が、データベースの orders テーブルのレコードとして正しく追加されたかどうかをチェックしてみましょう。
正しくレコードが追加されていない場合は、PHP によるデータベース操作処理が実行できるかはどうかは当然のこと、MDB2 ドライバのインストールや、Piece_ORM に必要なディレクトリの有無やパーミッション、 piece-orm-config.yaml に記述されているデータベース接続情報に問題がないかどうか、mappers ディレクトリのマッパー定義ファイル名が正しいかどうかなどを、エラーメッセージと合わせて確認してみてください。
フロー設計のポイント¶
Piece Framework でアプリケーションを開発する上で、フローを作成するということはアプリケーションをどのように動作させるかを設計するということになります。ここではフローを作成する、つまり設計する上でのポイントをいくつかご紹介します。
ビューから考える¶
今回の「ポテトバーガー注文アプリケーション」の実装でも最初にビューステートを作成し、次にアクションステートを作成しました。このように目に見えるビューから考えていくと比較的容易にフローを作成することができます。
ステートを細かく分ける¶
各ステートは PHP コードと関連していますので、実装しようと思えば、ひとつのステートのみ作成し、そのステートの activity であらゆる処理を行ってしまうことも可能です。また、Piece Framework や Flow Designer の操作に慣れるまでは、ついそのような実装を行ってしまいがちです。
ステートを細かく分けるというのはひとつのステートがひとつの処理を担当するということにほかなりません。例えば、ある条件で抽出したデータの一覧をページに表示する場合、一覧を表示するビューステートだけで処理するのではなく、データを取得するアクションステートと一覧を表示するビューステートに分けたフローを作成します。
ステートを細かく分けるメリットはふたつあります。ひとつは各ステートがひとつの処理を表すようになると、フロー自体がまさに処理の流れを表すものとなり、フローの設計情報を目に見える理解しやすい形で保存することができます。ふたつ目はステートが分かれるため必然的に PHP コードのメソッドも分離されます。ひとつのステートがひとつの処理を行うようにフローが作成されていれば、PHP コードのメソッドもひとつの処理のみを行うように記述されるため、PHP コード自体の可読性も向上します。
では、実際にステートをどのようにして細かくしていけばよいのでしょうか?これについては、ある程度の経験は必要になりますが、ひとつの指針としては「if 文などの条件分岐が 2 つ以上になったらステートを分割する」ということが挙げられます。アクションステートについては「処理 (Process) と 決定 (Decision) を分割する」という指針が挙げられます。また、「フローからどういった流れでどういった処理が行われているかを読み取ることができるか」ということを常に意識することも重要です。
適切な名称をつける¶
モデルエレメント (ステートや遷移イベント) の名称はそのものの適切な名称を付けるようにしましょう。現在以下の命名規約が推奨されています。
表 8 モデルエレメントの命名規約| モデルエレメント | 命名規約 | 例 |
|---|---|---|
| ビューステート | 入力、表示されるものを表す (頭文字は大文字) | MainMenu, Confirmation |
| アクションステート | 処理や決定を表す (頭文字は大文字) | IsValidForMainMenu, Register |
| ビューステートからの遷移イベント | ユーザが行う動作を表す (頭文字は小文字) | selectMainMenu |
| アクションステートからの遷移イベント | 処理や決定の結果を表す (頭文字は小文字) | valid, invalid, done |
命名規約に従ってステートや遷移イベントの名称を付けることで、意図が明確になりフローを理解しやすくなります。
おわりに¶
Piece Framework を使ったアプリケーション開発は、アプリケーションのフローをグラフィカルなエディタで描き、フローをベースにして各場面の処理を考え、それらの処理を PHP コードで書くというスタイルで進めていくことができます。この特性は、作成しようとする Web アプリケーションの 目的 をより明確にし、Web アプリケーションをシンプルにすることに繋がっていきます。そして Web アプリケーションとは、1 つの目的 (機能) が 1 つの図として描き出されるような小さな フロー が寄り合っているものであると認識することができるでしょう。
ここでは紹介しきれませんが、今回紹介した機能の付加要素的なものや、その他アプリケーション開発に役立つちょっとした機能、そして上級者向けにも強力な (かつマニアックで、そしてニッチな) 機能も準備されています。Piece Framework は、ちょっとした規模の Web サイトから企業向け Web アプリケーションまで幅広いアプリケーション開発に利用できます。ぜひチャレンジしてみてください。