決定寫這個系列的時候,其實猶豫過一個問題:市面上不缺「Laravel 新特性介紹」的文章,這個系列反而選了一個看起來比較樸素的角度——不講最新版本加了什麼新功能,只挑一套真實運作中的系統,看它怎麼用 Model、Job、Observer、Middleware、Service Container 這些已經存在很久、幾乎每個 Laravel 開發者都「知道」的機制,撐起一個要跟外部老舊系統對接、還要防資安攻擊的真實網站。
這個猶豫後來變成了寫作動機本身:知道一個機制存在,跟真正把它用紮實,中間有一段不小的距離。這 30 天想填的,正是這段距離。
第一部從系列介紹出發,一路看 Eloquent 模型設計、Model::shouldBeStrict() 抓出的真實 N+1 與 typo bug、Page 這個 Model 怎麼用 Nested Set 加上動態 type 撐起整個頁面樹、Query Scope 怎麼寫成可組合的 invokable class、Backed Enum 用 ::class 當值做型別對應查找表、Model Factory 的語意化設計。
這一部最想傳達的是:Model 不只是資料庫欄位的映射,它可以承擔更多職責——只要用對機制。Page 那篇是最好的示範:一個 Model 同時處理階層結構、依內容類型分派不同行為、動態計算對外連結,全部靠 Laravel 原生的 Attribute accessor 跟 Eloquent 特性完成,沒有額外引入什麼複雜的架構模式。
現在回頭看,這一部建立的視角,是後面 23 天反覆回到的同一個判斷:遇到一個看起來需要新工具才能解決的問題,先想想手上原生機制夠不夠用。
第二部從 Policy 授權基礎講起,經過角色可見性控制、一支修資料遷移缺失的 Command、排程怎麼跟「回應請求」刻意拆開、一個 Content-Type 的真實 bug、Queue Job 用 Generator 安全迭代抓外部資料、一支 Job 該做多少事的職責邊界討論、Observer 的兩種註冊方式、Event/Listener 怎麼靠自動探索變成一筆審計紀錄。
這一部踩坑密度最高的兩篇,一個是 sitemap Content-Type bug(後來在第三部才揭曉,根因其實是一支被錯誤套用的 middleware),一個是「一支 Job 該做多少事」——原本以為素材是「多支 Job 怎麼分工不搶工作」,實際查證後發現真正值得講的是一支只有十幾行、跟另一支超過百行的 Job 之間的職責邊界對照。這是這個系列反覆出現的模式:素材核實過程本身,常常比一開始的推測更有教學價值。
第三部是這個系列查證最紮實的一段:Service Container 怎麼用 Factory 綁定 SOAP Client、跟外部系統對接怎麼用 PSR-18 包起來、一次「先能動」到「補上超時控制」的真實演進、抄一篇公開文章的 XML 容錯解法也要懂它在防什麼、定期爬取外部網頁的分頁與重試組合技、一次真實資安事件後才補上的 webshell 防護、Docker 環境下抓不到真實 IP 的 Trusted Proxies 坑、兩支輕量但用途完全不同的 middleware。
這一部最誠實的兩篇,是 webshell 防護那篇跟 Trusted Proxies 那篇——兩個真實案例都是「先出事、後補防護」,不是設計完善才上線。這也是這一部想傳達的核心:跟外部系統對接、暴露在公開網路上的服務,防護常常是事後補的,這不丟臉,重點是有沒有補、補完之後有沒有記錄下這個教訓。
第四部從 Media Library 的路徑生成器覆寫講起,一路到套件化設定的真實演進、揭曉前面賣了好幾次關子的 SetTheme middleware 完整機制、資料遷移 command 的三個關鍵設計、稽核套件的能力邊界怎麼被延伸涵蓋登入事件。
這一部收束了整個系列一路埋下的伏筆——SetTheme middleware 在第 12 天先以「sitemap bug 的根因」形式出現,第 24 天先帶過它也是一支 middleware,直到第 27 天才完整拆解它怎麼用 View::getFinder()->prependLocation() 做到「一套路由、兩套畫面」。這不是刻意賣關子,是因為這支 middleware 牽涉到的東西太多,拆成三個層次分別講,比一次塞爆一篇文章更容易消化。
寫到這裡,如果只講「照著這些原生機制的用法做,就能撐起一個健壯的系統」,那就違背了這個系列一路想傳達的立場。
回頭看這 30 天引用的真實案例,幾乎沒有一個是「架構師事先想清楚、一次到位」的產物:Model::shouldBeStrict() 抓出的 N+1 是重構才發現的、webshell 防護是真的中招才補的、Trusted Proxies 是換了部署環境才炸出來的坑、sitemap 的 Content-Type bug 是一支 middleware 波及了不該波及的路由。這些都不是「事前設計」的成果,是「事後修正」的結果。
這正是這個系列想誠實傳達的一件事:一個真實運作中的系統,多數的「紮實」不是規劃出來的,是踩過坑之後修出來的。與其看一套「理想中該怎麼設計」的範例,不如看一套真實系統怎麼從有問題的狀態,一步一步修成現在的樣子——後者更貼近多數人維護中的系統的真實狀態,也更有參考價值。
這個系列一開始立下的主題句是:一個中小型專案,不靠額外框架特性,光是把 Laravel 原生的 Model/Job/Observer/Middleware/Service Container 用紮實,就能撐起一個要串接外部老舊系統、還要防資安攻擊的真實網站。
30 天寫完,這句話背後真正的意涵不是「Laravel 原生機制很強大」這種空泛的稱讚,而是:多數系統遇到的問題,不是缺少某個新框架特性才解決不了,是既有機制沒有被用到位。Page Model 用 Attribute accessor 處理多型行為、SetTheme middleware 用 View Finder 做主題切換、laravel-auditing 靠手動組資料延伸涵蓋登入事件——這些解法用到的機制,全部都是 Laravel 文件裡寫得清清楚楚的東西,差別只在於有沒有把它用到「剛好貼合這個系統的真實需求」這個程度。
如果你想把這個系列的觀察套進自己的專案,幾個立即可行的第一步:
ModelPathGenerator 只覆寫一個方法),不要急著整套重寫或放棄套件改自己刻。SetTheme middleware 波及 sitemap 路由的教訓,值得每次調整共用邏輯前先想一次。漸進式的技能發展路徑:先從 Model 層的 Attribute accessor、Query Scope 這些練起,這是投資報酬率最高的起點;穩定之後,往 Job/Observer/Event 這些背景工作機制推進;最後才是 Middleware 跟 Service Container 這種牽涉到框架執行流程本身的進階技巧,這個層次通常要親自踩過幾次坑才會真正理解。
回到 Day 1 那個問題:不靠額外框架特性,光把 Laravel 原生機制用紮實,真的能撐起一個要串接外部老舊系統、還要防資安攻擊的真實網站嗎?
寫完這 30 天,答案沒有變得更複雜,但變得更具體了——能,但前提是「用紮實」不是一次到位的事,是像這個系列展示的每一個案例一樣,踩過真實的坑、留下真實的修正紀錄,一點一點修出來的。這套機制還在持續運作、持續累積新的踩坑紀錄,這 30 天的記錄,只是先停在這裡。如果你在自己的專案裡也用這些原生機制解決過類似的問題,或是踩過不同的坑,很樂意聽聽你的版本長什麼樣。謝謝你陪我走完這 30 天。