热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

PHPMVC及模板引擎

模板引擎,这四个字听起来很高深的样子,一般用到“引擎”两字都会感觉比较高级,类似游戏3D引擎、Zend引擎等,其实都是唬人
模板引擎,这四个字听起来很高深的样子,一般用到“引擎”两字都会感觉比较高级,类似游戏3D引擎、Zend引擎等,其实都是唬人的,骗外行人的。所以在我初学PHP的那会,也因为这四个字导致了我觉得很难而没有去看他到底是什么样一个东西,直到很长时间以后使用Smarty才真正了解模板引擎的原理和作用。Smarty(http://smarty.php.net),PHP官方模板引擎,看名字给人感觉应该很快,其实很慢,即使他有预编译(另一个看起来很高级的名词,同样也是唬人的,下面我会讲到这个)。[注:我刚才点开Smarty发现他说他已经不是一个PHP子项目了,汗,看来确实唬人,哈玩笑^_^]。其实在PHP里,模板引擎扮演着View(其实通俗说就是页面,看英文有时候会给人很高级的错觉)的角色,这是一个很重要的角色,因为用户的交互啊,界面效果啊等等都在这里,这是最终用户看到的你的系统的样子。

  开头就说模板引擎,只是跟大家说明一下这个东西其实没有什么难理解的,明白其原理以后你会发现他是纸老虎,所以你要有信心你会很轻松看完此文。

  为了更好的说明模板引擎所扮演的角色,我不得不也谈谈MVC。这个话题恐怕互联网上谈及的很多,我也只能根据我的理解来描述,可能有不恰当的地方,欢迎讨论。通常的MVC是指Model、View和Controller。也就是模型、视图和控制器。我理解MVC也是在学了PHP不短时间后了,当时请教老廖(http://qeephp.com),才恍然大悟。

  先来说说Controller,也就是控制器,控制器是个什么东西呢?在PHP里他是扮演一个接收用户请求,把用户请求定位到指定数据模型的角色。解释起来感觉不是很好解释,来看一个简单的留言本的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//用户请求可能是 http://www.example.com/guest.php?module=list
$module=$_GET['module'];
  
switch($module) {
    case'list':
        require_once'list.php';
        break;
    case'add':
        require_once'add.php';
        break;
    case'del':
        require_once'del.php';
        break;
    default:
        require_once'list.php';
        break;
}

  是不是看起来很简单好像没什么东西呀,只是根据用户的请求参数包含不同的文件而已。没错,确实很容易,这个switch语句其实就一个最简单的控制器的实现。他控制什么?他控制你根据不同的用户请求参数调用不同的数据模型处理用户请求。那么这里的list可能是一个留言列表,add是添加留言,del是删除留言。Controller的传统实现可以这么简单,当然现在的很多技巧包括根据不同的用户请求包含不同的业务逻辑处理类,比如list自动定位到/model/List.class.php这样的一些技巧性操作等。

  再来说说Model,其实我们一般花比较长时间设计和编写的也是这块内容,也就是具体的业务逻辑实现。比如一个留言列表要处理些什么,都是在这里实现。还是直接看一个Model例子比较直观:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//Guest_List.class.php
classGuest_List {
    public$page= 1;
    publicfunction__construct() {
        $this->db = DB::init($GLOBALS['dsn']);
        $this->page = (int)$_GET['page'];
    }
  
    publicfunctiongetList() {
        $begin=$this->page * 10;
        $sql="SELECT * FROM guest ORDER BY addTime DESC LIMIT $begin, 10";
        return$this->db->getAll($sql);
    }
}

  这里的Guest_List就是一个简单的Model实现,构造函数取得页数page参数,getList方法查询留言列表并返回结果集。那么在list.php里可能是这样调用的:

1
2
3
4
//list.php
require_once'Guest_List.class.php';
$model=newGuest_List();
$lists=$model->getList();

  嗯,其实很多MVC框架都是这么实现的,只不过可能加了一些自动调用的机制,会根据用户请求自动调用类,自动执行方法,呵呵。Model大功告成。这里需要明确一点就是,Model只是返回视图上所可能需要用到的数据,他不负责任何和显示有关的事情,那么显示相关的就交给View来做了。我们是不是不知不觉已经把表现和业务逻辑分离了?没错,分离就是这么简单。

  好了,来看看View怎么利用Model返回的数据来显示页面吧。最简单的例子,我们只需要在list.php里增加一行即可。

1
2
3
4
5
6
//list.php
require_once'Guest_List.class.php';
$model=newGuest_List();
$lists=$model->getList();
//上面是Model,那么下面就是View
require_once'list.html';

  来看看View都做些什么吧,我们用list.html来表示留言列表所展现给用户的界面文件,用html来命名看起来会更直观一些,他好像是个html文件,负责输出html代码给浏览器。来看看list.html可能长什么样子:

1
2
3
4
5
6
7
8
9
10
  
  
    
    
    
  
  

  不难看出来这个文件所做的只不过是遍历留言数组$lists,然后输出每一行的留言,对留言的内容处理做了htmlspecialchars和date转换(与显示相关的处理),除了和显示相关的操作,他没有再做任何业务逻辑了(也不应该有)。

  我发现写到这里真的没有什么好写的了,MVC就是这些(或者再做一些扩展),至于怎么做到表现和业务分离,那么就是在你的Model里只返回数据,也就是你View所需要用到的数据,而你的View拿到这些数据后负责去显示他就可以了,不应该在你的Model里做显示和视觉相关的操作,也不应该在你的View里做一些业务逻辑相关的操作,把这两者分清楚,就自然而然的表现与业务分离了。

  接下来说说负责View的模板引擎吧,其实你在上面应该已经看到了一个最简陋的模板引擎,那就是View部分的 require_once 语句。厄,实在是太简单了,模板引擎其实是调度并解析模板的东西,其中调度模板由 require_once 搞定了,那么解析呢?这里由 PHP 引擎本身来搞定了。哈,没错,我一直都认为 PHP 是个最好的模板引擎。

  不过还是不得不说说传统的模板引擎的实现原理,一般来说会有这么几个步骤:
  1、注册变量,也就是把从Model返回的数据注册到模板引擎中,告诉模板引擎这个变量可以使用,其实所谓的注册也只不过是不得不这么做,因为一般引擎内部函数是没办法直接访问Model返回的变量的(变量作用域的问题),所以不得不加一个注册操作,把这些变量转换从模板引擎类的属性等。
  2、模板解析,就是读取模板文件,按照模板语法将标签解析成 PHP 语法,或者执行一些替换操作,用变量内容替换掉模板标签,其实效果都差不多。
  3、如果不是将变量内容替换掉模板标签,那么基本上第三步就是将注册的变量和解析完的模板融合在一起输出,类似于上面的list.html,是个解析完的文件,然后输出。

  一般模板引擎还会提供不少用于显示内容处理的插件,比如日期转换、字符串处理、生成表格、生成select等,这些给页面制作提供了一些方便。Smarty还包含了一些页面缓存机制,也很不错。

  很多模板引擎都顶着语法简单的嚎头,美其名曰降低美工的学习门槛。其实我不得不问,有多少模板是由美工来做的呢?而且对比两种语法,不觉得 PHP 的简单循环和输出有什么难以理解的,对比下面两种语法:

1
2
3
 

  和

1
2
3
{value['userName']}

  我左看右看都觉得他们差不多,呵呵,与其再学习一套语法,还不如直接用你已经非常熟悉的PHP呢,为什么要虐待自己呢?而且从可维护性的角度来讲,维护PHP语法和维护模板语法,哪种更容易呢?PHP是标准,只要会PHP都知道他怎么写,表示什么,但是模板引擎千奇百怪的,各种语法都有,不是一个统一的标准,我想谁维护一个从来没有用过的模板,都需要花不少时间去学习引擎语法。更何况即使模板可以那样写,最终还是需要一堆正则替换成PHP语法。我敢肯定,前面写的哪种模板引擎语法最终会被转换成它上面那种PHP。其实模板引擎的解析也就是将模板语法转换成PHP语法的过程。抛开效率来说,多此一举。就象《C专家编程》作者说的,即使你能用宏把C写成看起来好像另外一种语言,但是你不要这么做,同样的这句告诫是否适合于模板引擎呢,它看起来很像另外一种语言。当然我这篇文章不是来批判模板引擎的,哈。它既然存在,也有其存在的道理,某些场合还是不得不用的,比如如果你把模板提供给用户去制作和使用,那么你不得不采用标签以限制用户使用PHP语法,来增强系统安全性。

  再来说效率问题,由于模板引擎要解析模板语法,会用到很多正则匹配和替换,那么在实际运行中是比较消耗系统资源的,而且当模板标签非常复杂或者嵌套多层的时候,效率是比较低的,因为有了一种处理方法,就是预编译。所谓的预编译,就是把带有模板语法的模板,通过处理,转换成 PHP 语法的文件,只要模板文件没有被修改,那么直接包含编译后的文件即可,这样就不需要再次替换和匹配,可以大大提高效率,不过由于模板引擎的复杂性,导致编译后的结果文件仍然比我们一般写出来的PHP文件复杂得多。所以其实效率还是远低于直接编写PHP模板的。有兴趣的可以打开一个Smarty编译过的文件,看看其嵌套,其实要比直接循环来得复杂。

  本文写到这里也差不多了,具体模板引擎如何编译如何处理,各个模板引擎的方式都不一样,有兴趣的可以去下载几个比较经典的引擎看看,比如Smarty。随后我附上我自己用的PHP模板引擎。

  PS:其实我觉得MVC应该叫做CMV是不是更符合逻辑呢?没有考证过这个词的由来^_^。

我的真正PHP模板引擎:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/**
 * 模板引擎
 *
 * Copyright(c) 2005-2008 by 陈毅鑫(深空). All rights reserved
 *
 * To contact the author write to {@link mailto:shenkong@php.net}
 *
 * @author 陈毅鑫(深空)
 * @version $Id: Template.class.php 1687 2008-07-07 01:16:07Z skchen $
 * @package Template
 */
  
defined('FW') ||exit(header('HTTP/1.0 400 Bad Request'));
  
classTemplate {
    protectedstatic$obj;
  
    public$vars;
    public$includeFiles;
    public$includeFile;
    public$templates;
    public$template;
    public$contents;
    protected$_content;
    protected$_contents;
    protected$_path;
  
    protectedfunction__construct() {
        $this->vars =array();
        require_onceROOT_PATH ."lib/template.func.php";
    }
  
    /**
     * 初始化模板引擎
     *
     * @return object 模板引擎对象
     */
    publicstaticfunction&init() {
        if(is_null(self::$obj)) {
            self::$obj=newTemplate();
        }
        returnself::$obj;
    }
  
    /**
     * 注册模板变量
     *
     * 注册模板变量后在模板里就可以直接使用该变量,注册与被注册变量名不一定要一样
     * 如:$template->assign('var', $value);
     * 意思是将当前变量$value注册成模板变量var,在模板里就可以直接调用$val
     *
     * @param string $var 注册到模板里的变量名的字符串形式,不包含$
     * @param mixed $value 需要注册的变量
     */
    publicfunctionassign($var,$value) {
        if(is_array($var)) {
            foreach($varas$key=>$val) {
                $this->vars[$key] =$val;
            }
        }else{
            $this->vars[$var] =$value;
        }
    }
  
    /**
     * 解析模板文件
     *
     * 解析模板,并将变量植入模板,解析完后返回字符串结果
     *
     * @param unknown_type $templates
     * @return unknown
     */
    publicfunctionfetch($templates) {
        if(is_array($templates)) {
            $this->templates =$templates;
        }else{
            $this->templates = func_get_args();
        }
        extract($this->vars);
  
        $this->_cOntents='';
        foreach($this->templatesas$this->template) {
            ob_end_clean();
            ob_start();
            $this->_path =$this->getPath($this->template);
            require$this->_path;
            $this->_cOntent= ob_get_contents();
            ob_end_clean();
            ob_start();
            $this->_contents .=$this->_content;
            $this->contents[$this->template] =$this->_content;
        }
        return$this->_contents;
    }
  
    publicfunctiongetPath($path) {
        $path=explode(".",$path);
        $num=count($path);
        if($num== 1) {
            returnROOT_PATH ."template". DIRECTORY_SEPARATOR .$path[0] .".html";
        }elseif($num> 1) {
            $templatePath='';
            $templatePath=$path[$num- 1];
            array_pop($path);
            $templatePath= ROOT_PATH . implode(DIRECTORY_SEPARATOR,$path) . DIRECTORY_SEPARATOR .'template'. DIRECTORY_SEPARATOR .$templatePath.".html";
            return$templatePath;
        }else{
            returnfalse;
        }
    }
  
    publicfunctiondisplay($templates=array()) {
        if(!is_array($templates)) {
            $templates= func_get_args();
        }
        if(empty($templates)) {
            foreach($this->templatesas$this->template) {
                echo$this->contents[$this->template];
            }
        }else{
            echo$this->fetch($templates);
        }
    }
}
  
//end of script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
 * 模板扩充函数
 *
 * Copyright(c) 2005 by 陈毅鑫(深空). All rights reserved
 *
 * To contact the author write to {@link mailto:shenkong@php.net}
 *
 * @author 陈毅鑫(深空)
 * @version $Id: template.func.php 1687 2008-07-07 01:16:07Z skchen $
 * @package Template
 */
  
defined('FW') ||exit(header('HTTP/1.0 400 Bad Request'));
  
/**
 * 包含模板
 *
 * 当你需要在主模板文件里(有些模板引擎称之为layout布局模板,其实不是所有模板都是布局)
 * 再包含其他公共模板的时候,使用该函数进行包含,则所有已注册的变量均可在被包含文件里使
 * 用,貌似支持多层嵌套,没有测试过,参数可以使用数组,也可以使用多个参数,如:
 * 或者
 *
 *
 * @param string|array $filename 模板名(module.templateName形式)
 */
functionincludeFile($templates) {
    $template= Template::init();
    if(is_array($templates)) {
        $template->includeFiles =$templates;
    }else{
        $template->includeFiles = func_get_args();
    }
    extract($template->vars);
    foreach($template->includeFilesas$template->includeFile) {
        require$template->getPath($template->includeFile);
    }
}
  
//end of script

推荐阅读
  • 如何实现织梦DedeCms全站伪静态
    本文介绍了如何通过修改织梦DedeCms源代码来实现全站伪静态,以提高管理和SEO效果。全站伪静态可以避免重复URL的问题,同时通过使用mod_rewrite伪静态模块和.htaccess正则表达式,可以更好地适应搜索引擎的需求。文章还提到了一些相关的技术和工具,如Ubuntu、qt编程、tomcat端口、爬虫、php request根目录等。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 高质量SQL书写的30条建议
    本文提供了30条关于优化SQL的建议,包括避免使用select *,使用具体字段,以及使用limit 1等。这些建议是基于实际开发经验总结出来的,旨在帮助读者优化SQL查询。 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • 数据库锁的分类和应用
    本文介绍了数据库锁的分类和应用,包括并发控制中的读-读、写-写、读-写/写-读操作的问题,以及不同的锁类型和粒度分类。同时还介绍了死锁的产生和避免方法,并详细解释了MVCC的原理以及如何解决幻读的问题。最后,给出了一些使用数据库锁的实际场景和建议。 ... [详细]
  • Django + Ansible 主机管理(有源码)
    本文给大家介绍如何利用DjangoAnsible进行Web项目管理。Django介绍一个可以使Web开发工作愉快并且高效的Web开发框架,能够以最小的代价构建和维护高 ... [详细]
  • MVC中的自定义控件
    怎么样创建自定义控 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • Oracle Database 10g许可授予信息及高级功能详解
    本文介绍了Oracle Database 10g许可授予信息及其中的高级功能,包括数据库优化数据包、SQL访问指导、SQL优化指导、SQL优化集和重组对象。同时提供了详细说明,指导用户在Oracle Database 10g中如何使用这些功能。 ... [详细]
  • MySQL数据库锁机制及其应用(数据库锁的概念)
    本文介绍了MySQL数据库锁机制及其应用。数据库锁是计算机协调多个进程或线程并发访问某一资源的机制,在数据库中,数据是一种供许多用户共享的资源,如何保证数据并发访问的一致性和有效性是数据库必须解决的问题。MySQL的锁机制相对简单,不同的存储引擎支持不同的锁机制,主要包括表级锁、行级锁和页面锁。本文详细介绍了MySQL表级锁的锁模式和特点,以及行级锁和页面锁的特点和应用场景。同时还讨论了锁冲突对数据库并发访问性能的影响。 ... [详细]
  • ps:写的第一个,不足之处,欢迎拍砖---只是想用自己的方法一步步去实现一些框架看似高大上的小功能(比如说模型中的toArraytoJsonsetAtt ... [详细]
  • 今天写一篇blog,已经多长时间没有更了,两个月了吧,没办法,现在银行开发,不能连外网,天天用虚拟机,真烦今天随手写点东西,主要是这两天对于springboot启动的分析,有所领悟 ... [详细]
  • 从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之六 || API项目整体搭建 6.1 仓储模式
    代码已上传Github+Gitee,文末有地址  书接上文:前几回文章中,我们花了三天的时间简单了解了下接口文档Swagger框架,已经完全解放了我们的以前的Word说明文档,并且可以在线进行调 ... [详细]
  • 一、Struts2是一个基于MVC设计模式的Web应用框架在MVC设计模式中,Struts2作为控制器(Controller)来建立模型与视图的数据交互。Struts2优点1、实现 ... [详细]
author-avatar
Cornell和Janey的BabyPeter_580
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有