Laravelによる書籍管理システムの作成、第5回です。
今回は書籍の登録フォームを作成し、データベースにデータを登録する作成手順を追って行きたいと思います。
1. ルーティングの設定
1-1. 認証必要ルートに追加
「認証(ログイン)している人だけ」がアクセスできるグループの中に、登録用のルートを2つ作成します。
連載を読んでいる場合、前回のポストで作成したルーティングの、3番、4番のコメントアウトを外せばOKです。
編集ファイル: routes/web.php
|
1 2 3 4 5 6 7 8 |
// --- 【グループ内】ログイン(認証)が必要なルート --- Route::middleware('auth')->group(function () { // 3.書籍登録フォームの表示 (GET) Route::get('/books/create', [BookController::class, 'create'])->name('books.create'); // 4.書籍の保存処理 (POST) Route::post('/books', [BookController::class, 'store'])->name('books.store'); }); |
以前のポストにも書きましたが、最後の ->name() でそれぞれ create と store と名付けて、ビューから呼べるようにしています。
地味に便利。
1-2. ルート設定の追記(404エラー対策)
ルート設定ですが、前回のポストで書いたとおりにしていると create 画面に入ったときに 404エラー が表示されてしまいました。
原因は Laravel のルーティングは 「上から順番に」 判定されるからでした。
というのも、もし web.php で、詳細画面({book})の定義が、新規作成(create)よりも 上 に書いてあると、Laravel は「create という名前の書籍データを探そう」としてしまい、見つからずに404エラーを出してしまいます。
正しくルーティングを行うよう、/books と /books/{book} の記述を下記のように、/books/create よりも後で定義するように修正してください。
編集ファイル: routes/web.php
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
//------------------------------------------------------------ // 書籍管理システムのルートを追加 //------------------------------------------------------------ // --- 【グループ内】ログイン(認証)が必要なルート --- Route::middleware('auth')->group(function () { // 3.書籍登録フォームの表示 Route::get('/books/create', [BookController::class, 'create'])->name('books.create'); // 4.書籍の保存処理 Route::post('/books', [BookController::class, 'store'])->name('books.store'); // 5.書籍編集フォームの表示 // Route::get('/books/{book}/edit', [BookController::class, 'edit'])->name('books.edit'); // 6.書籍の更新処理 // Route::patch('/books/{book}', [BookController::class, 'update'])->name('books.update'); // 7.書籍の削除処理 // Route::delete('/books/{book}', [BookController::class, 'destroy'])->name('books.destroy'); }); // --- 【グループ外】誰でもアクセスできるルート --- // 1.書籍一覧画面 (URL: /books) Route::get('/books', [BookController::class, 'index'])->name('books.index'); // 2.書籍詳細画面 (URL: /books/{id}) Route::get('/books/{book}', [BookController::class, 'show'])->name('books.show'); |
↑ 認証しなくてもよい【グループ外】の記述を、後で評価するようにしています。
また、create フォームを表示させたい際は、ログイン認証を行ったあとでアクセスする必要があることをお忘れなく。
2. コントローラーの編集
2-1. フォーム用メソッドと、保存処理メソッドを作成する
登録画面では、「カテゴリ」や「出版社」を選択肢(プルダウン)として出す必要があるため、それらのデータを取得してビューに渡します。
編集ファイル: BookController.php
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Models\Book; use App\Models\Category; // 追加 use App\Models\Publisher; // 追加 class BookController extends Controller { // ... index, show メソッドはそのまま ... /** * 登録フォームの表示 */ public function create() { // プルダウンの選択肢として使用するため取得。また、is_deleted が 0 のデータだけを取得する $categories = Category::where('is_deleted', 0)->get(); $publishers = Publisher::where('is_deleted', 0)->get(); return view('books.create', compact('categories', 'publishers')); } /** * データの保存処理 */ public function store(Request $request) { // 1. バリデーション(入力チェック) $validated = $request->validate([ 'title' => 'required|max:255', 'category_id' => 'required|exists:m_categories,id', 'publisher_id' => 'required|exists:m_publishers,id', 'price' => 'nullable|numeric|min:0', 'description' => 'nullable|string', ]); // 2. データベースへ保存 $book = Book::create($validated); // 3. 一覧画面へ戻り、完了メッセージを出す return redirect()->route('books.index') ->with('status', '書籍を登録しました!'); } } |
2-2. プルダウン用データの取得
2-2-1. スコープなしの論理削除の場合(第一弾)
テーブルを設計する際、論理削除での仕様を考えたため、レコード取得の際は(削除フラグが0の場合)という条件を付与する必要があります。
なので、まず思いつくのが下記の形だと思います。
|
1 2 3 |
// プルダウンの選択肢として使用するため取得。また、is_deleted が 0 のデータだけを取得する $categories = Category::where('is_deleted', 0)->get(); $publishers = Publisher::where('is_deleted', 0)->get(); |
2-2-2. スコープを採用(第二弾)
「論理削除されていないデータだけを取得する」という処理は、登録画面だけでなく、一覧画面や他の場所でも何度も使います。そのたびに where('is_deleted', 0) と書くと、もし将来「1を有効、0を削除」に変更したくなったときに修正が大変です。
そこで、モデル側に 「ローカルスコープ」 という名前を付けた条件を定義しておくのが一般的です。
手順1. モデルにスコープを定義
App\Models\Category.php および App\Models\Publisher.php に以下を追記します。
なお、この際にメソッド名を scopeXXXX とすると、呼び出すときに XXXX() という名前で使えます。
|
1 2 3 4 5 6 7 |
/** * 有効なデータ(is_deleted = 0)のみに絞り込むスコープ */ public function scopeActive($query) { return $query->where('is_deleted', 0); } |
手順2. コントローラーで呼び出す
コントローラーが非常に読みやすくなります。
|
1 2 3 4 5 6 7 8 |
public function create() { // 「Activeなものだけ取得」という意図が明確になる $categories = Category::active()->get(); $publishers = Publisher::active()->get(); return view('books.create', compact('categories', 'publishers')); } |
2-2-3. マジックナンバーを避ける
where('is_deleted', 0) の 0 のような数字を直接コードに書き続けると、後で意味がわからなくなりがちです。今回のように「Active」という名前を付ける(スコープ化する)ことで、「何をしているコードか」が誰が見ても一目でわかるようになります。
まずは「スコープなし」で動作を確認し、慣れてきたら「スコープあり」に挑戦してコードを綺麗に保つのがおすすめ。
慣れる意味もこめて、スコープありのコード体系を構築しましょう。
ということで、上記手順1および2を実施し、スコープありのコードを採用することにします。
2-3. 論理削除について思うこと
テーブル設定で論理削除を是とするか否とするかは、本当にいろんな考えがあると思います。
その方法としても、今回は単純なフラグとしての is_deleted カラムを採用しましたが、削除の場合に日時を登録する方式(deleted_at)など、こちらも色々通ると思われます。
これは一概にどの方法が良いという答えはなく、プロジェクトやシステムに合った方式を熟考するというのが一般的な考えだと思われます。
また、論理削除ではないテーブル構造(=物理削除)の場合では全件を取得すれば良いとなるので、下記の取得方法でOKとなります。
|
1 2 3 |
// プルダウンの選択肢として使うために全件取得 $categories = Category::all(); $publishers = Publisher::all(); |
2-3-1. レコードの取得方法について
今更ですが、レコードの取得方法について。
上で挙げた all() は「テーブルの全データを無条件で取ってくる」というメソッドです。これは楽に使えます。
逆に条件を付け加えたいときは、クエリビルダという「条件を組み立てる道具」の where() を使い、最後に get() で実行するという流れになります。
参考ですが、クエリビルダで is_deleted が 0 という条件式は以下のように書きます。
|
1 |
$categories = Category::where('is_deleted', 0)->get(); |
3. バリデーションについて
3-1. 代表的なバリデーションルール
バリデーションの書き方と意味について軽くまとめます。
代表的なものを少し挙げると、下記のようなものがあります。
| バリデーションルール | チェックする内容 |
|---|---|
| integer | 整数値かどうか |
| max: 値 | 指定した値以下かどうか |
| min: 値 | 指定した値以上かどうか |
| requierd | 値が入っているかどうか |
| size: 値 | 指定した値と同じサイズ / 文字数かどうか |
他にも様々なバリデーションルールがありますので、公式マニュアルを読むのも良いでしょう。
3-2. 今回使用するバリデーションルール
今回のルールとして採用したものを見ていきましょう。
1項目につき複数のルールを当てる場合は | (パイプ文字)で区切ることで可能です。
|
1 2 3 4 5 6 7 8 |
// 1. バリデーション(入力チェック) $validated = $request->validate([ 'title' => 'required|max:255', 'category_id' => 'required|exists:m_categories,id', 'publisher_id' => 'required|exists:m_publishers,id', 'price' => 'nullable|numeric|min:0', 'description' => 'nullable|string', ]); |
‘title’ => ‘required|max:255’,
「必須」、「文字数255文字以下であること」という入力ルールとなります。
‘category_id’ => ‘required|exists:m_categories,id’,
exists : データベーステーブル名, カラム名 と明示的に指定することができます。
なので「m_categories テーブルの id カラムの値に含まれている値であること」というルールとなります。
‘publisher_id’ => ‘required|exists:m_publishers,id’,
同様に、「m_publishers テーブルの id カラム値に含まれている値であること」というルールとなります。
‘price’ => ‘nullable|numeric|min:0’,
「フィールドが null 値であることを許容」、「フィールドは数値であること」、「フィールドは0 以上であること」というルールとなります。
‘description’ => ‘nullable|string’,
「フィールドが null 値であることを許容」、「文字列タイプであること」というルールとなります。
4. ビューの作成
4-1. 登録フォーム
Breezeのコンポーネントスタイル(<x-app-layout>)で作成します。
前回でも書きましたが、ビューファイルには作成コマンドは無く、手動でファイルを作成します。
編集ファイル: resources/views/books/create.blade.php
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
<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 p-6 shadow sm:rounded-lg"> {{-- バリデーションエラーの表示 --}} @if ($errors->any()) <div class="mb-4 p-4 bg-red-100 text-red-700 rounded"> <ul> @foreach ($errors->all() as $error) <li>・{{ $error }}</li> @endforeach </ul> </div> @endif <form action="{{ route('books.store') }}" method="POST"> @csrf {{-- ★Laravelのセキュリティ対策(必須) --}} <div class="mb-4"> <label class="block font-medium text-sm text-gray-700">タイトル</label> <input type="text" name="title" value="{{ old('title') }}" class="w-full border-gray-300 rounded-md shadow-sm"> </div> <div class="mb-4"> <label class="block font-medium text-sm text-gray-700">カテゴリ</label> <select name="category_id" class="w-full border-gray-300 rounded-md shadow-sm"> <option value="">選択してください</option> @foreach($categories as $category) <option value="{{ $category->id }}" {{ old('category_id') == $category->id ? 'selected' : '' }}> {{ $category->category_name }} </option> @endforeach </select> </div> <div class="mb-4"> <label class="block font-medium text-sm text-gray-700">出版社</label> <select name="publisher_id" class="w-full border-gray-300 rounded-md shadow-sm"> <option value="">選択してください</option> @foreach($publishers as $publisher) <option value="{{ $publisher->id }}" {{ old('publisher_id') == $publisher->id ? 'selected' : '' }}> {{ $publisher->publisher_name }} </option> @endforeach </select> </div> <div class="mb-4"> <label class="block font-medium text-sm text-gray-700">価格</label> <input type="number" name="price" value="{{ old('price') }}" class="w-full border-gray-300 rounded-md shadow-sm"> </div> <div class="mb-4"> <label class="block font-medium text-sm text-gray-700">詳細説明</label> <textarea name="description" class="w-full border-gray-300 rounded-md shadow-sm">{{ old('description') }}</textarea> </div> <div class="flex items-center justify-end mt-4"> <a href="{{ route('books.index') }}" class="mr-4 text-sm text-gray-600 hover:underline">キャンセル</a> <x-primary-button> 登録する </x-primary-button> </div> </form> </div> </div> </div> </x-app-layout> |
4-2. ビュー作成におけるポイント解説
4-2-1. @csrf とは?
フォームの中に必ず書く「おまじない」です。
これは 「クロスサイト・リクエスト・フォージェリ」(CSRF) という攻撃を防ぐためのセキュリティトークンを発行します。これがないと、Laravelはセキュリティエラー(419エラー)を出して保存を拒否します。
以下は参考記事。
4-2-2. old('title') とは?
もし入力内容にミス(バリデーションエラー)があって画面が戻ってきたとき、「先程まで入力していた内容」 を消さずに残しておくための便利な機能です。
4-2-3. バリデーションエラーの表示
|
1 2 3 4 5 6 7 8 9 |
@if ($errors->any()) <div class="mb-4 p-4 bg-red-100 text-red-700 rounded"> <ul> @foreach ($errors->all() as $error) <li>・{{ $error }}</li> @endforeach </ul> </div> @endif |
trueを返します。@if (count($errors) > 0) のようにして判定も可能ですが、より洗練された書き方を使うのが良さそうです。4-3. 画面キャプチャ
今回作成した画面のキャプチャです。
4-3-1. 書籍登録画面全体
書籍登録画面です。
最低限の登録フォームのみです。(ISBNコード、発行年、タグなどは未実装)
4-3-2. カテゴリ・出版社(プルダウン)
カテゴリと出版社はプルダウンで選択できるようになっています。
4-3-3. バリデーションエラーの表示
何も入力せずに「登録する」ボタンを押下した場合。
タイトル、カテゴリ、出版社のバリデーションエラーとなります。
4-3-4. old() テスト
タイトルに「Javascript逆引きレシピ」と入力しした場合。
カテゴリ、出版社のバリデーションエラーとなりメッセージが表示されるのと、入力したタイトル用フォームに「Javascript逆引きレシピ」がそのまま残っています。
old() が効いているということですね。
4-3-5. 入力テスト
タイトル:テスト書籍1
カテゴリ:文芸書
出版社:株式会社 石田
概要:1
で登録ボタン押下します。
4-3-6. 登録後一覧画面
登録後の一覧ページの3ページ目の画面です。
4-3-7. 新規登録データの詳細画面
新規登録データの詳細ページです。
以上で、ひとまず最低限ですが CRUD の C と R が完成しました。
引き続き、不足している項目の追加、画像投稿、その後に 更新、削除と続けていきたいと思います。
関連するポスト











