🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
3.2提供了灵活的扩展机制,相对于3.1版本来说,则变得更加简单。 首先,要了解ThinkPHP有哪些扩展方式: - 配置扩展 - 函数扩展 - 类库扩展 - 行为扩展 - 驱动扩展(缓存、数据库、日志、session、上传、模板引擎、图像、存储) - 模型扩展 - 标签库扩展 - 应用模式扩展 - 控制器扩展 - Widget扩展 # 类库扩展 ThinkPHP的类库主要包括公共类库和应用类库,都是基于命名空间进行定义和扩展的。只要按照规范定义,都可以实现自动加载。 ## 公共类库 公共类库通常是指`ThinkPHP/Library`目录下面的类库,例如: ~~~ Think目录:系统核心类库 Org目录:第三方公共类库 ~~~ 这些目录下面的类库都可以自动加载,你只要把相应的类库放入目录中,然后添加或者修改命名空间定义。 你可以在Org/Util/目录下面添加一个Image.class.php 文件,然后添加命名空间如下: ~~~ namespace Org\Util; class Image { } ~~~ 这样,就可以用下面的方式直接实例化Image类了: ~~~ $image = new \Org\Util\Image; ~~~ 除了这些目录之外,你完全可以在`ThinkPHP/Library`目录下面添加自己的类库目录,例如,我们添加一个Com目录用于企业类库扩展: Com\Sina\App类(位于Com/Sina/App.class.php ) ~~~ namespace Com\Sina; class App { } ~~~ Com\Sina\Rank类(位于Com/Sina/Rank.class.php) ~~~ namespace Com\Sina; class Rank { } ~~~ 公共类库除了在系统的Library目录之外,还可以自定义其他的命名空间,我们只需要注册一个新的命名空间,在应用或者模块配置文件中添加下面的设置参数: ~~~ 'AUTOLOAD_NAMESPACE' => array( 'Lib' => APP_PATH.'Lib', ) ~~~ 我们在应用目录下面创建了一个Lib目录用于放置公共的Lib扩展,如果我们要把上面两个类库放到Lib\Sina目录下面,只需要调整为: Lib\Sina\App类(位于Lib/Sina/App.class.php ) ~~~ namespace Lib\Sina; class App { } ~~~ Lib\Sina\Rank类(位于Lib/Sina/Rank.class.php) ~~~ namespace Lib\Sina; class Rank { } ~~~ 如果你的类库没有采用命名空间的话,需要使用import方法先加载类库文件,然后再进行实例化,例如: 我们定义了一个Counter类(位于Com/Sina/Util/Counter.class.php): ~~~ class Counter { } ~~~ 在使用的时候,需要按下面方式调用: ~~~ import('Com.Sina.Util.Couter'); $object = new \Counter(); ~~~ ## 应用类库 应用类库通常是在应用或者模块目录下面的类库,应用类库的命名空间一般就是模块的名称为根命名空间,例如: Home\Model\UserModel类(位于Application\Home\Model) ~~~ namespace Home\Model; use Think\Model; class UserModel extends Model{ } ~~~ Common\Util\Pay类(位于Application\Common\Util) ~~~ namespace Common\Util; class Pay { } ~~~ Admin\Api\UserApi类(位于Application\Admin\Api) ~~~ namespace Admin\Api; use Think\Model; class UserApi extends Model{ } ~~~ > 记住一个原则,命名空间的路径和实际的文件路径对应的话 就可以实现直接实例化的时候自动加载。 # 驱动扩展 这里说的驱动扩展是一种泛指,ThinkPHP采用驱动式设计,很多功能的扩展都是基于驱动的思想,包括数据库驱动、缓存驱动、标签库驱动和模板引擎驱动等。 事实上,每个类库都可以设计自己的驱动,因此3.2版本的驱动目录没有独立出来,而是放到各个类库的命名空间下面,例如:`Think\Log`类的驱动放到 `Think\Log\Driver` 命名空间下面,`Think\Db`类的驱动放到了 `Think\Db\Driver` 命名空间下面。 > 当然,这只是建议的位置,你完全可以根据项目的需要,把自己的驱动独立存放,例如: `Home\Driver\Cache\Sae.class.php` 则是一种把Cache驱动独立存放的方式(内置的核心类库都支持给驱动指定单独的命名空间)。 # 行为扩展 行为(Behavior)是一个比较抽象的概念,你可以想象成在应用执行过程中的一个动作或者处理,在框架的执行流程中,各个位置都可以有行为产生,例如路由检测是一个行为,静态缓存是一个行为,用户权限检测也是行为,大到业务逻辑,小到浏览器检测、多语言检测等等都可以当做是一个行为,甚至说你希望给你的网站用户的第一次访问弹出Hello,world!这些都可以看成是一种行为,行为的存在让你无需改动框架和应用,而在外围通过扩展或者配置来改变或者增加一些功能。 而不同的行为之间也具有位置共同性,比如,有些行为的作用位置都是在应用执行前,有些行为都是在模板输出之后,我们把这些行为发生作用的位置称之为标签(位),当应用程序运行到这个标签的时候,就会被拦截下来,统一执行相关的行为,类似于AOP编程中的“切面”的概念,给某一个切面绑定相关行为就成了一种类AOP编程的思想。 系统核心提供的标签位置包括下面几个(按照执行顺序排列): ~~~ app_init 应用初始化标签位 path_info PATH_INFO检测标签位 app_begin 应用开始标签位 action_name 操作方法名标签位 action_begin 控制器开始标签位 view_begin 视图输出开始标签位 view_parse 视图解析标签位 template_filter 模板内容解析标签位 view_filter 视图输出过滤标签位 view_end 视图输出结束标签位 action_end 控制器结束标签位 app_end 应用结束标签位 ~~~ 在每个标签位置,可以配置多个行为定义,行为的执行顺序按照定义的顺序依次执行。除非前面的行为里面中断执行了(某些行为可能需要中断执行,例如检测机器人或者非法执行行为),否则会继续下一个行为的执行。 行为定义: 通过Common\Conf\tags.php配置文件定义,格式如下: ~~~ <?php return array( 'action_begin'=>array('Home\\Behaviors\\test','Home\\Behaviors\\test1'),//一个标签位可以有多个行为,使用数组即可。 // 如果是3.2.1以上版本 则需要改成 'action_begin'=>array('Home\\Behaviors\\testBehavior','Home\\Behaviors\\test1Behavior') ); ~~~ 上面注册了两个行为,分别是Home模块下的test和test1行为,类文件位于Home模块目录下的Behaviors目录,可以自定义目录。 行为必须是一个包含命名空间路径的类,如上的 Home\Behaviors\test 对应的类是 Home/Behaviors/test**Behavior**.class.php。 > 注意:注册行为的时候行为名不需要加Behavior后缀,但是创建类文件的时候需要在行为名test后面加上Behavior,以及类文件扩展名是.class.php。 除了这些系统内置标签之外,开发人员还可以在应用中添加自己的应用标签。 比如在控制器的_initialize方法中: ~~~ \Think\Hook::add('action_begin','Home\\Behaviors\\test1');//同时添加多个行为,只要将第二个参数换成数组即可。 // 3.2.1以上版本 需要改成 // \Think\Hook::add('action_begin','Home\\Behaviors\\test1Behavior'); ~~~ 行为类的定义,以上面的test行为为例: ~~~ <?php namespace Home\Behaviors; class testBehavior extends \Think\Behavior{ //行为执行入口 public function run(&$param){ } } ~~~ 行为类建议继承\Think\Behavior,必须实现run(&$param)方法,行为是通过这个方法执行的。 行为的触发: 只要在合适的地方通过以下代码 ~~~ \Think\Hook::listen('标签名'[,参数]); // 或者 // tag('标签名'[,参数]); ~~~ 当应用执行到这个地方的时候将自动触发指定标签名下的所有行为类。 > 注意:动态注册的行为必须在Hook::listen之前,即:先注册行为,才能触发行为。 > listen方法可以传入并且只接受一个参数,如果需要传入多个参数,请使用数组,该参数为引用传值,所以只能传入变量。 参数可以被run(&$param)中的$param接收。 # 标签库扩展 ### 标签库加载 模板中加载标签库,预加载自定义标签库,扩展内置标签库的加载 请参考:[http://document.thinkphp.cn/manual_3_2.html#taglib](http://document.thinkphp.cn/manual_3_2.html#taglib) ### 自定义标签库开发注意事项 - 标签库请放置 ThinkPHP\Library\Think\Template\TagLib 目录下,若需要存放在指定位置,请在加载标签库配置时使用命名空间,如 ~~~ <taglib name="Home\\TagLib\\MyTag"/> ~~~ - 标签库类请使用命名空间,否则无法加载类,主要是在类开头包含如下代码: ~~~ <?php namespace Think\Template\TagLib; use Think\Template\TagLib; defined('THINK_PATH') or exit(); ~~~ 自定义标签库位置的话,请自行修改第一行 namespace 的定义。 ### 关于标签库开发 暂时可以参考3.0的官方教程中关于标签库扩展的部分,建议自己分析内置标签库Cx。 # widget扩展 Widget扩展一般用于页面组件的扩展。 举个例子,我们在页面中实现一个分类显示的Widget,首先我们要定义一个Widget控制器层 CateWidget,如下: ~~~ namespace Home\Widget; use Think\Controller; class CateWidget extends Controller { public function menu(){ echo 'menuWidget'; } } ~~~ 然后,我们在模版中通过W方法调用这个Widget。 ~~~ {:W('Cate/Menu')} ~~~ 执行后的输出结果是: menuWidget ## 传入参数 如果需要在调用Widget的时候 使用参数,可以这样定义: ~~~ namespace Home\Widget; use Think\Controller; class CateWidget extends Controller { public function menu($id,$name){ echo $id.':'.$name; } } ~~~ 模版中的参数调用,使用: ~~~ {:W('Cate/Menu',array(5,'thinkphp'))} ~~~ > 传入的参数是一个数组,顺序对应了menu方法定义的参数顺序。 则会输出 ~~~ 5:thinkphp ~~~ ## 模板支持 Widget可以支持使用独立的模板,例如: ~~~ namespace Home\Widget; use Think\Controller; class CateWidget extends Controller { public function menu(){ $menu = M('Cate')->getField('id,title'); $this->assign('menu',$menu); $this->display('Cate:menu'); } } ~~~ CateWiget类渲染了一个模版文件 `View/Cate/menu.html`。 在menu.html模版文件中的用法: ~~~ <foreach name="menu" item="title"> {$key}:{$title} </foreach> ~~~ # 应用模式 应用模式提供了对核心框架进行改造的机会,可以让你的应用适应更多的环境和不同的要求。 每个应用模式有自己的模式定义文件,用于配置当前模式需要加载的核心文件和配置文件,以及别名定义、行为扩展定义等等。根据模式定义文件的定义位置和入口是否需要定义模式,可以分为**显式应用模式**和**隐含应用模式**。 ## 显式应用模式 显式应用模式的模式定义文件位于`ThinkPHP\Mode`目录,如果我们要增加一个应用模式,只需要在该目录下面定义一个模式定义文件即可,下面是一个典型的模式定义文件(lite.php): ~~~ return array( // 配置文件 'config' => array( THINK_PATH.'Conf/convention.php', // 系统惯例配置 CONF_PATH.'config.php', // 应用公共配置 ), // 别名定义 'alias' => array( 'Think\Exception' => CORE_PATH . 'Exception'.EXT, 'Think\Model' => CORE_PATH . 'Model'.EXT, 'Think\Db' => CORE_PATH . 'Db'.EXT, 'Think\Cache' => CORE_PATH . 'Cache'.EXT, 'Think\Cache\Driver\File' => CORE_PATH . 'Cache/Driver/File'.EXT, 'Think\Storage' => CORE_PATH . 'Storage'.EXT, ), // 函数和类文件 'core' => array( MODE_PATH.'Lite/functions.php', COMMON_PATH.'Common/function.php', MODE_PATH . 'Lite/App'.EXT, MODE_PATH . 'Lite/Dispatcher'.EXT, MODE_PATH . 'Lite/Controller'.EXT, MODE_PATH . 'Lite/View'.EXT, CORE_PATH . 'Behavior'.EXT, ), // 行为扩展定义 'tags' => array( 'view_parse' => array( 'Behavior\ParseTemplate', // 模板解析 支持PHP、内置模板引擎和第三方模板引擎 ), 'template_filter'=> array( 'Behavior\ContentReplace', // 模板输出替换 ), ), ); ~~~ 我们在ThinkPHP/Mode/Lite目录下面创建`functions.php`函数库文件,以及`App.class.php`、`Dispatcher.class.php`、`Controller.class.php`和`View.class.php`,这些类都是针对我们新的应用模式定制的核心类,但是和标准模式的命名空间是一致的,也就是说都在Think命名空间下面。 > ThinkPHP/Mode/Lite目录用于存放该应用模式下面的所有自定义文件。 应用模式定义文件定义好后,我们就可以在应用中使用该模式了,例如: ~~~ define('MODE_NAME','lite'); define('APP_PATH','./Application/'); require './ThinkPHP/ThinkPHP.php'; ~~~ ## 隐含应用模式 隐含应用模式的模式定义文件位于应用的配置目录下面 `Application/Common/Conf/core.php`,模式定义文件和显式应用模式的定义文件一样。 使用隐含应用模式的时候,不需要在入口文件中定义MODE_NAME,或者说存在隐含应用模式定义文件的时候,MODE_NAME定义无效。 > 注意:如果应用中定义的应用模式需要使用其他的存储类型,需要在入口文件中定义。 ~~~ define('STORAGE_TYPE','Bae'); ~~~ # 对扩展 老杨的看法 扩展的目的是什么?最大化的做到代码重用和行为可控制的一种架构方式。 最简单的道理,当某个一个逻辑代码写的次数多了。我们一般就会把他封装成函数。比如判断是否登录函数 其实就是看某个session值存不存在。 封装成函数的好处是什么。重用以后,如果有修改,只需要改定义的部分。使用的部分不需改变。 而对于某些逻辑光封装成函数是不够的。因为里面涉及很多数据(内部+外部+临时)。 这时候我们就要利用到面向对象的优势。 将一件事分析清楚,剥离开来,因此有了驱动。 比如上传驱动。上传驱动定义了上传相关的流程。提供了常用的接口方法。然后不同驱动里定义了相应的处理。比如SAE上传或FTP上传。抽象类只关心流程,而对应的具体实现,交给驱动去做,你给我统一的返回结果就好了。 当然也不是什么都要复用,有时候也需要隔离。所以才存在公共模块和其他模块的区别。