视图,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 = 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="' . URL_CSS . $css_path . '?v=' . UPDATE_TIME . '" rel="stylesheet" type="text/css"/>';
}
protected function addMoreJs($js_path) {
$this->more_js .= '<script type="text/javascript" src="' . URL_JS . $js_path . '?v=' . UPDATE_TIME . '"?></script>';
}
protected function addMoreFooterJs($js_path) {
$this->more_footer_js .= '<script type="text/javascript" src="' . URL_JS . $js_path . '?v=' . UPDATE_TIME . '"></script>';
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{$page_title}</title>
<link href="__CSSPATH__common.css?v=<?php echo UPDATE_TIME;?>" media="all" rel="stylesheet" type="text/css">
{$more_css}
{$more_js}
</head>
<body atk="{$ajax_token}">
<?php include ('common/header.html');?>
<div class="g-bd">
<?php if($page_tpl){ ?>
<?php include ($page_tpl);?>
<?php }?>
</div>
<?php include ('common/footer.html');?>
{$more_footer_js}
</body>
</html>
控制器使用时:
<?php
class main extends PCAction{
public function index(){
$this->name = 'frogluo';
$this->age = 32;
$this->uid = 123456;
$this->is_admin = 0;
$this->page_tpl = 'index.tpl';
$this->display();
//或
//$this->display('index.tpl');
}
}
对tmd_tpl变量使用自定义语法,采取替换,流程控制使用原生PHP语法,我觉得是比较好的一种实践组合。
tmd_tpl把模板引擎的基本思想都体现出来了,因此学习与改造都比较适合。
同时还改造了将模板中require/include引用其它模板的内容合并到主模板中,具体可见:
https://github.com/frogluo/php/tmd_tpl