こちらの記事は「Laravel10でCMS(ブログ機能)の開発」の続きになり、Laravel-adminは導入済みの状態から始めます。
開発環境
・PHP 8.2.4
・Laravel Framework 10.28.0
・Apache 2.4.52
・MariaDB 10.4.22
目次
1.管理画面の設定
2.お問い合わせフォームの作成
参考サイト
https://analyzegear.co.jp/blog/2109
https://into-the-program.com/laravel-create-contact-form/
https://zenn.dev/nshiro/articles/adcbc2e127f712
実装
1.管理画面の設定
1-1.管理画面に項目の作成
まずは管理画面のメニューの項目の追加を行います。
// add default menus.
// メニューの変更
Menu::truncate();
Menu::insert([
[
'parent_id' => 0,
'order' => 1,
'title' => 'ダッシュボード',
'icon' => 'fa-bar-chart',
'uri' => '/',
],
[
'parent_id' => 0,
'order' => 2,
'title' => 'サイト管理',
'icon' => 'fa-tasks',
'uri' => '',
],
[
'parent_id' => 2,
'order' => 3,
'title' => 'ユーザー',
'icon' => 'fa-users',
'uri' => 'auth/users',
],
[
'parent_id' => 2,
'order' => 4,
'title' => '役割',
'icon' => 'fa-user',
'uri' => 'auth/roles',
],
[
'parent_id' => 2,
'order' => 5,
'title' => '権限',
'icon' => 'fa-ban',
'uri' => 'auth/permissions',
],
[
'parent_id' => 2,
'order' => 6,
'title' => 'メニュー',
'icon' => 'fa-bars',
'uri' => 'auth/menu',
],
[
'parent_id' => 2,
'order' => 7,
'title' => 'オペレーションログ',
'icon' => 'fa-history',
'uri' => 'auth/logs',
],
[
'parent_id' => 0,
'order' => 8,
'title' => 'ブログ',
'icon' => 'fa-tasks',
'uri' => '',
],
[
'parent_id' => 8,
'order' => 9,
'title' => '記事',
'icon' => 'fa-tasks',
'uri' => 'blog/articles',
],
[
'parent_id' => 8,
'order' => 10,
'title' => 'カテゴリー',
'icon' => 'fa-tasks',
'uri' => 'blog/categories',
],
[
'parent_id' => 0,
'order' => 11,
'title' => 'お問い合わせ',
'icon' => 'fa-send-o',
'uri' => 'contact',
],
]);上記コードの一番下にお問い合わせのメニューを追加しました。
php artisan migrate:fresh --seed上記コマンド実行後、管理画面で新しくお問い合わせの項目が追加されていると思います。
現状はクリックしてもページに遷移できません。
1-2.管理画面でお問い合わせを閲覧や編集ができるように設定
Laravel-adminの編集を行うために、モデルとリソースコントローラーを作成します。
php artisan make:model Contact -m上記コマンドで下記の2つのファイルが生成されます。
・app\Models\Contact.php
・database\migrations\2023_11_04_184540_create_contacts_table.php
お問い合わせフォームの項目は、名前とメールアドレスとお問い合わせ内容の3つ実装します。
モデル:Contact.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Contact extends Model
{
use HasFactory;
protected $fillable = [
'name',
'email',
'content',
];
}マイグレーション:2023_11_04_184540_create_contacts_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('contacts', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email');
$table->text('content');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('contacts');
}
};ファイルの編集が完了したら、下記のコマンドを実行します。
php artisan migrate:fresh --seed
php artisan admin:make ContactController --model=App\Models\Contact以下のファイルが生成されますので、管理画面での表示を少し変更します。
app\Admin\Controllers\ContactController.php
<?php
namespace App\Admin\Controllers;
use App\Models\Contact;
use Encore\Admin\Controllers\AdminController;
use Encore\Admin\Form;
use Encore\Admin\Grid;
use Encore\Admin\Show;
class ContactController extends AdminController
{
/**
* Title for current resource.
*
* @var string
*/
protected $title = 'Contact';
/**
* Make a grid builder.
*
* @return Grid
*/
protected function grid()
{
$grid = new Grid(new Contact());
$grid->column('id', __('ID'));
$grid->column('name', __('お名前'));
$grid->column('email', __('メールアドレス'));
$grid->column('content', __('内容'));
$grid->column('created_at', __('お問い合わせ日'))->display(function($created) {
return date('Y年m月d日 H時i分s秒' ,strtotime($created));
});
$grid->column('updated_at', __('更新日'))->display(function($updated) {
return date('Y年m月d日 H時i分s秒' ,strtotime($updated));
});
return $grid;
}
/**
* Make a show builder.
*
* @param mixed $id
* @return Show
*/
protected function detail($id)
{
$show = new Show(Contact::findOrFail($id));
$show->field('id', __('ID'));
$show->field('name', __('お名前'));
$show->field('email', __('メールアドレス'));
$show->field('content', __('内容'));
$show->field('created_at', __('お問い合わせ日'));
$show->field('updated_at', __('更新日'));
return $show;
}
/**
* Make a form builder.
*
* @return Form
*/
protected function form()
{
$form = new Form(new Contact());
$form->text('name', __('お名前'));
$form->email('email', __('メールアドレス'));
$form->textarea('content', __('内容'));
return $form;
}
}ルーティング処理です。
<?php
use Illuminate\Routing\Router;
Admin::routes();
Route::group([
'prefix' => config('admin.route.prefix'),
'namespace' => config('admin.route.namespace'),
'middleware' => config('admin.route.middleware'),
'as' => config('admin.route.prefix') . '.',
], function (Router $router) {
$router->get('/', 'HomeController@index')->name('home');
// 追加
$router->resource('blog/articles', ArticleController::class);
$router->resource('blog/categories', ArticleCategoryController::class);
$router->resource('contact', ContactController::class);
});管理画面にログインして、添付のような状態であれば、成功です。

これで管理画面側は以上となります。
2.お問い合わせフォームの作成
2-1.環境ファイルの設定
今回はGmailで送信設定を行います。
パスワードの設定などは下記参照です。
https://qiita.com/hiro5963/items/df062ab19e8ceba4573f
MAIL_MAILER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=hoge@example.com
MAIL_PASSWORD=16桁のパスワード
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=hoge@example.com
MAIL_FROM_NAME="${APP_NAME}からの自動返信"上記修正後、キャッシュの削除を行います。
php artisan config:clear2-2.ルーティング処理
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ArticleController;
use App\Http\Controllers\ContactController;
Route::prefix('/')->group(function() {
Route::get('/', [ArticleController::class , 'index'] )->name("article.index");
Route::get('article/{item}', [ArticleController::class, 'article'])->name('article.article');
});
Route::prefix('contact')->group(function() {
Route::get('/', [ContactController::class , 'index'] )->name("contact.index");
Route::post('confirm', [ContactController::class, 'confirm'])->name('contact.confirm');
Route::post('thanks', [ContactController::class, 'thanks'])->name('contact.thanks');
});お問い合わせのURLは下記になります。
入力ページ:http://127.0.0.1:8000/contact
確認ページ:http://127.0.0.1:8000/contact/confirm
送信完了ページ:http://127.0.0.1:8000/contact/thanks
2-3.入力ページの作成
お問い合わせのコントローラーの作成をします。
php artisan make:controller ContactControllerコントローラーが作成で来たら、編集していきます。
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Article;
use App\Models\ArticleCategory;
class ContactController extends Controller
{
public function index()
{
return view('contact.index');
}
public function confirm(Request $request)
{
}
public function thanks(Request $request)
{
}
}まずindexメソッドは、特にデータベースから取得する情報もありませんので、そのままファイルの指定をします。
@extends('layouts.base')
@section('title')
<title>お問い合わせ | {{ config('app.name', 'Laravel') }}</title>
@endsection
@section('main')
<div id="content" class="content">
<div id="content-in" class="content-in wrap mt-4">
<main id="main" class="main">
<form method="POST" action="{{ route('contact.confirm') }}">
@csrf
<div class="bg-white flex flex-col md:ml-auto w-full">
<h3 class="font-bold text-2xl mb-4">お問い合わせ</h3>
<p class="leading-relaxed mb-5 text-red-600 font-bold">※全て必須項目です。</p>
<div class="relative mb-4">
<label for="name" class="leading-7 text-sm text-gray-600">お名前</label>
<input type="text" id="name" name="name" value="{{ old('name') }}" class="w-full bg-white rounded border border-gray-300 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-200 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out">
@if ($errors->has('name')) <p class="error-message text-red-600 font-bold text-sm">{{ $errors->first('name') }}</p> @endif
</div>
<div class="relative mb-4">
<label for="email" class="leading-7 text-sm text-gray-600">メールアドレス</label>
<input type="email" id="email" name="email" value="{{ old('email') }}" class="w-full bg-white rounded border border-gray-300 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-200 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out">
@if ($errors->has('email')) <p class="error-message text-red-600 font-bold text-sm">{{ $errors->first('email') }}</p> @endif
</div>
<div class="relative mb-4">
<label for="content" class="leading-7 text-sm text-gray-600">お問い合わせ内容</label>
<textarea id="content" name="content" class="w-full bg-white rounded border border-gray-300 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-200 h-40 text-base outline-none text-gray-700 py-1 px-3 resize-none leading-6 transition-colors duration-200 ease-in-out">{{ old('content') }}</textarea>
@if ($errors->has('content')) <p class="error-message text-red-600 font-bold text-sm">{{ $errors->first('content') }}</p> @endif
</div>
<button type="submit" class="text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded text-lg w-40 m-auto mt-8">確認画面へ</button>
</div>
</form>
</main>
@include('layouts.aside', ['categories' => $categories, 'new' => $new])
</div>
</div>
@endsection上記のフォームのソースは下記サイトを参考にしています。
分かりやすくすると、下記になります。
<form method="POST" action="{{ route('contact.confirm') }}">
@csrf
<div>
<div>
<label for="name">お名前</label>
<input type="text" id="name" name="name" value="{{ old('name') }}">
@if ($errors->has('name')) <p>{{ $errors->first('name') }}</p> @endif
</div>
<div class="relative mb-4">
<label for="email">メールアドレス</label>
<input type="email" id="email" name="email" value="{{ old('email') }}">
@if ($errors->has('email')) <p>{{ $errors->first('email') }}</p> @endif
</div>
<div class="relative mb-4">
<label for="content">お問い合わせ内容</label>
<textarea id="content" name="content">{{ old('content') }}</textarea>
@if ($errors->has('content')) <p>{{ $errors->first('content') }}</p> @endif
</div>
<button type="submit">確認画面へ</button>
</div>
</form>2-4.バリデーションの日本語化
バリデーションのエラーが英語表記になっているので、日本語化します。
php -r "copy('https://readouble.com/laravel/8.x/ja/install-ja-lang-files.php', 'install-ja-lang.php');"
php -f install-ja-lang.php
php -r "unlink('install-ja-lang.php');"上記コマンド実行後、下記4ファイルが作成されます。
・resources\lang\ja\auth.php
・resources\lang\ja\pagination.php
・resources\lang\ja\passwords.php
・resources\lang\ja\validation.php
laravel10から「lang」という言語専用のフォルダーが直下に作成されましたので、「lang\ja」に4ファイルを移動します。
resourcesのlangフォルダは削除しても問題ないです。
最後にカスタムバリデーションで、フォームの項目を設定します。
'attributes' => [
'name' => '名前',
'email' => 'メールアドレス',
'content' => '本文'
]2-6.フォームリクエスト作成
フォームリクエストとは、フロントのHTMLフォームから渡ってくる入力データを検証したり、アクセスしたユーザーがそのリクエストを実行する権限があるかを確認できます。
php artisan make:request ContactRequest「app\Http\Requests\ContactRequest.php」の編集をします。
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ContactRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
// false から true に変更
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
{
$rules = [
'name' => ['required', 'string', 'max:30'],
'email' => ['required', 'email'],
'content' => ['required', 'string', 'max:1000'],
];
return $rules;
}
// /lang/ja/validation.php で指定した内容を変更する場合に設定する必要がある
public function attributes()
{
return [
'name' => 'お名前',
'email' => 'メールアドレス',
'content' => 'お問い合わせ内容',
];
}
// エラーメッセージ
public function messages()
{
return [
'name.required' => 'お名前は必須項目です',
'email.email' => 'メールアドレスの形式で入力してください',
'content.required' => 'お問い合わせ内容は必須項目です',
];
}
}2-7.Mailableクラスの作成
Mailableクラスとは、メールの送信先、件名、本文、添付ファイルなどを設定するためのメソッドが用意されています。
管理者宛てとユーザー宛ての2つの自動返信メールを作成します。
php artisan make:mail ContactAdminMail
php artisan make:mail ContactUserMaillaravel10からbuildメソッドがなくなっています。
参考サイト:https://zenn.dev/nshiro/articles/adcbc2e127f712
・管理者用
<?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\Queue\SerializesModels;
use Illuminate\Mail\Mailables\Address;
class ContactAdminMail extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*/
public function __construct(public array $data)
{
//
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
// 「app.name」はenvファイルで設定されているAPP_NAME
subject: config('app.name', 'Laravel').'へお問い合わせがありました。',
// envファイルで設定されているMAIL_FROM_ADDRESS
from: new Address(env('MAIL_FROM_ADDRESS', 'hogehoge@gmail.com'), config('app.name', 'Laravel')),
to: env('MAIL_FROM_ADDRESS', 'hogehoge@gmail.com'),
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
// view: 'view.name',
text: 'contact.mail-admin',
);
}
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}・ユーザー用
<?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\Queue\SerializesModels;
use Illuminate\Mail\Mailables\Address;
class ContactUserMail extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*/
public function __construct(public array $data)
{
//
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
// 「app.name」はenvファイルで設定されているAPP_NAME
subject: config('app.name', 'Laravel').'へのお問い合わせ【自動返信】',
// envファイルで設定されているMAIL_FROM_ADDRESS
from: new Address(env('MAIL_FROM_ADDRESS', 'hogehoge@gmail.com'), config('app.name', 'Laravel')),
to: $this->data['email']
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
// view: 'view.name',
text: 'contact.mail-user',
);
}
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}
envelopeメソッドでsubject【件名】とfrom【送信者】とto【宛先】の設定を行っています。
contentメソッドで、下記2ファイルのテキストの自動返信メールの内容を記載しています。
・resources\views\contact\mail-admin.blade.php
・resources\views\contact\mail-user.blade.php
一度テスト送信するために、上記2ファイルを作成します。
mail-admin.blade.phpに下記のテキストを記述します。
お問い合わせがありましたtinkerを起動して、テスト送信を行います。
php artisan tinker
> use App\Mail\ContactAdminMail;
> Mail::send(new ContactAdminMail);メールが受信できていれば、問題ないです。
実際のメールの内容を記述します。
{{ $data['name'] }} 様より下記の内容のお問い合わせがありました
==============================
お問い合わせ内容
==============================
■お名前:{{ $data['name'] }}
■メールアドレス:{{ $data['email'] }}
■お問い合わせ内容:
{{ $data['content'] }}
------------------------------{{ $data['name'] }} 様
この度はお問い合わせいただき有難うございます。
==============================
お問い合わせ内容
==============================
■お名前:{{ $data['name'] }}
■メールアドレス:{{ $data['email'] }}
■お問い合わせ内容:
{{ $data['content'] }}
------------------------------2-8.確認ページの作成
コントローラーの編集をします。
use App\Http\Requests\ContactRequest;
public function index()
{
return view('contact.index');
}
public function confirm(ContactRequest $request)
{
$data = $request->validated();
return view('contact.confirm', compact('data'));
}フォームのバリデーションの設定と送信された全てのデータを確認ページに変数で渡しています。
@extends('layouts.base')
@section('title')
<title>お問い合わせ【確認画面】 | {{ config('app.name', 'Laravel') }}</title>
@endsection
@section('main')
<div id="content" class="content">
<div id="content-in" class="content-in wrap mt-4">
<main id="main" class="main">
<form method="POST" action="{{ route('contact.thanks') }}">
@csrf
<div class="bg-white flex flex-col md:ml-auto w-full">
<h3 class="font-bold text-2xl mb-4">お問い合わせ【確認画面】</h3>
<div class="relative mb-4">
<label for="name" class="leading-7 text-sm text-gray-600">お名前</label>
<div class="w-full bg-white rounded border border-gray-300 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-200 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out">{{ $contact['name'] }}</div>
<input type="hidden" id="name" name="name" value="{{ $contact['name'] }}">
</div>
<div class="relative mb-4">
<label for="email" class="leading-7 text-sm text-gray-600">メールアドレス</label>
<div class="w-full bg-white rounded border border-gray-300 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-200 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out">{{ $contact['email'] }}</div>
<input type="hidden" id="email" name="email" value="{{ $contact['email'] }}">
</div>
<div class="relative mb-4">
<label for="content" class="leading-7 text-sm text-gray-600">お問い合わせ内容</label>
<div class="w-full bg-white rounded border border-gray-300 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-200 text-base outline-none text-gray-700 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out">{{ $contact['content'] }}</div>
<input type="hidden" id="content" name="content" value="{{ $contact['content'] }}">
</div>
<div class="flex gap-4 justify-center mt-8">
<button type="submit" name="action" value="back" class="text-white bg-red-500 border-0 py-2 px-6 focus:outline-none hover:bg-red-600 rounded text-lg w-40">修正</button>
<button type="submit" name="action" value="submit" class="text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded text-lg w-40">送信</button>
</div>
</div>
</form>
</main>
@include('layouts.aside', ['categories' => $categories, 'new' => $new])
</div>
</div>
@endsection2-9.完了ページの作成
コントローラーの編集
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use App\Http\Requests\ContactRequest;
use App\Mail\ContactAdminMail;
use App\Mail\ContactUserMail;
use App\Models\Contact;
use App\Models\Article;
use App\Models\ArticleCategory;
class ContactController extends Controller
{
public function index()
{
return view('contact.index');
}
public function confirm(ContactRequest $request)
{
$data = $request->validated();
return view('contact.confirm', compact('data'));
}
public function thanks(ContactRequest $request)
{
$data = $request->validated();
// 戻るボタンと送信ボタンを取得
$action = $request->input('action');
// 戻るボタンの場合は、データを渡す
if($action == 'back') {
return redirect()->route('contact.index')->withInput($data);
} elseif($action == 'submit') {
// 管理者とユーザーへの自動返信メールの送信
Mail::send(new ContactAdminMail($data));
Mail::send(new ContactUserMail($data));
// データベースへ保存
$contact = new Contact();
$contact->name = $data['name'];
$contact->email = $data['email'];
$contact->content = $data['content'];
$contact->save();
//再送信を防ぐためにトークンを再発行
$request->session()->regenerateToken();
return view('contact.thanks');
}
}
}データに問題がなければ、管理者とユーザーへ自動返信メールの送信と、データベースの保存をしています。
完了ページのHTML
@extends('layouts.base')
@section('title')
<title>お問い合わせ【確認画面】 | {{ config('app.name', 'Laravel') }}</title>
@endsection
@section('main')
<div id="content" class="content">
<div id="content-in" class="content-in wrap mt-4">
<main id="main" class="main">
<h3 class="font-bold text-2xl mb-4">お問い合わせ【確認画面】</h3>
<p class="text-center mt-4">お問い合わせが完了いたしました。<br>自動返信メールを送信しております。<br>今しばらくお待ちください。</p>
<a href="/" class="block m-auto mt-4 text-center text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded text-lg w-40">トップへ</a>
</main>
@include('layouts.aside', ['categories' => $categories, 'new' => $new])
</div>
</div>
@endsection最後にフォームでテスト送信して、送受信できていれば、問題ないです。