View more

PHP開発実践!Laravelでお問い合わせフォームを作ろう

Blog

PHP開発実践!Laravelでお問い合わせフォームを作ろう

昨今のお問い合わせフォームはWebサービスを利用するケースやMAツールなどを導入することが多くなりましたが、柔軟な活用を考えた時に自身のサーバーに設置できると便利なこともあります。 今回はPHP製の人気フレームワークのLaravelを使用してお問い合わせフォームをローカルで構築してみたいと思います。 ただし本記事は「ローカルでのLaravelで開発」にフォーカスしており、セキュリティに関してはさまざまな対応が必要になるケースもありますので、管理するWebサイトや機能、運用に合わせた考慮が必要です。 また本番サーバーへの設置に関しては、本番サーバー側でのLaravelのインストールやsmtp設定など、もう一手間必要になると思います。

PHP開発実践!Laravelでお問い合わせフォームを作ろう

PHP開発実践!Laravelでお問い合わせフォームを作ろう

開発準備:Laravelの開発環境を整えよう

今回使用するLaravelは、公式でも紹介されているLaravel Sailという開発環境を簡単に設定できるツールを使用します。

Laravel Sailは、LaravelのデフォルトのDocker開発環境を操作するための軽量コマンドラインインターフェイスです。 SailはDockerを使用しますが、Dockerの経験がそれほどなくても、PHP、MySQL、Redisを使用してLaravelアプリケーションを構築するための優れた出発点を提供します。

また、今回開発するメールフォームからのメール送信のテストも行えるMailpitという開発用メールサーバーも含まれていますので、簡単に開発環境を設定できます。

開発準備:Docker Desktopをインストール

Docker Desktop

https://www.docker.com/products/docker-desktop/

こちらのページからDocker Desktopをダウンロードしてインストールします。

OSに合わせたものをダウンロードしてください。

インストールが完了したらDocker Desktopを立ち上げておきましょう。

開発準備:Laravel Sailをインストール

ドキュメントサイトのInstallationを参考にインストールを行います。

※本記事でのバージョンは10になります。

https://laravel.com/docs/10.x/sail

 

一番簡単な方法はbashなどで下記のコマンドを打ち込む事です。

下記はMac用のコマンドになります。

ソースコードを置きたいフォルダで下記を実行してください。

contactのところがアプリ名になりますので、任意のアプリ名にしてください。

# contactとして作成
curl -s "<https://laravel.build/contact>" | bash
 

 

Laravel アプリdockerコンテナ作成&立ち上げ

インストールが完了したら、下記のコマンドでdockerとしてのコンテナを作成します。

cd contact

./vendor/bin/sail up -d

.bashrcなどにコマンドのエイリアスを設定し、./vendor/bin/sailsail に変更しておくと便利です。

以降Sailコマンドを打つ時にはエイリアス設定をしているコマンドになりますのでご注意ください。

# sailコマンドのエイリアス設定
alias sail='[ -f sail ] && bash sail || bash vendor/bin/sail'

コンテナ作成ができたら、Docker Desktopを見てみてください。

今作成したコンテナが表示されていると思います。

 

 

問題なく立ち上がっていたらブラウザでlocalhostにアクセスしてみてください。

http://localhost/

Laravel開発開始

準備が整いましたので、開発をスタートしましょう!

今回構築するお問い合わせフォームの要件をまとめておきます。

  • Ajaxなどを行わないMPA型のメールフォーム。
  • 確認画面付き。
  • UX的には不要ですが要件としてはよくある「メールアドレスの確認」の入力欄も設ける。
  • 送信されるメール形式はテキストメール。
  • 自動返信メールも送信する。
  • [おまけ] UIはTailwindCSSで簡単な見た目を作成。

環境変数を設定

.envファイルの下記をご自身の環境に合わせて変更しましょう。

APP_NAME=Laravel

MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"

↓

APP_NAME="株式会社デパートお問い合わせフォーム"

MAIL_FROM_ADDRESS="XXXXXXXX@depart-inc.com"
MAIL_FROM_NAME="株式会社デパート"

タイムゾーン・ロケール変更

/config/app.php を開き timezone と locale の設定を日本に変更します。

return [

    中略

    'timezone' => 'Asia/Tokyo',
    'locale'   => 'ja',
    'fallback_locale' => 'ja',
    'faker_locale' => 'ja_JP',

    中略

];

上記はタイムゾーンや通常の言語設定の他に、システムで利用する部分の言語設定もしています。

日本語の言語ファイルが必要になりますので、laravel-langをインストールし、日本語パッケージも追加しましょう。

#   laravel-langをインストール
sail composer require laravel-lang/lang laravel-lang/publisher --dev

日本語パッケージ追加

#  jaを追加
sail artisan lang:add ja

コントローラ作成

フォーム用のコントローラ FormController を作成します。

# controller作成
sail artisan make:controller FormController

app/Http/Controllers/FormController.phpにファイルが作成されていると思うので確認してください。

 

入力、確認、完了ページ用アクション追加

各ページの設定を行います。

ルーティングとViewファイルを用意したくなりますが、一緒にメール送信部分も記述しておきます。

app/Http/Controllers/FormController.php

<?php

namespace App\\Http\\Controllers;

use Illuminate\\Http\\Request;
use Illuminate\\Support\\Facades\\Mail;
use App\\Http\\Requests\\ContactFormRequest;
use App\\Mail\\ContactFormAdminMail;
use App\\Mail\\ContactFormUserMail;

class FormController extends Controller
{
    /**
     * 入力ページ
     */
    public function index()
    {
        return view('contact.index');
    }

    /**
     * 確認ページ
     */
    public function confirm()
    {
        return view('contact.confirm');
    }

    /**
     * 完了ページ
     */
    public function complete()
    {
        return view('contact.complete');
    }

    /**
     * メール送信
     */
    public function sendMail(ContactFormRequest $request)
    {
        //
        $form_data = $request->validated();

        // submitボタンの値により分岐させる
        $submitBtnVal = $request->input('submitBtnVal');
        switch ($submitBtnVal) {
            case 'confirm':
                // 確認画面へ
                return to_route('contact.confirm')->withInput();
                break;
            case 'back':
                // 入力画面へ戻る
                return to_route('contact')->withInput();
                break;
            case 'complete':
                // 送信先メールアドレス
                $email_admin = env('MAIL_FROM_ADDRESS');
                $email_user  = $form_data['email'];

                // 管理者宛メール
                Mail::to($email_admin)->send(new ContactFormAdminMail($form_data));
                // ユーザー宛メール
                Mail::to($email_user)->send(new ContactFormUserMail($form_data));

                return to_route('contact.complete');
                break;
            default:
                // エラー
        }
    }
}

use宣言で各種クラスを追加

ルーティング設定

続いてルーティングの設定を行います。

お問い合わせ用にファイルを分けた方が後々わかりやすくなると思いますが、

今回はroutes/web.phpに直接記述していまいます。

ついでにデフォルトのwelcomeページもindexに変更してしまいましょう。

routes/web.php

<?php

use Illuminate\\Support\\Facades\\Route;
use App\\Http\\Controllers\\FormController;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "web" middleware group. Make something great!
|
*/

Route::get('/', function () {
    return view('index');
});

// お問合せフォーム
Route::get('/contact', [FormController::class, 'index'])->name('contact');
Route::post('/contact/confirm', [FormController::class, 'sendMail']);
Route::get('/contact/confirm', [FormController::class, 'confirm'])->name('contact.confirm');
Route::get('/contact/complete', [FormController::class, 'complete'])->name('contact.complete');

use宣言でFormControllerを追加

Bladeテンプレート

それぞれの画面を用意していきましょう。

HTML部分はサンプル的に簡素にしています。

 

Layout

resources/views/layouts/default.blade.php

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@yield('title', 'フォームAPP')</title>
</head>

<body>
    <header>{{-- // header --}}</header>

    <main>@yield('content')</main>

    <footer>{{-- // footer  --}}</footer>
</body>

</html>

index(Home)

resources/views/index.blade.php

@extends('layouts.default')
@section('title', 'Home')

<section>

    <h1>Home</h1>

    <div>
        <a href="/contact">お問い合わせ</a>
    </div>

</section>

お問い合わせ - 入力

resources/views/contact/index.blade.php

@extends('layouts.default')
@section('title', 'お問い合わせ')

@section('content')

<section>

    @if($errors->any())
    <div>
        <ul>
            @foreach($errors->all() as $error)
            <li>{{ $error }}</li>
            @endforeach
        </ul>
    </div>
    @endif

    <form action="{{ route('contact.confirm') }}" method="POST">
        @csrf

        <div>
            <label for="company">会社名<span>必須</span></label>
            <input id="company" type="text" name="company" value="{{ old('company') }}">
            @if($errors->has('company'))
            <p>{{ $errors->first('company') }}</p>
            @endif
        </div>

        <div>
            <label for="name">お名前<span>必須</span></label>
            <input id="name" type="text" name="name" value="{{ old('name') }}">
            @if($errors->has('name'))
            <p>{{ $errors->first('name') }}</p>
            @endif
        </div>

        <div>
            <label for="name_kana">フリガナ<span>必須</span></label>
            <input id="name_kana" type="text" name="name_kana" value="{{ old('name_kana') }}">
            @error('name_kana')
            <p>{{ $message }}</p>
            @enderror
        </div>

        <div>
            <label for="phone">電話番号</label>
            <input id="phone" type="text" name="phone" value="{{ old('phone') }}">
            @error('phone')
            <p>{{ $message }}</p>
            @enderror
        </div>

        <div>
            <label for="email">メールアドレス<span>必須</span></label>
            <input id="email" type="email" name="email" value="{{ old('email') }}">
            @if($errors->has('email'))
            <p>{{ $errors->first('email') }}</p>
            @endif
        </div>

        <div>
            <label for="email_confirmation">メールアドレスの確認<span>必須</span></label>
            <input id="email_confirmation" type="email" name="email_confirmation" value="{{ old('email_confirmation') }}">
            @if($errors->has('email_confirmation'))
            <p>{{ $errors->first('email_confirmation') }}</p>
            @endif
        </div>

        <div>
            <label for="body">お問い合わせ内容<span>必須</span></label>
            <textarea id="body" type="text" name="body">{{ old('body') }}</textarea>
            @if($errors->has('body'))
            <p>{{ $errors->first('body') }}</p>
            @endif
        </div>

        <div>
            <button type="submit" name="submitBtnVal" value="confirm">確認画面へ</button>
        </div>

    </form>
</section>
@endsection

お問い合わせ - 確認

resources/views/contact/confirm.blade.php

@extends('layouts.default')
@section('title', 'お問い合わせ確認')

@section('content')

<section>
    <form action="{{ route('contact.confirm') }}" method="POST">
        @csrf

        <div>
            <label for="company">会社名</label>
            {{ old('company') }}
            <input id="company" type="hidden" name="company" value="{{ old('company') }}">
        </div>

        <div>
            <label for="name">お名前</label>
            {{ old('name') }}
            <input id="name" type="hidden" name="name" value="{{ old('name') }}">
        </div>

        <div>
            <label for="name_kana">フリガナ</label>
            {{ old('name_kana') }}
            <input id="name_kana" type="hidden" name="name_kana" value="{{ old('name_kana') }}">
        </div>

        <div>
            <label for="phone">電話番号</label>
            {{ old('phone') }}
            <input id="phone" type="hidden" name="phone" value="{{ old('phone') }}">
        </div>

        <div>
            <label for="email">メールアドレス</label>
            {{ old('email') }}
            <input id="email" type="hidden" name="email" value="{{ old('email') }}">
            <input id="email_confirmation" type="hidden" name="email_confirmation" value="{{ old('email_confirmation') }}">
        </div>

        <div>
            <label for="body">お問い合わせ内容</label>
            {{ old('body') }}
            <input id="body" type="hidden" name="body" value="{{ old('body') }}">
        </div>

        <div>
            <button type="submit" name="submitBtnVal" value="back">戻る</button>
            <button type="submit" name="submitBtnVal" value="complete">送信</button>
        </div>

    </form>
</section>
@endsection

お問い合わせ - 完了

resources/views/contact/complete.blade.php

@extends('layouts.default')
@section('title', 'お問い合わせ完了')

<section>
    <h1>
        お問い合わせ完了
    </h1>

    <div><a href="/">Home</a></div>
</section>

フォームリクエスト作成

コントローラーのメール送信部分で読み込ませていたリクエストを作成していきます。

# request作成
sail artisan make:request ContactFormRequest

app/Http/Requests/ContactFormRequest.phpにファイルが作成されたと思いますので確認してみてください。

今回はログインなどがないため、authorizeをtrueにします。

public function authorize(): bool
{
    return true;
}

その他を記述すると下記のようになります。

app/Http/Requests/ContactFormRequest.php

<?php

namespace App\\Http\\Requests;

use Illuminate\\Foundation\\Http\\FormRequest;

class ContactFormRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \\Illuminate\\Contracts\\Validation\\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'company'   => ['required', 'string', 'max:30'],
            'name'      => ['required', 'string', 'max:30'],
            'name_kana' => ['required', 'string', 'max:30', 'regex:/^[ァ-ロワンヴー]*$/u'],
            'phone'     => ['nullable', 'regex:/^0(\\d-?\\d{4}|\\d{2}-?\\d{3}|\\d{3}-?\\d{2}|\\d{4}-?\\d|\\d0-?\\d{4})-?\\d{4}$/'],
            'email'     => ['required', 'email:strict,spoof,filter,dns', 'max:254', 'confirmed'],
            'email_confirmation'     => ['required', 'email:strict,spoof,filter,dns', 'max:254'],
            'body'      => ['required', 'string', 'max:5000'],
        ];
    }

    /**
     * 属性名
     */
    public function attributes()
    {
        //
        return [
            'company'               => '会社名',
            'name'                  => '名前',
            'name_kana'             => 'フリガナ',
            'phone'                 => '電話番号',
            'email'                 => 'メールアドレス',
            'email_confirmation'    => 'メールアドレスの確認',
            'body'                  => 'お問い合わせ内容'

        ];
    }

    /**
     * エラーメッセージ
     */
    public function messages()
    {
        //
        return [
            'phone.regex'   => ':attributeが正しくありません。'
        ];
    }
}

Mailableクラスの作成

Mailableクラスを使用して送信されるメール部分を作成していきます。

管理者宛のメールと、送信者宛の自動返信メールの2つを作成していきます。

# mailコマンドで作成
sail artisan make:mail ContactFormAdminMail
sail artisan make:mail ContactFormUserMail

app/Mail/ContactFormAdminMail.phpapp/Mail/ContactFormUserMail.phpのファイルが作成されたと思いますので確認してみてください。

app/Mail/ContactFormAdminMail.php

<?php

namespace App\\Mail;

use Illuminate\\Bus\\Queueable;
use Illuminate\\Contracts\\Queue\\ShouldQueue;
use Illuminate\\Mail\\Mailable;
use Illuminate\\Mail\\Mailables\\Content;
use Illuminate\\Mail\\Mailables\\Envelope;
use Illuminate\\Mail\\Mailables\\Address;
use Illuminate\\Queue\\SerializesModels;

class ContactFormAdminMail extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * Create a new message instance.
     */
    public function __construct(public array $form_data)
    {
        //
    }

    /**
     * Get the message envelope.
     */
    public function envelope(): Envelope
    {
        $from    = new Address($this->form_data['email'], $this->form_data['name']);
        $subject = '【' . env('APP_NAME') . '】お問い合せがありました';

        return new Envelope(
            from: $from,
            subject: $subject,
        );
    }

    /**
     * Get the message content definition.
     */
    public function content(): Content
    {
        return new Content(
            text: 'emails.contact.admin', // プレーンテキストで送信
        );
    }

    /**
     * Get the attachments for the message.
     *
     * @return array<int, \\Illuminate\\Mail\\Mailables\\Attachment>
     */
    public function attachments(): array
    {
        return [];
    }
}

use宣言で Address を追加

app/Mail/ContactFormUserMail.php

<?php

namespace App\\Mail;

use Illuminate\\Bus\\Queueable;
use Illuminate\\Contracts\\Queue\\ShouldQueue;
use Illuminate\\Mail\\Mailable;
use Illuminate\\Mail\\Mailables\\Content;
use Illuminate\\Mail\\Mailables\\Envelope;
use Illuminate\\Mail\\Mailables\\Address;
use Illuminate\\Queue\\SerializesModels;

class ContactFormUserMail extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * Create a new message instance.
     */
    public function __construct(public array $form_data)
    {
        //
    }

    /**
     * Get the message envelope.
     */
    public function envelope(): Envelope
    {
        $from    = new Address(env('MAIL_FROM_ADDRESS'), env('MAIL_FROM_NAME'));
        $subject = '【' . env('APP_NAME') . '】お問い合せありがとうございます';

        return new Envelope(
            from: $from,
            subject: $subject,
        );
    }

    /**
     * Get the message content definition.
     */
    public function content(): Content
    {
        return new Content(
            text: 'emails.contact.user', // プレーンテキストで送信
        );
    }

    /**
     * Get the attachments for the message.
     *
     * @return array<int, \\Illuminate\\Mail\\Mailables\\Attachment>
     */
    public function attachments(): array
    {
        return [];
    }
}

use宣言で Address を追加

メール用テンプレート追加

それぞれに送るメールのテンプレートを作成します。

PHPの変数以外の部分に関してはご自身の設定したいメール文にしてください。

resources/views/emails/contact/admin.blade.php

{{ $form_data['name'] }} 様より下記の内容のお問い合わせがありました

==============================
お問い合わせ内容
==============================
■会社名:
{{ $form_data['company'] }}
■お名前:
{{ $form_data['name'] }}
■フリガナ:
{{ $form_data['name_kana'] }}
■メールアドレス:
{{ $form_data['email'] }}
■電話番号:
{{ $form_data['phone'] }}
■お問い合わせ内容:
{{ $form_data['body'] }}
------------------------------

resources/views/emails/contact/user.blade.php

{{ $form_data['name'] }} 様
この度はお問い合わせいただきありがとうございます。

==============================
お問い合わせ内容
==============================
■会社名:
{{ $form_data['company'] }}
■お名前:
{{ $form_data['name'] }}
■フリガナ:
{{ $form_data['name_kana'] }}
■メールアドレス:
{{ $form_data['email'] }}
■電話番号:
{{ $form_data['phone'] }}
■お問い合わせ内容:
{{ $form_data['body'] }}
------------------------------

動作確認

ここまで完了したら動作確認を行いましょう。

もしも途中でエラーが出たら、それぞれのファイルの記述が間違ってないか確認してください。

また、送信されるメールの確認は下記のURLで確認ができます。

http://localhost:8025/

冒頭でご紹介したMailpitという開発用メールサーバーのGUI画面がメーラーのようになっていますので、管理者宛メールと送信者宛自動返信メールの両方を確認できると思います。

おまけ

今回構築したお問い合わせフォームにデザインをつけていきたいと思います。

Laravel SailはVite(ヴィート)というフロントエンド開発で人気のビルドツールが内包されています。

ViteではさまざまなNode.jsライブラリを使いJavascriptやCSSをビルドでき、Vue.jsやReactでの開発環境としても広く使われているツールです。

ViteではTailwindCSSという、CSSフレームワークを簡単に使用することができますので、今回はTailwindCSSでデザインをつけてみましょう。

 

TailwindCSSインストール

sailを経由しなくてもインストールはできるのですが、PCにインストールしているNode.jsのバージョンになってしまうため、sailを経由してdocker内でのバージョンで利用した方が良いと思われます。

~/dev/laravel/contact $ sail node -v
v20.9.0

~/dev/laravel/contact $ node -v
v18.14.2

ということでインストールです。

# sailコマンド経由でnpmコマンド実行
sail npm install -D tailwindcss postcss autoprefixer
sail npx tailwindcss init -p

postcss.config.jstailwind.config.jsが作成されたと思います。

 

Bladeテンプレートを対象にするため、設定を記述します。

その他利用する形式も追加しておくと良いと思います。

tailwind.config.js

/** @type {import('tailwindcss').Config} */
export default {
    content: ['./resources/**/*.{html,js,jsx,md,mdx,ts,tsx,vue,blade.php}'],
    theme: {
        extend: {},
    },
    plugins: [],
};

 

続いて、CSSの設定を行います。

resources/css/app.css

@tailwind base;
@tailwind components;
@tailwind utilities;

 

BladeからCSSを呼び出します。

resources/views/layouts/default.blade.php

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@yield('title', 'フォームAPP')</title>
    @vite('resources/css/app.css')
</head>

<body>
    <header>{{-- // header --}}</header>

    <main>@yield('content')</main>

    <footer>{{-- // footer  --}}</footer>
</body>

</html>

 

Viteを立ち上げて確認してみましょう。

# sailコマンド経由でnpmコマンド実行
sail npm run dev

 

こんな感じでデザインをつけてみました。

まとめ

今回はお問い合わせフォームを題材に、Laravel Sailを活用したPHPのローカルでの開発環境の設定と開発を実践をしてみました。

PHPはWebページ制作において柔軟に利用することができると思いますし、お問い合わせフォーム以外にも独自CMSを開発することもできます。

 

今回は基礎的な内容になっていますので、PHPフレームワークではどのようなことが行われているのかを理解していく一助にしていただければと思います。

デパート採用情報

株式会社デパートでは一緒に働く仲間を募集しています