Yii2 `yii\base\Theme` 透過**路徑映射替換**實現視圖主題化:將原視圖路徑 (如 `@app/views`) 映射到主題目錄 (如 `@app/themes/basic`),若主題目錄缺檔則回退原視圖,形成**繼承鏈**。本文深度解析配置、核心方法、資源處理、動態切換與多主題架構。
<?php
// config/main.php
return [
'components' => [
'view' => [
'theme' => [
'class' => 'yii\base\Theme',
'pathMap' => [
'@app/views' => ['@app/themes/basic', '@app/themes/default'],
'@app/modules' => '@app/themes/default/modules',
'@app/widgets' => '@app/themes/default/widgets',
],
'baseUrl' => '@web/themes/basic', // CSS/JS/圖片公開 URL
'basePath' => '@app/themes/basic', // 資源實體路徑
],
],
],
];
?>
pathMap => [
'@app/views' => [
'@app/themes/dark', // 第 1 優先:深色主題專屬視圖
'@app/themes/basic', // 第 2 優先:基礎主題覆蓋
'@app/themes/default', // 第 3 優先:框架預設視圖
],
]
請求 `@app/views/site/index.php` → 依序檢查:
public function init() {
parent::init();
if (empty($this->pathMap)) {
if (($basePath = $this->getBasePath()) === null) {
throw new InvalidConfigException('The "basePath" property must be set.');
}
// 相容舊版:無 pathMap 時以 basePath 對應應用基礎路徑
$this->pathMap = [Yii::$app->getBasePath() => [$basePath]];
}
}
public function applyTo($path) {
$path = FileHelper::normalizePath($path);
foreach ($this->pathMap as $from => $tos) {
$from = FileHelper::normalizePath(Yii::getAlias($from)) . DIRECTORY_SEPARATOR;
if (strpos($path, $from) === 0) {
$n = strlen($from);
foreach ((array) $tos as $to) {
$to = FileHelper::normalizePath(Yii::getAlias($to)) . DIRECTORY_SEPARATOR;
$file = $to . substr($path, $n);
if (is_file($file)) return $file;
}
}
}
return $path; // 全部失敗回退原路徑
}
<?php
// 控制器/基類
public function beforeAction($action) {
$theme = Yii::$app->request->get('theme', 'basic');
$allowed = ['basic', 'dark', 'mobile'];
if (in_array($theme, $allowed)) {
Yii::$app->view->theme = [
'class' => 'yii\base\Theme',
'pathMap' => [
'@app/views' => ["@app/themes/$theme", '@app/themes/default'],
],
'baseUrl' => "@web/themes/$theme",
'basePath' => "@app/themes/$theme",
];
// 可存入 Session/Cookie 持久化
}
return parent::beforeAction($action);
}
?>
pathMap => [
'@app/views' => ['@app/themes/basic', '@app/themes/default'],
'@vendor/yiisoft/yii2-jui/views' => '@app/themes/basic/jui', // 覆蓋 JUI 視圖
'@app/modules/admin/views' => ['@app/themes/admin', '@app/themes/basic/modules/admin'],
]
| 現象 | 原因 | 解決 |
|---|---|---|
| 主題不生效 | pathMap 鍵別名錯誤、大小寫不符 (Linux 區分大小寫) | 用 `Yii::getAlias('@app/views')` 驗證實際路徑 |
| CSS/JS 404 | baseUrl/basePath 未配置或資源未發布 | 檢查 `baseUrl` 指向正確 Web 可訪問目錄 |
| 繼承鏈失效 | 值為字串而非陣列,或順序錯誤 | 確保值為陣列,優先級高→低 |
| 模組視圖未主題化 | pathMap 缺少模組路徑映射 | 添加 `@app/modules/ModuleID/views` 映射 |
更多 Yii2 主題進階:AssetBundle 整合主題、動態編譯 Less/Sass/PostCSS、多租戶主題隔離、主題市場架構設計、視圖渲染效能優化 (快取 `applyTo` 結果)、測試主題切換功能,請參考本站專題系列。