傳統 MVC 模式下,控制器需預先查詢資料庫並 assign 給模板,區塊多時控制器代碼冗長且難維護。本文示範開發 Smarty 3 函數插件 {load_data},讓模板直接宣告資料來源(表名、條件、限制),自動查詢並指派變數,配合 {foreach} 即時輸出。
建立 plugins/function.load_data.php 放置於 Smarty plugins 目錄(預設 libs/plugins/ 或專案自訂目錄)。
<?php
/**
* Smarty {load_data} 函數插件
* 用法:{load_data assign="變數名" table="表名" where="條件" limit="筆數" class="模型類別"}
*/
function smarty_function_load_data($params, &$smarty) {
// 1. 解析參數
$class = (!isset($params['class']) || empty($params['class'])) ? 'cls_crud' : trim($params['class']);
if (!isset($params['table']) || empty($params['table'])) {
trigger_error('{load_data}: `table` attribute is required', E_USER_ERROR);
return;
}
// 2. 實例化模型(假設採用工廠模式)
$db = $class::factory(array('table' => $params['table']));
// 3. 執行查詢
$where = isset($params['where']) ? $params['where'] : '';
$limit = isset($params['limit']) ? $params['limit'] : '';
$data = $db->get_block_list(array($where), $limit);
// 4. 指派給模板變數
if (!empty($params['assign'])) {
$smarty->assign($params['assign'], $data);
}
// 若未指定 assign,可選擇直接輸出或忽略
}
?>
{* 載入商品列表,指派給 $products *}
{load_data assign="products" table="products" where="`status`=1" limit=20 class="ProductModel"}
{foreach from=$products item=product}
<div class="product-card">
<h3>{$product.name}</h3>
<span class="price">{$product.price|number_format}</span>
</div>
{/foreach}
{* 載入最新消息,直接在區塊中迴圈 *}
{load_data assign="news" table="news" where="`pub_date`<='{date("Y-m-d")}'" limit=5}
{foreach from=$news item=n}
<a href="/news/{$n.id}">{$n.title}</a><br>
{/foreach}
$order = isset($params['order']) ? $params['order'] : '';
$fields = isset($params['fields']) ? $params['fields'] : '*';
$join = isset($params['join']) ? $params['join'] : '';
$data = $db->get_block_list(array($where), $limit, $order, $fields, $join);
// 例如:敏感詞過濾、日期格式化、金額分轉元
foreach ($data as &$row) {
$row['content'] = sensitive_filter($row['content']);
$row['created_at'] = date('Y-m-d H:i', strtotime($row['created_at']));
$row['price'] = $row['price'] / 100;
}
$cache_id = md5(serialize($params));
if ($smarty->caching && $cached = $smarty->getCachedTemplate($cache_id)) {
$data = $cached;
} else {
$data = $db->get_block_list(...);
if ($smarty->caching) $smarty->saveCachedTemplate($cache_id, $data);
}
<?php
class cls_crud {
protected $table;
public static function factory($config) {
$instance = new static();
$instance->table = $config['table'];
return $instance;
}
// 回傳二維陣列
public function get_block_list($where = array(), $limit = '', $order = '', $fields = '*', $join = '') {
$sql = "SELECT {$fields} FROM `{$this->table}`";
if ($join) $sql .= " {$join}";
if ($where) $sql .= " WHERE " . implode(' AND ', $where);
if ($order) $sql .= " ORDER BY {$order}";
if ($limit) $sql .= " LIMIT {$limit}";
// 使用 PDO 或資料庫抽象層執行
return $this->db->query($sql)->fetchAll(PDO::FETCH_ASSOC);
}
}
?>
| 優點 | 缺點/風險 |
|---|---|
| 控制器極度精簡,區塊獨立維護 | 模板包含商業邏輯(SQL 條件),違反嚴格 MVC |
| 區塊複用性高,不同頁面直接引用 | SQL 注入風險:where 參數直接拼接,需嚴格過濾或參數化 |
| 前端人員可自行調整查詢條件 | 難以進行單元測試、效能分析 |
| 減少控制器與模型的樣板代碼 | 資料庫連線數可能失控(每個 {load_data} 獨立查詢) |
大型專案建議:
ViewModel 物件,聚合該頁面所需所有資料。ViewModel 封裝查詢邏輯、快取、權限檢查。assign 整個 ViewModel 或陣列。{include} 子模板 + {function} 區塊函數。where 參數禁止直接拼接 SQL,改用參數綁定或白名單欄位/運算子。table、class 參數嚴格白名單驗證,防止任意表讀取/類別實例化。max(limit, 1000)),防止撈全表導致 OOM。security_policy,限制可用修飾器與 PHP 函數。