Memo

メモ > 技術 > フレームワーク: Laravel10 > 複雑なCRUDの新規作成例(記事管理を作成)

複雑なCRUDの新規作成例(記事管理を作成)
■テーブルの作成
$ sail artisan make:migration create_entries_table
以下のファイルが作成される。 database/migrations/2024_04_18_090608_create_entries_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('entries', function (Blueprint $table) { $table->id(); $table->timestamps(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('entries'); } };
以下のとおり修正する。
<?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('entries', function (Blueprint $table) { $table->id(); $table->dateTime('datetime')->comment('日時'); $table->string('title')->comment('タイトル'); $table->text('text')->comment('本文'); $table->foreignId('user_id'); $table->timestamps(); $table->softDeletes(); $table->comment('記事'); }); Schema::create('category_entry', function (Blueprint $table) { $table->foreignId('category_id'); $table->foreignId('entry_id'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('entries'); Schema::dropIfExists('category_entry'); } };
マイグレーションを実行。
$ sail artisan migrate
■モデルの作成
$ sail artisan make:model Entry
以下のファイルが作成される。 app/Models/Entry.php
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Entry extends Model { use HasFactory; }
以下のとおり修正する。
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; class Entry extends Model { use SoftDeletes; protected $fillable = [ 'datetime', 'title', 'text', 'user_id', ]; protected $casts = [ 'datetime' => 'datetime', ]; public function user(): BelongsTo { return $this->belongsTo(User::class); } public function categories(): BelongsToMany { return $this->belongsToMany(Category::class); } }
■リクエストの作成
$ sail artisan make:request EntryCreateRequest
以下のファイルが作成される。 app/Http/Requests/EntryCreateRequest.php
<?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class EntryCreateRequest extends FormRequest { /** * Determine if the user is authorized to make this request. */ public function authorize(): bool { return false; } /** * Get the validation rules that apply to the request. * * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string> */ public function rules(): array { return [ // ]; } }
以下のとおり修正する。
<?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; class EntryCreateRequest 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 [ 'datetime' => ['required'], 'title' => ['required', 'string', 'max:80'], 'text' => ['required', 'string', 'max:1000'], 'user_id' => ['required', 'integer'], 'categories' => ['required'], ]; } public function attributes(): array { return [ 'datetime' => '日時', 'title' => 'タイトル', 'text' => '本文', 'user_id' => 'ユーザ', 'categories' => 'カテゴリ', ]; } }
以下のファイルを作成する。 app/Http/Requests/EntryUpdateRequest.php
<?php namespace App\Http\Requests; class EntryUpdateRequest extends EntryCreateRequest { }
■コントローラーの作成
$ sail artisan make:controller EntryController
以下のファイルが作成される。 app/Http/Controllers/EntryController.php
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class EntryController extends Controller { // }
以下のとおり修正する。
<?php namespace App\Http\Controllers; use App\Http\Requests\EntryCreateRequest; use App\Http\Requests\EntryUpdateRequest; use Illuminate\Http\RedirectResponse; use App\Models\Entry; use App\Models\User; use App\Models\Category; use Illuminate\Support\Facades\Redirect; use Illuminate\View\View; class EntryController extends Controller { public function index(): View { $entry = new Entry; return view('entry.index', [ 'entries' => $entry->with('user')->get(), ]); } public function create(): View { $users = new User; $categories = new Category; return view('entry.form', [ 'categories' => $categories->get(), 'users' => $users->get(), ]); } public function store(EntryCreateRequest $request): RedirectResponse { $entry = new Entry; $entry->fill($request->all())->save(); $entry->categories()->sync($request->categories); return Redirect::route('entry.index')->with('message', '登録しました。'); } public function edit($id): View { $entry = Entry::findOrFail($id); $users = new User; $categories = new Category; return view('entry.form', [ 'entry' => $entry, 'categories' => $categories->get(), 'users' => $users->get(), ]); } public function update(EntryUpdateRequest $request, $id): RedirectResponse { $entry = Entry::findOrFail($id); $entry->fill($request->all())->save(); $entry->categories()->sync($request->categories); return Redirect::route('entry.index')->with('message', '編集しました。'); } public function destroy($id): RedirectResponse { $entry = Entry::findOrFail($id); $entry->delete(); return Redirect::route('entry.index')->with('message', '削除しました。'); } }
■ビューの作成 以下のファイルを作成する。 resources/views/entry/index.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 space-y-6"> <div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg"> <div class="max-w-xl"> <section> <header> <h2 class="text-lg font-medium text-gray-900"> 記事一覧 </h2> <p class="mt-1 text-sm text-gray-600"> 記事を管理します </p> </header> @if (session('message')) <p class="text-green-600 bg-green-100 my-4 p-4 border-l-4 border-green-400">{{ session('message') }}</p> @elseif (session('error')) <p class="text-red-600 bg-red-100 my-4 p-4 border-l-4 border-red-400">{{ session('error') }}</p> @endif <p class="my-4"><a class="underline text-gray-600 hover:text-gray-900" href="{{ route('entry.create') }}">記事登録</a></p> <table class="w-full border shadow"> <thead> <th class="border p-2">日時</th> <th class="border p-2">タイトル</th> <th class="border p-2">ユーザ</th> <th class="border p-2">カテゴリ</th> <th class="border p-2">編集</th> </thead> <tbody> @foreach ($entries as $entry) <tr> <td class="border p-2"><div>{{ $entry->datetime->format('Y/m/d H:i:s') }}</div></td> <td class="border p-2"><div>{{ $entry->title }}</div></td> <td class="border p-2"><div>{{ $entry->user->name }}</div></td> <td class="border p-2"> @foreach ($entry->categories as $category) <div>{{ $category->name }}</div> @endforeach </td> <td class="border p-2"><a class="underline text-gray-600 hover:text-gray-900" href="{{ route('entry.edit', ['id' => $entry->id]) }}">編集</a></td> </tr> @endforeach </tbody> </table> </section> </div> </div> </div> </div> </x-app-layout>
resources/views/entry/form.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 space-y-6"> <div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg"> <div class="max-w-xl"> <section> <header> <h2 class="text-lg font-medium text-gray-900"> @if (Request::is('*/edit')) {{ '編集' }} @else {{ '登録' }} @endif </h2> <p class="mt-1 text-sm text-gray-600"> 記事を管理します </p> </header> <form method="post" action="{{ Request::is('entry/edit/*') ? route('entry.update', ['id' => $entry->id]) : route('entry.store') }}" class="mt-6 space-y-6"> @if (Request::is('entry/edit/*')) {{ method_field('patch') }} @endif {{ csrf_field() }} <div> <x-input-label for="datetime" value="日時" /> <x-text-input id="datetime" name="datetime" type="text" class="mt-1 block w-full" value="{{ old('datetime', isset($entry) ? $entry->datetime : '') }}" autofocus autocomplete="datetime" /> <x-input-error class="mt-2" :messages="$errors->get('datetime')" /> </div> <div> <x-input-label for="title" value="タイトル" /> <x-text-input id="title" name="title" type="text" class="mt-1 block w-full" value="{{ old('title', isset($entry) ? $entry->title : '') }}" autocomplete="title" /> <x-input-error class="mt-2" :messages="$errors->get('title')" /> </div> <div> <x-input-label for="text" value="本文" /> <textarea id="text" name="text" rows="10" cols="10" class="border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm mt-1 block w-full" class="form-control">{{ old('text', isset($entry) ? $entry->text : '') }}</textarea> <x-input-error class="mt-2" :messages="$errors->get('text')" /> </div> <div> <x-input-label for="user_id" value="ユーザ" /> <select name="user_id" class="border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm mt-1 block"> <option value=""></option> @foreach ($users as $user) <option value="{{ $user->id }}" @if (old('user_id', isset($entry) ? $entry->user_id : '') == $user->id) selected @endif>{{ $user->name }}</option> @endforeach </select> <x-input-error class="mt-2" :messages="$errors->get('user_id')" /> </div> <div> <x-input-label for="categories" value="カテゴリ" /> @foreach ($categories as $category) <div> <label><input type="checkbox" name="categories[]" value="{{ $category->id }}" class="border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm" @if (in_array($category->id, old('categories', isset($entry) ? array_column($entry->categories->toArray(), 'id') : []))) checked @endif> {{ $category->name }}</label> </div> @endforeach <x-input-error class="mt-2" :messages="$errors->get('categories')" /> </div> <div class="flex items-center gap-4"> <x-primary-button>@if (!Request::is('*/create')) {{ '編集' }} @else {{ '登録' }} @endif</x-primary-button> </div> </form> </section> </div> </div> @if (Request::is('entry/edit/*')) <div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg"> <div class="max-w-xl"> <section> <header> <h2 class="text-lg font-medium text-gray-900"> 削除 </h2> <p class="mt-1 text-sm text-gray-600"> 記事を削除します </p> </header> <form method="post" action="{{ route('entry.destroy', ['id' => $entry->id]) }}" class="mt-6 space-y-6"> {{ method_field('delete') }} {{ csrf_field() }} <div class="flex items-center gap-4"> <x-danger-button>削除</x-danger-button> </div> </form> </section> </div> </div> @endif </div> </div> </x-app-layout>
■ルーティングの設定 以下のとおり設定する。 routes/web.php
use App\Http\Controllers\EntryController; Route::middleware('auth')->group(function () { Route::get('/entry', [EntryController::class, 'index'])->name('entry.index'); Route::get('/entry/create', [EntryController::class, 'create'])->name('entry.create'); Route::post('/entry/store', [EntryController::class, 'store'])->name('entry.store'); Route::get('/entry/edit/{id}', [EntryController::class, 'edit'])->name('entry.edit'); Route::patch('/entry/update/{id}', [EntryController::class, 'update'])->name('entry.update'); Route::delete('/entry/destroy/{id}', [EntryController::class, 'destroy'])->name('entry.destroy');
■動作確認 http://laravel.local/entry

Advertisement