Laravelでポートフォリオ作り vol.5【DB登録画面編】

前回に引き続き、ポートフォリオ作りを進めていきます。
今回は登録画面の実装をしていきます。
前回は一覧画面のモックアップを実装をしましたが、データが存在しないため一覧表示処理が作成できません。
そのため、今回はデータ登録処理を実装していきます。

前回の記事

画面表示までの処理を作成

まずは画面表示部分を作成します。
やることは一覧画面のモックアップ作成と大きく変わりませんが、今回のリンクは画面上部のメニューではなく、一覧画面内に作成します。
メニューからの画面遷移ではなく、一覧画面から登録画面に遷移する導線とするわけですね。

以降のソースコードは一部省略されている場合があります。(ソースコード量が増えるため)
既存のコードを残したり、どのあたりに記述するかも合わせて書いていくので、注意して読み進めてください。
まずはルーティングの定義をしていきます。
生徒一覧のルーティング設定の下に、新たにルート設定を定義します。
// 以降のルーティングはこの下に書いていく
Route::get('/student', [StudentController::class, 'index'])->name('student.index'); // 生徒一覧
Route::get('/student/create', [StudentController::class, 'create'])->name('student.create'); // 生徒登録

続いてController内にメソッドを作成します。
今回は「生徒情報を管理する」という観点でControllerファイルをまとめていきます。
そのため、生徒一覧と同じようにStudentControllerを使用します。このあたりのファイル(クラス)のまとめ方も考えていけると良いですね。

前回作成したindexメソッドの下にcreateメソッドを作成します。
bladeは作成していませんが、メソッド名と同じようにcreateという名前を使用予定なので、先に記述してしまいます。

class StudentController extends Controller
{
    /**
     * 一覧画面
     */
    public function index()
    {
       // << 省略  >>
    }

    /**
     * 登録画面
     */
    public function create()
    {
        return view('student.create');
    }
}

最後にbladeを作成します。
一覧画面のindexと同じように、「resources/views/student」ディレクトリにcreate.blade.phpを作成し、テンプレート部分のみ記述しておきます。

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            生徒登録
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div class="p-6 text-gray-900">
                    生徒登録フォーム
                </div>
            </div>
        </div>
    </div>
</x-app-layout>

最後に画面遷移のためのリンクを前回作成した一覧画面のbladeに記述します。

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            生徒一覧
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div class="p-6 text-gray-900">
                    {{-- 生徒登録URLを追加 --}}
                    <a class="text-sky-500 mr-3" href="{{ route('student.create') }}">
                        生徒登録
                    </a>
                    @foreach($students as $l_student)
                        <div class="bg-white border shadow-sm rounded-xl p-4 mb-3">
                            <h3 class="text-lg font-bold text-gray-800">
                                {{ $l_student['student_name'] }}
                            </h3>
                            <p class="mt-1 text-xs font-medium text-gray-500">
                                期間({{ $l_student['kikan_from'] }}~{{ $l_student['kikan_to'] }})
                            </p>
                            <p class="mt-2 text-gray-800">
                                {{ $l_student['student_memo'] }}
                            </p>
                            <div class="flex mt-3">
                                <a class="text-sky-500 mr-3" href="{{ $l_student['student_detail_url'] }}">
                                    生徒詳細
                                </a>
                                <a class="text-orange-500" href="{{ $l_student['student_mendan_url'] }}">
                                    面談登録
                                </a>
                            </div>
                        </div>
                    @endforeach
                </div>
            </div>
        </div>
    </div>
</x-app-layout>

一覧画面にアクセスして、画面遷移できればOKです。

登録画面の入力フォーム作成

createのbladeファイルに入力フォームを作成していきます。
コンテンツ部分を書き換えました。
※HTMLのルール的におかしい部分がありますが、後程修正していくためスルーしてください。。。

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            生徒登録
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div class="p-6 text-gray-900">
                    <div class="w-full">
                        <form class="px-8 pt-6 pb-8 mb-3">
                            {{-- 生徒氏名 --}}
                            <div class="mb-4 flex">
                                <div class="mr-2">
                                    <label class="block text-gray-700 text-sm font-bold mb-1" for="username">
                                        生徒氏名(姓)
                                    </label>
                                    <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="username" type="text" placeholder="姓">
                                </div>
                                <div>
                                    <label class="block text-gray-700 text-sm font-bold mb-1" for="username">
                                        生徒氏名(名)
                                    </label>
                                    <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="username" type="text" placeholder="名">
                                </div>
                            </div>

                            {{-- 生徒受講期間 --}}
                            <div class="mb-4 flex items-center">
                                <div class="mr-2">
                                    <label class="block text-gray-700 text-sm font-bold mb-1" for="username">
                                        受講開始日
                                    </label>
                                    <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="username" type="date" placeholder="">
                                </div>
                                <div class="mr-2">
                                    <label class="block">
                                         
                                    </label>
                                    ~
                                </div>
                                <div>
                                    <label class="block text-gray-700 text-sm font-bold mb-1" for="username">
                                        受講終了日
                                    </label>
                                    <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="username" type="date" placeholder="名">
                                </div>
                            </div>

                            {{-- 生徒メモ--}}
                            <div class="mb-4">
                                <div class="mr-2">
                                    <label class="block text-gray-700 text-sm font-bold mb-1" for="username">
                                        生徒メモ
                                    </label>
                                    <textarea class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="username" type="date" placeholder=""></textarea>
                                </div>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
</x-app-layout>

これで入力用の画面が実装されました。

登録処理の実装

ルーティングの定義

続いて登録処理の実装をしていきます。
まずは登録処理のルーティングから作成します。
登録画面のルーティングの下に定義します。処理名は「store」としました。
また、今回は「get」ではなく「post」で定義します。
ざっくりですが、検索系は「get」登録系は「post」で定義すると覚えておくと大体うまくいきます。
※登録系でなくてもログイン処理のように秘匿情報を含む場合はpostで実装します

    // 以降のルーティングはこの下に書いていく
    Route::get('/student', [StudentController::class, 'index'])->name('student.index'); // 生徒一覧
    Route::get('/student/create', [StudentController::class, 'create'])->name('student.create'); // 生徒登録
    Route::post('/student/store', [StudentController::class, 'store'])->name('student.store'); // 生徒登録処理

メソッドの定義

コントローラにメソッドを定義します。
登録処理はリクエストパラメータから登録値を取得する必要があります。そのため、メソッドの引数でRequestを定義してRequestを扱えるようにしておきます。

Controllerクラスにこのようなメソッドを追加してみました。

/**
 * 登録処理
 */
public function store(Request $req){
    dd($req->all());
}

これで$reqという変数にクライアントから送信されるリクエスト情報が格納され、Controllerで扱うことができます。
もちろんPHPに定義されているものを使用しても良いですが、Laravelで用意されているものを利用した方が開発効率がよくなるので、この定義は覚えておきましょう。

ddメソッドは処理を終了して画面に引数の内容を出力してくれます。
現在のstoreメソッドは登録処理ではなく、送信されたものを画面表示する処理になっているわけですね。

値送信のためにbladeを修正

続いて、値を送信するためのbladeに修正を加えます。
とりあえず見た目だけ作成しましたが、まだ送信するためのコーディングができていません。

まず、formタグを修正して送信のためのメソッド(post)の記述と、送信先URLの記述を行います。

修正前

<form class="px-8 pt-6 pb-8 mb-3">

修正後

<form method="post" action="{{route('student.store')}}" class="px-8 pt-6 pb-8 mb-3">

methodに通信の種類(get/post)、actionに送信先URLを記述します。
methodが指定されていない場合はgetでの通信となるので、post通信が必要な場合は必ず記述しましょう。

続いて、各入力要素の修正を行っていきます。
特に注意すべきはname属性となります。このname属性が情報を受け取ったサーバが扱う値の格納先になるので、分かりやすい名前を定義しましょう。
また、先ほどの入力フォームのbladeはid属性の値も重複しています。id属性はページ内での重複が許容されないので併せて修正します。

今回はname属性とid属性にDBのカラム物理名を設定していきます。
例えば、「生徒氏名(姓)」には「student_sei」を指定するといった感じですね。
また、labelタグのfor属性には対応するinput要素のid属性の値を定義しましょう。

<label class="block text-gray-700 text-sm font-bold mb-1" for="student_sei">
    生徒氏名(姓)
</label>
<input class="省略" id="student_sei" name="student_sei" type="text" placeholder="姓">

また、今のままでは値の入力を行っても送信ができません。
送信ボタンが無いからですね。ボタンは様々なタグで実装できますが、今回はbuttonタグを使用していきます。
buttonタグはformタグ内に記載することで、自身が含まれるformを送信するという挙動をしてくれます。

全て修正したソースがこちらです。

<form method="post" action="{{route('student.store')}}" class="px-8 pt-6 pb-8 mb-3">
    {{-- 生徒氏名 --}}
    <div class="mb-4 flex">
        <div class="mr-2">
            <label class="block text-gray-700 text-sm font-bold mb-1" for="student_sei">
                生徒氏名(姓)
            </label>
            <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="student_sei" name="student_sei" type="text" placeholder="姓">
        </div>
        <div>
            <label class="block text-gray-700 text-sm font-bold mb-1" for="student_mei">
                生徒氏名(名)
            </label>
            <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="student_mei" name="student_mei" type="text" placeholder="名">
        </div>
    </div>

    {{-- 生徒受講期間 --}}
    <div class="mb-4 flex items-center">
        <div class="mr-2">
            <label class="block text-gray-700 text-sm font-bold mb-1" for="student_from_date">
                受講開始日
            </label>
            <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="student_from_date" name="student_from_date" type="date" placeholder="">
        </div>
        <div class="mr-2">
            <label class="block">
                 
            </label>
            ~
        </div>
        <div>
            <label class="block text-gray-700 text-sm font-bold mb-1" for="student_to_date">
                受講終了日
            </label>
            <input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="student_to_date" name="student_to_date" type="date" placeholder="">
        </div>
    </div>

    {{-- 生徒メモ --}}
    <div class="mb-4">
        <div class="mr-2">
            <label class="block text-gray-700 text-sm font-bold mb-1" for="student_memo">
                生徒メモ
            </label>
            <textarea class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" id="student_memo" name="student_memo" placeholder=""></textarea>
        </div>
    </div>
    
    {{-- 登録ボタン --}}
    <button class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 mr-2 mb-2 focus:outline-none">送信</button>
</form>

それでは送信ボタンを押下してみましょう。

。。。
。。

あれ?送信されません。。。
正確には「419 | PAGE EXPIRED」となってしまいます。
細かい説明を省きますが、セキュリティエラーが発生している状態になります。
Laravelが通信を監視していて、不正な通信の場合にこのエラーが出てきます。
これを回避するためには「トークン」をformタグに記述して、通信が不正でないことの証明をします。
formタグ内に「@csrf」と記述することでトークンを設置できます。

<form method="post" action="{{route('student.store')}}" class="px-8 pt-6 pb-8 mb-3">
    @csrf
    省略
</form>

ではもう一度送信、、、
※ブラウザの「戻る」を利用した場合はページのリロードをしましょう

このように画面に送信内容が表示されればOK。
これがddメソッドの挙動になります。

今回はここまで!
画面の入力値をサーバーに送信することが出来ました。
次回はこの送信値をDBに登録していきます。

Tips:bladeファイルのコメントアウトについて

bladeファイルのコメントでは2通りの書き方が使用できます。

  • <!– コメント –>
  • {{– コメント –}}

この2種類ですね。実は1つ目のコメントアウトはHTMLのコメント記述方法となっています。
bladeでのコメントでは{{– コメント –}}を使用しましょう。

<!– コメント –>の場合、HTMLのコメントアウトのため、ブラウザの開発者ツールでソースを確認すると、コメントの内容が確認できてしまいます。
{{– コメント –}}でのコメントの場合はHTMLに出力されないため、ブラウザの開発者ツールを見てもコメントが存在しません
開発時のコメントが見えたとしても良いことはないので、何かの意図が無い限りは{{– コメント –}}でコメントを記述しましょう。