入力チェック
Data::Validator を使った入力チェックを組み込みます、要領はこれまでのフレームワークと同じです
1. FillInForm プラグインを読み込む
確認画面から入力画面へ遷移した場合や、入力チェックに弾かれた場合、入力内容を復元して差し上げます。
package Present::Context;
use strict;
use warnings;
use parent 'Pickles::Context';
__PACKAGE__->load_plugins(qw(Encode Session FillInForm));
1;
2. 使う型を定義しておく
詳しくは Mouse::Util::TypeConstraints を参照して下さい
package Hamburger::Types;
use strict;
use Mouse::Util::TypeConstraints;
subtype 'Hamburger::Type::LoginID'
=> as 'Str'
=> where { /^[a-z][a-z0-9_-]{2,19}(?!\n)$/ }
;
subtype 'Hamburger::Type::Password'
=> as 'Str'
=> where {
/^[a-zA-Z0-9\-\_%\$\#]{6,12}(?!\n)$/
}
;
no Mouse::Util::TypeConstraints;
1;
3. 入力チェック用のクラスを用意する
異常時に stash に値を勝手にセットしています、異常がない場合チェック済みのパラメーターを返します。
変換が行われている場合もあるので、呼び出し元ではこのパラメーターを処理に使用します。
package Hamburger::Validator;
use strict;
use warnings;
use Data::Validator;
use Hamburger::Types;
sub new { bless { c => $_[1] }, $_[0] }
sub validate {
my ($class, $c, %rule) = @_;
my $v = Data::Validator->new(%rule)->with('NoThrow');
my $args = $v->validate($c->req->parameters->mixed);
if ($v->has_errors) {
for my $error ( @{$v->clear_errors} ) {
if (length $args->{$error->{name}}) {
$c->stash->{valid}->{invalid}->{$error->{name}} = $error;
} else {
$c->stash->{valid}->{missing}->{$error->{name}} = $error;
}
}
return $args;
}
return $args;
}
1;
4. コントローラー
package Hamburger::Controller::Login;
use strict;
use warnings;
use parent 'Pickles::Controller';
use Hamburger::Validator;
sub index {
my( $self, $c ) = @_;
#
$c->stash->{VIEW_TEMPLATE} = 'login/index';
}
sub login {
my( $self, $c ) = @_;
my $args = $c->get('Validator')->validate(
login_id => { isa => 'Hamburger::Type::LoginID' },
password => { isa => 'Hamburger::Type::Password' },
'.next' => { isa => 'Str' },
button => { isa => 'Str' }
);
if ($c->stash->{valid}) {
$c->stash->{fdat} = $args;
$c->stash->{login_failure}++;
$self->index( $c );
return;
}
my $dbh = $c->get('DBH');
my $member = $dbh->single('member', { login_id => $args->{login_id} });
unless ($member) {
$c->stash->{fdat} = $args;
$c->stash->{login_failure}++;
$self->index( $c );
warn "missing member";
return;
}
my $csh = $c->get('CYPHER');
unless ($csh->validate($member->password, $args->{password})) {
$c->stash->{fdat} = $args;
$c->stash->{login_failure}++;
$self->index( $c );
warn "invalid password";
return;
}
$c->session->regenerate_session_id(1);
$c->session->set('member_id', $member->id);
$c->redirect($args->{'.next'} || '/member/');
}
sub logout {
my( $self, $c ) = @_;
$c->session->remove('member');
$c->session->expire;
$c->redirect($c->uri_for('/login/', { logout => 1 }));
}
1;
5. 入力画面
未入力、フォーマットエラー、ログイン失敗をそれぞれ案内しています。
ログイン失敗はログインIDの有無なのかパスワード不一致かは案内しません。
フォーマットについてより詳しい分岐はテンプレート中に埋めても良いかなと思います。
<html>
<head>
<title>[% c.appname | html %]</title>
</head>
<body>
<h1>Hello [% c.appname | html %]!</h1>
<form action="login" method="post">
[% IF c.req.param('logout') %]
<div>logout.</div>
[% ELSIF login_failure %]
<div>invalid login.</div>
[% END %]
<div>
login_id: <input type="text" name="login_id" value="" />
[% IF valid.missing.login_id %]<div>missing login_id.</div>[% END %]
[% IF valid.invalid.login_id %]<div>invalid login_id.</div>[% END %]
</div>
<div>password: <input type="password" name="password" value="" />
[% IF valid.missing.password %]<div>missing password.</div>[% END %]
[% IF valid.invalid.password %]<div>invalid password.</div>[% END %]
</div>
<input type="hidden" name=".next" value="[% c.req.param('.next') %]" />
<div><input type="submit" name="button" value="login" /></div>
</form>
</body>
</html>