傳統 <include> 只能靜態包含模板片段,無法處理「導航菜單需查詢資料庫並根據權限動態生成」此類業務邏輯。ThinkPHP 的 Widget (小工具) 機制正是為此設計:封裝「數據獲取 + 模板渲染」為可復用組件,支援參數傳遞、緩存、獨立控制器邏輯。本文從基礎到進階,展示 Widget 完整應用模式。
Application/Widget/ 目錄下 (ThinkPHP 3.x) 或 app/widget/ (5.x/6.x)。{Name}Widget 繼承 Widget 基類 (或 Controller),方法對應渲染動作。Widget/{Name}/ 目錄下的 .html 檔案。{:W('WidgetName/action', params)} 或 {:R('WidgetName/action', params, 'Widget')}。<?php
// Application/Widget/CateWidget.class.php
namespace Widget;
use Think\Controller;
class CateWidget extends Controller {
public function menu($limit = 10, $cacheTime = 300) {
// 1. 嘗試讀取緩存
$cacheKey = "cate_menu_{$limit}";
$menu = S($cacheKey);
if ($menu === false) {
// 2. 緩存未命中,查詢數據庫
$menu = M('Category')
->where(['status' => 1, 'is_nav' => 1])
->order('sort ASC')
->limit($limit)
->select();
// 3. 寫入緩存
S($cacheKey, $menu, $cacheTime);
}
// 4. 分配變量並渲染專屬模板
$this->assign('menuList', $menu);
$this->display('Cate:menu'); // 渲染 Widget/Cate/menu.html
}
}
<!-- Widget/Cate/menu.html -->
<ul class="nav navbar-nav">
<volist name="menuList" id="vo">
<li><a href="<?php echo U('Category/index', ['id'=>$vo['id']]); ?>"><?php echo $vo['title']; ?></a></li>
</volist>
</ul>
<!-- Tpl/Home/Index/index.html -->
<div class="header-nav">
{:W('Cate/menu', array(15, 600))}
</div>
<?php
class StatsWidget extends Controller {
public function onlineCount() {
$count = M('Session')->where(['expire' => ['GT', time()]])->count();
echo "<span class='badge'>在線: {$count} 人</span>";
}
}
模板調用需用 {~W('Stats/onlineCount')} (~ 表示直接輸出不捕獲緩衝區)。
public function tagCloud($limit = 20, $order = 'count DESC', $template = 'tag_cloud') {
$tags = M('Tag')->order($order)->limit($limit)->select();
$this->assign('tags', $tags);
$this->display("Tag:{$template}");
}
調用:{:W('Tag/tagCloud', ['limit'=>30, 'order'=>'rand()'])}
<?php
class IndexController extends Controller {
public function loadNav() {
// 直接返回 Widget 渲染的 HTML 片段
$html = R('Cate/menu', [], 'Widget'); // R 返回字串
$this->ajaxReturn(['html' => $html, 'time' => time()]);
}
}
public function hotProducts($cacheKey = 'hot_products', $expire = 1800) {
// 使用 ThinkPHP 內建 S() 函數 (支援 File/Redis/Memcache)
$data = S($cacheKey);
if (!$data) {
$data = M('Product')->where(['is_hot'=>1, 'status'=>1])
->field('id,name,price,thumb')
->limit(8)->select();
S($cacheKey, $data, $expire);
}
$this->assign('products', $data);
$this->display('Product:hot');
}
| 特性 | Widget (W/R) | R() 方法調用 | <include> |
|---|---|---|---|
| 業務邏輯 | 獨立控制器類 | 任意控制器方法 | 無 (純模板) |
| 數據查詢 | 內建 | 內建 | 需控制器預先賦值 |
| 參數傳遞 | 陣列/字串 | 陣列/字串 | 模板變量 |
| 獨立模板 | 是 (Widget/目錄) | 共用模板目錄 | 同級或絕對路徑 |
| 緩存整合 | 原生支援 S() | 手動處理 | 無 |
| 適用場景 | 高復用、有邏輯、跨頁面組件 | 控制器複用、AJAX 片段 | 靜態佈局片段 (header/footer) |
app\widget 命名空間並配置自動載入。$this->display('Cate:menu') 會尋找 Widget/Cate/menu.html,注意大小寫敏感 (Linux 環境)。$this->assign() 的變量不會污染主模板,反之亦然。SHOW_PAGE_TRACE 觀察 Widget 渲染耗時,超過 50ms 建議加緩存或異步載入。對於前後端分離架構,Widget 角色可由前端組件承接:
/api/widget/cate-menu 介面返回 JSON。<CateMenu /> 組件負責獲取、渲染、緩存 (SWR/React Query)。think-view 組件化渲染保持 SEO。Widget 是 ThinkPHP 模板引擎「邏輯與視圖分離」的重要補充,將「動態片段」封裝為自包含組件,極大提升了模板復用性與維護性。掌握「控制器邏輯 + 專屬模板 + 緩存策略」三位一體的開發模式,並根據專案架構適時遷移至前端組件化,是構建可演進 Web 應用的關鍵。