メモ > 技術 > フレームワーク: 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