CodeIgniter 內建 View 機制較簡單 (純 PHP),前端設計師需懂 PHP 語法。PHPCMS 模板引擎提供 標籤語法 (類 Smarty)、編譯快取、區塊標籤 等功能,更適合設計師協作。本文完整移植 PHPCMS 模板核心到 CI。
<?php
// application/libraries/Template_cache.php
defined('BASEPATH') OR exit('No direct script access allowed');
class Template_cache {
public $cache_path;
public function __construct() {
$this->cache_path = APPPATH . 'views';
}
// 編譯模板:原始模板 -> 編譯後 PHP -> 寫入快取
public function template_compile($module, $template, $style = 'default') {
$tplFile = $this->cache_path . DIRECTORY_SEPARATOR . $module . DIRECTORY_SEPARATOR . $template . '.php';
if (!file_exists($tplFile)) {
show_error($tplFile, 500, 'Template does not exist');
}
$content = file_get_contents($tplFile);
$content = $this->template_parse($content); // 核心解析
$cacheDir = $this->cache_path . DIRECTORY_SEPARATOR . 'caches_template' . DIRECTORY_SEPARATOR . $style . DIRECTORY_SEPARATOR . $module . DIRECTORY_SEPARATOR;
if (!is_dir($cacheDir)) {
mkdir($cacheDir, 0777, true);
}
$compiledFile = $cacheDir . $template . '.php';
$len = file_put_contents($compiledFile, $content);
chmod($compiledFile, 0777);
return $len;
}
// 核心解析:標籤語法 -> PHP 程式碼
public function template_parse($str) {
// 模板繼承/包含
$str = preg_replace("/\{template\s+(.+)\}/", "", $str);
$str = preg_replace("/\{include\s+(.+)\}/", "", $str);
$str = preg_replace("/\{view\s+(.+)\}/", "load->view('\\1'); ?>", $str);
// PHP 直寫
$str = preg_replace("/\{php\s+(.+)\}/", "", $str);
// 條件判斷 (雙層大括號防衝突)
$str = preg_replace("/\{\{if\s+(.+?)\}\}/", "``if \\1``", $str);
$str = preg_replace("/\{\{else\}\}/", "``else``", $str);
$str = preg_replace("/\{\{\/if\}\}/", "``/if``", $str);
$str = preg_replace("/\{if\s+(.+?)\}/", "", $str);
$str = preg_replace("/\{else\}/", "", $str);
$str = preg_replace("/\{elseif\s+(.+?)\}/", "", $str);
$str = preg_replace("/\{\/if\}/", "", $str);
// 還原雙層
$str = preg_replace("/\`\`if\s+(.+?)\`\`/", "{{if \\1}}", $str);
$str = preg_replace("/\`\`else\`\`/", "{{else}}", $str);
$str = preg_replace("/\`\`\/if\`\`/", "{{/if}}", $str);
// 迴圈
$str = preg_replace("/\{for\s+(.+?)\}/", "", $str);
$str = preg_replace("/\{\/for\}/", "", $str);
// 自增/自減
$str = preg_replace("/\{\+\+(.+?)\}/", "", $str);
$str = preg_replace("/\{\-\-(.+?)\}/", "", $str);
$str = preg_replace("/\{(.+?)\+\+\}/", "", $str);
$str = preg_replace("/\{(.+?)\-\-\}/", "", $str);
// 迴圈標籤 (核心)
$str = preg_replace("/\{loop\s+(\S+)\s+(\S+)\}/", "", $str);
$str = preg_replace("/\{loop\s+(\S+)\s+(\S+)\s+(\S+)\}/", " \\3) { ?>", $str);
$str = preg_replace("/\{\/loop\}/", "", $str);
// 函數呼叫標籤
$str = preg_replace("/\{([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff:]*\(([^{}]*)\))\}/", "", $str);
$str = preg_replace("/\{\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff:]*\(([^{}]*)\))\}/", "", $str);
// 變數輸出
$str = preg_replace("/\{(\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\}/", "", $str);
$str = preg_replace("/\{(\$[a-zA-Z0-9_\[\]\'\"\$\x7f-\xff]+)\}/es", "\$this->addquote('')", $str);
// 常數輸出
$str = preg_replace("/\{([A-Z_\x7f-\xff][A-Z0-9_\x7f-\xff]*)\}/s", "", $str);
// PC 區塊標籤 (PHPCMS 核心功能)
$str = preg_replace("/\{pc:(\w+)\s+([^}]+)\}/ie", "self::pc_tag('$1','$2', '\$0')", $str);
$str = preg_replace("/\{\/pc\}/ie", "self::end_pc_tag()", $str);
// 安全防護標頭
return "" . $str;
}
// 陣列轉義輔助
public function addquote($var) {
return str_replace('\\"', '"', preg_replace("/\[([a-zA-Z0-9_\-\.\x7f-\xff]+)\]/s", "['\\1']", $var));
}
// PC 標籤處理 (動態載入模組標籤類)
public static function pc_tag($op, $data, $html) {
// 解析屬性: pc:content action="lists" catid="1" num="10"
preg_match_all("/([a-z]+)\=[\"]?([^\"]+)[\"]?/i", stripslashes($data), $matches, PREG_SET_ORDER);
$arr = ['action','num','cache','page','pagesize','urlrule','return','start','setpages'];
$tools = ['json','xml','block','get'];
$datas = [];
$tagId = md5(stripslashes($html));
$strDatas = 'op='.$op.'&tag_md5='.$tagId;
foreach ($matches as $v) {
$strDatas .= $strDatas ? "&$v[1]=".(strpos($v[2], '$')===0 ? $v[2] : urlencode($v[2])) : "$v[1]=".(strpos($v[2], '$')===0 ? $v[2] : urlencode($v[2]));
if (in_array($v[1], $arr)) { $$v[1] = $v[2]; continue; }
$datas[$v[1]] = $v[2];
}
$str = '';
$setpages = isset($setpages) && intval($setpages) ? intval($setpages) : 10;
$num = isset($num) && intval($num) ? intval($num) : 20;
$cache = isset($cache) && intval($cache) ? intval($cache) : 0;
$return = isset($return) && trim($return) ? trim($return) : 'data';
$urlrule = isset($urlrule) ? $urlrule : '';
// 快取檢查
if (!empty($cache) && !isset($page)) {
$str .= "\$tag_cache_name = md5(implode('&',".self::arr_to_html($datas).").'".$tagId."');";
$str .= "if(!\$".$return." = tpl_cache(\$tag_cache_name,".$cache.")){";
}
// 工具類標籤
if (in_array($op, $tools)) {
switch ($op) {
case 'json':
if (isset($datas['url'])) {
$str .= "\$json = @file_get_contents('".$datas['url']."');";
$str .= "\$".$return." = json_decode(\$json, true);";
}
break;
case 'block':
$str .= "\$block_tag = pc_base::load_app_class('block_tag', 'block');";
$str .= "echo \$block_tag->pc_tag(".self::arr_to_html($datas).");";
break;
}
} else {
// 業務模組標籤 (如 content, category 等)
if (!isset($action) || empty($action)) return false;
$tagClass = $op . '_tag';
$tagFile = APPPATH . 'libraries' . DIRECTORY_SEPARATOR . $tagClass . '.php';
if (file_exists($tagFile)) {
$str .= 'if(!isset($CI))$CI =& get_instance();';
$str .= '$CI->load->library("'.$tagClass.'");';
$str .= 'if (method_exists($CI->'.$tagClass.', \''.$action.'\')) {';
if (isset($start) && intval($start)) {
$datas['limit'] = intval($start).','.$num;
} else {
$datas['limit'] = $num;
}
if (isset($page)) {
$str .= "\$pagesize = $num;";
$str .= "\$page = intval($page) ? intval($page) : 1; if(\$page<=0){\$page=1;}";
$str .= "\$offset = (\$page - 1) * \$pagesize; \$urlrule=\"$urlrule\";";
$datas['limit'] = '$offset.",".$pagesize';
$datas['action'] = $action;
$str .= "\$".$op."_total = \$CI->".$tagClass."->count(".self::arr_to_html($datas).");";
$str .= "if(\$".$op."_total>\$pagesize){ \$pages = pages(\$".$op."_total, \$page, \$pagesize, \$urlrule); } else { \$pages=\"\" ;}";
}
$str .= "\$".$return." = \$CI->".$tagClass."->".$action."(".self::arr_to_html($datas).");";
$str .= '}';
}
}
if (!empty($cache) && !isset($page)) {
$str .= "if(!empty(\$".$return.")){setcache(\$tag_cache_name, \$".$return.", 'tpl_data');}";
$str .= '}';
}
return "";
}
static private function end_pc_tag() {
return '\';}?>';
}
private static function arr_to_html($data) {
if (is_array($data)) {
$str = 'array(';
foreach ($data as $key => $val) {
if (is_array($val)) {
$str .= "'$key'=>".self::arr_to_html($val).",";
} else {
if (strpos($val, '$')===0) {
$str .= "'$key'=>$val,";
} else {
$str .= "'$key'=>'".self::new_addslashes($val)."',";
}
}
}
return $str.')';
}
return false;
}
function new_addslashes($string) {
if (!is_array($string)) return addslashes($string);
foreach ($string as $key => $val) $string[$key] = $this->new_addslashes($val);
return $string;
}
}
?>
<?php
// application/helpers/global_helper.php
if (!function_exists('template')) {
function template($module = 'content', $template = 'index', $style = 'default', $returnFullPath = true) {
$CI =& get_instance();
$CI->load->library('template_cache');
if (!$style) $style = 'default';
// 編譯後快取路徑
$compiledFile = $CI->template_cache->cache_path . DIRECTORY_SEPARATOR
. 'caches_template' . DIRECTORY_SEPARATOR . $style . DIRECTORY_SEPARATOR
. $module . DIRECTORY_SEPARATOR . $template . '.php';
// 原始模板路徑
$tplFile = APPPATH . 'views' . DIRECTORY_SEPARATOR . $module . DIRECTORY_SEPARATOR . $template . '.php';
// 檢查是否需重新編譯
if (file_exists($tplFile)) {
if (!file_exists($compiledFile) || filemtime($tplFile) > filemtime($compiledFile)) {
$CI->template_cache->template_compile($module, $template, $style);
}
} else {
// 回退預設風格
$compiledFile = str_replace($style, 'default', $compiledFile);
$tplFile = str_replace($style, 'default', $tplFile);
if (!file_exists($compiledFile) || (file_exists($tplFile) && filemtime($tplFile) > filemtime($compiledFile))) {
$CI->template_cache->template_compile($module, $template, 'default');
} elseif (!file_exists($tplFile)) {
show_error($tplFile, 500, 'Template does not exist');
}
}
return $returnFullPath ? $compiledFile : 'caches_template' . DIRECTORY_SEPARATOR . $style . DIRECTORY_SEPARATOR . $module . DIRECTORY_SEPARATOR . $template;
}
}
?>
<?php
// application/core/MY_Controller.php
class MY_Controller extends CI_Controller {
protected $pageData = [];
public function __construct() {
parent::__construct();
$this->pageData['controller_name'] = $this->router->fetch_class();
$this->pageData['module_name'] = $this->router->fetch_method();
}
// 統一模板輸出
protected function view($viewFile, $data = [], $cache = false) {
// 自動解析模組/模板路徑
$module = $this->pageData['controller_name'];
$template = $viewFile;
// 載入模板 (自動編譯/快取)
$compiledTemplate = template($module, $template, 'default');
// 合併資料
$viewData = array_merge($this->pageData, $data);
// 輸出
$this->load->view($compiledTemplate, $viewData);
}
}
// 實際控制器
class Article extends MY_Controller {
public function index() {
$data['articles'] = $this->article_model->get_list();
$this->view('article_list', $data); // 自動對應 views/article/article_list.php
}
}
?>
<!-- views/article/article_list.php -->
<html>
<head><title>{$SEO_TITLE}</title></head>
<body>
<div class="container">
<h1>文章列表</h1>
<!-- 區塊標籤:動態載入內容模組 lists 動作 -->
{pc:content action="lists" catid="$catid" num="10" page="$page" order="id DESC"}
<ul class="article-list">
{loop $data $v}
<li>
<a href="{$v[url]}">{$v[title]}</a>
<span class="date">{$v[inputtime]}</span>
</li>
{/loop}
</ul>
<!-- 分頁 -->
{if $pages}
<div class="pagination">{$pages}</div>
{/if}
{/pc}
</div>
</body>
</html>
| PHPCMS 原生 | CI 移植版 | 說明 |
|---|---|---|
pc_base::load_app_class | $CI->load->library() | CI 無 pc_base,改用標準庫載入 |
tpl_cache()/setcache() | 需自行實作或用 CI Cache | 建議對接 Redis/Memcached |
pages() 分頁函數 | 需移植或用 CI Pagination | 語法需適配 |
IN_ADMIN 常數 | CI 無此常數 | 後台編輯狀態判斷需自行實作 |
| 模板目錄結構 | views/module/template.php | CI 預設結構相容 |
content_tag 等類別載入後快取實例,避免重複 load->library()