視圖,MVC中的V,View,如何將數據通過合適的格式展現給用戶或調用方。
當然使用什麽格式展現由控制器直接控制,但根本原因由人或系統決定。
本文主要描述的是如何在MVC的web框架中輸出網頁視圖,也就是HTML格式的視圖。
從開始學習PHP的教程,一般都是直接在PHP中嵌入HTML,這種方式簡單粗暴,但也是最根本最高效的方式。
回想一下,上面部分代碼是邏輯和數據準備,下面部分是嵌入HTML的PHP代碼。
這裏不能容忍的不是PHP中嵌入HTML,而是業務邏輯與視圖不分離,從代碼分離、人員分工等角度來說都不是很好的做法。
MVC的主要思想是層次分明,功能邏輯與視圖分離,而是否在視圖中使用PHP原生語法,站在MVC角度並不是主要關心的。所以作為MVC中視圖的模板引擎,第一要素是分離,其次才是考慮性能與語法。
對PHP有是否使用模板引擎之爭,以分離為核心的MVC,並不一定要使用模板引擎,但使用模板引擎能更好地分離,分離得更清晰。
如果以過程式開發,可以在A文件寫邏輯,然後最後require B,B寫視圖文件。這種情況,不強調模板引擎。
而對於面向對象的開發,上述方式顯得異樣;或者說受Java或ruby on rails影響,會抽象出專門的模板引擎來渲染視圖。這種情況講模板引擎才更有意義。
對於一些開源項目或框架的做法,我遇到過的這裏簡單列舉一下。
1. 跟入門教程一樣,業務邏輯與視圖顯示部分混在一起。
2. 把頁面元素封裝成一個個構件方法。這種方式不把流程看下來,都不知道哪輸出了視圖,而且布局、定位方面不太容易理解。
3. 通常意義下的視圖做法,組合一個模板引擎,初始化模板引擎,assign數據,display顯示視圖。比如:
$this->view = new View();
$this->view->tpl_dir = PATH_PAGE_VIEW;
$this->view->cache_dir = PATH_PAGE_CACHE;
$this->view->cache_time = TPL_CACHE_TIME;
$view->assign("name","lory");
$view->assign("uid",123456);
$view->display("index.tpl");// or $view->renderHtml('index.tpl);
4. 因為每次assign顯示很麻煩,有些做法是把整個控制器對象傳給視圖,由視圖取出public屬性。
這種方式挺好,推薦使用。
接下來是兩個主題,怎麽實現一個模板引擎和怎樣使用模板引擎。
如何實現模板引擎
說到模板引擎,smarty對PHP來說是繞不開的,各種方法、功能和機制完備,其中上所有場景有考慮到吧,但實際上在項目中使用時,只使用了其很少的幾個特性和功能,同時其性能也不好。因此選擇其它模板引擎或自己實現一個,也是有必要的,關鍵是不難。
看一下smarty的實現,或搜索一下PHP模板引擎,很多相關實現,本方也不詳細說明。可以看看tmd_tpl,我們項目就是在tmd_tpl基礎上修改了一下。
可以看出,模板引擎編譯出來的緩存文件,都是PHP中嵌入HTML的方式,模板引擎只是做了轉換的角色,最終執行的是緩存出來的緩存文件。
如果模板引擎自定義了語法,都是將自定義語法通過匹配替換的方式轉化為PHP語法的過程,雖然編譯過程效率不高,但如果直接加載緩存結果,也不會有多大損耗。
在自己實現時,頁面如何接收數據,可以關註方法extract,或是將頁面變量替換成一個方法來接收值。
如何使用模板引擎
一般模板引擎都有使用說明,下面是我們項目中的做法,僅為參考。
將模板引擎組合到控制器父類中,而每一個業務控制器作為子類,即可使用模板引擎。
一般情況下,需要對模板引擎做些封裝,子類不會直接調用到模板引擎的方法,而是通過父類方法間接調用。
在tmd_tpl基礎上,增加了方法:
function assignObj($obj) {
$data = http://www.ithao123.cn/get_object_vars ( $obj );
foreach ( $data as $key => $value ) {
$this->Assign ( $key, $value );
}
}
這樣只要是類的public屬性,即可傳遞給模板,而不用一個一個assign。
<?php require_once PATH_LIB . 'tmd_tpl.php'; /** */ class PCAction { protected $view; public $page_title = '模板引擎示例'; /** * 網站header/footer部分 */ public $header_tpl = 'common/header.html'; public $footer_tpl = 'common/footer.html'; /** * 網站內容部分 */ public $page_tpl; /** * $page_frame裏包含了header_tpl/footer_tpl/page_tpl的布局 */ public $page_frame = 'common/main_frame.html'; public $more_css = ''; public $more_js = ''; public $more_footer_js; function __construct() { } /** * 設置模板必要的參數。 */ function initViewConfig() { $this->view = new tmd_tpl (); $this->view->tpl_dir = PATH_PAGE_VIEW; $this->view->cache_dir = PATH_PAGE_CACHE; $this->view->cache_time = TPL_CACHE_TIME; $this->view->my_rep = array ( '~__ROOT__~' => URL_HOST, '~__JSPATH__~' => URL_JS, '~__CSSPATH__~' => URL_CSS, '~__PLUGINPATH__~' => URL_PLUGIN, '~__IMAGEPATH__~' => URL_IMAGE, '~__VERSION__~' => UPDATE_TIME ); } /** * PC端WEB頁面的顯示。 * * @param string $tpl * 如果指定了,只顯示tpl模板,沒有指定則顯示整個框架模板。 */ protected function display($tpl = '') { $this->initViewConfig (); $this->view->assignobj ( $this ); if ($tpl) { $this->view->display ( $tpl ); } else { if (! $this->page_tpl) { $this->page_tpl = 'index.html'; } $this->view->display ( $this->page_frame ); } exit (); } protected function addMoreCss($css_path) { $this->more_css .= '<link href=http://www.ithao123.cn/"' . URL_CSS . $css_path . '?v=' . UPDATE_TIME . '" rel="stylesheet" type="text/css"/>'; } protected function addMoreJs($js_path) { $this->more_js .= ''; } protected function addMoreFooterJs($js_path) { $this->more_footer_js .= ''; } }
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>{$page_title}</title> <link href=http://www.ithao123.cn/"__CSSPATH__common.css?v=<?php echo UPDATE_TIME;?>" media="all" rel="stylesheet" type="text/css"> {$more_css} {$more_js} <?php include ('common/header.html');?><?php if($page_tpl){ ?> <?php include ($page_tpl);?> <?php }?><?php include ('common/footer.html');?> {$more_footer_js}