The Document of Aska

入力チェック

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>