iT邦幫忙

2026 iThome 鐵人賽

DAY 16
0
Modern Web

Laravel Filament 從入門到實戰系列 第 16 篇

Day 15:接上 Laravel 既有的 Policy,誰能看見這個操作按鈕

  • 分享至 

  • xImage
  •  

Day 14:儲存前後可以掛什麼,庫存異動的自動計算 結尾留下一句話,庫存異動的邏輯已經自動計算好了,但目前任何登入這套後台的使用者都能無條件觸發,倉管人員異動庫存合情合理,業務或財務人員是否也該擁有一模一樣的操作範圍,目前完全沒有區分。這句話今天要正面回應。

誰都能異動庫存,這件事該被擋下來了

把這個落差講白一點,昨天處理的是「資料變動之後,該連動什麼」,今天要處理的是完全不同的另一件事,「誰有資格觸發這個變動」。這兩件事聽起來很接近,實際上分屬兩個層次,昨天已經把庫存連動這條線接得很牢固,今天要接的是身分那條線。

這個落差其實不是今天才出現的新問題。往回翻,從 Day 03:建立第一個 Resource,五分鐘做出一套 CRUD 介面 做出第一個商品 Resource 開始,這套後台就只有「登入」跟「未登入」兩種狀態,登入之後能做的事情完全一致,沒有任何細節區分。這幾天的篇幅一直聚焦在資料本身,商品跟訂單怎麼關聯、表單跟表格怎麼呈現、資料儲存前後可以掛什麼邏輯,都是在處理資料,還沒有一天正式停下來處理「這個系統裡不同身分的人,該看到與能做的事情理應不同」這件事。

今天要做的事情很明確,接上 Laravel 既有的 Policy 機制,讓 Filament Resource 能依照 Policy 的判斷結果,自動決定某個操作介面該不該出現。先講清楚,今天只打算建立這套機制本身,示範裡的身分判斷會刻意寫得很粗略,倉管、業務、財務這幾個角色各自的實際權限範圍,留到明天才展開。

定案 Policy 整合,Resource 自動讀取既有的判斷邏輯

先把今天要定案的機制講清楚。Filament Resource 自動讀取 Laravel 既有 Policy 判斷是否顯示對應操作介面的機制,這是本篇要處理的核心,日後系列裡談到權限判斷,都會用這個說法指稱它。

運作原理其實沒有想像中複雜。只要一個 Model 存在對應的 Policy 類別,並且依 Laravel 慣例正確註冊,Filament Resource 在渲染建立、編輯、刪除這類操作介面之前,會自動呼叫 Policy 對應的方法進行判斷。create 方法回傳 false,建立按鈕就不會出現;update 方法回傳 false,編輯按鈕就不會出現;delete 方法回傳 false,刪除按鈕也是同樣的道理。這整套判斷不需要在 Resource 或 Action 裡額外寫任何一行程式碼,Filament 會自己去找有沒有對應的 Policy,找到了就照它的判斷結果辦事。

這裡可以借用一個生活化一點的畫面。Policy 像是門口的警衛,Resource 只是照著警衛的指示決定要不要開門,警衛自己判斷的邏輯寫在警衛室裡,不需要每一扇門各自貼一張規則。Resource 不需要知道判斷的細節,只需要問一句「這個人能不能進來」,答案已經在 Policy 裡準備好了。

讀到這裡,可能會聯想到前幾天在 Day 08:Action 是什麼,表格列、頁首、表單內三種掛載位置 看過的 visible()、hidden() 這類寫法,兩者性質不同,值得分開來看。visible() 是針對單一個 Action 手動寫的顯示條件,屬於個別客製化,這個按鈕要不要顯示,取決於當下這一筆資料的某個欄位狀態,或是某個臨時的業務判斷,每寫一個 Action 就要決定一次。Policy 整合則是系統性的機制,一次性地讓整個 Resource 的預設操作介面都自動遵循同一份 Policy 判斷。兩者可以並存,一個 Action 完全可以同時受 Policy 判斷跟 visible() 條件約束,只是服務的層次不同,visible() 管的是這一個按鈕在這個情境下該不該出現,Policy 整合管的是這個人在這個系統裡有沒有資格做這件事。

這套機制真正的價值,在於權限判斷的邏輯從此集中寫在 Policy 類別裡,不會散落在每個 Resource 或每個 Action 裡各自重複判斷。日後權限規則有調整,例如某個角色原本不能刪除訂單,後來改成可以,只需要修改對應 Policy 裡的那一個方法,不需要翻遍所有 Resource 檔案,一個一個找出寫死的判斷條件。

動手接上,訂單 Resource 第一次有了身分之分

拿訂單 Resource 來示範,正好延續昨天處理的庫存落差。用 Artisan 指令建立對應的 Policy 類別:

php artisan make:policy OrderPolicy --model=Order

指令執行完成,app/Policies/OrderPolicy.php 會產生一個包含 viewAny、view、create、update、delete 這幾個慣例方法的類別骨架。今天不打算細緻到角色矩陣的程度,先用一個最基本的二分概念,判斷目前登入的使用者是不是具備某個基本的管理身分:

namespace App\Policies;

use App\Models\Order;
use App\Models\User;

class OrderPolicy
{
    public function create(User $user): bool
    {
        return $user->is_admin;
    }

    public function update(User $user, Order $order): bool
    {
        return $user->is_admin;
    }

    public function delete(User $user, Order $order): bool
    {
        return $user->is_admin;
    }
}

這裡的 is_admin 只是暫時借用的一個欄位,代表「具不具備管理身分」這種最粗略的二分,實際團隊裡倉管、業務、財務各自該有什麼判斷依據,明天才會真正展開,今天先讓機制本身跑起來。

Policy 類別寫好,還要讓 Laravel 知道 Order 這個 Model 對應到 OrderPolicy。多數情況下,只要兩個類別的命名跟位置遵循 Laravel 慣例,這層對應會被自動解析,不需要額外註冊。如果專案結構稍有調整導致無法自動解析,可以在 AppServiceProvider 的 boot() 方法裡手動綁定:

use App\Models\Order;
use App\Policies\OrderPolicy;
use Illuminate\Support\Facades\Gate;

public function boot(): void
{
    Gate::policy(Order::class, OrderPolicy::class);
}

註冊完成,回到訂單 Resource 的畫面實際驗證。用一個 is_admin 為 false 的帳號登入,訂單列表頁原本掛在表格列上的編輯、刪除這類操作,跟頁首那顆新增按鈕,回頭對照 Day 08:Action 是什麼,表格列、頁首、表單內三種掛載位置 定案的三種掛載位置,這幾個位置上的按鈕全部從畫面上消失,不需要在 Resource 或 Action 裡多寫一行判斷邏輯。換一個 is_admin 為 true 的帳號登入,這些按鈕又恢復正常顯示。

還有一件事值得補充。這套機制不只是隱藏按鈕而已,即使那個不具備管理身分的使用者知道編輯頁面的網址,直接在網址列輸入試圖進入,Policy 判斷為否時仍然會被系統擋下,回應一個未授權的錯誤頁面,進不去那個表單。這是系統性權限機制跟單純把按鈕藏起來的關鍵差異,畫面上看不到是一回事,實際擋住存取行為才是這套機制真正在做的事。

開關接上了,但還只是最粗略的二分

回顧今天定案的內容,Filament Resource 現在會自動讀取 Laravel 既有 Policy,判斷是否顯示對應操作介面,訂單 Resource 第一次有了身分之分,不具備權限的使用者看不到,也進不去對應的操作。

老實說,今天示範的判斷邏輯還很粗略,is_admin 這種欄位只能大致區分「有沒有管理身分」,是一種最基本的二分判斷。這間批發商實際團隊裡存在倉管、業務、財務這幾個角色,各自該看到與操作的範圍理應不同,倉管人員異動庫存合情合理,業務人員接單,財務人員對帳,三種角色的操作範圍顯然不會是同一件事,今天的機制還沒有回答這個更貼近真實團隊結構的問題。

第四階段從補齊表單驗證細節,走到生命週期掛載點,再到今天接上的 Policy 機制,權限判斷的地基算是打好了。設計一份貼近真實團隊的角色權限矩陣,是接下來要處理的內容。


上一篇
Day 14:儲存前後可以掛什麼,庫存異動的自動計算
下一篇
Day 16:設計一份貼近真實團隊的角色權限矩陣
系列文
Laravel Filament 從入門到實戰 共 19 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言