CI 內建視圖機制功能單一,缺乏模板繼承、區塊覆寫、快取、沙箱安全等企業級特性。Smarty 3 作為成熟模板引擎,提供:
{block}/{extends} 實現佈局複用|escape, |date_format, |truncate 等CI_Controller (核心)
└── MY_Controller (應用基礎控制器)
├── $this->smarty (CISmarty 實例)
├── assign($key, $val) -> $this->smarty->assign()
├── display($tpl) -> $this->smarty->display()
└── fetch($tpl) -> $this->smarty->fetch()
CISmarty extends Smarty
├── __construct() 載入 CI 配置
├── template_dir = APPPATH . 'views/'
├── compile_dir = APPPATH . 'templates_c/'
├── cache_dir = APPPATH . 'cache/smarty/'
├── left_delimiter = '<{'
├── right_delimiter = '}>'
└── caching = FALSE (開發) / TRUE (生產)
# Composer 安裝 (推薦) composer require smarty/smarty ^3.1 # 或手動下載:https://www.smarty.net/download # 解壓 libs/ 至 application/libraries/Smarty-3.1.48/
application/libraries/CISmarty.php<?php
defined('BASEPATH') OR exit('No direct script access allowed');
require_once APPPATH . 'libraries/Smarty-3.1.48/libs/Smarty.class.php';
class CISmarty extends Smarty {
protected $CI;
public function __construct() {
parent::__construct();
$this->CI =& get_instance();
// 載入專用配置
$this->CI->config->load('smarty', TRUE, TRUE);
$cfg = $this->CI->config->item('smarty');
// 目錄配置
$this->setTemplateDir($cfg['template_dir'] ?? APPPATH . 'views/');
$this->setCompileDir($cfg['compile_dir'] ?? APPPATH . 'templates_c/');
$this->setCacheDir($cfg['cache_dir'] ?? APPPATH . 'cache/smarty/');
$this->setConfigDir($cfg['config_dir'] ?? APPPATH . 'configs/');
// 分隔符 (避免與 JS 模板衝突)
$this->left_delimiter = $cfg['left_delimiter'] ?? '<{';
$this->right_delimiter = $cfg['right_delimiter'] ?? '}>';
// 快取與編譯檢查
$this->caching = $cfg['caching'] ?? (ENVIRONMENT !== 'production');
$this->cache_lifetime = $cfg['cache_lifetime'] ?? 3600;
$this->compile_check = (ENVIRONMENT !== 'production');
$this->force_compile = (ENVIRONMENT === 'development');
// 安全設定:沙箱模式 (生產環境建議開啟)
if (ENVIRONMENT === 'production') {
$this->security_policy = 'php'; // 允許 PHP 標籤但受限
$this->php_handling = Smarty::PHP_PASSTHRU; // 或 PHP_REMOVE
$this->security_settings['PHP_HANDLING'] = Smarty::PHP_REMOVE;
}
// 註冊 CI 專用修飾詞/函數
$this->registerPlugin('modifier', 'base_url', function($url) {
return $this->CI->config->base_url($url);
});
$this->registerPlugin('modifier', 'site_url', function($uri) {
return $this->CI->config->site_url($uri);
});
$this->registerPlugin('function', 'csrf_field', function() use ($CI) {
return $CI->security->get_csrf_token_name() . '="' . $CI->security->get_csrf_hash() . '"';
});
}
}
application/config/smarty.php<?php
defined('BASEPATH') OR exit('No direct script access allowed');
$config['template_dir'] = APPPATH . 'views/';
$config['compile_dir'] = APPPATH . 'templates_c/';
$config['cache_dir'] = APPPATH . 'cache/smarty/';
$config['config_dir'] = APPPATH . 'configs/';
$config['left_delimiter'] = '<{';
$config['right_delimiter'] = '}>';
$config['caching'] = (ENVIRONMENT === 'production');
$config['cache_lifetime'] = 3600;
$config['compile_check'] = (ENVIRONMENT !== 'production');
$config['force_compile'] = (ENVIRONMENT === 'development');
mkdir -p application/templates_c application/cache/smarty application/configs chmod 777 application/templates_c application/cache/smarty # Windows: 右鍵屬性 → 安全性 → 編輯 → Everyone → 完全控制
application/config/autoload.php$autoload['libraries'] = ['database', 'session', 'CISmarty']; $autoload['helper'] = ['url', 'form', 'html'];
application/core/MY_Controller.php<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class MY_Controller extends CI_Controller {
public function __construct() {
parent::__construct();
}
// 代理常用方法,保持 Smarty 原生呼叫習慣
public function assign($key, $val = null) {
if (is_array($key)) {
foreach ($key as $k => $v) $this->smarty->assign($k, $v);
} else {
$this->smarty->assign($key, $val);
}
return $this; // 支援鏈式呼叫
}
public function display($template, $cache_id = null, $compile_id = null) {
$this->smarty->display($template, $cache_id, $compile_id);
}
public function fetch($template, $cache_id = null, $compile_id = null) {
return $this->smarty->fetch($template, $cache_id, $compile_id);
}
// 便利方法:直接渲染並輸出 JSON (API 專用)
public function render_json($data, $status = 200) {
$this->output
->set_status_header($status)
->set_content_type('application/json', 'utf-8')
->set_output(json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES))
->_display();
exit;
}
}
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Welcome extends MY_Controller {
public function index() {
$this->assign([
'title' => '歡迎來到 CI + Smarty',
'user' => ['name' => 'John', 'role' => 'admin'],
'items' => ['Apple', 'Banana', 'Cherry'],
'base_url' => base_url(),
]);
// 直接渲染模板
$this->display('welcome.tpl');
}
public function api_users() {
$users = $this->db->get('users')->result_array();
$this->render_json(['code'=>0, 'data'=>$users]);
}
}
application/views/welcome.tpl<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><{$title}></title>
<link href="<{base_url('assets/css/app.css')}>" rel="stylesheet">
</head>
<body>
<header>
<h1><{$title}></h1>
<p>當前用戶: <{$user.name}> (<{$user.role}>)</p>
</header>
<main>
<ul>
<{foreach $items as $item}>
<li><{$item}></li>
<{/foreach}>
</ul>
</main>
<{* 模板繼承範例 *}>
<{extends file="layouts/main.tpl"}>
<{block name="content"}>
<p>這是子頁面內容</p>
<{/block}>
</body>
</html>
application/views/layouts/main.tpl<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><{block name="title"}>預設標題<{/block}></title>
<{block name="head"}><{/block}>
</head>
<body>
<header><{block name="header"}>共用導航列<{/block}></header>
<main><{block name="content"}><{/block}></main>
<footer><{block name="footer"}>© 2024 My App<{/block}></footer>
<{block name="scripts"}><{/block}>
</body>
</html>
// 子頁面 application/views/user/profile.tpl
<{extends file="layouts/main.tpl"}>
<{block name="title"}>用戶檔案 - <{$user.name}><{/block}>
<{block name="content"}>
<div class="profile">
<h2><{$user.name}></h2>
<p>Email: <{$user.email}></p>
</div>
<{/block}>
$this->smarty->registerPlugin('modifier', 'json', 'json_encode'); → 模板用 <{$data|json}>$this->smarty->display('page.tpl', 'cache_id_123'); 針對不同用戶/參數快取$this->smarty->debugging = true; 顯示調試主控台$this->smarty->security_policy = 'php'; $this->smarty->php_handling = Smarty::PHP_REMOVE;| 現象 | 原因 | 解決 |
|---|---|---|
| 模板變更不生效 | compile_check=FALSE 或快取未清除 | 刪除 templates_c/ 內容;開發環境設定 force_compile=TRUE |
| Class 'Smarty' not found | require_once 路徑錯誤 | 確認 Smarty.class.php 實際路徑,使用 APPPATH 常數 |
| 分隔符衝突 (Vue/React {{ }}) | 預設 { } 被前端框架佔用 | 設定 left_delimiter='<{', right_delimiter='}>' |
| 中文亂碼 | 模板檔案編碼非 UTF-8 | 所有 .tpl 檔案儲存為 UTF-8 without BOM |
| 權限錯誤 (無法寫入 compile_dir) | 目錄不可寫 | chmod 777 templates_c/ cache/smarty/ |
透過 CISmarty 封裝庫 + MY_Controller 代理方法 + 專用配置檔,實現 CI 3 與 Smarty 3 的無縫整合。開發者在控制器中使用 $this->assign()/$this->display() 即可享受模板繼承、沙箱安全、編譯快取等企業級特性,同時保持 CI 原生開發習慣。建議新專案直接採用此架構,老專案可漸進式遷移視圖檔案。