アプリケーション構築手順(3)
次に、もう少し実際的な内容ということで、ログイン処理(画面の表示ではなく)を例にとって
- フォーム値の取得方法
- 基本的なエラー処理方法
- ビューへのデータ設定方法
といった点をご説明したいと思います。
なお、このページはアプリケーション構築手順(1)〜アプリケーション構築手順(2)の続きとなっていますので、一応ご注意下さい。
(10) ログイン画面の変更
まず、前節(8)で作成したテンプレートファイル(template/ja/login.tpl)をもうちょっとログイン画面っぽいものに作り変えておきます。具体的には以下のようにしてみます。
template/ja/login.tpl:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head></head> <body> <form action="{$script}" method="post"> <input type="hidden" name="action_login_do" value="dummy"> <table border="0"> <tr> <td>メールアドレス</td> <td><input type="text" name="mailaddress" value=""></td> </tr> <tr> <td>パスワード</td> <td><input type="password" name="password" value=""></td> </tr> </table> <p> <input type="submit" name="action_login_do" value="ログイン"> </p> </form> </body> </html>
通常のHTMLファイル(あるいはSmartyのテンプレートファイル)ですが、2点ほどEthna独自の点がありますのでここでご説明します。
- {$script}はテンプレート表示前にEthnaフレームワークが設定する変数で、現在実行中のPHPスクリプトを表します(Ethna_ViewClass::_getTemplateEngine()をご参照下さい)
もちろん、/index.phpで全てを処理する場合は、action="/"と記述しても全く問題ありません - hiddenタグの"action_login_do"は、このフォームをsubmitした際に、「login_do」というアクションを実行することを表します
「login_do」というはStrutsの慣習をそのまま使っているだけなので、「login」と重ならなければ「login_exec」でも「login_submit」でも何でも構いません
Smartyテンプレートの詳細については
などを見てください。
以上で準備は完了です。
http://some.host/~foo/?action_login=true
にアクセスしてログイン画面が表示されることを確認してください。
(11) ログインアクションの追加
フォームがsubmitされた際に実行されるアクション「login_do」を前節(5)の場合と同様に追加します。
$ ethna add-action login_do generating action script for [login_do]... action script(s) successfully created [/tmp/sample/app/action/Login/Do.php]
この状態ではやはり'login_do'という遷移名を返すだけのアクションになっています。実際には'login_do'という遷移名は不要なので、以下のように変更しておきます。
app/action/Login/Do.php:
66 function perform() 67 { 68 - return 'login_do'; 68 + return 'index'; 69 }
以上の状態で、ログインボタンを押すと
- コントローラがlogin_doアクションを実行します
- Sample_Action_LoginDo::perform()メソッドが実行されます
- 遷移名としてindexが返されます
という処理の流れで、トップページが表示されると思います。
実際にはトップページに遷移する前に、(あたりまえですが)フォーム値のチェックや認証処理を行い、エラーが発生した場合はエラーメッセージを表示させる必要があります。
(12) フォーム値の取得
認証を行うためには、フォームから送信された値を取得する必要があります。フォームから送信された値にアクセスするには、アクションクラスと1対1で生成されるアクションフォームというオブジェクトを利用します。アクションフォームのクラス定義は、ethna add-action を実行すると、アクションクラスと同時に生成されていますので、まずは何も考えず、app/action/Login/Do.phpに以下のようなコードを追加してみてください。
app/action/Login/Do.php:
23 var $form = array( 24 + 'mailaddress' => array( 25 + 'type' => VAR_TYPE_STRING, 26 + ), ... 69 function perform() 70 { 71 + print $this->action_form->get('mailaddress');
以上の状態で、フォームの「メールアドレス」に適当な文字を入力してsubmitすると、その値が表示されるかと思います。これで何となくお分かりかと思いますが、フォーム値にアクセスするには(非常に大雑把に言うと)
- アクションフォームに受け取るフォーム値を定義する
- アクションクラスにメンバ変数として設定されている$action_formオブジェクトのアクセサ(get()/set())を通じて値を取得/設定する
という手順になります。なお、フォーム値にアクセスする度に
$this->action_form->get('foo');
と書くのは面倒なので、省略形として
$this->af->get('foo');
と書くことも出来ます。
(13) フォーム値の検証
たかがフォーム値にアクセスするのに何故こんな面倒な手順が必要なのか、にはいくつか理由がありますが(ほとんどはセキュリティ上の理由)、この方法の最大のメリットはフォーム値の自動検証です。
アクションスクリプトのスケルトンを生成すると、アクションフォームに以下のようなコメントも生成されているかと思います。
27 /* 28 'sample' => array( 29 'name' => 'サンプル', // 表示名 30 'required' => true, // 必須オプション(true/false) 31 'min' => null, // 最小値 32 'max' => null, // 最大値 33 'regexp' => null, // 文字種指定(正規表現) 34 'custom' => null, // メソッドによるチェック 35 'filter' => null, // 入力値変換フィルタオプション 36 'form_type' => FORM_TYPE_TEXT // フォーム型 37 'type' => VAR_TYPE_INT, // 入力値型 38 ), 39 */
上記のように、各フォーム値には'name'〜'type'まで計9つの属性を設定することが出来ます(必須なのは'type'のみです)。Ethnaでは、ここで設定されら属性を利用したフォーム値の自動検証機能を提供しています。
ここで先ほどのフォーム値'mailaddress'を利用して実際に試してみます。まず、先ほどの'mailaddress'の属性を下記のように変更します。
24 'mailaddress' => array( 25 + 'name' => 'メールアドレス', 26 + 'required' => true, 27 'type' => VAR_TYPE_STRING, 28 ), 29 + 'password' => array( 30 + 'name' => 'パスワード', 31 + 'required' => true, 32 + 'type' => VAR_TYPE_STRING, 33 + ),
これは、フォーム値'mailaddress'の表示名が「メールアドレス」であり、また入力が必須であることを示しています。ついでにpasswordも必須としてしまいます。
次に、アクションクラスで自動入力処理を行います。具体的には、アクションクラスのprepare()メソッドに以下のような処理を追加します。
65 function prepare() 66 { 67 + if ($this->af->validate() > 0) { 68 + return 'login'; 69 + }
アクションフォームのvalidate()メソッドは、定義に従ってフォーム値を自動検証し、検出したエラーの数を戻り値として返します(発生したエラーの扱い等については後述します)。
この状態で、メールアドレスを空欄にしてsubmitすると以前と異なりトップページは表示されず、再度ログインページが表示されるのが分かるかと思います。
以上が、フォーム値の検証に関する基本的な説明でした。なお、アクションクラスのprepare()メソッドとperform()メソッドの関係は以下のようになっていて(なんちゃってシーケンス図-しかもスペルチェックエラー*1)、まずはprepare()メソッドが呼ばれ、prepare()メソッドがnullを返した場合のみperform()メソッドが呼び出されます。
ようするに、prepare()メソッドでフォーム値の検証を行うこと、perform()メソッドでは全てのデータはサニタイズされているという前提で処理を行うことが出来る、安全且つ簡潔なコードが書けるというわけです(やっぱりStrutsの真似)。
なお、フォーム値の自動検証詳細については以下をご覧下さい。
see also: フォーム値の自動検証を行う
(14) エラー処理(フォーム値の表示)
(自動にせよ手動にせよ)フォーム値の検証を行ってエラーが発生したら、それに伴って幾つかの処理を行う必要があります。
まずは最低限の処理その1ということで、入力されたフォーム値をvalue属性に設定してみます。
フォーム値はEthnaフレームワークによって自動的にSmarty変数として割り当てられるので、実際にはテンプレートで
{$form.フォーム項目名}
と記述すればOKです。ですのでここではlogin.tplを以下のように変更します。
template/ja/login.tpl:
8 <tr> 9 <td>メールアドレス</td> 10 - <td><input type="text" name="mailaddress" value=""></td> 10 + <td><input type="text" name="mailaddress" value="{$form.mailaddress}"></td> 11 </tr>
この状態で、メールアドレスのみを入力してsubmitすると、(パスワードが入力されていないのでエラーにはなりますが)メールアドレスのフォーム値が失われずに表示されていると思います。
なお、{$form.*}で表示される値は常にエスケープされていますので、サニタイズ等は考慮する必要はありません。*2
(15) エラー処理(エラーメッセージの表示)
次に最低限の処理その2である、エラーメッセージの表示を行います。発生したエラーは、やはりEthnaフレームワークによって自動的にテンプレート変数として割り当てられ、
- 全てのエラーメッセージ一覧
- 指定されたフォーム名に対応するエラーメッセージ
という形でアクセスすることが可能です。
まず全てのエラーメッセージを表示させてみます。エラーメッセージは配列として{$errors}というSmarty変数に割り当てられていますので:
template/ja/login.tpl:
4 <body> 5 + {if count($errors)} 6 + <ul> 7 + {foreach from=$errors item=error} 8 + <li>{$error}</li> 9 + {/foreach} 10+ </ul> 11+ {/if}
というように書くと、全てのエラーメッセージを表示させることが出来ます。
また、特定のフォーム名に対応するエラーメッセージを表示させるには、Ethnaフレームワークの提供するSmarty関数"message"を利用します。
template/ja/login.tpl:
15 <tr> 16 <td>メールアドレス</td> 17 <td><input type="text" name="mailaddress" value="{$form.mailaddress} ">{message name="mailaddress"}</td> 18 </tr> 19 <tr> 20 <td>パスワード</td> 21 <td><input type="password" name="password" value="">{message name="p assword"}</td> 22 </tr>
Ethnaフレームワークにおけるエラー処理ポリシーについては以下をご覧下さい。
see also: エラー処理ポリシー
また、自動検証で設定されるエラーメッセージは(もちろん)任意にカスタマイズすることが出来ます。
see also: 自動検証のエラーメッセージをカスタマイズする
(16) ロジックの記述(概念)
フォーム値の検証が完了したら、いよいよロジック部分(実際のアプリケーションとしての動作)を記述します。
と、その前にアクションクラスとアプリケーションの関連を表した図を以下に示します。
ちょっと分かりにくいかもしれませんが、上記のようにアクションクラス(perform()メソッド)にはアプリケーションの核となる処理を記述してはいけません。
基本的にはほぼ全ての処理はアプリケーションの核となるクラス(app/ディレクトリに置かれるスクリプト)に記述し、アクションクラスはそれらを単純に呼び出すのみ、というイメージです。例えば
perform() { // メールアドレスをキーにしてユーザオブジェクトを生成 $user =& new Sample_User($this->backend, $this->af->get('mailaddress')); // 認証処理 $result = $user->auth($this->af->get('password'); // 以降結果によってビューを変更、等... }
というようになります。これには、各アクションクラス間での処理の重複を防ぐ目的もありますが、主な目的は、アクションクラスはフロントエンドに徹することで、低コストで異なるクライアントに対応できる、と言うことです。具体的には以下のようなイメージです。
このあたりは実験段階ですが、一応モバイル(仮にAU)とSOAPクライアントに関しては実績がありますので、ブラッシュアップすればなかなか使えるものになっていくと思います。
最後に改めてアクションクラスのperform()メソッドを記述する際の注意事項を挙げてさせて頂きます。
- アクションクラスにアプリケーションの核となる処理を記述しない
- アクションクラスはどんなに長くても100〜200行程度におさめる
- 他のアクションクラスと重複する処理を記述しない
重複する処理がある場合は、そのアクションクラスを継承するか、アプリケーションのマネージャ的処理に移行する
(17) ロジックの記述(実際)
(16)の概念を元に、アクションクラスにロジック部分の処理を記述していきます(正確には、ロジック部分を記述したクラスを別に作成し、それをアクションクラスから呼び出します)。
まず、アプリケーションのクラスを作成します。ここでは簡単に以下のようなスクリプトを作成してみます。
app/Sample_UserManager.php:
<?php class Sample_UserManager { function auth($mailaddress, $password) { // 実際にはまともに認証処理を行う if ($mailaddress != $password) { return Ethna::raiseNotice('メールアドレスまたはパスワードが正しくありません', E_SAMPLE_AUTH); } return 0; } } ?>
ついでにエラーコードを追加します。
app/Sample_Error.php:
18 */ 19 + /** エラーコード: ユーザ認証エラー */ 20 + define('E_SAMPLE_AUTH', -128); 21 ?>
先ほど作成したSample_UserManager.phpをControllerでインクルードします。appディレクトリとlibディレクトリは、プロジェクトスケルトンを生成した時点でinclude_pathに追加されていますので、ファイル名を記述するだけでOKです。
app/Sample_Controller.php:
21 include_once('Sample_Error.php'); 22 + include_once('Sample_UserManager.php'); 23
最後に、アクションクラスのperform()メソッドを記述します。ここでは、ユーザマネージャで認証処理を行うだけです。
app/actoin/Login/Do.php
80 function perform() 81 { 82 $um =& new Sample_UserManager(); 83 $result = $um->auth($this->af->get('mailaddress'), $this->af->ge t('password')); 84 if (Ethna::isError($result)) { 85 $this->ae->addObject(null, $result); 86 return 'login'; 87 } 88 89 return 'index'; 90 }
ここではauth()メソッドからエラーオブジェクトが返ってきた場合は再度ログイン画面を表示させ、認証が成功した場合はトップページを表示しています。
エラー処理詳細につきましては下記をご覧下さい。
ses also: エラー処理ポリシー
以上が、結構長くなってしまいましたが基本的なアプリケーションの構築手順となります。なんとなくご理解いただけると嬉しいです。
なお、実際のアプリケーション開発ではその他いろいろ、例えば下記のようなパターンも必要となってくるかと思いますので、それらについては順次howtoの方でご説明していきますので、開発中に「あれ?これってどうやるんだろう?」あるいは「この処理、かったるくてやってらんない」と思った場合は、howtoを御覧頂くか、あるいはエントリがない場合はご意見/ご要望ページでお知らせ下さい。
- セレクトボックスを作成する
- チェックボックスを作成する
- セッションを利用する
- ログを出力する
- DBを利用する
- アラートメールを送信する
- ...