Smarty 預設需在 PHP 端 assign 變數才能在模板使用,若區塊眾多會導致控制器臃腫。本文示範如何修改 Smarty 3 編譯器,新增 {load}…{/load} 標籤,讓模板直接宣告資料來源並自動迴圈,語法類似 {foreach}。
Smarty_Compiler.class.php_compile_tag() 中註冊標籤<?php
// 載入開始標籤
case 'load':
$this->_push_tag('load');
return $this->_complie_load_start($tag_args);
break;
// 載入結束標籤
case '/load':
$this->_pop_tag('load');
return "";
break;
_complie_load_start()<?php
/**
* 編譯 {load} 開始標籤
* 語法:{load table="表名" where="條件" limit="筆數" class="模型類別" item="迴圈變數"}
*/
function _complie_load_start($tag_args) {
// 依參數生成唯一暫存變數名,避免巢狀衝突
$key = substr(md5($tag_args), 8, 16);
// 解析屬性
$attrs = $this->_parse_attrs($tag_args);
// 預設模型類別
$class = (!isset($attrs['class']) || empty($attrs['class'])) ? 'cls_crud' : trim($attrs['class']);
// 必填:table
if (!isset($attrs['table']) || empty($attrs['table'])) {
exit('`table` attribute is empty!');
}
$table = substr($attrs['table'], 1, -1); // 去除引號
// 實例化模型並取得資料
$db = $class::factory(array('table' => $table));
$where = isset($attrs['where']) ? substr($attrs['where'], 1, -1) : '';
$limit = isset($attrs['limit']) ? $attrs['limit'] : '';
// 指派給 Smarty 臨時變數
$this->_tpl_vars[$key] = $db->get_block_list(array($where), $limit);
// 將 from 導向該變數,交由 foreach 編譯器處理
$tag_args = "from=\$" . $key . " " . $tag_args;
return $this->_compile_foreach_start($tag_args);
}
{load table="test" where="`id`<100" limit=10 item=rec}
<div class="item">
<h3>{$rec.title}</h3>
<p>{$rec.content}</p>
</div>
{/load}
編譯後相當於:
<?php
$_from = $this->_tpl_vars[''];
if (is_array($_from) && count($_from)):
foreach ($_from as $this->_tpl_vars['rec']):
?>
<div class="item">...</div>
<?php
endforeach;
endif;
unset($_from);
?>
_parse_attrs 解析更多屬性,傳給 get_block_list。Cache::get/set,避免重複查詢。debug=true 參數,編譯時輸出 SQL 日誌。where 直接拼入 SQL,務必在模型層使用參數綁定或白名單過濾。item 不衝突。若不想修改編譯器,可撰寫區塊函數 smarty_block_load:
<?php
function smarty_block_load($params, $content, $smarty, &$repeat) {
if ($repeat) return; // 只執行一次
$class = isset($params['class']) ? $params['class'] : 'cls_crud';
$table = $params['table'];
$where = isset($params['where']) ? $params['where'] : '';
$limit = isset($params['limit']) ? $params['limit'] : '';
$db = $class::factory(array('table' => $table));
$data = $db->get_block_list(array($where), $limit);
$out = '';
foreach ($data as $row) {
$smarty->assign('rec', $row);
$out .= $smarty->fetch('string:' . $content); // 子模板渲染
}
return $out;
}
?>
模板寫法:
{load table="test" where="id<100" limit=10}
{$rec.title} - {$rec.content}<br>
{/load}
此法無需修改核心,相容性較佳,適合輕量專案。