
Chào mừng các bạn đến với buổi học hôm nay, nơi chúng ta sẽ cùng nhau 'mổ xẻ' một trong những công cụ quyền năng nhất của Laravel để quản lý quyền hạn: Policies.
Trong thế giới lập trình, việc ai được làm gì, với cái gì, là một bài toán đau đầu hơn cả việc chọn cà phê buổi sáng. Giả sử bạn đang xây dựng một ứng dụng, một nền tảng xã hội chẳng hạn. Liệu mọi người dùng có thể xóa bài viết của người khác không? Hay chỉ chủ sở hữu bài viết mới được phép? Đây chính là lúc "kiểm soát viên" Policies của chúng ta ra tay.
1. Policy là gì và để làm gì?
Hãy hình dung thế này: Ứng dụng của bạn là một tòa nhà chọc trời với vô số căn hộ (tài nguyên như bài viết, bình luận, sản phẩm...). Mỗi căn hộ này lại có những quy tắc riêng về việc ai được vào, ai được sửa sang, ai được đập phá. Nếu bạn cứ nhét tất cả các quy tắc đó vào một "bảng thông báo chung" ở sảnh chính (tức là nhồi nhét logic kiểm tra quyền vào controller), thì chẳng mấy chốc nó sẽ trở thành một mớ hỗn độn, đọc vào muốn "tẩu hỏa nhập ma".
Policies trong Laravel chính là những "luật sư riêng" được chỉ định cho từng loại tài nguyên cụ thể (ví dụ: một luật sư cho Post, một luật sư cho Comment, một luật sư cho Product). Thay vì có một "bảng thông báo chung" khổng lồ, mỗi "luật sư" (Policy) này sẽ nắm giữ toàn bộ các quy tắc liên quan đến tài nguyên mà họ phụ trách. Họ biết ai được xem, ai được tạo, ai được chỉnh sửa, ai được xóa một Post cụ thể, hay một Comment cụ thể.
Mục đích chính của Policies:
- Phân tách rõ ràng logic (Separation of Concerns): Giúp tách biệt hoàn toàn logic kiểm tra quyền hạn ra khỏi controller hoặc các thành phần khác, giữ code của bạn sạch sẽ và dễ đọc hơn.
- Tái sử dụng (Reusability): Một khi đã định nghĩa các quy tắc trong Policy, bạn có thể dễ dàng sử dụng lại chúng ở bất cứ đâu trong ứng dụng (controller, blade template, route...).
- Dễ bảo trì và mở rộng: Khi cần thay đổi quy tắc quyền hạn cho một loại tài nguyên, bạn chỉ cần sửa ở một chỗ duy nhất là file Policy tương ứng, thay vì phải "lần mò" khắp các controller.

2. Code Ví Dụ Minh Họa Rõ Ràng
Chúng ta sẽ xây dựng một ví dụ đơn giản với mô hình Post (bài viết). Người dùng có thể tạo, xem, chỉnh sửa và xóa bài viết của chính họ.
Giả định: Bạn đã có project Laravel, model User (mặc định) và model Post với migration tương ứng (có user_id để liên kết với người tạo).
Bước 1: Tạo Policy cho Post
Laravel cung cấp Artisan command để tạo Policy cực kỳ tiện lợi. Chúng ta sẽ thêm cờ --model để tự động tạo các phương thức cơ bản.
php artisan make:policy PostPolicy --model=Post
Lệnh này sẽ tạo ra file app/Policies/PostPolicy.php với cấu trúc cơ bản:
<?php
namespace App\Policies;
use App\Models\User;
use App\Models\Post;
use Illuminate\Auth\Access\Response;
class PostPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
// Mọi người dùng đều có thể xem danh sách bài viết
return true;
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Post $post): bool
{
// Mọi người dùng đều có thể xem một bài viết cụ thể
return true;
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
// Chỉ người dùng đã đăng nhập mới có thể tạo bài viết
return (bool) $user;
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Post $post): bool
{
// Chỉ chủ sở hữu bài viết mới có thể cập nhật bài viết của họ
return $user->id === $post->user_id;
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Post $post): bool
{
// Chỉ chủ sở hữu bài viết mới có thể xóa bài viết của họ
return $user->id === $post->user_id;
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Post $post): bool
{
// Ví dụ: chỉ admin mới có quyền restore
return $user->isAdmin(); // Giả sử có method isAdmin() trong User model
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Post $post): bool
{
// Ví dụ: chỉ admin mới có quyền xóa vĩnh viễn
return $user->isAdmin();
}
/**
* Optional: Before method to grant all abilities to super admins.
* This method runs before any other policy methods.
*/
public function before(User $user, string $ability)
{
if ($user->isSuperAdmin()) {
return true; // Super admin có quyền làm tất cả
}
}
}
Giải thích các phương thức:
- Mỗi phương thức trong Policy nhận đối tượng
Userhiện tại và đối tượngModelđang được kiểm tra (trừviewAnyvàcreate). - Nó trả về
truenếu người dùng được phép, vàfalsenếu không. before(): Một phương thức đặc biệt được gọi trước tất cả các phương thức khác. Rất hữu ích cho các quyền "super admin" hoặc "developer" có thể bỏ qua mọi kiểm tra khác.
Bước 2: Đăng ký Policy
Để Laravel biết Policy nào áp dụng cho Model nào, bạn cần đăng ký nó trong AuthServiceProvider.php.
// app/Providers/AuthServiceProvider.php
namespace App\Providers;
use App\Models\Post;
use App\Policies\PostPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The model to policy mappings for the application.
*
* @var array<class-string, class-string>
*/
protected $policies = [
Post::class => PostPolicy::class,
// 'App\Models\Post' => 'App\Policies\PostPolicy', // Cách cũ
];
/**
* Register any authentication / authorization services.
*/
public function boot(): void
{
//
}
}
Bước 3: Sử dụng Policy trong Controller
Đây là cách bạn "triệu hồi" luật sư Policy của mình trong Controller. Laravel cung cấp method authorize() rất tiện lợi.
// app/Http/Controllers/PostController.php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class PostController extends Controller
{
public function __construct()
{
$this->middleware('auth')->except(['index', 'show']); // Yêu cầu đăng nhập cho hầu hết các action
}
/**
* Display a listing of the resource.
*/
public function index()
{
// Không cần kiểm tra quyền, vì viewAny đã được định nghĩa là true cho mọi người dùng
// Tuy nhiên, bạn vẫn có thể gọi nếu muốn rõ ràng:
// $this->authorize('viewAny', Post::class);
$posts = Post::all();
return view('posts.index', compact('posts'));
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
$this->authorize('create', Post::class); // Kiểm tra quyền tạo bài viết
return view('posts.create');
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$this->authorize('create', Post::class);
$validated = $request->validate([
'title' => 'required|max:255',
'body' => 'required',
]);
Auth::user()->posts()->create($validated);
return redirect()->route('posts.index')->with('success', 'Bài viết đã được tạo thành công!');
}
/**
* Display the specified resource.
*/
public function show(Post $post)
{
$this->authorize('view', $post); // Kiểm tra quyền xem bài viết cụ thể
return view('posts.show', compact('post'));
}
/**
* Show the form for editing the specified resource.
*/
public function edit(Post $post)
{
$this->authorize('update', $post); // Kiểm tra quyền cập nhật bài viết
return view('posts.edit', compact('post'));
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, Post $post)
{
$this->authorize('update', $post);
$validated = $request->validate([
'title' => 'required|max:255',
'body' => 'required',
]);
$post->update($validated);
return redirect()->route('posts.show', $post)->with('success', 'Bài viết đã được cập nhật!');
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Post $post)
{
$this->authorize('delete', $post); // Kiểm tra quyền xóa bài viết
$post->delete();
return redirect()->route('posts.index')->with('success', 'Bài viết đã được xóa!');
}
}
Nếu người dùng không được phép thực hiện hành động, phương thức authorize() sẽ tự động ném ra một Illuminate\Auth\Access\AuthorizationException, và Laravel sẽ xử lý để hiển thị trang lỗi 403 (Forbidden).
Bước 4: Sử dụng Policy trong Blade Templates
Bạn cũng có thể sử dụng Policies để điều khiển việc hiển thị các nút hoặc liên kết trong giao diện người dùng, thông qua các directive @can và @cannot.
<!-- resources/views/posts/show.blade.php -->
<h1>{{ $post->title }}</h1>
<p>{{ $post->body }}</p>
<p>Tác giả: {{ $post->user->name }}</p>
@can('update', $post) {{-- Kiểm tra xem người dùng hiện tại có quyền 'update' bài viết này không --}}
<a href="{{ route('posts.edit', $post) }}">Chỉnh sửa</a>
@endcan
@can('delete', $post)
<form action="{{ route('posts.destroy', $post) }}" method="POST" style="display:inline;">
@csrf
@method('DELETE')
<button type="submit" onclick="return confirm('Bạn có chắc chắn muốn xóa bài viết này?')">Xóa</button>
</form>
@endcan
@can('create', App\Models\Post::class) {{-- Kiểm tra quyền tạo bài viết mới --}}
<a href="{{ route('posts.create') }}">Tạo bài viết mới</a>
@endcan
3. Mẹo Vặt & Best Practices Từ Giảng Đường Harvard
Khi đã nắm vững cơ bản, hãy cùng đào sâu một vài "bí kíp" để Policies của bạn trở nên "sắc bén" và "tinh tế" hơn:
- "Deny by Default" là chân lý: Luôn mặc định từ chối mọi quyền hạn nếu không có quy tắc rõ ràng cho phép. Điều này an toàn hơn rất nhiều so với việc mặc định cho phép và sau đó cố gắng chặn lại. Nếu bạn không định nghĩa một method trong Policy, Laravel sẽ ngầm hiểu là
false(không được phép). - Khi nào dùng Gates, khi nào dùng Policies?
- Policies: Dùng khi bạn cần kiểm soát quyền hạn liên quan đến một model cụ thể (e.g., ai được chỉnh sửa
Postnày, ai được xemUserkia). Policies là cách tổ chức cácGatexung quanh một model. - Gates: Dùng cho các quyền hạn chung chung, không gắn với một model cụ thể (e.g.,
can-access-admin-panel,is-premium-member).
- Policies: Dùng khi bạn cần kiểm soát quyền hạn liên quan đến một model cụ thể (e.g., ai được chỉnh sửa
- Sử dụng
Response::deny()để trả về thông báo rõ ràng: Thay vì chỉreturn false, bạn có thể trả về mộtResponsevới thông báo lỗi tùy chỉnh. Điều này rất hữu ích khi debug hoặc muốn hiển thị thông báo thân thiện hơn cho người dùng.use Illuminate\Auth\Access\Response; public function update(User $user, Post $post): Response { return $user->id === $post->user_id ? Response::allow() : Response::deny('Bạn không có quyền cập nhật bài viết này.'); } - Giữ Policy "gọn gàng, tập trung": Mỗi Policy chỉ nên tập trung vào việc định nghĩa quyền hạn cho một model duy nhất. Đừng cố gắng nhồi nhét logic của nhiều model vào một Policy, bạn sẽ lại tạo ra một "bảng thông báo chung" khác.
viewAnyvàviewkhác nhau thế nào?viewAnykiểm tra xem người dùng có thể xem bất kỳ bản ghi nào của loại đó (thường là xem danh sách).viewkiểm tra xem người dùng có thể xem một bản ghi cụ thể.
4. Ứng Dụng Thực Tế
Policies không phải là khái niệm xa vời, chúng hiện diện khắp mọi nơi trong các ứng dụng web mà bạn sử dụng hàng ngày:
- Mạng xã hội (Facebook, Twitter, Instagram):
- Chỉ bạn mới có thể chỉnh sửa hoặc xóa bài đăng, bình luận của mình (
update,deletePolicy choPost,Comment). - Bạn không thể xem hồ sơ cá nhân của người dùng đã chặn bạn (
viewPolicy choUser). - Chỉ quản trị viên nhóm mới có thể chấp thuận thành viên mới (
approvePolicy choGroupMember).
- Chỉ bạn mới có thể chỉnh sửa hoặc xóa bài đăng, bình luận của mình (
- Nền tảng E-commerce (Shopify, Amazon Seller Central):
- Chỉ người bán sở hữu sản phẩm mới có thể cập nhật thông tin sản phẩm hoặc quản lý kho hàng (
update,manageInventoryPolicy choProduct). - Chỉ quản trị viên hệ thống mới có thể xem báo cáo doanh thu toàn cầu (
viewReportsPolicy choAdminDashboard).
- Chỉ người bán sở hữu sản phẩm mới có thể cập nhật thông tin sản phẩm hoặc quản lý kho hàng (
- Hệ thống Quản lý Nội dung (WordPress, Medium):
- Chỉ tác giả bài viết hoặc biên tập viên mới có thể xuất bản/chỉnh sửa bài viết (
publish,updatePolicy choArticle). - Người dùng thường chỉ có thể bình luận, không thể xóa bình luận của người khác (
createPolicy choComment,deletePolicy chỉ cho chủ sở hữu comment).
- Chỉ tác giả bài viết hoặc biên tập viên mới có thể xuất bản/chỉnh sửa bài viết (
- Công cụ Quản lý Dự án (Jira, Trello):
- Chỉ thành viên của dự án mới có thể xem các tác vụ (
viewPolicy choTask). - Chỉ người được giao nhiệm vụ mới có thể đánh dấu tác vụ là hoàn thành (
completePolicy choTask). - Chỉ người tạo hoặc quản trị viên dự án mới có thể xóa dự án (
deletePolicy choProject).
- Chỉ thành viên của dự án mới có thể xem các tác vụ (
Như bạn thấy, Policies là nền tảng vững chắc để xây dựng các ứng dụng có hệ thống phân quyền phức tạp, nhưng vẫn giữ được sự gọn gàng và dễ quản lý. Hãy coi Policies như "người gác cổng" thông minh và tận tâm, đảm bảo mỗi người dùng chỉ được phép làm những gì họ được ủy quyền, không hơn không kém. Chúc các bạn thành công trong việc xây dựng những ứng dụng an toàn và mạnh mẽ!
Thuộc Series: Lavarel
Bài giảng này được tự động xuất bản ngẫu nhiên từ thư viện kiến thức. Đừng quên đón xem các Từ khoá Hướng Dẫn tiếp theo nhé!