【Laravel】お問い合わせフォームを作成する|簡単な掲示板アプリの作成
LaravelでWebアプリの必須機能であるお問い合わせフォームを作成します。入力・確認・完了画面の作り方から、入力値のバリデーション、そしてMailableクラスを使ったメール送信機能までを解説。管理者とユーザーへ自動返信メールを送る方法も学べます。
開発環境
- OS: Windows10
- Visual Studio Code: 1.73.0
- PHP: 8.3.11
- Laravel: 11.29.0
- laravel/breeze: 2.2
サンプルコード
/app/Http/Controllers/ContactController.php
/app/Http/Controllers/ContactController.php1+<?php 2+ 3+namespace App\Http\Controllers; 4+ 5+use Illuminate\Http\Request; 6+use Illuminate\Support\Facades\Mail; 7+use App\Mail\ContactMail; 8+ 9+class ContactController extends Controller 10+{ 11+ /** 12+ * お問い合わせフォームの表示 13+ */ 14+ public function index() 15+ { 16+ return view('contact.index'); 17+ } 18+ 19+ /** 20+ * 確認画面の表示 21+ */ 22+ public function confirm(Request $request) 23+ { 24+ $request->validate([ 25+ 'name' => 'required|string|max:255', 26+ 'email' => 'required|email|max:255', 27+ 'message' => 'required|string|max:1000', 28+ ]); 29+ 30+ // 確認画面を表示 31+ $contactData = $request->all(); 32+ return view('contact.confirm', compact('contactData')); 33+ } 34+ 35+ /** 36+ * お問い合わせ内容の送信処理 37+ */ 38+ public function send(Request $request) 39+ { 40+ $request->validate([ 41+ 'name' => 'required|string|max:255', 42+ 'email' => 'required|email|max:255', 43+ 'message' => 'required|string|max:1000', 44+ ]); 45+ 46+ $contactData = $request->only('name', 'email', 'message'); 47+ 48+ // メールの送信(運営者) 49+ Mail::to(config('mail.from.address'))->send(new ContactMail($contactData, 'admin')); 50+ 51+ // メールの送信(ユーザー) 52+ Mail::to($request->email)->send(new ContactMail($contactData, 'user')); 53+ 54+ return redirect()->route('contact.complete'); 55+ } 56+ 57+ public function complete() 58+ { 59+ return view('contact.complete'); 60+ } 61+} 62
/app/Mail/ContactMail.php
/app/Mail/ContactMail.php1+<?php 2+ 3+namespace App\Mail; 4+ 5+use Illuminate\Bus\Queueable; 6+use Illuminate\Contracts\Queue\ShouldQueue; 7+use Illuminate\Mail\Mailable; 8+use Illuminate\Mail\Mailables\Content; 9+use Illuminate\Mail\Mailables\Envelope; 10+use Illuminate\Queue\SerializesModels; 11+ 12+class ContactMail extends Mailable 13+{ 14+ use Queueable, SerializesModels; 15+ 16+ public $contact; 17+ public $role; 18+ 19+ /** 20+ * Create a new message instance. 21+ */ 22+ public function __construct($contact, $role) 23+ { 24+ $this->contact = $contact; 25+ $this->role = $role; 26+ } 27+ 28+ /** 29+ * Get the message envelope. 30+ */ 31+ public function envelope(): Envelope 32+ { 33+ return new Envelope( 34+ subject: $this->role === 'admin' ? '新しいお問い合わせがありました' : 'お問い合わせありがとうございます', 35+ ); 36+ } 37+ 38+ /** 39+ * Get the message content definition. 40+ */ 41+ public function content(): Content 42+ { 43+ return new Content( 44+ view: 'emails.contact', // 実際のビューのパスに修正 45+ with: [ 46+ 'contact' => $this->contact, // 送信内容をビューに渡す 47+ 'role' => $this->role 48+ ], 49+ ); 50+ } 51+ 52+ /** 53+ * Get the attachments for the message. 54+ * 55+ * @return array<int, \Illuminate\Mail\Mailables\Attachment> 56+ */ 57+ public function attachments(): array 58+ { 59+ return []; 60+ } 61+} 62
/resources/views/contact/complete.blade.php
/resources/views/contact/complete.blade.php1+@extends('layout') 2+ 3+@section('content') 4+ <h1>お問い合わせが完了しました</h1> 5+ <p>メールが正常に送信されました。ご連絡ありがとうございます。</p> 6+ <p>今後ともよろしくお願いいたします。</p> 7+ <a href="{{ route('posts.index') }}">掲示板一覧に戻る</a> 8+@endsection 9
/resources/views/contact/confirm.blade.php
/resources/views/contact/confirm.blade.php1+@extends('layout') 2+ 3+@section('content') 4+ <h2>お問い合わせ確認</h2> 5+ 6+ <form action="{{ route('contact.send') }}" method="POST"> 7+ @csrf 8+ <input type="hidden" name="name" value="{{ $contactData['name'] }}"> 9+ <input type="hidden" name="email" value="{{ $contactData['email'] }}"> 10+ <input type="hidden" name="message" value="{{ $contactData['message'] }}"> 11+ 12+ <div class="mb-3"> 13+ <label class="form-label">お名前</label> 14+ <p>{{ $contactData['name'] }}</p> 15+ </div> 16+ <div class="mb-3"> 17+ <label class="form-label">メールアドレス</label> 18+ <p>{{ $contactData['email'] }}</p> 19+ </div> 20+ <div class="mb-3"> 21+ <label class="form-label">メッセージ</label> 22+ <p>{{ nl2br(e($contactData['message'])) }}</p> 23+ </div> 24+ 25+ <button type="button" onclick="history.back()" class="btn btn-secondary">修正</button> 26+ <button type="submit" class="btn btn-primary">送信</button> 27+ </form> 28+@endsection 29
/resources/views/contact/index.blade.php
/resources/views/contact/index.blade.php1+@extends('layout') 2+ 3+@section('content') 4+ <h2>お問い合わせ</h2> 5+ 6+ <form action="{{ route('contact.confirm') }}" method="POST"> 7+ @csrf 8+ <div class="mb-3"> 9+ <label for="name" class="form-label">お名前</label> 10+ <input type="text" name="name" id="name" class="form-control" value="{{ old('name') }}" required> 11+ </div> 12+ <div class="mb-3"> 13+ <label for="email" class="form-label">メールアドレス</label> 14+ <input type="email" name="email" id="email" class="form-control" value="{{ old('email') }}" required> 15+ </div> 16+ <div class="mb-3"> 17+ <label for="message" class="form-label">メッセージ</label> 18+ <textarea name="message" id="message" class="form-control" rows="5" required>{{ old('message') }}</textarea> 19+ </div> 20+ <button type="submit" class="btn btn-primary">確認</button> 21+ </form> 22+@endsection 23
/resources/views/emails/contact.blade.php
/resources/views/emails/contact.blade.php1+<!DOCTYPE html> 2+<html lang="ja"> 3+<head> 4+ <meta charset="UTF-8"> 5+ <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6+ <title>お問い合わせ</title> 7+</head> 8+<body> 9+ <h1>{{ $role === 'admin' ? '新しいお問い合わせ' : 'お問い合わせ内容' }}</h1> 10+ 11+ <p><strong>お名前:</strong> {{ $contact['name'] }}</p> 12+ <p><strong>メールアドレス:</strong> {{ $contact['email'] }}</p> 13+ <p><strong>メッセージ:</strong></p> 14+ <p>{{ $contact['message'] }}</p> 15+</body> 16+</html> 17
/resources/views/layout.blade.php
/resources/views/layout.blade.php1 <title>掲示板アプリ</title> 2 <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"> 3 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"> 4- {{-- <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script> 5- <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.0.0/dist/umd/popper.min.js"></script> 6- <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script> --}} 7 </head> 8 <body> 9 <nav class="navbar navbar-expand-lg bg-primary"> 10 <a class="nav-link text-white" href="{{ route('register') }}">新規登録</a> 11 </li> 12 @endif 13+ <li class="nav-item"> 14+ <a class="nav-link text-white" href="{{ route('contact.index') }}">お問い合わせ</a> 15+ </li> 16 </ul> 17 </div> 18 </div> 19
/routes/web.php
/routes/web.php1 use App\Http\Controllers\PostController; 2 use App\Http\Controllers\CommentController; 3 use App\Http\Controllers\LikeController; 4+use App\Http\Controllers\ContactController; 5 use Illuminate\Support\Facades\Route; 6 7 Route::get('/', function () { 8 // いいねを投稿するルーティング 9 Route::post('/posts/{post}/like', [LikeController::class, 'toggleLike'])->name('posts.like')->middleware('auth'); 10 11+// お問い合わせフォーム 12+Route::get('/contact', [ContactController::class, 'index'])->name('contact.index'); 13+Route::post('/contact/confirm', [ContactController::class, 'confirm'])->name('contact.confirm'); 14+Route::post('/contact/send', [ContactController::class, 'send'])->name('contact.send'); 15+Route::get('/contact/complete', [ContactController::class, 'complete'])->name('contact.complete'); 16+ 17 require __DIR__.'/auth.php'; 18
コード解説
変更点: お問い合わせフォーム用のルーティングを追加
/routes/web.php1+use App\Http\Controllers\ContactController; 2... 3+// お問い合わせフォーム 4+Route::get('/contact', [ContactController::class, 'index'])->name('contact.index'); 5+Route::post('/contact/confirm', [ContactController::class, 'confirm'])->name('contact.confirm'); 6+Route::post('/contact/send', [ContactController::class, 'send'])->name('contact.send'); 7+Route::get('/contact/complete', [ContactController::class, 'complete'])->name('contact.complete');
ここでは、お問い合わせフォーム機能に必要な4つのURL(ルート)を定義しています。routes/web.phpファイルは、ブラウザからのアクセスと、それに対応する処理(コントローラーのメソッド)を結びつける役割を持ちます。
/contact(GET): お問い合わせ入力画面を表示します。/contact/confirm(POST): 入力されたデータを検証し、確認画面を表示します。/contact/send(POST): 確認されたデータをメールで送信します。/contact/complete(GET): 送信完了画面を表示します。
name()メソッドは各ルートに名前を付けており、これによりビューファイルなどでURLを直接書く代わりに route('contact.index') のように呼び出せるため、後からURLを変更する際に便利です。
変更点: ナビゲーションバーにお問い合わせページへのリンクを追加
/resources/views/layout.blade.php1+ <li class="nav-item"> 2+ <a class="nav-link text-white" href="{{ route('contact.index') }}">お問い合わせ</a> 3+ </li>
全てのページで共通して使われるレイアウトファイル layout.blade.php のナビゲーションバーに、お問い合わせフォームへのリンクを追加しました。href属性には、先ほどroutes/web.phpで定義した名前付きルート contact.index を route() ヘルパー関数を使って指定しています。これにより、どのページからでもお問い合わせページへ移動できるようになります。
変更点: お問い合わせフォームの処理を担うコントローラーを作成
/app/Http/Controllers/ContactController.php1+<?php 2+ 3+namespace App\Http\Controllers; 4+ 5+use Illuminate\Http\Request; 6+use Illuminate\Support\Facades\Mail; 7+use App\Mail\ContactMail; 8+ 9+class ContactController extends Controller 10+{ 11+ // ...メソッドの定義... 12+}
お問い合わせフォームに関する一連の処理をまとめるための ContactController を新しく作成しました。ファイルの先頭では、use文を使って、これから使用するクラス(Request, Mail, ContactMail)を読み込んでいます。これにより、クラス名を省略して記述できるようになります。
変更点: お問い合わせ入力フォームを表示する
/app/Http/Controllers/ContactController.php1+ /** 2+ * お問い合わせフォームの表示 3+ */ 4+ public function index() 5+ { 6+ return view('contact.index'); 7+ }
ContactController 内に index メソッドを追加しました。このメソッドは、GET /contact というURLへのアクセスに対応します。処理内容は非常にシンプルで、view() ヘルパー関数を使って resources/views/contact/index.blade.php というビューファイルを表示するよう指示しています。これが、ユーザーが最初に見るお問い合わせの入力画面となります。
変更点: お問い合わせ内容の入力フォームを作成
/resources/views/contact/index.blade.php1+@extends('layout') 2+ 3+@section('content') 4+ <h2>お問い合わせ</h2> 5+ 6+ <form action="{{ route('contact.confirm') }}" method="POST"> 7+ @csrf 8+ <div class="mb-3"> 9+ <label for="name" class="form-label">お名前</label> 10+ <input type="text" name="name" id="name" class="form-control" value="{{ old('name') }}" required> 11+ </div> 12+ <div class="mb-3"> 13+ <label for="email" class="form-label">メールアドレス</label> 14+ <input type="email" name="email" id="email" class="form-control" value="{{ old('email') }}" required> 15+ </div> 16+ <div class="mb-3"> 17+ <label for="message" class="form-label">メッセージ</label> 18+ <textarea name="message" id="message" class="form-control" rows="5" required>{{ old('message') }}</textarea> 19+ </div> 20+ <button type="submit" class="btn btn-primary">確認</button> 21+ </form> 22+@endsection
お問い合わせ内容を入力するためのHTMLフォーム contact/index.blade.php を作成しました。
<form>タグのaction属性には、データの送信先としてcontact.confirmルートを指定しています。method="POST"は、データを送信する方法を指定するものです。@csrfは、Laravelが提供するセキュリティ機能(CSRF対策)です。フォームを安全に送信するために必須の記述です。- 各入力項目には
name属性(name,email,message)が設定されており、これがコントローラーでデータを受け取る際のキーになります。 value="{{ old('name') }}"は、バリデーションエラーなどで入力画面に戻された際に、直前に入力した値をフォームに再表示するための記述です。
変更点: 入力内容を検証し、確認画面へ渡す
/app/Http/Controllers/ContactController.php1+ /** 2+ * 確認画面の表示 3+ */ 4+ public function confirm(Request $request) 5+ { 6+ $request->validate([ 7+ 'name' => 'required|string|max:255', 8+ 'email' => 'required|email|max:255', 9+ 'message' => 'required|string|max:1000', 10+ ]); 11+ 12+ // 確認画面を表示 13+ $contactData = $request->all(); 14+ return view('contact.confirm', compact('contactData')); 15+ }
ContactController に confirm メソッドを追加しました。入力フォームからPOSTされたデータは、引数の Request $request で受け取ります。
$request->validate()は、入力値の検証(バリデーション)を行うメソッドです。各項目に対してルール(例:requiredは必須、emailはメール形式)を指定し、ルールに違反した場合は自動的に入力画面にエラーメッセージ付きで戻されます。- バリデーションを通過した場合、
$request->all()で全ての入力データを配列として取得します。 - 最後に
view('contact.confirm', compact('contactData'))で、取得したデータをcontactDataという変数名で確認画面のビュー (contact.confirm) に渡して表示します。
変更点: 入力内容の確認画面を作成
/resources/views/contact/confirm.blade.php1+@extends('layout') 2+ 3+@section('content') 4+ <h2>お問い合わせ確認</h2> 5+ 6+ <form action="{{ route('contact.send') }}" method="POST"> 7+ @csrf 8+ <input type="hidden" name="name" value="{{ $contactData['name'] }}"> 9+ <input type="hidden" name="email" value="{{ $contactData['email'] }}"> 10+ <input type="hidden" name="message" value="{{ $contactData['message'] }}"> 11+ 12+ <div class="mb-3"> 13+ <label class="form-label">お名前</label> 14+ <p>{{ $contactData['name'] }}</p> 15+ </div> 16+ {{-- ...メールアドレスとメッセージの表示... --}} 17+ 18+ <button type="button" onclick="history.back()" class="btn btn-secondary">修正</button> 19+ <button type="submit" class="btn btn-primary">送信</button> 20+ </form> 21+@endsection
入力内容の確認画面 contact/confirm.blade.php を作成しました。
- コントローラーから渡された
$contactDataを使い、入力された名前、メールアドレス、メッセージを表示します。 formの送信先は、メール送信処理を行うcontact.sendルートに設定されています。- 表示されているデータを次の処理に引き継ぐため、
<input type="hidden">を使って、画面には見えない形でフォームにデータを含めています。 - 「修正」ボタンには
onclick="history.back()"というJavaScriptを記述し、ブラウザの「戻る」機能で入力画面に戻れるようにしています。
変更点: 送信するメールの内容を定義するMailableクラスを作成
/app/Mail/ContactMail.php1+<?php 2+ 3+namespace App\Mail; 4+ 5+use Illuminate\Bus\Queueable; 6+use Illuminate\Contracts\Queue\ShouldQueue; 7+use Illuminate\Mail\Mailable; 8+use Illuminate\Mail\Mailables\Content; 9+use Illuminate\Mail\Mailables\Envelope; 10+use Illuminate\Queue\SerializesModels; 11+ 12+class ContactMail extends Mailable 13+{ 14+ use Queueable, SerializesModels; 15+ 16+ public $contact; 17+ public $role; 18+ 19+ public function __construct($contact, $role) 20+ { 21+ $this->contact = $contact; 22+ $this->role = $role; 23+ } 24+ 25+ public function envelope(): Envelope 26+ { 27+ return new Envelope( 28+ subject: $this->role === 'admin' ? '新しいお問い合わせがありました' : 'お問い合わせありがとうございます', 29+ ); 30+ } 31+ 32+ public function content(): Content 33+ { 34+ return new Content( 35+ view: 'emails.contact', 36+ with: [ 37+ 'contact' => $this->contact, 38+ 'role' => $this->role 39+ ], 40+ ); 41+ } 42+}
送信するメールの構造を定義するための ContactMail クラスを新しく作成しました。これはLaravelのMailableクラスを継承しており、メールに関する設定をカプセル化(一つのクラスにまとめること)できます。
__constructメソッド(コンストラクタ)は、このクラスが使われる際に、コントローラーからお問い合わせ内容 ($contact) と送信先の役割 ($role) を受け取ります。envelopeメソッドでは、メールの件名(subject)を設定します。$this->roleが'admin'かどうかで件名を動的に切り替えています。contentメソッドでは、メール本文に使用するビューファイル (emails.contact) と、そのビューに渡すデータ($contactと$role)を指定しています。
変更点: メール本文のテンプレートを作成
/resources/views/emails/contact.blade.php1+<!DOCTYPE html> 2+<html lang="ja"> 3+<body> 4+ <h1>{{ $role === 'admin' ? '新しいお問い合わせ' : 'お問い合わせ内容' }}</h1> 5+ 6+ <p><strong>お名前:</strong> {{ $contact['name'] }}</p> 7+ <p><strong>メールアドレス:</strong> {{ $contact['email'] }}</p> 8+ <p><strong>メッセージ:</strong></p> 9+ <p>{{ $contact['message'] }}</p> 10+</body> 11+</html>
メールの本文となるHTMLファイル emails/contact.blade.php を作成しました。これは ContactMail クラスから呼び出されます。Mailableクラスから渡された変数 $role と $contact を使って、管理者向けとユーザー向けでタイトルを切り替えたり、お問い合わせ内容を表示したりしています。通常のBladeビューと同じ構文が使用できます。
変更点: メールを送信し、完了画面へリダイレクトする
/app/Http/Controllers/ContactController.php1+ /** 2+ * お問い合わせ内容の送信処理 3+ */ 4+ public function send(Request $request) 5+ { 6+ $request->validate([ 7+ 'name' => 'required|string|max:255', 8+ 'email' => 'required|email|max:255', 9+ 'message' => 'required|string|max:1000', 10+ ]); 11+ 12+ $contactData = $request->only('name', 'email', 'message'); 13+ 14+ // メールの送信(運営者) 15+ Mail::to(config('mail.from.address'))->send(new ContactMail($contactData, 'admin')); 16+ 17+ // メールの送信(ユーザー) 18+ Mail::to($request->email)->send(new ContactMail($contactData, 'user')); 19+ 20+ return redirect()->route('contact.complete'); 21+ }
ContactController に send メソッドを追加しました。確認画面から送信されたデータを使って、実際にメールを送信する処理です。
- まず、不正なアクセスを防ぐため、
confirmメソッドと同様に再度バリデーションを実行しています。 Mail::to()->send()を使ってメールを送信します。- 1通目は管理者宛です。送信先は
config('mail.from.address')で設定ファイルから取得し、ContactMailクラスには役割として'admin'を渡しています。 - 2通目はユーザー宛の自動返信メールです。送信先はフォームに入力されたメールアドレス (
$request->email) を指定し、役割として'user'を渡しています。 - 全てのメール送信処理が終わったら、
redirect()->route('contact.complete')で完了画面へリダイレクト(ページ遷移)させます。
変更点: 送信完了画面を表示する
/app/Http/Controllers/ContactController.php1+ public function complete() 2+ { 3+ return view('contact.complete'); 4+ }
ContactController に complete メソッドを追加しました。send メソッドからリダイレクトされた後、このメソッドが呼び出されます。処理内容は index メソッドと同様にシンプルで、resources/views/contact/complete.blade.php ファイルをブラウザに表示するだけです。
変更点: お問い合わせ完了メッセージを表示する画面を作成
/resources/views/contact/complete.blade.php1+@extends('layout') 2+ 3+@section('content') 4+ <h1>お問い合わせが完了しました</h1> 5+ <p>メールが正常に送信されました。ご連絡ありがとうございます。</p> 6+ <p>今後ともよろしくお願いいたします。</p> 7+ <a href="{{ route('posts.index') }}">掲示板一覧に戻る</a> 8+@endsection
お問い合わせが完了したことをユーザーに伝えるための contact/complete.blade.php を作成しました。このページは、完了メッセージとトップページ(掲示板一覧)へ戻るためのリンクを表示する静的な内容となっています。
おわりに
この記事では、お問い合わせフォームの入力からメール送信、完了画面までの一連の流れを実装しました。ContactControllerでバリデーションによって入力値をチェックし、入力・確認・完了と画面を遷移させる方法を学びました。メール送信ではMailableクラスを利用し、管理者とユーザーで件名や本文を切り替えて自動返信する仕組みを作りました。ここで学んだフォーム処理は、様々なWebアプリケーションで応用できる重要な基本機能です。