Trong các bài trước, chúng ta đã xây dựng CRUD Categories và CRUD Posts.
Ở bài này, chúng ta tiếp tục xây dựng một chức năng rất quan trọng của Blog CMS:
👤 Quản lý người dùng — User Management
Sau bài học, trang quản trị có thể:
👥 Hiển thị danh sách User
🔎 Tìm kiếm User
🛡️ Đổi Role
🔒 Khóa / mở khóa tài khoản
🔑 Reset Password
🗑️ Xóa User
📄 Phân trang
🔗 Kết hợp với Middleware / Authorization
1. Mục tiêu bài học
Chúng ta sẽ xây dựng trang:
/admin/users
Giao diện quản trị dự kiến:
┌──────────────────────────────────────────────────────────────┐
│ 👥 Quản lý Users │
├──────────────────────────────────────────────────────────────┤
│ 🔍 [ Tìm kiếm... ] [ + Thêm User ] │
├────┬────────────────┬─────────────────┬─────────┬────────────┤
│ # │ User │ Email │ Role │ Trạng thái │
├────┼────────────────┼─────────────────┼─────────┼────────────┤
│ 1 │ Admin │ admin@gmail.com │ Admin │ 🟢 Active │
│ 2 │ Nguyễn Văn A │ a@gmail.com │ User │ 🔴 Locked │
│ 3 │ Trần Văn B │ b@gmail.com │ Editor │ 🟢 Active │
└────┴────────────────┴─────────────────┴─────────┴────────────┘
Người quản trị có thể thực hiện:
User
│
├── Xem danh sách
├── Tìm kiếm
├── Đổi Role
├── Khóa tài khoản
├── Mở khóa
├── Reset Password
└── Xóa
2. Kiến trúc User Management
Chức năng User Management vẫn tuân theo mô hình MVC:
Browser
│
▼
Route
│
▼
UserController
│
▼
User Model
│
▼
MySQL
Sau đó Controller trả dữ liệu về Blade:
UserController
│
▼
resources/views/users/
│
├── index.blade.php
├── edit.blade.php
└── ...
3. Kiểm tra bảng users
Laravel đã có bảng users từ lúc cài đặt Breeze.
Thông thường bảng có các trường:
users
├── id
├── name
├── email
├── email_verified_at
├── password
├── remember_token
├── created_at
└── updated_at
Tuy nhiên Blog CMS cần thêm thông tin quản trị.
Chúng ta cần ít nhất:
role
is_active
Trong đó:
role
Xác định quyền của User:
admin
editor
user
is_active
Xác định tài khoản có đang hoạt động hay không:
1 = Active
0 = Locked
4. Tạo migration thêm role và is_active
Nếu project của bạn chưa có hai trường này, chạy:
php artisan make:migration add_role_and_is_active_to_users_table --table=users
Mở migration vừa tạo:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('role')
->default('user')
->after('email');
$table->boolean('is_active')
->default(true)
->after('role');
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn([
'role',
'is_active',
]);
});
}
};
Sau đó:
php artisan migrate
5. Cấu trúc bảng Users
Sau migration:
users
│
├── id
├── name
├── email
├── role
├── is_active
├── email_verified_at
├── password
├── remember_token
├── created_at
└── updated_at
Ví dụ:
1 | Admin | admin@gmail.com | admin | 1
2 | Nguyễn A | a@gmail.com | user | 1
3 | Trần B | b@gmail.com | editor | 0
6. Cập nhật User Model
Mở:
app/Models/User.php
Thêm role và is_active vào $fillable.
protected $fillable = [
'name',
'email',
'password',
'role',
'is_active',
];
Laravel 12 cũng hỗ trợ khai báo cast theo cách hiện đại bằng casts().
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
'is_active' => 'boolean',
];
}
Như vậy:
$user->is_active
sẽ trả về:
true
hoặc:
false
thay vì chỉ làm việc với 0 và 1.
7. Tạo User Controller
Chúng ta sử dụng Resource Controller:
php artisan make:controller UserController --resource
Laravel tạo:
app/Http/Controllers/UserController.php
Resource Controller có các method:
index()
create()
store()
show()
edit()
update()
destroy()
Trong bài này chúng ta chủ yếu sử dụng:
index()
edit()
update()
destroy()
8. Import User Model
Mở:
app/Http/Controllers/UserController.php
Thêm:
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
9. Hiển thị danh sách Users
Method index():
public function index(Request $request)
{
$search = $request->input('search');
$users = User::query()
->when($search, function ($query, $search) {
$query->where('name', 'like', "%{$search}%")
->orWhere('email', 'like', "%{$search}%");
})
->latest()
->paginate(10)
->withQueryString();
return view('users.index', compact('users'));
}
Ở đây chúng ta sử dụng:
when()
để chỉ tìm kiếm khi người dùng nhập từ khóa.
Ví dụ:
/admin/users?search=nguyen
Laravel sẽ tìm:
name LIKE '%nguyen%'
hoặc:
email LIKE '%nguyen%'
10. Route
Mở:
routes/web.php
Thêm:
use App\Http\Controllers\UserController;
Route::middleware('auth')->group(function () {
Route::resource('users', UserController::class);
});
Nếu muốn URL có dạng:
/admin/users
thì nên tổ chức route:
Route::middleware('auth')
->prefix('admin')
->name('admin.')
->group(function () {
Route::resource('users', UserController::class);
});
Khi đó:
GET /admin/users
GET /admin/users/create
POST /admin/users
GET /admin/users/{user}
GET /admin/users/{user}/edit
PUT/PATCH /admin/users/{user}
DELETE /admin/users/{user}
Tên route:
admin.users.index
admin.users.create
admin.users.store
admin.users.show
admin.users.edit
admin.users.update
admin.users.destroy
11. View danh sách Users
Tạo:
resources/views/users/index.blade.php
Ví dụ:
<x-app-layout>
<div class="max-w-7xl mx-auto py-8">
<x-ui.card
title="Quản lý Users"
description="Quản lý tài khoản người dùng">
{{-- SEARCH --}}
<form
action="{{ route('admin.users.index') }}"
method="GET"
class="mb-6">
<div class="flex gap-2">
<input
type="text"
name="search"
value="{{ request('search') }}"
placeholder="Tìm theo tên hoặc email..."
class="flex-1 rounded-lg border-gray-300">
<button
type="submit"
class="px-4 py-2 bg-gray-800 text-white rounded-lg">
🔍 Tìm kiếm
</button>
</div>
</form>
{{-- TABLE --}}
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="border-b">
<th class="text-left p-3">
#
</th>
<th class="text-left p-3">
User
</th>
<th class="text-left p-3">
Email
</th>
<th class="text-left p-3">
Role
</th>
<th class="text-left p-3">
Trạng thái
</th>
<th class="text-right p-3">
Thao tác
</th>
</tr>
</thead>
<tbody>
@forelse ($users as $user)
<tr class="border-b">
<td class="p-3">
{{ $user->id }}
</td>
<td class="p-3 font-semibold">
{{ $user->name }}
</td>
<td class="p-3">
{{ $user->email }}
</td>
<td class="p-3">
@if ($user->role === 'admin')
🛡️ Admin
@elseif ($user->role === 'editor')
✏️ Editor
@else
👤 User
@endif
</td>
<td class="p-3">
@if ($user->is_active)
<span class="text-green-600">
🟢 Active
</span>
@else
<span class="text-red-600">
🔴 Locked
</span>
@endif
</td>
<td class="p-3">
<div class="flex justify-end gap-2">
<a
href="{{ route('admin.users.edit', $user) }}"
class="px-3 py-1 bg-blue-600 text-white rounded">
Sửa
</a>
<form
action="{{ route('admin.users.destroy', $user) }}"
method="POST"
onsubmit="return confirm('Bạn có chắc muốn xóa User này?')">
@csrf
@method('DELETE')
<button
type="submit"
class="px-3 py-1 bg-red-600 text-white rounded">
Xóa
</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td
colspan="6"
class="p-6 text-center text-gray-500">
Không tìm thấy User.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
{{-- PAGINATION --}}
<div class="mt-6">
{{ $users->links() }}
</div>
</x-ui.card>
</div>
</x-app-layout>
12. Tìm kiếm User
Form:
<form method="GET">
sẽ gửi:
/admin/users?search=admin
Controller nhận:
$search = $request->input('search');
Sau đó:
->when($search, function ($query, $search) {
$query->where('name', 'like', "%{$search}%")
->orWhere('email', 'like', "%{$search}%");
})
Ví dụ database:
Nguyễn Văn A
admin@gmail.com
editor@gmail.com
Tìm:
admin
sẽ trả về:
admin@gmail.com
13. Chỉnh sửa User
Method:
public function edit(User $user)
{
return view('users.edit', compact('user'));
}
Laravel tự động Route Model Binding:
User $user
Ví dụ:
/admin/users/5/edit
Laravel tự tìm:
User::findOrFail(5);
14. Form Edit User
Tạo:
resources/views/users/edit.blade.php
<x-app-layout>
<div class="max-w-3xl mx-auto py-8">
<x-ui.card
title="Chỉnh sửa User"
description="Cập nhật thông tin tài khoản">
<form
action="{{ route('admin.users.update', $user) }}"
method="POST">
@csrf
@method('PUT')
{{-- NAME --}}
<div class="mb-4">
<label class="block font-semibold mb-2">
Họ tên
</label>
<input
type="text"
name="name"
value="{{ old('name', $user->name) }}"
class="w-full rounded-lg border-gray-300">
@error('name')
<p class="text-red-600 text-sm mt-1">
{{ $message }}
</p>
@enderror
</div>
{{-- EMAIL --}}
<div class="mb-4">
<label class="block font-semibold mb-2">
Email
</label>
<input
type="email"
name="email"
value="{{ old('email', $user->email) }}"
class="w-full rounded-lg border-gray-300">
@error('email')
<p class="text-red-600 text-sm mt-1">
{{ $message }}
</p>
@enderror
</div>
{{-- ROLE --}}
<div class="mb-4">
<label class="block font-semibold mb-2">
Role
</label>
<select
name="role"
class="w-full rounded-lg border-gray-300">
<option
value="user"
@selected(old('role', $user->role) === 'user')>
👤 User
</option>
<option
value="editor"
@selected(old('role', $user->role) === 'editor')>
✏️ Editor
</option>
<option
value="admin"
@selected(old('role', $user->role) === 'admin')>
🛡️ Admin
</option>
</select>
</div>
{{-- STATUS --}}
<div class="mb-6">
<label class="block font-semibold mb-2">
Trạng thái
</label>
<label class="flex items-center gap-2">
<input
type="checkbox"
name="is_active"
value="1"
@checked(old('is_active', $user->is_active))>
<span>
🟢 Tài khoản đang hoạt động
</span>
</label>
</div>
{{-- BUTTON --}}
<div class="flex gap-2">
<button
type="submit"
class="px-5 py-2 bg-blue-600 text-white rounded-lg">
💾 Lưu thay đổi
</button>
<a
href="{{ route('admin.users.index') }}"
class="px-5 py-2 bg-gray-200 rounded-lg">
Hủy
</a>
</div>
</form>
</x-ui.card>
</div>
</x-app-layout>
15. Validation
Trong update():
$request->validate([
'name' => [
'required',
'string',
'max:255',
],
'email' => [
'required',
'email',
'max:255',
'unique:users,email,' . $user->id,
],
'role' => [
'required',
'in:admin,editor,user',
],
'is_active' => [
'nullable',
'boolean',
],
]);
Điểm quan trọng:
'unique:users,email,' . $user->id
cho phép User giữ nguyên email hiện tại.
Ví dụ User:
id = 5
email = admin@gmail.com
khi sửa User số 5 vẫn được phép giữ:
admin@gmail.com
16. Cập nhật User
Method hoàn chỉnh:
public function update(Request $request, User $user)
{
$validated = $request->validate([
'name' => [
'required',
'string',
'max:255',
],
'email' => [
'required',
'email',
'max:255',
'unique:users,email,' . $user->id,
],
'role' => [
'required',
'in:admin,editor,user',
],
'is_active' => [
'nullable',
'boolean',
],
]);
$validated['is_active'] = $request->boolean('is_active');
$user->update($validated);
return redirect()
->route('admin.users.index')
->with('success', 'Cập nhật User thành công.');
}
17. Khóa tài khoản
Đây là chức năng rất quan trọng.
Không nhất thiết phải xóa User.
Thay vào đó:
is_active = 0
Ví dụ:
Nguyễn Văn A
email: a@gmail.com
role: user
is_active: 0
Tài khoản vẫn tồn tại trong database nhưng bị khóa.
18. Kiểm tra tài khoản bị khóa
Chúng ta có thể kiểm tra trong Middleware.
Ví dụ:
if (! auth()->user()->is_active) {
auth()->logout();
return redirect()
->route('login')
->withErrors([
'email' => 'Tài khoản của bạn đã bị khóa.',
]);
}
Tuy nhiên với hệ thống thực tế, nên tạo Middleware riêng.
Ví dụ:
php artisan make:middleware CheckUserActive
Sau đó đưa logic kiểm tra tài khoản vào Middleware.
Đây sẽ là nền tảng tốt để phát triển hệ thống quản trị hoàn chỉnh.
19. Reset Password
Admin cũng cần khả năng reset password cho User.
Laravel cung cấp:
Hash::make()
để hash password.
Import:
use Illuminate\Support\Facades\Hash;
Ví dụ:
$user->update([
'password' => Hash::make('12345678'),
]);
Tuy nhiên không nên hard-code password mặc định trong hệ thống thực tế.
Tốt hơn là Admin nhập password mới.
20. Thêm Reset Password vào Form
Trong form edit:
<div class="mb-6">
<label class="block font-semibold mb-2">
Password mới
</label>
<input
type="password"
name="password"
class="w-full rounded-lg border-gray-300"
placeholder="Để trống nếu không muốn đổi">
@error('password')
<p class="text-red-600 text-sm mt-1">
{{ $message }}
</p>
@enderror
</div>
Validation:
'password' => [
'nullable',
'string',
'min:8',
'confirmed',
],
Form cần thêm:
<input
type="password"
name="password_confirmation"
class="w-full rounded-lg border-gray-300">
21. Cập nhật Password an toàn
Không nên làm:
$user->password = $request->password;
vì password sẽ được lưu trực tiếp.
Nên dùng:
$user->password = Hash::make($request->password);
Hoặc Laravel có thể tự hash nếu Model đã khai báo:
'password' => 'hashed',
trong casts().
Khi đó:
$user->update([
'password' => $request->password,
]);
Laravel sẽ xử lý việc hash.
22. Hoàn chỉnh method update()
Có thể viết:
public function update(Request $request, User $user)
{
$validated = $request->validate([
'name' => [
'required',
'string',
'max:255',
],
'email' => [
'required',
'email',
'max:255',
'unique:users,email,' . $user->id,
],
'role' => [
'required',
'in:admin,editor,user',
],
'password' => [
'nullable',
'string',
'min:8',
'confirmed',
],
'is_active' => [
'nullable',
'boolean',
],
]);
$validated['is_active'] = $request->boolean('is_active');
if (empty($validated['password'])) {
unset($validated['password']);
}
$user->update($validated);
return redirect()
->route('admin.users.index')
->with('success', 'Cập nhật User thành công.');
}
Nhờ:
unset($validated['password']);
nếu Admin không nhập password mới thì password cũ vẫn được giữ nguyên.
23. Xóa User
Method:
public function destroy(User $user)
{
$user->delete();
return redirect()
->route('admin.users.index')
->with('success', 'Đã xóa User.');
}
Form:
<form
action="{{ route('admin.users.destroy', $user) }}"
method="POST">
@csrf
@method('DELETE')
<button type="submit">
🗑️ Xóa
</button>
</form>
24. Không nên cho Admin tự xóa chính mình
Đây là một lỗi logic rất dễ xảy ra.
Ví dụ Admin:
admin@gmail.com
đăng nhập vào Dashboard.
Sau đó bấm:
Xóa
chính tài khoản của mình.
Kết quả:
User bị xóa
↓
Session vẫn đang tồn tại
↓
Hệ thống có thể phát sinh lỗi
Do đó nên kiểm tra:
if ($user->id === auth()->id()) {
return back()
->with('error', 'Bạn không thể xóa chính tài khoản của mình.');
}
Method:
public function destroy(User $user)
{
if ($user->id === auth()->id()) {
return back()
->with('error', 'Bạn không thể xóa chính tài khoản của mình.');
}
$user->delete();
return redirect()
->route('admin.users.index')
->with('success', 'Đã xóa User.');
}
25. Không cho Admin tự khóa mình
Tương tự:
Admin
↓
Khóa tài khoản
↓
is_active = false
Sau đó chính Admin sẽ không thể đăng nhập nữa.
Vì vậy cần ngăn:
if ($user->id === auth()->id()) {
...
}
Đây là một nguyên tắc quan trọng trong Admin CMS:
🔐 Không cho tài khoản hiện tại tự vô hiệu hóa quyền truy cập của chính mình.
26. Bảo vệ User Management
Hiện tại:
Route::middleware('auth')
chỉ kiểm tra:
Đã đăng nhập?
Nhưng chưa kiểm tra:
Có phải Admin?
Ví dụ:
User thường
↓
/admin/users
↓
❌ Không được phép
Trong khi:
Admin
↓
/admin/users
↓
✅ Được phép
Đây chính là lý do chúng ta cần:
Authorization
đã học ở Bài 25.
27. Kết hợp Role với Middleware
Ví dụ tạo Middleware:
php artisan make:middleware AdminMiddleware
Logic:
public function handle($request, Closure $next)
{
if (! auth()->check()) {
return redirect()->route('login');
}
if (auth()->user()->role !== 'admin') {
abort(403);
}
return $next($request);
}
Sau đó bảo vệ khu vực Admin:
Route::middleware(['auth', 'admin'])
->prefix('admin')
->name('admin.')
->group(function () {
Route::resource('users', UserController::class);
});
Khi đó:
Guest
↓
Login
User
↓
403 Forbidden
Editor
↓
403 Forbidden
Admin
↓
✅ User Management
28. Sơ đồ quyền
Sau bài này hệ thống có thể có:
┌─────────────┐
│ ADMIN │
└──────┬──────┘
│
┌─────────────┼──────────────┐
↓ ↓ ↓
Users Posts Categories
│
┌─────┼─────┐
↓ ↓ ↓
Role Lock Reset
29. Kiểm tra Route
Chạy:
php artisan route:list
Bạn sẽ thấy:
GET|HEAD admin/users
POST admin/users
GET|HEAD admin/users/{user}/edit
PUT|PATCH admin/users/{user}
DELETE admin/users/{user}
Nếu muốn lọc:
php artisan route:list --path=admin
30. Tạo dữ liệu User bằng Factory
Nếu muốn kiểm tra giao diện với nhiều User:
php artisan tinker
Sau đó:
User::factory()
->count(30)
->create();
Nếu muốn tạo Admin:
User::factory()->create([
'name' => 'Administrator',
'email' => 'admin@example.com',
'role' => 'admin',
'is_active' => true,
]);
31. Cập nhật UserFactory
Trong:
database/factories/UserFactory.php
có thể thêm:
'role' => 'user',
'is_active' => true,
Ví dụ:
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => static::$password
??= Hash::make('password'),
'remember_token' => Str::random(10),
'role' => 'user',
'is_active' => true,
];
}
32. Seeder Admin
Một hệ thống CMS nên có ít nhất một Admin.
Trong:
database/seeders/DatabaseSeeder.php
có thể tạo:
User::factory()->create([
'name' => 'Admin',
'email' => 'admin@example.com',
'role' => 'admin',
'is_active' => true,
]);
Sau đó:
php artisan db:seed
Hoặc:
php artisan migrate:fresh --seed
⚠️ migrate:fresh --seed sẽ xóa toàn bộ database rồi tạo lại.
Chỉ sử dụng khi đang phát triển hoặc khi bạn thực sự muốn reset database.
33. Những gì chúng ta đã xây dựng
Sau Bài 28:
👥 USER MANAGEMENT
│
├── Danh sách User
│
├── 🔍 Search
│
├── 📄 Pagination
│
├── ✏️ Edit User
│
├── 🛡️ Đổi Role
│
├── 🔒 Lock / Unlock
│
├── 🔑 Reset Password
│
└── 🗑️ Delete User
Database:
users
│
├── id
├── name
├── email
├── role
├── is_active
├── password
├── created_at
└── updated_at
34. Cấu trúc project sau Bài 28
Project Blog CMS lúc này có thể có:
app/
├── Http/
│ ├── Controllers/
│ │ ├── CategoryController.php
│ │ ├── PostController.php
│ │ └── UserController.php
│ │
│ └── Middleware/
│ ├── AdminMiddleware.php
│ └── CheckUserActive.php
│
├── Models/
│ ├── Category.php
│ ├── Post.php
│ └── User.php
│
resources/
└── views/
├── categories/
│ ├── index.blade.php
│ ├── create.blade.php
│ └── edit.blade.php
│
├── posts/
│ ├── index.blade.php
│ ├── create.blade.php
│ └── edit.blade.php
│
└── users/
├── index.blade.php
└── edit.blade.php
35. Luồng hoạt động hoàn chỉnh
Khi Admin truy cập:
/admin/users
Laravel xử lý:
Browser
│
▼
Route
│
▼
auth Middleware
│
▼
admin Middleware
│
▼
UserController@index()
│
▼
User Model
│
▼
MySQL
│
▼
$users
│
▼
users/index.blade.php
│
▼
Browser
Khi sửa:
/admin/users/10/edit
luồng:
Route
↓
UserController@edit
↓
User Model
↓
User #10
↓
users/edit.blade.php
Khi lưu:
PUT /admin/users/10
↓
UserController@update
↓
Validation
↓
User::update()
↓
MySQL
↓
Redirect
↓
/admin/users
36. Bài tập thực hành
Bài tập 1
Tạo trang:
/admin/users
hiển thị:
ID
Name
Email
Role
Status
Action
Bài tập 2
Thêm tìm kiếm:
Name
Email
Bài tập 3
Thêm:
Pagination 10 User/page
Bài tập 4
Cho phép Admin đổi:
User
Editor
Admin
Bài tập 5
Thêm:
Active
Locked
Bài tập 6
Không cho Admin:
Tự xóa mình
Bài tập 7
Không cho Admin:
Tự khóa mình
Bài tập 8
Thêm reset password:
Password mới
Confirm Password
🎯 Tổng kết Bài 28
Trong bài này chúng ta đã xây dựng một User Management cơ bản cho Blog CMS.
Các kiến thức quan trọng:
Resource Controller
↓
User Model
↓
Validation
↓
Search
↓
Pagination
↓
Role
↓
Account Status
↓
Password Reset
↓
Authorization
Điểm quan trọng nhất của bài này không chỉ là CRUD User.
Chúng ta bắt đầu chuyển từ:
CRUD thông thường
sang:
QUẢN TRỊ HỆ THỐNG
Một User không còn đơn giản chỉ là:
name
email
password
mà bắt đầu có:
ROLE
STATUS
PERMISSION
AUTHORIZATION
Đây chính là nền móng để xây dựng một Admin Dashboard thực tế.
📚 Sau Bài 28
Blog CMS hiện đã có:
BLOG CMS
│
┌────────────┼────────────┐
↓ ↓ ↓
Categories Posts Users
│ │ │
↓ ↓ ↓
CRUD CRUD CRUD
│
┌─────────┼─────────┐
↓ ↓ ↓
Role Lock Password
Bài 29 — Search & DataTables sẽ nâng cấp hệ thống danh sách hiện tại, đặc biệt là Users và Posts, với:
🔎 Search nâng cao
↕️ Sort
🏷️ Filter
📄 Pagination
📊 DataTables.net
⚡ Tương tác bảng dữ liệu chuyên nghiệp hơn




Không có nhận xét nào:
Đăng nhận xét