現在Laravelの教材を鋭意制作中です!完成まで今しばらくお待ちください

#21 バリデーションとRequired

ユーザーから送信されたフォームの内容は、そのまま保存せずにきちんと検証(バリデーション)することが重要です。

これは「必須項目が空だった」「メールアドレスの形式がおかしい」「年齢が数字じゃない」といった入力ミスを防ぎ、アプリ全体の信頼性を保つための基本的な仕組みです。

Laravel では、バリデーションを簡潔かつ柔軟に扱える機能が用意されており、まずは validate() メソッドで手軽にチェックを行い、後半では「FormRequest」クラスを使ってロジックを整理・再利用可能な形に分離していきます。

なぜバリデーションを実装するのか

フォームは、ユーザーが自由に値を入力できる反面、予期せぬ内容や不正なデータも送られてくる可能性があります

そのため、バリデーションの導入は、「ただの装飾」ではなくユーザー体験とデータの健全性を守るための防御壁としての役割を持っています。

バリデーションが必要な理由は以下の通りです。

1.不正なデータ入力を防ぐため

ユーザーが入力する内容は、必ずしも正しいとは限りません。

  • 名前やメールアドレスが未入力なのに送信される
  • メール欄に「aaa@」や「aaa@@bbb」など不完全な文字列を入力
  • 本文に無関係な文字だけ入れる

上記のように、これらをサーバー側でチェックすることで、誤ったデータの保存や表示を防げます

2.ユーザーにとって親切な導線になる

バリデーションは「エラーを出すため」ではなく、「正しい入力へ導くための道案内」です。

  • 送信ボタンを押した場合に「お名前は必須です」と明示されれば入力ミスに気づける
  • 年齢欄に「数字を入れてください」と出れば形式が分かる

つまり、ユーザーに対する親切なエラー表示はフォームの信頼度アップにも繋がります。

3.サーバーの安全性を保つ

バリデーションなしで受け取ったリクエストには、場合によってはSQLインジェクションやスクリプト埋め込みのリスクも含まれます。

補足

SQLインジェクションとは「外部から意図的にSQL文を改ざんして、データベースに不正な操作をさせる攻撃手法」です。

Laravelのバリデーションはこれらをルールによって排除してくれる防御手段です。

4.データベースを汚さないため

ユーザーの入力ミスがそのまま保存されてしまうと、DBに無意味な行が増えて管理も難しくなります。

バリデーションを掛けることによって「保存されるのは適切なデータだけ」にすることで、後の管理・表示処理が楽になります

バリデーションとrequired

バリデーションにはいくつかの種類がありますが、最も基本的かつ重要なのが「入力必須項目」のチェックです。

これに該当するのが required というルールで、required 属性は、ブラウザが送信前に入力欄の未記入をチェックしてくれる機能です。

実はこの required は、以下の2つの側面に存在します。

1. クライアントサイド(HTML)の required

HTML フォームの <input><textarea> タグには、required 属性を付けることで、空欄での送信を防止できます。

<input type="text" name="name" required>

このように設定すると、ブラウザが自動でチェックし、入力がないまま送信しようとすると警告を表示します。

これはユーザー体験向上のための「手前チェック」であり、UXに非常に効果的です。

2. サーバーサイド(Laravel)の required

クライアントだけでなく、サーバーでも同様に required を使ってデータの信頼性を担保します。

Laravel では以下のように記述します。

$request->validate([
  'name' => 'required',
  'email' => 'required|email',
]);

これにより、ブラウザのチェックをすり抜けて来た未入力データをサーバー側で確実に排除できます。

以下は2つの required の比較するための表になります。

比較項目 HTML(required) Laravel(バリデーション)
実行される場所 ブラウザ サーバー
制御できる範囲 ユーザーの入力有無 フォーマット・形式・値なども可能
拡張性 基本的な未入力チェックのみ 複雑な条件でも柔軟に定義可能
セキュリティ 無効化できる(HTML改変) サーバー側なので強固

つまり、required はあくまで「フロントでの親切なガイド」であり、必ず Laravel 側でのバリデーションとセットで使うのが安全策です。

Laravel によるサーバーサイドバリデーションが「最終防衛ライン」という認識を持っているとわかりやすいのではないでしょうか。

フロントで優しく導き、サーバーで厳しく守る——それが理想的なフォーム設計であり、両方使うことこそが「ベストプラクティス」になり得ます。

1. pullとブランチの作成

まずは、毎回恒例のやつから。作業に入る前に前回mergeしたメインブランチを更新しておきましょう。

# ブランチをpullする
git pull origin main

以上で完了です。mainブランチが前章で作成されたブランチと同じになれば、次に進みます。

git switch -C make-validate

今回はバリデーションを作成するので、安直ですがmake-validateと言う名前のブランチを作成しました。ブランチ名は任意となりますので、お好きなブランチ名を付けて構いません。

2.バリデーションの実装

まずはバリデーションをコントローラー内に実装してみましょう。

contactController.php

public function store(Request $request)
{
  $validated = $request->validate([
    'name' => 'required|string|max:255',
    'email' => 'required|email',
    'age' => 'required|integer|between:18,65',
    'gender' => 'required|in:男性,女性,その他',
    'interests' => 'nullable|array',
    'interests.*' => 'string',
    'message' => 'required|string|min:10|max:1000',
  ]);
  // チェックボックスを文字列に変換(必要なら)
  $validated['interests'] = isset($validated['interests'])
    ? implode(',', $validated['interests'])
    : null;

  Contact::create($validated);

  return redirect('/thanks');
}

validate() メソッドは Request オブジェクトに用意されている バリデーション専用メソッドで、引数として渡した配列形式のルール定義に従って、各項目をチェックしてくれます。

成功した場合は、validateされた値だけを $validated に格納して返し、エラーがある場合は、Blade側のフォームに自動的に戻り、バリデーションメッセージが表示されます。

バリデーションルールの意味については以下の通りです。

  • 'name' => 'required|string|max:255' → 必須。文字列で、最大255文字まで
  • 'email' => 'required|email' → 必須。メール形式でなければエラー
  • 'age' => 'required|integer|between:18,65', → 必須。数字で、18以上65以下であること
  • 'gender' => 'required|in:男性,女性,その他' → 必須。「男性/女性/その他」以外はエラー
  • 'interests' => 'nullable|array' → 空でもOK。チェックボックスは配列として受け取る
  • 'interests.*' => 'string' → 配列の中身はすべて文字列であること(安全確認)
  • 'message' => 'required|string|max:1000' → 必須。最大1000文字まで

これらのバリデーションルールは非常によく使用されますので、覚えておくと便利です。

ただ、バリデーションを設定しても、エラーメッセージが表示されなければ意味がありません。

エラーメッセージが表示されるようにフォームを修正しましょう。

3.contact.blade.phpの修正

@extends('layouts.app')

@section('title', 'お問い合わせフォーム')

@section('content')
<h2>お問い合わせフォーム</h2>

<form action="/contact/store" method="POST">
@csrf

<!-- 名前 -->
<div>
  <label for="name">お名前:</label><br>
  <input type="text" id="name" name="name" required>
  @error('name')
    <div style="color: red;">{{ $message }}</div>
  @enderror
</div><br>

<!-- メールアドレス -->
<div>
  <label for="email">メールアドレス:</label><br>
  <input type="email" id="email" name="email" required>
  @error('email')
    <div style="color: red;">{{ $message }}</div>
  @enderror
</div><br>

<!-- 年齢(セレクトボックス) -->
<div>
  <label for="age">年齢:</label><br>
  <select name="age" id="age" required>
    <option value="">選択してください</option>
    @for ($i = 18; $i <= 65; $i++)
      <option value="{{ $i }}">{{ $i }}歳</option>
    @endfor
  </select>
  @error('age')
    <div style="color: red;">{{ $message }}</div>
  @enderror
</div><br>

<!-- 性別(ラジオボタン) -->
<div>
  <label>性別:</label><br>
  <input type="radio" id="gender_male" name="gender" value="男性" required>
  <label for="gender_male">男性</label>
  <input type="radio" id="gender_female" name="gender" value="女性">
  <label for="gender_female">女性</label>
  <input type="radio" id="gender_other" name="gender" value="その他">
  <label for="gender_other">その他</label>
  @error('gender')
    <div style="color: red;">{{ $message }}</div>
  @enderror
</div><br>

<!-- 興味のあるサービス領域(チェックボックス) -->
<div>
  <label>興味のある分野:</label><br>
  <label><input type="checkbox" name="interests[]" value="Webサイト制作"> Webサイト制作</label><br>
  <label><input type="checkbox" name="interests[]" value="システム開発"> システム開発</label><br>
  <label><input type="checkbox" name="interests[]" value="WordPress運用"> WordPress運用</label><br>
  <label><input type="checkbox" name="interests[]" value="Laravel構築"> Laravel構築</label><br>
  <label><input type="checkbox" name="interests[]" value="SEO相談"> SEO相談</label>
  @error('interests')
    <div style="color: red;">{{ $message }}</div>
  @enderror
</div><br>

<!-- 本文 -->
<div>
  <label for="message">お問い合わせ内容:</label><br>
  <textarea name="message" id="message" rows="5" required></textarea>
  @error('message')
    <div style="color: red;">{{ $message }}</div>
  @enderror
</div><br>

<!-- 送信ボタン -->
<button type="submit">内容を確認する</button>
</form>
@endsection

コードの解説

<input type="text" id="name" name="name" required>

inputタグの中に required が付いたことによって、この項目は「必須項目」となりました。

HTMLサイドでは、このようにタグの中に required をつけるだけでOKです。

<select name="age" id="age" required>

/*中略*/

<input type="radio" id="gender_male" name="gender" value="男性" required>

セレクトボックスの場合は <select> タグに、ラジオボタンの場合は最初の項目のみ required をつけるだけで必須となります。

@error('name')
  <div style="color: red;">{{ $message }}</div>
@enderror

このコードはバリデーションエラーを フォームの該当箇所に表示するための構文で、$errorsname フィールドのバリデーションエラーがある場合のみ実行されます。

$message には、そのフィールドに対するエラーメッセージ(FormRequest や validate() )で定義したものが格納されます。

ユーザーがフォームを送信し、name が空などのバリデーションエラーが発生したとしましょう。

Laravelは $errors にメッセージを保存してリダイレクトして、Blade側で @error('name') を使って該当エラーのみ表示されるという手順になります。

ただ、今回のように小さなアプリであれば、コントローラー内にバリデーション処理を直接記述する方法でも有効です。

しかし、例えば、同じバリデーションを複数箇所で使いたい場合は都度コピー&ペーストが必要ですし、本来、このような入力チェックは「入力チェックの専用クラス」で担当すべき処理となっています。

そこで、Laravelが提供する「FormRequest」を活用することで、バリデーションロジックを専用クラスに切り出し、より整理された設計を実現することができます。

次の章にて紹介する FormRequest クラスを使うと、さらに見通し良く、再利用可能な設計になります。

次章:#22 FormRequest による分離設計