Laravel 與後端開發

驗證規則終於全員有方法可叫:Laravel 13.36 的 Rule 建構器、DB::olap() 與佇列黏性連線重置

Laravel News 於 2026/10/14 報導 Laravel 13.36.0:所有原本只有字串語法的驗證規則都有了 Rule:: 建構器(如 requiredWithout、url、uuid、distinct、confirmed),帶參數的規則以陣列傳給驗證器,含逗號的值不再被誤拆;新增 DB::olap() 讓 PlanetScale 等 Vitess 資料庫暫時切換到 OLAP 工作負載;可選的 Worker::$flushStickyConnections;Mercure 廣播預設改為同步;佇列郵件支援 DeleteWhenMissingModels 與 FailOnTimeout;並修正 Markdown 郵件中使用者輸入的跳脫。本文整理升級重點與團隊實務建議。

扁平插畫:可愛的寫字板角色堆疊五塊打勾的彩色規則積木,右側有資料庫圓柱、長條圖與帶盾牌的信封,珊瑚紅配色,無文字。
驗證規則終於全員有方法可叫:Laravel 13.36 的 Rule 建構器、DB::olap() 與佇列黏性連線重置

Laravel 13.36 替所有只能寫字串的驗證規則補上 Rule:: 方法,含逗號的值不再被拆成兩半;還加入 PlanetScale/Vitess 用的 DB::olap()、可選的佇列工作間黏性連線重置、Mercure 預設同步廣播,並修補 Markdown 郵件的跳脫問題。

寫 Laravel 的人都有過這種經驗:表單驗證寫到第 40 行,'required_without:email|url|...' 這串字像咒語一樣,少一個冒號、多一個逗號,錯誤訊息還會很有禮貌地告訴你「一切正常」,然後在正式環境出包。

2026 年 10 月 14 日,Laravel News 的 Paul Redmond 整理了 Laravel 13.36.0 的更新。標題就是今天的主角:每一條驗證規則都有 Rule 建構器了。 但這版不只是語法糖,還有幾個對正式環境很有感的改動,特別是佇列、資料庫與郵件。

Rule 建構器:IDE 終於可以幫你補完了

貢獻者 @ericmp33 替所有「原本只有字串寫法」的驗證規則補上了 Rule:: 方法。像 required_with、accepted_if、url、uuid、distinct 這些,現在都能用 IDE 自動完成的方法寫:

use Illuminate\Validation\Rule;

$request->validate([
    'phone' => [Rule::requiredWithout('email')],
    'website' => ['nullable', Rule::url()->protocols(['https'])],
    'device_id' => [Rule::uuid()->version(4)],
    'tags.*' => [Rule::distinct()->ignoreCase()],
    'password' => ['required', Rule::confirmed()],
]);

細節很值得注意:

  • 沒有參數的規則(如 Rule::required()、Rule::nullable())直接回傳原本的字串,所以和舊寫法完全相容;
  • 需要欄位名稱或值的規則會回傳物件,以陣列形式把參數交給驗證器。這代表含逗號的值不會再被拆成兩個值。字串語法是用冒號分隔規則與參數、再用逗號分隔參數,所以比對值若是「台北市, 信義區」這種本身就帶逗號的字串,過去就會被切成兩個參數,而且不會報錯,只會默默驗證錯;
  • 流暢的 Rule::date() 建構器也多了 equals()。

對團隊來說,最大的好處其實是可讀性與可審查性。Code review 時,Rule::url()->protocols(['https']) 一眼就知道「只接受 https」,比在一串管線符號裡找規則舒服太多。

實戰範例:把一張會員表單翻譯成人話

假設客戶的會員註冊表單規格是:電話與 Email 至少填一個、個人網站只收 https、裝置識別碼必須是 UUID v4、興趣標籤不能重複(不分大小寫)、密碼要再輸入一次確認。以前我們會寫成一長串字串,新人接手時得先查文件才看得懂;現在每一行都能直接對照規格書:

public function rules(): array
{
    return [
        'phone' => [Rule::requiredWithout('email')],
        'email' => ['nullable', 'email'],
        'website' => ['nullable', Rule::url()->protocols(['https'])],
        'device_id' => [Rule::uuid()->version(4)],
        'tags.*' => [Rule::distinct()->ignoreCase()],
        'password' => ['required', Rule::confirmed()],
    ];
}

PM 拿著規格書逐條核對、QA 寫測試案例、後端做 code review,大家看的都是同一種語言。對工作室來說,這種「規格能直接對到程式碼」的寫法,比省下幾個字元更重要:驗收時少吵一次架,就是賺到。

DB::olap():PlanetScale 上跑大報表不再撞牆

Jack Bayliss 替 MySQL 連線加入了 olap() 與 isVitess()。背景是:PlanetScale 建立在 Vitess 上,預設的 OLTP 工作負載會限制查詢,單一查詢最多回傳 100,000 筆資料。年度營收、三年訂單彙總這種報表,很容易就撞到。

olap() 會在回呼期間切換到 OLAP 工作負載,結束後切回來:

$revenue = DB::olap(fn () => Order::query()
    ->selectRaw('customer_id, sum(total) as revenue')
    ->where('created_at', '>=', now()->subYears(3))
    ->groupBy('customer_id')
    ->get());

它會同時設定讀取與寫入連線;如果資料庫不是 Vitess,就只是單純執行回呼。也就是說,同一段程式碼在本機 MySQL 和 PlanetScale 上都能跑,不用寫一堆 if (app()->environment(...))。

佇列工作間的黏性連線:一個小旗標,解決一個大困惑

如果你的讀寫分離連線開了 sticky,一次寫入後,同一請求後續的讀取會改走寫入連線,避免讀到還沒同步的舊資料。問題是佇列 worker 是一個長時間執行的行程:某個工作寫了一次,之後這個 worker 處理的每個工作,讀取都可能一路黏在寫入連線上。 主庫壓力莫名變大、讀取副本閒著沒事,就是這種症狀。

13.36 加入可選旗標,讓 worker 在每個工作之前重置狀態:

use Illuminate\Queue\Worker;

// AppServiceProvider::boot()
Worker::$flushStickyConnections = true;

預設是關閉的,因為可能有工作依賴「讀到前一個工作剛寫入的資料」。開啟前請先盤點:有沒有連續工作是「A 寫入、B 馬上讀」的設計?如果有,那其實該改成在同一個工作裡完成,或明確指定讀寫連線。

怎麼判斷自己有沒有中招?最簡單的方法是看資料庫監控:在佇列尖峰時段,比較主庫與讀取副本的查詢量。如果主庫的讀取查詢跟著佇列量一起飆高,而副本的曲線幾乎是平的,就很可能是黏性連線在作怪。這種問題平常不會報錯,只會讓你的雲端帳單和主庫 CPU 默默變胖,所以特別值得在月度健檢時看一眼。

Mercure 廣播預設改為同步

Kévin Dunglas 把 Mercure 廣播器改成立即發布,不再排入佇列。只針對 Mercure 連線的事件,行為會像 ShouldBroadcastNow;同時還要送到其他連線的事件,仍然會排入佇列。想恢復非同步,設 MERCURE_ASYNC=true,而且非同步模式需要設定 Mercure 的 url,因為 worker 要透過 HTTP 發布:

MERCURE_URL="${APP_URL}/.well-known/mercure"
MERCURE_ASYNC=true

同一個 PR 也修正了 BROADCAST_CONNECTION=mercure 但沒設 url、又沒有 FrankenPHP 的 mercure_publish() 時,Artisan 指令會崩潰的問題。用 FrankenPHP+Mercure 做即時通知的專案,升級後請實測一次廣播流程。

佇列郵件:模型不見了就別硬寄

Volodya Kurshudyan 修正了佇列郵件會忽略 #[DeleteWhenMissingModels] 與 #[FailOnTimeout] 屬性的問題:

  • 訂單在寄信前被刪除,工作會直接被刪掉,而不是丟出 ModelNotFoundException 失敗;
  • 加上 #[FailOnTimeout],逾時的郵件會被標記為失敗,而不是重試後可能寄出兩次。

「出貨通知寄了兩封」這種客訴,聽起來很小,對電商客戶來說卻是品牌信任的扣分。

資安小修:Markdown 郵件的跳脫

Stephen Rees-Carter 修正了 Markdown 郵件中使用者輸入的跳脫:放在 Markdown 連結文字裡的值,過去可以用反斜線跳脫右中括號,進而改變連結目標。現在反斜線會和中括號一起被跳脫。如果你的通知信會把使用者暱稱、公司名稱放進 Markdown 連結文字,這個更新值得優先上。

其他值得一提的修正

完整變更清單很長,以下幾條對正式環境特別有感:

  • PgBouncer 的 SSL EOF 容錯切換錯誤會被視為「連線遺失」;
  • failover 與 roundrobin 郵件驅動不再忽略 retry_after;
  • Redis 佇列的通知推送改為批次;
  • queue:work --json 的暫停、恢復與停止輸出加入連線與佇列資訊;
  • 12.x 也同步收到更新,例如支援具名的 S3 憑證提供者與 Cloud IAM 磁碟。

工作室升級建議

  1. 先在預備環境跑完整測試,尤其是有 Mercure、讀寫分離、佇列郵件的專案。
  2. 新寫的 Form Request 改用 Rule 建構器;舊程式不必一次重寫,遇到含逗號值或難讀規則時再逐步替換。
  3. 用 PlanetScale 的客戶,把大型報表包進 DB::olap(),並設定合理的排程時段。
  4. 觀察主庫負載:若讀取副本長期很閒,評估開啟 Worker::$flushStickyConnections。
  5. 重要郵件加上 #[FailOnTimeout],減少重複寄送。

你可能會問

Q:舊的字串規則還能用嗎?
A:可以。沒有參數的 Rule 方法本來就回傳原字串,新舊寫法可以混用。

Q:不用 PlanetScale,DB::olap() 對我有影響嗎?
A:沒有。非 Vitess 資料庫只會執行回呼本身。

Q:開啟黏性連線重置會不會讓資料不一致?
A:如果有工作依賴前一個工作剛寫入的資料,就可能讀到副本上的舊資料,這也是預設關閉的原因。先盤點再開。

Q:Rule 建構器會不會讓效能變差?
A:原文沒有提到效能差異。這次改動的重點是寫法與參數傳遞方式;一般表單驗證的瓶頸通常在資料庫查詢類規則,而不是規則物件本身。

結語

13.36 是那種「標題看起來只是語法糖、實際上替維運省下很多深夜」的版本。Rule 建構器讓驗證程式碼更好讀、更不容易被逗號暗算;DB::olap()、黏性連線重置與郵件修正,則直接對應到報表撞牆、主庫莫名吃緊、重複寄信這些真實客訴。

今天就挑一個專案跑 composer update laravel/framework,在預備環境驗證一輪——順便把那條 40 行的驗證咒語,翻譯成人看得懂的 Rule 方法。

來源: Rule Builders for Every Validation Rule in Laravel 13.36(Paul Redmond,Laravel News,2026/10/14)。

← 返回技術文章