見出し画像

確認画面のあるフォームを作ろう #Laravel基礎 #Laravel頻出パターン

はじめに

本稿ではLaravelのフォーム機能を使って確認画面を出すやり方を説明していきます。


■ 課題
・複数画面を行き来する時の手法について学ぶ
・セッションの利用方法について学ぶ


# 設計

作成するアプリは次のような仕様とします。

1. /formにアクセスするとフォームが表示
2. 送信すると/form/confirmにリダイレクトして確認画面が表示
3. 確認画面から戻るボタンで入力内容が修正出来る
4. 確認画面から送信ボタンで完了画面(/form/thanks)を表示

よくあるお問い合わせフォームをイメージしています。今回は送信処理については省略しますが、確認画面から遷移してメールを送信すれば問い合わせフォームになります。


# ルーティングの設定

アプリケーションの仕様に合わせてルーティングを設定します。

このような一覧の流れがある場合、処理順に考えていくとわかりやすいです。

一連の流れを起こすと次のようになります。

・get 問い合わせフォームを表示
・post 問い合わせフォーム遷移先
・get 確認画面
・post 確認画面からフォーム遷移先
・get 完了画面

これらをルーティングの設定に書き込んでいきましょう。

routes/web.php

Route::get('/form', "SampleFormController@show")->name("form.show");
Route::post('/form', "SampleFormController@post")->name("form.post");

Route::get('/form/confirm', "SampleFormController@confirm")->name("form.confirm");
Route::post('/form/confirm', "SampleFormController@send")->name("form.send");

Route::get('/form/thanks', "SampleFormController@complete")->name("form.complete");


ルーティングで後ろにname()をつけています。これは各ルーティングに名前をつける処理です。

名前をつけるメリットは、redirect()処理などでここで設定した名前を使うことができることです。

例えばこのフォームトップにリンクを貼る場合、通常は

<a href="{{ url('/form') }}">お問い合わせはこちら</a>

と書きます。

このルーティングに名前をつけていると次のように書くことができます。

<a href="{{ route('form.show') }}">お問い合わせはこちら</a>

また、Laravelではもう一つ手法があります。それはaction()関数を使うやり方です

<a href="{{ action('SampleFormController@show') }}">お問い合わせはこちら</a>

action()を使うとルーティングで設定したURLが表示されます。


つまり、まとめますと現在の設定では次の全てが同じ形になります。

<a href="{{ url('/form') }}">お問い合わせはこちら</a>
<a href="{{ route('form.show') }}">お問い合わせはこちら</a>
<a href="{{ action('SampleFormController@show') }}">お問い合わせはこちら</a>

welcome.blade.phpを書き換えてソースコードを確認すると次のような出力になっています。(※SampleFormControllerを作成していないのでこの段階では実際は表示できません)

スクリーンショット 2020-06-24 23.31.14

スクリーンショット 2020-06-24 23.31.24

この3つの書き方にはそれぞれ長所と短所があります。

■ url() 
長所:URLがどうなるかわかりやすい
短所:各機能のURLを知っておく必要がある

■ route()
長所:記述がシンプル。URLを書き換える時ルーティングの設定ファイルだけで良い
短所:URLがどうなるかわかりにくい

■ action()
長所:クラス名と関数名がわかるので、遷移先の処理が追いかけやすい
短所:クラス名が長いのテンプレートが読みづらくなる

それぞれの長所と短所を見つつ、今回はこれらを組み合わせていきます。


# 確認画面をどう作るか

確認画面を作る方法は何パターンかありますが、今回はセッションを使います。

大きな流れは次のような形です。

・フォームからの遷移先でセッションに入力値を保存
・確認画面の表示はセッションの入力値を使う
・確認画面からの遷移先もセッションの入力値を使う
・送信処理(確認画面からの遷移先)で二重投稿にならないようにセッションの値を空にする

関数と処理の概要

スクリーンショット 2020-06-24 23.39.09


では、この概念図に沿ってコントローラーを作っていきましょう。


# Controller フォームの表示

まずはmake:controllerを使ってコントローラーを作成します。作成するクラスは「SampleFormController」とします。

php artisan make:controller SampleFormController

作成されたSampleFormControllerを修正し、show()関数とpost()関数を実装していきます。

app/Http/Controllers/SampleFormController.php

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Validator;

class SampleFormController extends Controller
{
	private $formItems = ["name", "title", "body"];

	private $validator = [
		"name" => "required|string|max:100",
		"title" => "required|string|max:100",
		"body" => "required|string|max:100"
	];

	function show(){
		return view("form");
	}

	function post(Request $request){
		
		$input = $request->only($this->formItems);
		
		$validator = Validator::make($input, $this->validator);
		if($validator->fails()){
			return redirect()->action("SampleFormController@show")
				->withInput()
				->withErrors($validator);
		}

		//セッションに書き込む
		$request->session()->put("form_input", $input);

		return redirect()->action("SampleFormController@confirm");
	}
}
use Validator;

バリデーションを使うのせuse句を忘れないようにしましょう。

private $formItems = ["name", "title", "body"];
private $validator = [
    "name" => "required|string|max:100",
    "title" => "required|string|max:100",
    "body" => "required|string|max:100"
];

このように複数の画面を行ったり来たりするコントローラーの場合、フォームの名前やバリデーションの情報は各関数に書くよりもこのようにプロパティに持つ方がコードが見やすくなります

function show(){
    return view("form");
}

show()関数は問い合わせフォームを表示します。

続いてpost()関数です。

$input = $request->only($this->formItems);
$validator = Validator::make($input, $this->validator);

プロパティにフォームの値やバリデーションの情報をもたせたのでコードが短くなっています。

redirect()->action("SampleFormController@show")

リンクと同じようにredirect()の時でもクラス名と関数名を使うことが出来ます。リンクと違いredirect()->action()で指定する必要があります。

このようにクラスの中は「クラス名@関数名」の書き方にすると処理が追いかけやすいです。

$request->session()->put("form_input", $input);

form_inputというキーでフォームの入力値を保存します。

return redirect()->action("SampleFormController@confirm");

confirm()関数のルーティングにリダイレクトします。この書き方で「/form/confirm」にリダイレクトします。


# Controller 確認画面

次は確認画面です。confirm()関数を作っていきます。

class SampleFormController extends Controller
{
	function confirm(Request $request){
		//セッションから値を取り出す
		$input = $request->session()->get("form_input");
		
		//セッションに値が無い時はフォームに戻る
		if(!$input){
			return redirect()->action("SampleFormController@show");
		}
		return view("form_confirm",["input" => $input]);
	}	
$input = $request->session()->get("form_input");

セッションの値を取り出します。直接/form/confirmにアクセスされるなどの不正な操作を対策するためにセッションから取り出した値をチェックします。

confirm()関数ではpost()関数で保存したセッションの値を確認して入力値をViewに渡すだけで処理が終わります。


# Controller 送信処理

今度は送信処理です。send()関数を実装していきます。

今回はサンプルなので送信処理は何も行いませんが、送信処理が終わればセッションの値を空にします。

class SampleFormController extends Controller
{
	function send(Request $request){
		//セッションから値を取り出す
		$input = $request->session()->get("form_input");
		
		//セッションに値が無い時はフォームに戻る
		if(!$input){
			return redirect()->action("SampleFormController@show");
		}

		//ここでメールを送信するなどを行う

		//セッションを空にする
		$request->session()->forget("form_input");

		return redirect()->action("SampleFormController@complete")
	}
	
$request->session()->forget("form_input");

セッションから値を削除するときはforget()関数を使います。

return redirect()->action("SampleFormController@complete")

完了画面に遷移します。


# Controller 完了画面

完了画面を表示するcomplete()関数を作成します。フォームと同じく、Viewを表示するだけです。

class SampleFormController extends Controller
{

	function complete(){	
		return view("form_complete");
	}
	


コントローラーは以上で作成完了です。続いてテンプレートを作成していきます。


# テンプレートの作成 フォーム

show()関数で呼び出されているテンプレートを作成していきます。

resources/views/form.blade.php

<h3>フォーム</h3>
@if ($errors->any())
<div style="color:red;">
<ul>
	@foreach ($errors->all() as $error)
	<li>{{ $error }}</li>
	@endforeach
</ul>
</div>
@endif

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

	<label>Name</label>
	<div>
		<input type="text" name="name" value="{{ old('name') }}" />
	</div>
	<label>Title</label>
	<div>
		<input type="text" name="title" value="{{ old('title') }}" />
	</div>
	<label>Body</label>
	<div>
		<textarea name="body">{{ old('body') }}</textarea>
	</div>

	<input class="btn btn-primary" type="submit" value="送信" />
</form>
@if ($errors->any())
<div style="color:red;">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif

バリデーションが実行されるため、エラーの表示を設定しています。

<form method="post" action="{{ route('form.post') }}">

フォームの遷移先はルーティングで設定した名前で指定しています。ルーティングの設定により「action="/form"」と出力されます。

<input type="text" name="name" value="{{ old('name') }}" />

各要素はバリデーション後に戻ってくることがあるため、old()関数を使います。

ブラウザでフォームを表示して、動作を確認してみましょう。

スクリーンショット 2020-06-24 23.55.26


# テンプレートの作成 確認画面

confirm()関数で呼び出されているテンプレートを作成していきます。


resources/views/form_confirm.blade.php

<h3>確認</h3>
<form method="post" action="{{ route('form.send') }}">
	@csrf
	<label>Name</label>
	<div>
		{{ $input["name"] }}
	</div>
	<label>Title</label>
	<div>
		{{ $input['title'] }}
	</div>
	<label>Body</label>
	<div>
		{{ $input['body'] }}
	</div>

	<input name="back" type="submit" value="戻る" />
	<input type="submit" value="送信" />

</form>
<form method="post" action="{{ route('form.send') }}">

フォームの遷移先はルーティングで設定した名前で指定しています。ルーティングの設定により「action="/form/confirm"」と出力されます。

{{ $input["name"] }}

Controllerから$inputで渡された値を表示します。

<input name="back" type="submit" value="戻る" />
<input type="submit" value="送信" />

送信ボタンと、修正するための戻るボタンを作成します。戻るボタンを押した時の動作はまだ作成していません。

ブラウザでフォームを表示して、確認画面に遷移するか、動作を確認してみましょう。

スクリーンショット 2020-06-25 0.07.41


# 戻るボタンを押した時の処理

戻るボタンも送信ボタンも、フォームの送信先はsend()関数のままです。send()関数を修正し、戻るボタンを押した時の動作を作っていきます。

<input type="submit" />で作成したサブミットボタンは「押したときだけnameで指定した値が送信される」という仕組みになっています。

入力値のチェック方法はいくつか方法がありますが、$request->has("キー")を使うと値の有無が確認できます。

if($request->has("back")){
     //戻るボタンが押された
}

この書き方で戻るボタン(テンプレートでname="back"で指定したボタン)が押されたか判定できます。

これを利用して戻るボタンが押された時の動作をsend()関数に実装していきます。(+が追加箇所)


class SampleFormController extends Controller
{
	function send(Request $request){
		//セッションから値を取り出す
		$input = $request->session()->get("form_input");
		
+        //戻るボタンが押された時
+		if($request->has("back")){
+			return redirect()->action("SampleFormController@show")
+				->withInput($input);
+		}

		//セッションに値が無い時はフォームに戻る
		if(!$input){
			return redirect()->action("SampleFormController@show");
		}
		//ここでメールを送信するなどを行う
		//セッションを空にする
		$request->session()->forget("form_input");
		return redirect()->action("SampleFormController@complete");
	}
	
return redirect()->action("SampleFormController@show")
->withInput($input)

フォームの表示関数にリダイレクトします。この時withInput()を使うことでセッションから取り出した値をそのまま渡すことが出来ます。withInput()が空の時はポストされた値が入りますし、このように指定することもできます。

フォームのテンプレートは修正する必要がありません。{{ old("name") }}がそのまま動作する仕組みです。


# 完了画面

完了画面を作成します。

resources/views/form_complete.blade.php

<h3>完了</h3>
<p>送信しました!</p>

<a href="{{ route('form.show') }}">戻る</a>

フォームに戻るためのリンクを設置して、完了画面の作成は完了です。

ブラウザでフォームの一連の流れを確認してみてください。

スクリーンショット 2020-06-25 0.07.47


# まとめ

本稿ではセッションを使った確認画面の作成方法について解説しました。

一つのControllerで複数の画面を行き来する場合、redirect()でURLを書くとどうしてもわかりにくくなります。その場合、action()を使ったクラス名と関数のルーティングが効果的です。

テンプレートでは逆にroute()を使った名前を指定したルーティングやurl()を使ったURLの指定が綺麗です。

使い分けは色々な意見があると思いますが、次のパターンがおすすめかなと考えています。

Controllerで複数画面を行き来する場合action()を使ったルーティング
複数画面を行き来する場合のテンプレートではroute()を使った名前をつけたルーティング
・それ以外はurl()を使ったルーティング

この当たりは実際に使ってみてこっちの方が綺麗だなと考えながらやっていくのが良いでしょう。

サンプルコードはGitHubにで配布しています。



おつかれさまでした!


完成まで突っ走る意気込みです。サポートしていただけると非常に嬉しいです。応援よろしくお願いします。