Laravel 5.7 で問い合わせフォームを作る

メモ:

Laravel 5.7 について少し勉強できたので、問い合わせフォームを作成してみます。

ここで作成する問い合わせフォームは、入力 -> 確認 -> 送信完了 の3画面で動作し問い合わせ内容をメールで送信する仕様を想定しています。

メール送信の準備

.env ファイルを修正してメール送信のための環境を設定します。

MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=********
MAIL_PASSWORD=********
MAIL_ENCRYPTION=null

ドキュメントには、 config/mail.php ファイルが設定ファイルと書かれていますが config/mail.php の中は .env を見ています。共通の from アドレスを使用したい場合は、 .env ファイルに MAIL_FROM_ADDRESS と MAIL_FROM_NAME を追加します。

MAIL_FROM_ADDRESS=bnote@hoge.net
MAIL_FROM_NAME=BBB

内容に応じて from アドレスを変えたい場合は、プログラム中で指定することができるので用途に応じて設定を行います。

コントローラとフォームリクエストと mailable クラスの作成

Laravel のプロジェクトディレクトリへ移動し、artisan コマンドでコントローラーの雛形とフォームリクエストの雛形、mailable クラスの雛形を作成します。

$ php artisan make:controller ContactController
Controller created successfully.

$ php artisan make:request ContactRequest
Request created successfully.

$ php artisan make:mail Contact
Mail created successfully.

app\Http\Controllers\ ディレクトリに artisan コマンドで指定した ContactController.php が作成され、app\Http\Requests\ ディレクトリに ContactRequest.php が作成され、 app\Mail\ ディレクトリに Contact.php が作成されます。

ルーティングの設定

routes\ ディレクトリにある web.php に次のようにルーティングを設定します。

入力画面の表示、入力内容の確認処理、メッセージの送信処理の3つの処理を用意しています。

Route::get('contact', 'ContactController@index')->name('contact');
Route::post('contact/confirm', 'ContactController@confirm')->name('confirm');
Route::post('contact/sent', 'ContactController@sent')->name('sent');

name メソッドは、ルートに名前を付けることができ設定した名前を使ってリダイレクトできるなど便利な機能になっています。

ベースとなるテンプレートの作成

3つの画面を用意する前に共通部分となるテンプレートを作成します。ここでは、 resources\views\layouts\ ディレクトリに default.blade.php を次のように作成します。

<!DOCTYPE html>
<html lang="{{ app()->getLocale() }}">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <link href="{{ asset('css/app.css') }}" rel="stylesheet" type="text/css">

    <title>
        @section('title')
        @show
        : bnote
    </title>
</head>
<body>
    <div class="container">
        @yield('content')
    </div>
</body>
</html>

入力画面の作成

resources\views\contact\ ディレクトリに default.blade.php(共通部分を定義したテンプレート) を継承した form.blade.php を作成します。

@extends('layouts.default')

@section('title','contact')

@section('content')
<div class="row">
    <h1>お問い合わせ</h1>
</div>
<div class="row">
    <form action="contact/confirm" method="post">
    <div class="form-group">
        <label for="InputEmail">メールアドレス</label>
        <input type="email" name="email" class="form-control" id="InputEmail" value="{{ old('email') }}">
        @if($errors->has('email'))
            <p class="text-danger">{{ $errors->first('email')}}</p>
        @endif
    </div>
    <div class="form-group">
        <label for="InputSubject">件名</label>
        <input type="text" name="subject" class="form-control" id="InputSubject" value="{{ old('subject') }}">
        @if($errors->has('subject'))
            <p class="text-danger">{{ $errors->first('subject')}}</p>
        @endif
    </div>
    <div class="form-group">
        <label for="InputMessage">メッセージ</label>
        <textarea name="message" id="InputMessage" class="form-control" cols="40" rows="4">
        {{ old('message') }}
        </textarea>
        @if($errors->has('message'))
            <p class="text-danger">{{ $errors->first('message')}}</p>
        @endif
    </div>
    @csrf
    <button type="submit" name="action" class="btn btn-primary" value="sent">送信する</button>
    </form>
</div>
@endsection

value 属性には、確認画面から戻ってきた場合に入力した値がクリアされないよう old() 関数を使って直前の入力値をセットするようにしています。

また、バリデーションでエラーが発生した場合 $errors->has() の部分でエラーメッセージを表示するようにしています。

作成したテンプレートが表示されるよう、コントローラに index メソッドを作成します。

class ContactController extends Controller
{
    public function index(){
        return view('contact.form');
    }

確認画面の作成

入力された問い合わせ内容を確認する画面を用意します。

resources\views\contact\ ディレクトリに confirm.blade.php ファイルを作成し次のようなテンプレートを用意します。

@extends('layouts.default')

@section('title','お問い合わせ内容の確認')

@section('content')
<div class="row">
    <h1>お問い合わせ内容の確認</h1>
</div>
<div class="row">
    <p>下記、お問い合わせ内容にて送信します。よろしければ「送信」ボタンを押して下さい。</p>

    <table class="table table-bordered">
    <tr>
    <td class="table-secondary" style="width:20%">メールアドレス</td>
    <td>{{ $email }}</td>
    </tr>
    <tr>
    <td class="table-secondary">件名</td>
    <td>{{ $subject }}</td>
    </tr>
    <tr>
    <td class="table-secondary">メッセージ</td>
    <td>{!! nl2br(e($message)) !!}</td>
    </tr>
    </table>
    <form action="sent" method="post">
        <input type="hidden" name="email" class="form-control" id="InputEmail" value="{{ $email }}">
        <input type="hidden" name="subject" class="form-control" id="InputSubject" value="{{ $subject }}">
        <input type="hidden" name="message" class="form-control" id="InputMessage" value="{{ $message }}">
    @csrf
    <button type="submit" name="action" class="btn btn-primary" value="back">戻る</button>
    <button type="submit" name="action" class="btn btn-primary" value="sent">送信</button>
    </form>
</div>
@endsection

入力された内容は、 Table タグで表示し送信用には隠し属性で form を用意しています。input タグの value には入力された値がセットされるようにしています。

メッセージの部分は、複数行入力できるようになっているため php の nl2br() 関数を使用して改行コードを <br /> タグに変換されるようにしています。php の関数を使用するため 「{!! !!}」(エスケープされない)を使用し、その代わりに e() 関数で $message をエスケープしています。

確認後の修正を考え、「戻る」ボタンを用意しています。どちらのボタンが押されたのか判定できるよう name に action という名前を設定し、value にそれぞれの値を設定しています。コントローラでは、 action の値を確認して処理を決定します。

入力フォームから受け取った値を確認画面へ表示するようコントローラに confirm メソッドを追加します。

class ContactController extends Controller
{
    ・・・
    public function confirm(ContactRequest $request){
      
        $contact = $request->all();

        $request->session()->regenerateToken();

        return view('contact.confirm',$contact);
    }

二重送信防止のためトークンを再生成しています。

送信処理

コントローラに「戻る」ボタンが押された場合と「送信」ボタンが押された場合の処理を作成します。

class ContactController extends Controller
{
    ・・・
    public function sent(ContactRequest $request){
      
        $contact = $request->all();
        if($request->action === 'back') {
            return redirect()->route('contact')->withInput($contact);
        }

        $request->session()->regenerateToken();
        
        Mail::to('hogehoge@gmail.com')->send(new Contact($contact));

        return view('contact.thanks');
    }
}

確認画面のボタンの値がセットされている action を確認し、「back」 が設定されていた場合に入力画面に戻るよう処理を行います。入力画面に戻った時に入力値がクリアされないよう withInput を使用して値を渡すようにしています。

「送信」ボタンが押された場合、メールを送信して送信完了画面を表示するようにしています。

送信完了画面のテンプレートは、 thanks.blade.php というファイルで次のように用意しています。

@extends('layouts.default')

@section('title','お問い合わせ送信完了')

@section('content')
<div class="row">
    <h1>お問い合わせ送信完了</h1>
</div>
<div class="row">
    <p>お問い合わせありがとうございました。</p>
</div>
<div class="row">
    <p>ご入力いただいた内容は正しく送信されました。</p>
</div>
@endsection

バリデーション

入力値を検証するため、用意しておいたフォームリクエストを次のように修正します。

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class ContactRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;  // [falseからtrueへ変更]
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'email' => 'required|email',
            'subject' => 'required|max:255',
            'message' => 'required',
        ];
    }
}

authorize メソッドでは、リクエストされた処理を実行する権限があるか確認するため true を返し処理が可能(認可された)な状態にしています。

rules メソッドに、バリデーションルールを記述します。

この設定により、コントローラの confirm メソッドではバリデーションが実行されるようになります。

メールメッセージの構築

Mailable クラスを使用することでメールに必要な情報を一括で取り扱うことができます。

<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;

class Contact extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * Create a new message instance.
     *
     * @return void
     */

    public $contact;

    public function __construct($contact)
    {
        //
        $this->contact = $contact;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->subject('【問い合わせ】'.$this->contact['subject'])
                    ->view('contact.mail'); 
    }
}

コンストラクタを修正してフォームに入力された値を受け取ります。

次に build メソッドでメールに必要な情報を準備します。ここでは、件名のセットとメール本文に使用するテンプレートを指定しています。

メール本文のテンプレートは、 mail.blade.php ファイルを作成して次のように用意します。

<p>
E-Mail:{{ $contact['email'} }}
</P>
<p>
件名:{{ $contact['subject'} }}
</P>
<p>
メッセージ:{{ $contact['message'} }}
</P>

以上で問い合わせフォームができました。