@nqounetです。
「ユーザー登録バリデータで学ぶ責任の連鎖」シリーズの第1回です。
このシリーズは、「Mooで覚えるオブジェクト指向プログラミング」シリーズの応用編です。まだ読んでいない方は、先にこちらをご覧ください。
今回は、ユーザー登録フォームの入力検証(バリデーション)を実装します。まずはシンプルなif文で書いてみて、バリデーションの基本を学びましょう。
バリデーションとは
バリデーション(validation)とは、ユーザーが入力したデータが正しいかどうかをチェックする処理です。Webアプリケーションでは、フォームから送信されたデータを受け取る前に、必ずバリデーションを行います。
たとえば、ユーザー登録フォームでは以下のようなチェックが必要です。
- 名前が入力されているか(必須チェック)
- メールアドレスが入力されているか(必須チェック)
- メールアドレスの形式が正しいか(形式チェック)
これらのチェックを通過したデータだけを、データベースに保存します。
シンプルなバリデーションを書いてみる
まずは、名前とメールアドレスの検証を行うスクリプトを書いてみましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
| # validate_user.pl
# Perl v5.36+, 外部依存なし
use v5.36;
use utf8;
use warnings;
binmode STDOUT, ':utf8';
sub validate_user ($input) {
# 名前の必須チェック
my $name = $input->{name} // '';
if ($name eq '') {
say "エラー: 名前を入力してください";
return 0;
}
# メールアドレスの必須チェック
my $email = $input->{email} // '';
if ($email eq '') {
say "エラー: メールアドレスを入力してください";
return 0;
}
# メールアドレスの形式チェック
if ($email !~ /\A[^@\s]+\@[^@\s]+\.[^@\s]+\z/) {
say "エラー: メールアドレスの形式が正しくありません";
return 0;
}
say "検証成功: $name ($email)";
return 1;
}
# テストしてみる
say "=== テスト1: 正常なデータ ===";
validate_user({ name => '山田太郎', email => 'yamada@example.com' });
say "\n=== テスト2: 名前が空 ===";
validate_user({ name => '', email => 'yamada@example.com' });
say "\n=== テスト3: メールアドレスが空 ===";
validate_user({ name => '山田太郎', email => '' });
say "\n=== テスト4: メールアドレスの形式が不正 ===";
validate_user({ name => '山田太郎', email => 'invalid-email' });
|
実行すると、以下のような結果が表示されます。
1
2
3
4
5
6
7
8
9
10
11
| === テスト1: 正常なデータ ===
検証成功: 山田太郎 (yamada@example.com)
=== テスト2: 名前が空 ===
エラー: 名前を入力してください
=== テスト3: メールアドレスが空 ===
エラー: メールアドレスを入力してください
=== テスト4: メールアドレスの形式が不正 ===
エラー: メールアドレスの形式が正しくありません
|
動きましたね。これがバリデーションの基本形です。
コードのポイント
このコードにはいくつかのポイントがあります。
1. 早期リターン
エラーが見つかった時点で return 0 して関数を終了しています。これを「早期リターン」と呼びます。ネストが深くならず、コードが読みやすくなります。
2. 正規表現によるメール形式チェック
$email !~ /\A[^@\s]+\@[^@\s]+\.[^@\s]+\z/ は、メールアドレスの簡易的な形式チェックです。
\A - 文字列の先頭[^@\s]+ - @とスペース以外の1文字以上\@ - @記号[^@\s]+ - @とスペース以外の1文字以上\. - ドット[^@\s]+ - @とスペース以外の1文字以上\z - 文字列の末尾
これは完璧なメールアドレス検証ではありませんが、多くの場合は十分です。
3. 未定義値のハンドリング
$input->{name} // '' は、キーが存在しない場合やundefの場合に空文字列を使う書き方です。// は「defined-or演算子」と呼ばれます。
すべてのエラーをまとめて返す
先ほどのコードは、最初のエラーで処理を終了していました。しかし実際のアプリケーションでは、すべてのエラーをまとめて表示したいことがあります。
ハッシュを使ってエラーメッセージを収集するように改良してみましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
| # validate_user_v2.pl
# Perl v5.36+, 外部依存なし
use v5.36;
use utf8;
use warnings;
binmode STDOUT, ':utf8';
sub validate_user ($input) {
my %errors;
# 名前の必須チェック
my $name = $input->{name} // '';
if ($name eq '') {
$errors{name} = '名前を入力してください';
}
# メールアドレスの必須チェック
my $email = $input->{email} // '';
if ($email eq '') {
$errors{email} = 'メールアドレスを入力してください';
}
# メールアドレスの形式チェック(必須チェックを通過した場合のみ)
elsif ($email !~ /\A[^@\s]+\@[^@\s]+\.[^@\s]+\z/) {
$errors{email} = 'メールアドレスの形式が正しくありません';
}
# エラーがあるかどうかを判定
if (%errors) {
say "検証失敗:";
for my $field (sort keys %errors) {
say " - $field: $errors{$field}";
}
return (0, \%errors);
}
say "検証成功: $name ($email)";
return (1, undef);
}
# テストしてみる
say "=== テスト1: 正常なデータ ===";
validate_user({ name => '山田太郎', email => 'yamada@example.com' });
say "\n=== テスト2: 名前とメールアドレスが両方空 ===";
validate_user({ name => '', email => '' });
say "\n=== テスト3: メールアドレスの形式が不正 ===";
validate_user({ name => '山田太郎', email => 'invalid-email' });
say "\n=== テスト4: すべてのフィールドが未定義 ===";
validate_user({});
|
実行すると、複数のエラーがまとめて表示されます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| === テスト1: 正常なデータ ===
検証成功: 山田太郎 (yamada@example.com)
=== テスト2: 名前とメールアドレスが両方空 ===
検証失敗:
- email: メールアドレスを入力してください
- name: 名前を入力してください
=== テスト3: メールアドレスの形式が不正 ===
検証失敗:
- email: メールアドレスの形式が正しくありません
=== テスト4: すべてのフィールドが未定義 ===
検証失敗:
- email: メールアドレスを入力してください
- name: 名前を入力してください
|
フィールド名をキーにしたハッシュでエラーを管理することで、どのフィールドにどんなエラーがあるかを呼び出し側で把握できるようになりました。
まとめ
- バリデーションとは、ユーザー入力が正しいかチェックする処理である
- 必須チェックと形式チェックが基本的な検証項目である
- 早期リターンでネストを浅く保つとコードが読みやすくなる
- ハッシュでエラーを収集すると、すべてのエラーをまとめて返せる
次回予告
今回はシンプルなif文でバリデーションを実装しました。しかし、実際のアプリケーションではパスワードの強度チェック、確認パスワードの一致、利用規約への同意など、検証ルールがどんどん増えていきます。
次回は、検証ルールが増えてコードが複雑化する問題を体験します。if/elseのネストが深くなって保守が困難になる「あるある」な状況を見てみましょう。
完成コード
この回の完成コードを1つのスクリプトにまとめました。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
| #!/usr/bin/env perl
# form-validation-01.pl
# ユーザー登録バリデーション(基本版)
# Perl v5.36+, 外部依存なし
use v5.36;
use utf8;
use warnings;
binmode STDOUT, ':utf8';
sub validate_user ($input) {
my %errors;
# 名前の必須チェック
my $name = $input->{name} // '';
if ($name eq '') {
$errors{name} = '名前を入力してください';
}
# メールアドレスの必須チェック
my $email = $input->{email} // '';
if ($email eq '') {
$errors{email} = 'メールアドレスを入力してください';
}
# メールアドレスの形式チェック(必須チェックを通過した場合のみ)
elsif ($email !~ /\A[^@\s]+\@[^@\s]+\.[^@\s]+\z/) {
$errors{email} = 'メールアドレスの形式が正しくありません';
}
# 結果を返す
if (%errors) {
return { ok => 0, errors => \%errors };
}
return { ok => 1, data => { name => $name, email => $email } };
}
# === 実行例 ===
my @test_cases = (
{ name => '山田太郎', email => 'yamada@example.com' },
{ name => '', email => '' },
{ name => '山田太郎', email => 'invalid-email' },
{},
);
for my $i (0 .. $#test_cases) {
say "=== テスト" . ($i + 1) . " ===";
my $result = validate_user($test_cases[$i]);
if ($result->{ok}) {
say "検証成功: $result->{data}{name} ($result->{data}{email})";
}
else {
say "検証失敗:";
for my $field (sort keys $result->{errors}->%*) {
say " - $field: $result->{errors}{$field}";
}
}
say "";
}
|