iT邦幫忙

2026 iThome 鐵人賽

DAY 27
0
Software Development

一套真實運作中的 Laravel 系統,拆解它的原生機制系列 第 27 篇

Day 27:一套路由、兩套畫面——用 View Finder 切換主題,而不是複製一份路由

  • 分享至 

  • xImage
  •  

前言:「要同時維護兩套介面版本」,第一直覺通常是錯的

「系統要同時支援兩套介面版本,是不是要把路由複製一份,各自對應一套 controller?」

這是多數人聽到「一套系統要同時維護兩個版本的畫面」時,第一個冒出來的直覺做法——路由加前綴分版本、controller 各寫一份。但今天要看的真實案例,用的是一個完全不同、而且更輕量的做法:同一組路由,靠一支 middleware 動態決定要載入哪一套畫面。

今日目標

  • 理解「路由版本控制」跟「View 主題切換」這兩種做法的本質差異
  • 拆解一支真實 middleware:怎麼在執行期切換整套 View 的載入位置
  • 認識 Laravel 的 View Finder 機制,這是這個技巧背後的核心
  • 理解這個設計解決了什麼問題,又帶來了什麼新的風險(前幾天已經預告過的那個 bug)

本文主體

兩套畫面,但不是兩套路由

這個系列的素材專案有兩套前台介面(先稱它們主題一、主題二),乍看之下很容易讓人以為背後是兩套獨立的路由跟 controller——實際上完全不是。翻開路由設定檔會發現:根本沒有版本前綴、也沒有分開的路由檔案。同一個路由名稱、同一個 controller,兩套主題共用同一套後端邏輯。

真正決定「這次請求要看到哪一套畫面」的,是一支 middleware。

真實案例:SetTheme middleware 怎麼運作

這支 middleware 的核心邏輯,簡化後大致是這樣:

protected function registerThemeViewPaths(string $theme): void
{
    $themePath = resource_path("views/{$theme}");

    $finder = View::getFinder();
    $finder->prependLocation($themePath);

    Blade::anonymousComponentPath($themePath.'/components');
}

它做的事情是:先決定這次請求該用哪一套主題(可能來自 query string 參數、也可能來自 session 裡記住的使用者偏好),然後呼叫 Laravel 的 View::getFinder()->prependLocation(),把對應主題的 View 目錄,插進 View 尋找路徑的最前面。

關鍵機制:Laravel 的 View Finder 怎麼找 View 檔案

要理解這支 middleware 為什麼有效,得先知道 Laravel 怎麼把一個 View 名稱(例如 home)解析成實際要載入的檔案。Laravel 內部有一個 View Finder,維護著一份「要去哪些目錄底下找 View 檔案」的路徑清單,預設情況下這份清單通常只有一個目錄(resources/views)。當程式碼呼叫 view('home') 時,View Finder 會依序在清單裡的每個目錄找有沒有 home.blade.php,找到第一個符合的就用它。

prependLocation() 這個方法,做的事情是把一個新的目錄插到清單最前面,而不是加到最後。這代表:只要這個目錄底下有同名的 View 檔案,它就會比預設目錄更早被找到、優先被使用。

所以整個機制串起來是這樣:controller 完全不知道現在是哪一套主題,它一樣呼叫 view('home');但因為 middleware 已經在請求進來的時候,把對應主題的目錄插到 View Finder 清單最前面,所以最終被載入的,會是那套主題底下的 home.blade.php,而不是預設目錄裡的版本。同一段程式碼路徑,因為執行期的 View Finder 狀態不同,渲染出完全不同的畫面。

Anonymous component(不用註冊、直接用檔名當標籤名稱的 Blade component)的路徑也用同樣的邏輯註冊,確保兩套主題各自的元件庫也能正確切換。

為什麼這樣設計,而不是複製一份路由

理解機制之後,回頭看這個設計的取捨:如果改用「複製一份路由、各自對應一套 controller」的做法,兩套主題共用的業務邏輯(資料查詢、授權判斷、路由參數解析)全部都要維護兩份,任何一個邏輯修正都要同步改兩個地方,很容易漏改其中一份導致兩套主題行為不一致。

用 View Finder 切換主題,controller 跟業務邏輯只有一份,兩套主題之間唯一的差異被限制在「畫面長什麼樣」這一層,不會外溢到業務邏輯層。這也解釋了為什麼這個系列稍早看到的測試檔案裡,會有兩組幾乎同名的測試(分屬兩套主題各自的資料夾)——它們測的是同一個 controller/路由,在不同 View Finder 設定下渲染出的不同畫面,不是兩套獨立的後端邏輯。

一個機制被多處依賴的風險

這支 middleware 解決了「兩套畫面共用一份邏輯」的問題,但也帶來一個容易被忽略的風險——這個系列前幾天講過的 sitemap Content-Type bug(第 12 天),根因正是這支 middleware 原本被套用在不該套用的路由上:sitemap.xml 這個路由回傳的是 XML 內容,完全不需要主題切換這個機制,但因為 middleware 掛在一個共用的路由群組上,這個路由也被牽連,SetTheme 執行過程中觸碰到的 session 邏輯,干擾了 XML 回應本身。

這是一個值得記住的教訓:一個機制被多個路由共用,代表任何時候要調整這個機制,都要盤點清楚「所有依賴它的路由」,而不能只想著「我要改的那個路由」——因為改動的影響範圍,往往比你以為的更廣。

今日思考題

回想你的專案裡,有沒有一個 middleware 或共用邏輯,掛在一個比較大的路由群組上,但其實只有部分路由真的需要它?如果現在要調整這個邏輯,你有把握列出所有真正依賴它的路由清單嗎?

今日重點回顧

  • 「一套系統要維護兩套介面版本」不一定要複製路由,可以靠 middleware 動態切換 View 載入位置
  • 核心機制:View::getFinder()->prependLocation() 把對應主題的目錄插進 View 尋找路徑最前面,controller 完全不用知道現在是哪套主題
  • 這個設計讓兩套主題共用同一份業務邏輯,只有「畫面長什麼樣」這一層各自獨立
  • 一個機制被多處路由共用時,任何調整都要盤點所有依賴它的路由——這個系列前面看過的 sitemap bug,根因正是這支 middleware 波及了不該波及的路由

明日預告

明天要看另一個真實案例:當上傳的檔案結構換了新的路徑規則,累積在舊路徑底下的歷史資料要怎麼安全搬過去,而不會在搬遷過程中弄丟任何一個檔案。


上一篇
Day 26:套件化設定——第三方追蹤碼從寫死到後台可設定的真實演進
下一篇
Day 28:一次資料遷移——dry-run/chunk/三種結果統計,安全搬歷史資料的範本
系列文
一套真實運作中的 Laravel 系統,拆解它的原生機制 共 28 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言