ThinkPHP框架设计及扩展详解


Posted in PHP onNovember 25, 2014

ThinkPHP框架是国内知名度很高应用很广泛的php框架,我们从一些简单的开发示例中来深入了解一下这个框架给我们带来的开发便捷性,以及游刃有余的扩展设计。同时也从源码分析的角度看看框架的一些不足,尽量做全面客观的评价。这里假设大家已经使用过ThinkPHP框架,基本使用方法请参考官方文档。

一、框架分层及url路由

框架的安装非常简单,下载后放入web服务器的目录即可,但是建议大家不要用默认的入口文件位置,而是放入单独的目录,便于保护代码和数据。例如我的入口文件和web服务器配置目录在web目录(外层框架里的index.php没有删除但是没有使用):
ThinkPHP框架设计及扩展详解

同大多数MVC框架一样,我们只需要按框架的目录结构,扩展自己的Controller和View,一些页面就开发完成了。ThinkPHP提供Module、Controller、Action三层结构来组织自己的url(3.1版本叫分组、Action和method,3.2更加国际范),目录结构如下:
ThinkPHP框架设计及扩展详解

这里强烈建议大家:
1、业务单独分层,不用放在Controller和Model里,例如我这里通过扩展函数库Application/Common/Common/function.php强制定义业务层名称为Service:

function service($name)
{
    return D($name, 'Service');
}

好处是复用性好,假如将来要开发wap页面,写了不同的Controller,就可以复用service,假如以后的数据存储变了,比如把数据库从mysql迁移到mongodb之类,那修改Model就可以,service还是不需要任何修改。

2、基础模块和业务模块分开,不要相互引用。基础模块(例如用户基本信息)只提供数据接口没有Controller和View。
三层目录已经可以应对一般的web应用,更加复杂的web应用我们可以定义不同的入口文件加载不同的Application来解决。更更复杂的应用?门户和超大规模网站么,那就不是一个php框架能解决所有问题的了,需要自己的中间件和定制框架。

ThinkPHP的支持4种url访问模式,分别是:

1、普通模式,传统url模式,所有参数分开,例如
http://localhost/tp/index.php?m=Ucai&c=User&a=index¶=xxx
路由参数:m参数表示模块,c表示控制器,a表示访问方法
2、兼容模式
http://localhost/tp/index.php?s=/Ucai/User/index/para/xxx
路由参数通过s参数组装,当然数据参数也可以不必放在s参数里
3、pathinfo模式
http://localhost/tp/index.php/Ucai/User/index/para/xxx
这种模式把入口文件和真实脚本放在一起,含义明确,也便于SEO
4、rewrite模式
http://localhost/tp/Ucai/User/index/para/xxx
这种模式通过web服务器的rewrite配置隐藏入口文件,显得更加友好
其中pathinfo和rewrite模式需要web服务器支持。ThinkPHP有个配置需要设置为哪种模式,其实是用在U方法里生成url链接的时候用到的,访问的时候只要web服务器支持用哪种方式都可以。
也建议ThinkPHP其实不需要配置,而是记住用户访问的方式,只要第一个访问用的是哪种模式,以后生成的url都用这种方式生成,因为用户都已经访问到了就不存在支不支持的问题了。

如果正常的url不能达到我们的要求,还可以通过配置路由进一步优化url,例如我们想把url配置的更加简单
http://localhost/tp/Ucai/login/xxx
我们只需要在模块配置文件中添加如下的路由配置即可,如果用正则表达式则可以更加简化

'URL_ROUTE_RULES'   =>  array(
        'login/:para' => 'Ucai/User/index',
        'login' => 'Ucai/User/index',
    ),

到这里我们可以看到,ThinkPHP框架支持的层次结构和url配置非常丰富,能满足各种不同的需求。当然我们建议大家不要滥用路由配置,适当少量的配置能带来更好的seo效果,但是大量的配置会给项目的维护和修改带来困难。

二、ThinkPHP扩展

ThinkPHP本身含有丰富的组件和驱动,我们以数据库驱动扩展和行为扩展为例来了解一下ThinkPHP的扩展设计。

三、数据库驱动扩展

虽然ThinkPHP提供了众多的数据库驱动,但是也并不能满足所有的需求。例如我们的数据很可能不是通过直接访问数据库去实现,而是通过一些中间件(例如C程序)进行转发,从而获得更好的性能,这时就需要扩展数据库驱动来支持。
扩展非常简单,在DB/Driver目录下新建自己的驱动,例如Custom.php,然后实现request和execute方法扩展就算完成了,然后再配置文件里配置DB_TYPE='custom',就可以使用了。这里的request表示查询,execute表示更改数据,所有其他操作都会在Model里进行解析,包装成sql语句调用这两个方法执行。
例如我所实现的最简单的query方式,通过shell命令调用sqlite执行sql语句:

public function query($str) {
        $cmd = sprintf('sqlite3 %s "%s"', $this->config['params']['dbfile'], $str);
        exec($cmd, $arr);
}

当然这个只是示例,ThinkPHP本身就支持sqlite3,通过pdo的方式去连接就可以。实际的应用环境可能是通过连接4层协议访问中间层端口获取数据。

四、Behavior行为扩展

Behavior行为设计是ThinkPHP框架的核心,通过行为配置和扩展,为系统的伸缩性和定制性提供了最大的支持。
假如我们要加入登录验证的功能,按照常规我们会设计自己的父类Controller,然后所有其他的Controller都从这里继承。但有了Behavior会变得更加简单和灵活,我们只需要在tags.php(没有的话在配置目录新建)添加一个Behavior就可以了:

return array(
    'action_begin' => array('Ucai\Behavior\AuthBehavior'),
    'view_begin' => array('Ucai\Behavior\OutputBehavior'),
);

程序在执行到action_begin流程时就会调用这个Behavior,我们可以根据状态进行跳转或终止执行。

namespace Ucai\Behavior;
class AuthBehavior {
     // 行为扩展的执行入口必须是run
     public function run(&$return) {
        //不需要验证的action设置为true
         if (!$return['AUTH_PUBLIC']) {
            if (service('User')->checkLogin())
            {
                $return = true;
            }
            else
            {
                header('Content-Type: text/html; charset=utf-8');
                redirect(U('User/index', array('url' => $_SERVER['HTTP_REFERER'])), 5, '需要登录,5秒后跳转。。。');
            }
         }
     }
}

对于不需要登录的页面我们可以在Controller里添加配置,所有不配置的都会要求登录验证。

public $config = array('AUTH_PUBLIC' => true);

这里大家对继承和Behavior实现登录验证做一个对比,可能觉得区别不大。但是在一个复杂的项目里,这种功能会非常多,如果每个功能都放到父类里,就会非常庞大,并且部分子类可能又不需要,这时候用Behavior去定制流程就会显得游刃有余。
在上面的配置中我们还发现了一个配置OutputBehavior更能说明问题,大家有没有猜到,这个Behavior我是用来在view里输出一些共有变量,例如jscss的域名和路径等。在没有Behavior之前,大家是不是需要一个公共方法,然后每个页面都去调用一次,或者改写View的类代码?有了Behavior就显得方便许多。

namespace Ucai\Behavior;
class OutputBehavior {
     public function run(&$return) {
        $view = \Think\Think::instance('Think\View');
        $view->assign('STATIC_URL', 'http://p3.ucai.cn/static');
     }
}

扩展总结:通过Behavior扩展和数据库驱动扩展大家可以看到,ThinkPHP提供了很灵活的扩展和增强机制,能满足众多需求。其他存储、缓存、日志、模板引擎等如果需要也能很方便的扩展。

五、源码分析与不足

首先我们来分析一下框架执行的大致流程:
index.php(入口、调试模式、应用路径)
--> ThinkPHP.php(定义路径与访问模式)
--> Think\Think(类加载器、异常处理、读取共有配置)
--> Think\App(请求url调度解析、执行调度解析结果)
--> exec 执行用户定义的Controller的Action方法
--> Think\Dispatcher(根据url模式解析M、C、A和参数,加载模块配置)
--> Think\Controller(调用视图、包装和重定向)
可以看到,框架的内部流程其实比较简单,还有2个很重要的类:
Think\Hook: 监听App、Action、View的各个阶段,执行Behavior
Think\Behavior: 可配置(配置文件)可增删(代码)

在分析源代码的过程中,我们也看到了一些不足:

1、宏定义过多,难于维护和修改
建议:只在个别文件定义极少数几个宏,其余用类常量包装
2、面向过程代码过多,封装不清晰
建议:用面向对象思想包装
例如:url的解析和包装,现在是在Dispatcher里生成APP宏,然后在U方法里读取宏并生成最终url。其实完全可以定义一个类来包装例如UrlHelper,而类的二个方法parse和generate分别负责解析和生成url,这样代码结构会清晰很多。
3、有的函数和类代码封装过多,复用和改进不方便
建议:用组合来封装独立功能内容
例如:Model的校验功能,完全可以独立成类,也可以用于非Model对象调用。而现在的校验接口是Model的保护性方法,只能在Model的create函数调用,外面必须通过create方法才能校验。
4、代码规范和风格问题
希望代码风格能更加规范和标准,例如DB类作为模板方法的父类,应该用抽象方法或抛出异常形式定义所有Model需用到的方法。事实上有些方法子类是不需要的,而Db类却没有实现。

六、总结

ThinkPHP作为国内热门的php框架,确实给我们的开发带来了便利。框架开发者对web流程理解的很透彻,对php的函数应用炉火纯青。框架定义了灵活的配置和扩展适应各种需求,提供了丰富的组件和模块来加速开发。最后说一点,ThinkPHP的文档和社区支持非常完善,这也是框架流行不可缺少的重要一环。我们也希望ThinkPHP以后能更加完善自身的结构,打造成最优秀的php框架。

PHP 相关文章推荐
PHP出错界面
Oct 09 PHP
Notice: Trying to get property of non-object problem(PHP)解决办法
Mar 11 PHP
php删除文件夹及其文件夹下所有文件的函数代码
Jan 23 PHP
基于CakePHP实现的简单博客系统实例
Jun 28 PHP
WordPress中的shortcode短代码功能使用详解
May 17 PHP
浅谈php中的循环while、do...while、for、foreach四种循环
Nov 05 PHP
thinkphp利用模型通用数据编辑添加和删除的实例代码
Nov 20 PHP
php实现生成带二维码图片并强制下载功能
Feb 24 PHP
PHP连接MySQL数据库并以json格式输出
May 21 PHP
PHP常用日期加减计算方法实例小结
Jul 31 PHP
CentOS7编译安装php7.1的教程详解
Apr 18 PHP
Laravel关系模型指定条件查询方法
Oct 10 PHP
Yii入门教程之目录结构、入口文件及路由设置
Nov 25 #PHP
Yii入门教程之Yii安装及hello world
Nov 25 #PHP
php 伪造ip以及url来路信息方法汇总
Nov 25 #PHP
浅析php适配器模式(Adapter)
Nov 25 #PHP
浅析php原型模式
Nov 25 #PHP
浅析php创建者模式
Nov 25 #PHP
浅析php工厂模式
Nov 25 #PHP
You might like
substr()函数中文版
2006/10/09 PHP
如何将一个表单同时提交到两个地方处理
2006/10/09 PHP
PHP编程中八种常见的文件操作方式
2006/11/19 PHP
php一句话cmdshell新型 (非一句话木马)
2009/04/18 PHP
php将fileterms函数返回的结果变成可读的形式
2011/04/21 PHP
mac下使用brew配置环境的步骤分享
2011/05/23 PHP
Apache服务器无法使用的解决方法
2013/05/08 PHP
PHP中strtr字符串替换用法详解
2014/11/26 PHP
PHP查询快递信息的方法
2015/03/07 PHP
php实现网页缓存的工具类分享
2015/07/14 PHP
详解PHP的Yii框架中组件行为的属性注入和方法注入
2016/03/18 PHP
PHP面试常用算法(推荐)
2016/07/22 PHP
php封装的smartyBC类完整实例
2016/10/19 PHP
checkbox 复选框不能为空
2009/07/11 Javascript
jQuery 菜单随滚条改为以定位方式(固定要浏览器顶部)
2012/05/24 Javascript
IE下写xml文件的两种方式(fso/saveAs)
2013/08/05 Javascript
使用JS获取当前地理位置方法汇总
2014/12/18 Javascript
在JavaScript中使用对数Math.log()方法的教程
2015/06/15 Javascript
Jquery组件easyUi实现表单验证示例
2016/08/23 Javascript
JS正则表达式修饰符中multiline(/m)用法分析
2016/12/27 Javascript
vue实现随机验证码功能的实例代码
2019/04/30 Javascript
JS轮播图的实现方法
2020/08/24 Javascript
vue 页面跳转的实现方式
2021/01/12 Vue.js
使用Python和Prometheus跟踪天气的使用方法
2019/05/06 Python
Python使用百度api做人脸对比的方法
2019/08/28 Python
Python3开发实例之非关系型图数据库Neo4j安装方法及Python3连接操作Neo4j方法实例
2020/03/18 Python
通过案例解析python鸭子类型相关原理
2020/10/10 Python
美国咖啡批发网站:Coffee.org
2017/06/29 全球购物
Maison Lab荷兰:名牌Outlet购物
2018/08/10 全球购物
客服服务心得体会
2013/12/30 职场文书
大三学生入党思想汇报
2014/01/02 职场文书
优秀教师事迹材料
2014/12/15 职场文书
2014年小学教导处工作总结
2014/12/19 职场文书
个人党性锻炼总结
2015/03/05 职场文书
小学三年级班主任工作经验交流材料
2015/11/02 职场文书
小学信息技术教学反思
2016/02/16 职场文书