首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

CodeIgniter 整合 PHPCMS 模板引擎:模板編譯、快取與標籤解析完整移植

     編輯:PHP 相關知識

移植動機

CodeIgniter 內建 View 機制較簡單 (純 PHP),前端設計師需懂 PHP 語法。PHPCMS 模板引擎提供 標籤語法 (類 Smarty)、編譯快取、區塊標籤 等功能,更適合設計師協作。本文完整移植 PHPCMS 模板核心到 CI。

核心類別:Template_Cache (模板編譯與快取)

<?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;
    }
}
?>

全域輔助函數:template()

<?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;
    }
}
?>

MY_Controller 整合

<?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
    }
}
?>

模板檔案範例 (PHPCMS 語法)

<!-- 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.phpCI 預設結構相容

效能優化建議

  1. Opcode 快取:生產環境必開啟 OPcache,編譯後 PHP 直接執行無解析開銷
  2. 模板快取永久化:生產環境關閉自動重新編譯 (檢查 filemtime),手動清除快取部署
  3. 標籤類快取:content_tag 等類別載入後快取實例,避免重複 load->library()
  4. 區塊標籤非同步:前端 JS 載入區塊內容 (BigPipe 模式),首屏更快
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved