[Laravel] 富都青年的 Policy 議題

Kunyu
5 min readMar 3, 2024
Photo by Ibrahim Boran on Unsplash

系統通常都需要進行使用者授權,也就是判斷使用者是否有權限執行某些操作。在 Laravel 中,我們可以將授權邏輯寫在 Controller 當中,但是這種做法並不優雅,因為會導致 Controller 檔案過於臃腫,而且不易維護。

<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;

class PostController extends Controller
{
public function editPost(Post $post)
{
// 權限判斷,只能編輯自己的文章。
if ($post->user_id !== auth()->user()->id) {
return '您無此權限。';
}

// ...後續邏輯(略)
}
}

設定 Policy

為了解決這個問題,Laravel 提供了 Policy 機制,讓我們可以將授權邏輯獨立出來,並以更優雅的方式進行管理。

(1) 首先需要建立一個 Policy 類別。

php artisan make:policy PostPolicy --model=Post

(2) 撰寫 Policy 規則

<?php

namespace App\Policies;

use App\Models\Post;
use App\Models\User;
use Illuminate\Auth\Access\Response;

class PostPolicy
{
public function delete(User $user, Post $post): bool
{
return $user->id === $post->user_id;
}
}

(3) 註冊 Policy

<?php

namespace App\Providers;

use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
//
Post::class => PostPolicy::class
];
}

使用 Policy

Blade 使用 @can 限制是否顯示內容

@can('update', $post)
<span class="pt-2">
<a href="#" class="text-primary mr-2" data-toggle="tooltip" data-placement="top" title="Edit"><i class="fas fa-edit"></i></a>
<form class="delete-post-form d-inline" action="/post/{{$post->id}}" method="POST">
@csrf
@method('DELETE')
<button class="delete-post-button text-danger" data-toggle="tooltip" data-placement="top" title="Delete"><i class="fas fa-trash"></i></button>
</form>
</span>
@endcan

我們可以在 Controller 使用 can(), cannot() 來做邏輯判斷。

<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Support\Str;
use Illuminate\Http\Request;

class PostController extends Controller
{
public function delete(Post $post)
{
if (auth()->user()->cannot('delete', $post)) {
return '你不能刪除此文章';
}
$post->delete();

return redirect('/profile/' . auth()->user()->username)->with('success', '文章刪除成功。');
}
}

也可以在 Middleware 來做判斷。

Route::delete('/post/{post}', [PostController::class, 'delete'])->middleware('can:delete,post');
Route::get('/post/{post}/edit', [PostController::class, 'showEditForm'])->middleware('can:update,post');
Route::put('/post/{post}', [PostController::class, 'actuallyUpdate'])->middleware('can:update,post');

--

--