S2Container+S2Dao.PHP5を使ってみる(その8)
さて、カートの画面が一応出来たところで、ちょっと戻って考えます。ログイン画面で、入力されたemailとpasswordの入力値の検証はどうしようか?と。入力値のvalidationにはいくつかのレベルがあると思いますが、データベースからデータを引っ張ってこないと判断のしようのない部分はserviceまで持ち越すとして、入力されて即検証できるレベルのもの、つまり入力値が数値なのか文字列なのかだとか、最大/最小、最長/最短、はたまたある正規表現にマッチするかどうか、といったことはactionでやってしまいたいところ。UI上でJavaScriptとかで検証...とかもありますがそれは置いといて、requestで受けとった値を簡単に検証してみよう、ということで、実はここでまたAOPが使えます。SessionFilterを作ったときと同じinterceptorコマンドの、今度はvalidateタイプを選んでフィルタを生成します。入力値の型、数値の最大値/最小値、文字列の最長値/最短値、正規表現へのマッチを検証できる、BasicValidatorを作ってみました。
LoginSample\app\modules\Login\interceptor
に自動生成されますので、これに実装していきます。
validation時には、後述のvalidate設定iniファイルが使用されるのですが、template名."Suffix".iniという名前のその設定ファイルの"Suffix"の部分を、getSuffix()のreturn値に設定してやります。今回はSuffixを"validation"としました。あとはvalidate()の中身を書いていくのですが、問題のある値があれば"リクエストのパラメータ名"+Errorという名前でviewにassignしておくことにしました。これで、templateで入力エラー情報を表示させることが出来ます。validate設定ファイルの内容が解析されて$ruleという連想配列に設定されているので、それを利用してvalidationを行います。return値でvalidation後の遷移先templateの指定もできます(基本的にreturn nullでいいと思いますが)。という訳で改良の余地はかなりありそうですが、ともかく下記のとおり実装しました。
- BasicValidator.class.php
<?php /** * available properties. * protected $invocation; * protected $request; * protected $moduleName; * protected $actionName; * protected $action; * protected $view; * protected $rule; * protected $controller; */ class BasicValidator extends S2Base_AbstractValidateFilter { public function getSuffix() { return "validation"; } public function validate() { if($this->rule == null) { return null; } $page = null; foreach($this->rule as $key => $val) { $paramVal = $this->request->getParam($key); if(isset($val['regex']) and $this->_regexValidation($val['regex'], $paramVal) == false) { $page = $val['page']; $this->view->assign($key . 'Error', $val['msg']); } if(isset($val['type'])) { if($this->_typeValidation($val['type'], $paramVal) == false) { $page = $val['page']; $this->view->assign($key . 'Error', $val['msg']); } if(isset($val['min']) and !isset($val['max'])) { if($this->_rangeValidation($val['type'], $val['min'], null, $paramVal) == false) { $page = $val['page']; $this->view->assign($key . 'Error', $val['msg']); } } elseif(!isset($val['min']) and isset($val['max'])) { if($this->_rangeValidation($val['type'], null, $val['max'], $paramVal) == false) { $page = $val['page']; $this->view->assign($key . 'Error', $val['msg']); } } elseif(isset($val['min']) and isset($val['max'])) { if($this->_rangeValidation($val['type'], $val['min'], $val['max'], $paramVal) == false) { $page = $val['page']; $this->view->assign($key . 'Error', $val['msg']); } } } } $this->rule = null; return $page; } private function _regexValidation($pattern = null, $subject = null) { if($pattern == null or $pattern == "") { return true; } if(preg_match($pattern, $subject) == false) { return false; } return true; } private function _typeValidation($type = null, $subject = null) { if($type == null or $type == "") { return true; } if($type == "numeric" and is_numeric($subject) == false) { return false; } if($type == "string" and is_string($subject) == false) { return false; } return true; } private function _rangeValidation($type = null, $minVal = null, $maxVal = null, $subject = null) { if($type == "string") { if($maxVal == null and $minVal <= strlen($subject)) { return true; } elseif($minVal == null and strlen($subject) <= $maxVal) { return true; } elseif($minVal <= strlen($subject) and strlen($subject) <= $maxVal) { return true; } return false; } elseif($type == "numeric") { if($maxVal == null and $minVal <= $subject) { return true; } elseif($minVal == null and $subject <= $maxVal) { return true; } elseif($minVal <= $subject and $subject <= $maxVal) { return true; } return false; } return true; } } ?>
次に、validate設定iniファイルについてですが、
LoginSample\app\modules\Login\validate\memberLogin.validation.ini
を作成します(これは手動でディレクトリとファイルを作成します)。また、例によってモジュールの設定ファイルLogin.inc.phpでinclude_pathに\validateが追加されるようにします。
設定ファイルの内容は、リクエストパタメータの値をセクションとして、その後にvalidateフィルタのruleに格納されるべきキーと値を並べていきます。今回は、string/numericの型チェックと、numericの場合は最大値/最小値、stringの場合は最長値/最短値、そして正規表現チェックを行い、遷移先画面、エラーメッセージを設定するというようなvalidatorとしたので、下記のようになります。ちなみに、妥当なメールアドレスを確認する正規表現が分からなかったのですが、調べると滅茶苦茶長いのが出てきました。しかも、実はこれでもパーフェクトではなく、実際はメールアドレスを正規表現で完全に表現するのは不可能とのこと!まあ、ここは適当に"/[^@]+@\w+\..+/"辺りにしときましたが、チェックは緩めにしといて、登録時には実際にそのアドレスにメール送信し、受信できたかどうかを確認するのが現実的ではないかと思います。
- MemberLogin.validation.ini
[email] type = "string" regex = "/[^@]+@\w+\..+/" min = 1 max = 255 page = null msg = "メールアドレスを入力してください" [password] type = "string" regex = "" min = 1 max = 20 page = null msg = "パスワードを入力してください"
あとはMemberLoginAction.diconでaspectを追加します。フィルター用のdiconファイルは作っていないので、
<component name="basicValidator" class="BasicValidator"> </component>
と追加しています。
- MemberLoginAction.dicon
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN" "http://www.seasar.org/dtd/components21.dtd"> <components namespace="memberLoginAction"> <include path="%S2BASE_PHP5_ROOT%/app/modules/Login/dicon/MemberLoginService.dicon"/> <include path="%S2BASE_PHP5_ROOT%/app/modules/Login/dicon/SessionFilter.dicon"/> <component name="action" class="MemberLoginAction"> <aspect>sessionFilter.filter</aspect> <aspect>basicValidator</aspect> <property name="dto">dto</property> <property name="service">memberLoginService.service</property> </component> <component name="dto" class="MemberLoginDto"> </component> <component name="basicValidator" class="BasicValidator"> </component> </components>
以上で、memberLoginActionの前にリクエストパラメータの値がチェックされるようになりました。あとはactionとtemplateで、validatorによって設定されたエラー情報の表示を制御してやります。変更後のtemplateは下記のようになりました。テキストボックスのあとに、エラー情報である{$emailError},{$passwordError}が追加されています。入力エラーがない場合はこれらの変数はviewに設定されていない状態になりますが、その場合はtemplateの{$emailError}などの記述は無視されるだけのようなのでこれで良さそうです。
- memberLogin.tpl
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Login</title> </head> <body> <form action="d.php" method="post"> <input type="hidden" name="mod" value="Login"/> <input type="hidden" name="act" value="memberLogin"/> メールアドレスとパスワードを入力してログインしてください <br/> e-mail <br/> <input type="text" name="email" size="30" value="{$dto->getEmail()}"/>{$emailError} <br/> password <br/> <input type="password" name="password" size="30" value="{$dto->getPassword()}"/>{$passwordError} <br/> {$dto->getMessage()} <br/> <input type="submit" name="memberLogin:viewCart" value="ログイン"/> <br /> <input type="submit" name="memberLogin:memberEntry" value="新規登録"/> </form> </body> </html>
最後に、このままだとログイン画面に最初に入ってきたときもvalidatorによって入力エラーが検出され、templateに出力されてしまうので、actionにちょっとだけ細工します。追加したのは下記だけです。submitボタン以外で直接この画面にやってきたときは、dtoをviewにassignする前に、それまでにviewに割り当てられた値を全てクリアしてしまいます。これで、入力エラー情報もクリアされるという訳です。まあ、実はこれは他にも良い方法があるのかも知れませんが...。
$view->clear_all_assign();
- MemberLoginAction.class.php
<?php class MemberLoginAction implements S2Base_Action { private $service; private $dto; public function setService(MemberLoginService $service = null){ $this->service = $service; } public function getService(){ return $this->service; } public function setDto(MemberLoginDto $dto = null){ $this->dto = $dto; } public function getDto(){ return $this->dto; } public function execute(S2Base_Request $request, S2Base_View $view) { if($request->getParam('memberLogin:memberEntry') != null) { return "redirect:memberEntry"; } elseif($request->getParam('memberLogin:viewCart') != null) { $this->dto->setEmail($request->getParam('email')); $this->dto->setPassword($request->getParam('password')); $this->service->authorize($this->dto); if($this->dto->getIsAuthorized() == true) { $this->dto->setMessage(""); $_SESSION['sessionMemberId'] = $this->dto->getId(); $_SESSION['sessionPassword'] = $this->dto->getPassword(); $view->assign('dto',$this->dto); return "redirect:viewCart"; } $this->dto->setMessage("会員データが見つかりませんでした"); $view->assign('dto', $this->dto); return "memberLogin.tpl"; } else { $view->clear_all_assign(); $view->assign('dto', $this->dto); return "memberLogin.tpl"; } } } ?>
続く...