上一篇使用 Response::macro() 實現主題切換,但有缺點:Blade @extends 必須顯式指定路徑,主題間模板不完全隔離。本文介紹更規範的 **View Namespace** 機制。
Laravel 視圖系統支援命名空間語法:
<?php
// 註冊命名空間對應路徑
View::addNamespace('default', app_path('views/default'));
View::addNamespace('blue', app_path('views/blue'));
View::addNamespace('red', app_path('views/red'));
View::addNamespace('green', app_path('views/green'));
// 使用語法:namespace::path.to.view
return View::make('green::index.index'); // 解析為 views/green/index/index.blade.php
return View::make('blue::layouts.app'); // 解析為 views/blue/layouts/app.blade.php
app/
├── views/
│ ├── default/ # 預設主題 (fallback)
│ │ ├── index/
│ │ │ └── index.blade.php
│ │ └── layouts/
│ │ └── app.blade.php
│ ├── blue/ # 藍色主題
│ │ ├── index/
│ │ └── layouts/
│ ├── red/ # 紅色主題
│ └── green/ # 綠色主題
Laravel 內建 View 類別**無 setDefaultNamespace() 方法**,需自行實作:
<?php
// App\Providers\ThemeServiceProvider.php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Config;
class ThemeServiceProvider extends ServiceProvider
{
public function boot()
{
// 從設定/資料庫讀取主題名
$theme = Config::get('app.theme', 'default');
// 註冊所有主題命名空間
$themes = ['default', 'blue', 'red', 'green'];
foreach ($themes as $t) {
View::addNamespace($t, app_path("views/{$t}"));
}
// 設定預設命名空間 (核心技巧)
View::addNamespace('theme', app_path("views/{$theme}"));
// 現在可直接使用 theme:: 不帶前綴
// View::make('theme::index.index') 等同 View::make('green::index.index')
}
}
<?php
// 在 AppServiceProvider::boot()
View::composer('*', function ($view) {
$theme = config('app.theme', 'default');
$view->with('currentTheme', $theme);
});
// Blade 模板中使用
@extends("{$currentTheme}::layouts.app")
// 或
@include("{$currentTheme}::partials.header")
建立 App\View\ThemeViewFactory 繼承 Illuminate\View\Factory,覆寫 make() 自動加入主題前綴,並在 AppServiceProvider 綁定替換預設 view 服務。此法最徹底但複雜度較高,適合大型多主題系統。
若 green 主題缺少某模板,自動回退至 default:
<?php
// 註冊時建立 fallback 鏈
View::addNamespace('green', [
app_path('views/green'),
app_path('views/default'), // 陣列形式:依序尋找
]);
注意: 此功能需 Laravel 5.5+,早期版本不支援陣列路徑。
<?php
// 控制器
public function switchTheme(Request $request)
{
$theme = $request->input('theme');
$allowed = ['default', 'blue', 'red', 'green'];
if (!in_array($theme, $allowed)) {
return back()->withErrors(['theme' => '不支援的主題']);
}
// 寫入資料庫/設定檔/Session
Setting::set('theme', $theme); // 或 config(['app.theme' => $theme])
return back()->with('success', '主題已切換,重新整理生效');
}
| 測試項目 | 預期結果 |
|---|---|
| 切換主題後前台樣式變化 | 正確載入對應主題 CSS/佈局 |
| 主題缺少模板時回退 default | 自動使用 default 主題對應檔案 |
| @extends/@include 動態主題前綴 | 正確解析路徑,無硬編碼 |
| 快取清除後主題生效 | php artisan view:clear 後正常 |
| 多語系配合主題 | lang/{theme}/{locale} 結構正常 |
使用 View::addNamespace() 實現主題功能,優勢在於:
@extends('theme::layouts.app') 語法自然這是 Laravel 實現多主題系統的**標準作法**,建議取代舊版 Response Macro 方案。