Laravel 6 を使って、問い合わせフォームのサンプルを作ってみます。仕様は以下の通り。
- 画面は入力画面、確認画面、完了画面の3つ。
- 入力欄は名前(最大20文字)、Eメール(RFC準拠)、メッセージ(最大1024文字)の3つ。いずれも必須。
- 送信された問い合わせデータは、Slackの指定したチャンネルに投稿する。
- Bot対策にreCAPTCHA v3を使用する。
長くなるので、3回に分割して解説します。
初回はフォームの基本的な部分を実装します。
自分の環境
- Ubuntu 18.04
- PHP 7.4
- Laravel 6
作る
フォームリクエスト
コントローラをすっきりさせるために、フォームリクエストを使います。雛型をartisanコマンドで生成します。
$ php artisan make:request InquiryRequest
app/Http/Requests/InquiryRequest.php
が生成されたと思います。実装していきます。
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class InquiryRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => ['required', 'max:20'],
'email' => ['required', 'email:rfc'],
'message' => ['required', 'max:1024'],
];
}
/**
* Get custom messages for validator errors.
*
* @return array
*/
public function messages()
{
return [
'required' => ':attributeは必須です。',
'max' => ':attributeは最大:max文字で入力してください。',
'rfc' => '正しいメールアドレスを入力してください。',
];
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes()
{
return [
'name' => 'お名前',
'email' => 'メールアドレス',
'message' => 'メッセージ',
];
}
}
name
, email
, message
に対する検証を行うフォームリクエストを実装しました。後でreCaptchaのトークンの検証の際、再登場します。
なお簡略化のため messages()
メソッドでカスタムエラーメッセージを指定していますが、今回のように属性名を指定しない場合、言語ファイルで指定する方が良いと思います。
コントローラ
コントローラの雛型もartisanコマンドで生成します。
$ php artisan make:controller InquiryController
app/Http/Controllers/InquiryController.php
が生成できたら、実装していきます。
<?php
namespace App\Http\Controllers;
use App\Http\Requests\InquiryRequest;
use Illuminate\Http\Request;
class InquiryController extends Controller
{
private const FORM_DATA_KEY = 'inquiry.form';
public function show()
{
return view('inquiry.index');
}
public function confirm(InquiryRequest $request)
{
$form_data = $request->validated();
$request->session()->put(self::FORM_DATA_KEY, $form_data);
return view('inquiry.confirm', $form_data);
}
public function finish(Request $request)
{
if (!$request->session()->has(self::FORM_DATA_KEY)) {
return redirect()->route('inquiry');
}
$form_data = $request->session()->pull(self::FORM_DATA_KEY);
// ここでSlack送信処理
return view('inquiry.finish');
}
}
今回は3つのアクションメソッドを実装しています。
- show() … 入力画面を表示する。
- confirm() … 送信された入力を検証し、OKならフォームデータをセッションに保存し、確認画面を表示する。NGなら入力画面へ戻す。
- finish() … セッションに保存されたデータをSlackへ送信する(予定)。その後、完了画面を表示する。
ルーティング
routes/web.php
に以下を追加します。
Route::get('/inquiry', 'InquiryController@show')->name('inquiry');
Route::post('/inquiry/confirm', 'InquiryController@confirm');
Route::post('/inquiry/finish', 'InquiryController@finish');
ビュー
resource/views
下に4つのビューファイルを用意します。
layouts/
app.blade.php
… 共通用
inquiry/
index.blade.php
… 入力画面用confirm.blade.php
… 確認画面用finish.blade.php
… 完了画面用
layouts/app.blade.php
他のビューのベースとなるビューです。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="{{ asset('/css/inquiry.css') }}" rel="stylesheet">
<title>Simple Form - @yield('title')</title>
@section('script')
@show
</head>
<body>
<div class="container">
@yield('content')
</div>
</body>
</html>
inquiry/index.blade.php
入力画面です。後でreCAPTCHAの処理が入るのでjsでsubmitしています。
@extends('layouts.app')
@section('title', 'お問合わせ')
@section('script')
<script>
function onClick(e) {
document.getElementById("contactform").submit();
}
</script>
@endsection
@section('content')
@error('token')
<div class="alert alert-danger">{{ $message }}</div>
@enderror
<form method="POST" id="contactform" action="/inquiry/confirm">
@csrf
<table class="table-form">
<tr>
<th>お名前</th>
<td>
<input type="text" name="name" value="{{ old('name') }}"/>
@error('name')
<div class="alert alert-danger">{{ $message }}</div>
@enderror
</td>
</tr>
<tr>
<th>メールアドレス</th>
<td>
<input type="email" name="email" value="{{ old('email') }}"/>
@error('email')
<div class="alert alert-danger">{{ $message }}</div>
@enderror
</td>
</tr>
<tr>
<th>メッセージ</th>
<td>
<textarea name="message">{{ old('message') }}</textarea>
@error('message')
<div class="alert alert-danger">{{ $message }}</div>
@enderror
</td>
</tr>
</table>
<div class="button-area">
<button type="button" class="go" onclick="onClick(event)">確認する</button>
</div>
</form>
@endsection
inquiry/confirm.blade.php
確認画面です。簡素にするためフォーム送信時の制御はしてないです。
@extends('layouts.app')
@section('title', 'お問合わせ確認')
@section('content')
<form method="POST" action="/inquiry/finish">
@csrf
<table class="table-form">
<tr>
<th>お名前</th>
<td>{{ $name }}</td>
</tr>
<tr>
<th>メールアドレス</th>
<td>{{ $email }}</td>
</tr>
<tr>
<th>メッセージ</th>
<td>{{ $message }}</td>
</tr>
</table>
<div class="button-area">
<button type="button" class="back" onclick="javascript:window.history.back(-1);return false;">戻る</button>
<button type="submit" class="go">送信する</button>
</div>
</form>
@endsection
inquiry/finish.blade.php
すごくシンプルな完了画面です。
@extends('layouts.app')
@section('title', 'お問合わせ送信完了')
@section('content')
<p>お問合わせは正常に送信されました。<br />ありがとうございました。</p>
@endsection
CSSの準備
動きを試すだけであればなくても大丈夫ですが、さすがに寂しいです。それっぽいものを public/css/inquiry.css
に用意してください。以下は参考です。
*, *:before, *:after {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-o-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
html, body{
margin: 0;
padding: 0;
width:100%;
}
html {
font-size: 18px;
}
.container {
max-width: 960px;
min-width: 360px;
margin-right: auto;
margin-left: auto;
}
.alert-danger {
color: red;
}
.table-form {
width: 100%;
border-collapse: collapse;
}
.table-form tr {
border-top: 1px #f0f0f0 solid;
}
.table-form tr:first-child {
border-top: none;
}
.table-form th, .table-form td {
padding: 1rem 0.5rem;
}
.table-form th {
width: 25%;
text-align: right;
}
.table-form td {
width: 75%;
}
.table-form input, .table-form textarea {
border: 1px #69adce solid;
padding: 0.5rem 0.3rem;
border-radius: 4px;
width: 100%;
}
.table-form textarea {
resize: none;
height: 10rem;
}
.table-form input:focus, .table-form textarea:focus {
background: #EEFFFF;
box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.5);
}
.button-area {
width: 100%;
text-align: center;
}
.button-area button {
display: inline-block;
margin: 0.5rem 1rem;
border: none;
border-radius: 4px;
font-weight: bold;
font-size: 1.2rem;
box-shadow: 0 0 1px 0 rgba(0, 0, 0, 0.5);
height: 3rem;
width: 10rem;
}
.button-area button.go {
background: #4C9ED9;
color: #fff;
}
.button-area button.back {
background: #bfbfbf;
color: #fff;
}
@media (max-width: 959px) {
.table-form th, .table-form td {
display: block;
width: 100%;
text-align: left;
}
.table-form th {
padding-bottom: 0.2rem;
}
.table-form td {
padding-top: 0.2rem;
}
}
動かしてみる
ここまで来たら、触れるものはできているはずです。ブラウザで自分のホスト名/inquiry
へアクセスしてみてください。入力画面が表示されます。

バリデーションも問題なく動きます。

適切に入力していれば、こちらの確認画面が表示されます。

確認画面で「送信する」を押すと完了画面へ遷移します。現状だと何もやってないですが、本来はこのタイミングで問い合わせ内容がSlackへ送信されます。

ということで、フォームの基本部分ができました。次回は入力された内容をSlackへ送信する部分の処理を実装します。