【Laravel】CRUD機能を作成する③更新機能(Update)
はじめに
下記の続きになります。
ユーザーの一覧から編集を押すと編集画面表示され、データを更新できるようにしたいと思います。
編集ボタンの追加
@extends('layouts.app')
@section('title', 'ユーザー一覧')
@section('content')
<h1 class="text-2xl font-bold mb-4">ユーザー一覧</h1>
<!-- ユーザー登録ページへのリンク -->
<a href="{{ route('users.create') }}"
class="mb-4 inline-block bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
ユーザー登録
</a>
<!-- セッションメッセージ -->
@if (session('message'))
<div><strong>{{ session('message') }}</strong></div>
@endif
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead>
<tr>
<th class="px-6 py-3 bg-gray-50 text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th>
<th class="px-6 py-3 bg-gray-50 text-xs font-medium text-gray-500 uppercase tracking-wider">名前</th>
<th class="px-6 py-3 bg-gray-50 text-xs font-medium text-gray-500 uppercase tracking-wider">操作</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@foreach ($users as $user)
<tr>
<td class="px-6 py-4 whitespace-nowrap">{{ $user->id }}</td>
<td class="px-6 py-4 whitespace-nowrap"><a href="{{ route('users.show', $user->id) }}"
class="text-blue-500 hover:underline">{{ $user->name }}</a></td>
<td class="px-6 py-4 whitespace-nowrap">
<a href="{{ route('users.edit', $user->id) }}"
class="text-indigo-600 hover:text-indigo-900">編集</a>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@endsection
編集画面の追加
resources/views/users/edit.blade.php
を作成してください。
php artisan make:view users/edit
パスワードは①の登録機能の場合と異なり、入力された場合のみ変更するようにしています。
@extends('layouts.app')
@section('title', 'ユーザー情報編集')
@section('content')
<form method="POST" action="{{ route('users.update', $user->id) }}">
@csrf
@method('PUT')
<div>
<h1 class='text-center font-bold '>ユーザー情報編集(変更する箇所のみ入力してください)</h1>
<!-- 名前フィールド -->
<div class="mt-4">
<label for="name">名前</label>
<input id="name" type="text"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
name="name" value="{{ old('name', $user->name) }}" required placeholder="">
@error('name')
<div class="text-red-500">{{ $message }}</div>
@enderror
</div>
<!-- メールアドレスフィールド -->
<div class="mt-4">
<label for="email">メールアドレス</label>
<input id="email" type="email"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
name="email" value="{{ old('email', $user->email) }}" required placeholder="">
@error('email')
<div class="text-red-500">{{ $message }}</div>
@enderror
</div>
<!-- パスワードフィールド -->
<div class="mt-4">
<label for="password">新しいパスワード(変更する場合のみ入力)</label>
<input id="password" type="password"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
name="password" placeholder="">
@error('password')
<div class="text-red-500">{{ $message }}</div>
@enderror
</div>
<!-- セッションメッセージ -->
@if (session('message'))
<div><strong>{{ session('message') }}</strong></div>
@endif
<!-- ユーザー情報の表示 -->
@if (session('user'))
<div>
<h2>登録したユーザーの情報</h2>
<p>名前: {{ session('user')->name }}</p>
<p>メールアドレス: {{ session('user')->email }}</p>
@if (session('passwordChanged'))
<p>パスワード: ********</p>
@endif
</div>
@endif
<!-- 更新ボタン -->
<div class="flex items-center justify-center my-4">
<button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
更新を完了する
</button>
</div>
<div class="mt-4">
<a href="{{ route('users.index') }}" class="text-blue-500 hover:underline">ユーザー一覧に戻る</a>
</div>
</div>
</form>
@endsection
ルート設定
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\UsersController;
Route::prefix('users')->group(function () {
Route::get('/', [UsersController::class, 'index'])->name('users.index');
Route::get('/create', [UsersController::class, 'create'])->name('users.create');
Route::post('/store', [UsersController::class, 'store'])->name('users.store');
Route::get('/{user}', [UsersController::class, 'show'])->name('users.show');
Route::get('/{user}/edit', [UsersController::class, 'edit'])->name('users.edit');
Route::put('/{user}', [UsersController::class, 'update'])->name('users.update');
});
ルートモデルバインディング
②の読み込み機能ではRoute::get('/{id}', [UsersController::class, 'show'])->name('users.show');
と記述していたものの{id}
を{user}
に変更しています。
これはルートモデルバインディングを使用しており、コントローラーのメソッドの引数としてモデルのインスタンスを直接受け取ることができます。
先ほどのルート設定では、{user}
というパラメーターがUser
モデルのインスタンスと紐付いています。一致するモデルのインスタンスがデータベースで見つからない場合、404 HTTP レスポンスが自動的に生成されます。
次に、UsersController
のshow
メソッドを次のように変更します。
public function show(User $user)
{
return view('users.edit', compact('user'));
}
このようにすることで、edit
メソッドの引数としてUser
モデルのインスタンスが自動的に注入されます。これにより、コントローラー内で明示的にデータを取得する必要がなくなり、より簡潔なコードを書くことができます。
ちなみに、ルートモデルバインディングもfind
と同様に対象のモデルを検索する際に、ユーザーからの入力値を直接クエリに組み込むのではなく、プレースホルダーを使用して安全に検索を行っているため、SQLインジェクション対策が行われています。
id以外のカラムを基準にするには、下記のようにルート設定を行ってください。
Route::get('/{user:email}', [UsersController::class, 'show'])->name('users.show');
または、getRouteKeyName
メソッドを使用することで基準を指定できます。
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use HasFactory, Notifiable;
/**
* Get the route key for the model.
*
* @return string
*/
public function getRouteKeyName()
{
return 'email';
}
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* @var array<int, string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed', // パスワードをハッシュ化
];
}
}
getRouteKeyName
メソッドを使用した場合には、ルート設定は以下のままで問題ないです。
Route::get('/{user}', [UsersController::class, 'show'])->name('users.show');
リクエストクラスの作成
①で登録用のリクエストは作成しましたが、更新した場合のリクエストはまだないので作成します。
php artisan make:request UpdateUserRequest
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class UpdateUserRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules(): array
{
$userId = $this->user->id;
$rules = [
'name' => 'required|string|max:255',
'email' => [
'required',
'string',
'email',
'max:255',
Rule::unique('users')->ignore($userId), // 重複チェックで自分のメールアドレスを除外
],
];
// パスワードが入力されている場合にのみ、パスワードのバリデーションルールを適用
if (!empty($this->input('password'))) {
$rules['password'] = ['string', 'min:8', 'regex:/^(?=.*[a-zA-Z])(?=.*\d)(?=.*[@$!%*?&])/',];
}
return $rules;
}
public function messages(): array
{
return [
'password.regex' => 'パスワードは少なくとも1つの半角英字、数字、および記号(@$!%*?&)を含む必要があります。',
];
}
}
Rule::
Laravelのバリデーションルールを定義するためのクラスです。
バリデーションルールが複雑になった場合は、make:rule
Artisanコマンドを使用してバリデーションルールを作成した方が良いと思います。
FormRequest
$this->input('password')
を使用して入力情報を取得できるのは、FormRequest
クラスがリクエストに含まれるデータを取得するための便利なメソッドを提供しているからです
コントローラー設定
update
メソッドを作成してください。
public function update(UpdateUserRequest $request, User $user)
{
$data = $request->validated();
$passwordChanged = false; // パスワードが変更されたかどうかのフラグ
// パスワードが入力されている場合のみ更新
if (!empty($data['password'])) {
$passwordChanged = true;
} else {
unset($data['password']); // パスワードフィールドをデータ配列から削除
}
DB::beginTransaction();
try {
$user->update($data);
DB::commit();
return
redirect()->route('users.edit', $user->id)->with(compact('user', 'passwordChanged'));;
} catch (\Exception $e) {
DB::rollback();
return back()->with(['message' => '更新中にエラーが発生しました。' . $e->getMessage()]);
}
}
次はCRUDの④削除機能(Delete)についての記事を書きたいと思います!
終わりに
何かありましたらお気軽にコメント等いただけると助かります。
ここまでお読みいただきありがとうございます🎉
Discussion