thinkPhp 框架学习——架构

1)、模块化设计

一个典型的URL访问规则是(我们以默认的PATHINFO模式为例说明,当然也可以支持普通的URL模式):

 
  
  1. http://serverName/index.php(或者其他应用入口文件)/模块/控制器/操作/[参数名/参数值...]

ThinkPHP3.2的应用可以支持切换到命令行访问,如果切换到命令行模式下面的访问规则是:

 
  
  1. >php.exe index.php(或其它应用入口文件) 模块/控制器/操作/[参数名/参数值...]

解释下其中的几个概念:

名称 描述
应用 基于同一个入口文件访问的项目我们称之为一个应用。
模块 一个应用下面可以包含多个模块,每个模块在应用目录下面都是一个独立的子目录。
控制器 每个模块可以包含多个控制器,一个控制器通常体现为一个控制器类。
操作 每个控制器类可以包含多个操作方法,也可能是绑定的某个操作类,每个操作是URL访问的最小单元。

模块化设计的思想下面模块是最重要的部分,模块其实是一个包含配置文件、函数文件和MVC文件(目录)的集合。
公共模块

公共模块的位置可以通过COMMON_PATH常量改变,我们可以在入口文件中重新定义COMMON_PATH如下:

 
  
  1. define('COMMON_PATH','./Common/');
定义之后,Application目录下面就不再需要Common目录了。
自动生成模块目录

如果我们需要生成一个Admin模块用于后台应用,在应用入口文件中定义如下:

 
  
  1. // 绑定Admin模块到当前入口文件
  2. define('BIND_MODULE','Admin');

然后访问URL地址

 
  
  1. http://serverName/index.php

就会生成Admin模块的目录,并生成一个默认的控制器类Admin\Controller\IndexController。 如果需要生成更多的控制器类,可以定义BUILD_CONTROLLER_LIST常量,例如:

 
  
  1. // 绑定Admin模块到当前入口文件
  2. define('BIND_MODULE','Admin');
  3. define('BUILD_CONTROLLER_LIST','Index,User,Menu');

访问后会自动生成三个指定的控制器类:

 
  
  1. Admin\Controller\IndexController
  2. Admin\Controller\UserController
  3. Admin\Controller\MenuController
注意:默认生成的控制器类都是继承 Think\Controller,如果需要继承其他的公共类需要另外调整。 如果在应用的公共配置文件中设置关闭了  APP_USE_NAMESPACE的话,生成的控制器类则不会采用命名空间定义。

还可以自己手动调用Think\Build类的方法来生成控制器类,例如:

 
  
  1. // 生成Admin模块的Role控制器类
  2. // 默认类库为Admin\Controller\RoleController
  3. // 如果已经存在则不会重新生成
  4. \Think\Build::buildController('Admin','Role');
复制代码

同样,也可以定义BUILD_MODEL_LIST支持生成多个模型类:

 
  
  1. // 绑定Admin模块到当前入口文件
  2. define('BIND_MODULE','Admin');
  3. define('BUILD_CONTROLLER_LIST','Index,User,Menu');
  4. define('BUILD_MODEL_LIST','User,Menu');
  5. define('APP_PATH','./Application/');
  6. require './ThinkPHP/ThinkPHP.php';

访问会自动生成模型类:

 
  
  1. Admin\Model\UserModel
  2. Admin\Model\MenuModel

注意:默认生成的模型类都是继承Think\Model,如果需要继承公共的模型类需要另外调整。 如果在应用的公共配置文件中设置关闭了 APP_USE_NAMESPACE的话,生成的模型类则不会采用命名空间定义。

也可以自己手动调用Think\Build类的方法来生成模型类,例如:

 
  
  1. // 生成Admin模块的Role模型类
  2. // 默认类库为Admin\Model\RoleModel
  3. // 如果已经存在则不会重新生成
  4. \Think\Build::buildModel('Admin','Role');
禁止访问模块
默认配置中是禁止访问 Common 模块和 Runtime 模块(Runtime目录是默认的运行时目录),我们可以增加其他的禁止访问模块列表
 
  
  1. // 设置禁止访问的模块列表
  2. 'MODULE_DENY_LIST' => array('Common','Runtime','Api'),
复制代码
设置后,Api模块不能通过URL直接访问,事实上,可能我们只是在该模块下面放置一些公共的接口文件,因此都是内部调用即可。
设置访问列表

如果你的应用下面模块比较少,还可以设置允许访问列表和默认模块,这样可以简化默认模块的URL访问。

 
  
  1. 'MODULE_ALLOW_LIST' => array('Home','Admin','User'),
  2. 'DEFAULT_MODULE' => 'Home',
设置之后,除了Home、Admin和User模块之外的模块都不能被直接访问,并且Home模块是默认访问模块(可以不出现在URL地址)。
单模块设计

如果你的应用够简单,那么也许仅仅用一个模块就可以完成,那么可以直接设置:

 
  
  1. // 关闭多模块访问
  2. 'MULTI_MODULE' => false,
  3. 'DEFAULT_MODULE' => 'Home',
复制代码

一旦关闭多模块访问后,就只能访问默认模块(这里设置的是Home)。

单模块设计后公共模块依然有效
多入口设计

3.2.0版本写法:

 
  
  1. // 绑定Home模块到当前入口文件
  2. $_GET['m'] = 'Home'; 

3.2.1以上版本写法:

 
  
  1. // 绑定Home模块到当前入口文件
  2. define('BIND_MODULE','Home');
如果你更改了系统默认的变量设置,则需要做对应的模块绑定的变量调整。

绑定模块后,原来的访问地址

 
  
  1. http://serverName/index.php/Home/Index/index

就变成

 
  
  1. http://serverName/home.php/Index/index
同样的方式,我们也可以在入口文件中绑定控制器,例如:
3.2.0版本写法:
 
  
  1. $_GET['m'] = 'Home'; // 绑定Home模块到当前入口文件
  2. $_GET['c'] = 'Index'; // 绑定Index控制器到当前入口文件

3.2.1以上版本写法:

 
  
  1. define('BIND_MODULE', 'Home'); // 绑定Home模块到当前入口文件
  2. define('BIND_CONTROLLER','Index'); // 绑定Index控制器到当前入口文件

绑定模块和控制器后,原来的访问地址:

 
  
  1. http://serverName/index.php/Home/Index/index

就变成:

 
  
  1. http://serverName/home.php/index
不同的入口文件还可以用于绑定不同的应用模式,参考 应用模式 部分。

2)、URL模式

入口文件是应用的单一入口,对应用的所有请求都定向到应用入口文件,系统会从URL参数中解析当前请求的模块、控制器和操作:

 
  
  1. http://serverName/index.php/模块/控制器/操作

这是3.2版本的标准URL格式。

可以通过设置模块绑定或者域名部署等方式简化URL地址中的模块及控制器名称。

URL大小写

ThinkPHP框架的URL是区分大小写(主要是针对模块、控制器和操作名,不包括应用参数)的,这一点非常关键,因为ThinkPHP的命名规范是采用驼峰法(首字母大写)的规则,而URL中的模块和控制器都是对应的文件,因此在Linux环境下面必然存在区分大小写的问题。

框架内置了一个配置参数用于解决URL大小写的问题,如下:

 
  
  1. 'URL_CASE_INSENSITIVE' => true,

URL_CASE_INSENSITIVE设置为true的时候表示URL地址不区分大小写,这个也是框架在部署模式下面的默认设置。

当开启调试模式的情况下,这个参数是false,因此你会发现在调试模式下面URL区分大小写的情况。

URL模式

如果我们直接访问入口文件的话,由于URL中没有模块、控制器和操作,因此系统会访问默认模块(Home)下面的默认控制器(Index)的默认操作(index),因此下面的访问是等效的:

 
  
  1. http://serverName/index.php
  2. http://serverName/index.php/Home/Index/index

这种URL模式就是系统默认的PATHINFO模式,不同的URL模式获取模块和操作的方法不同,ThinkPHP支持的URL模式有四种:普通模式、PATHINFO、REWRITE和兼容模式,可以设置URL_MODEL参数改变URL模式。

URL模式 URL_MODEL设置
普通模式 0
PATHINFO模式 1
REWRITE模式 2
兼容模式 3
如果你整个应用下面的模块都是采用统一的URL模式,就可以在应用配置文件中设置URL模式,如果不同的模块需要设置不同的URL模式,则可以在模块配置文件中设置。

普通模式

普通模式也就是传统的GET传参方式来指定当前访问的模块和操作,例如:http://localhost/?m=home&c=user&a=login&var=value

m参数表示模块,c参数表示控制器,a参数表示操作(当然这些参数都是可以配置的),后面的表示其他GET参数。

如果默认的变量设置和你的应用变量有冲突的话,你需要重新设置系统配置,例如改成下面的:

 
  
  1. 'VAR_MODULE' => 'module', // 默认模块获取变量
  2. 'VAR_CONTROLLER' => 'controller', // 默认控制器获取变量
  3. 'VAR_ACTION' => 'action', // 默认操作获取变量
复制代码

上面的访问地址则变成: http://localhost/?module=home&controller=user&action=login&var=value

注意,VAR_MODULE只能在应用配置文件中设置,其他参数可以则也可以在模块配置中设置

PATHINFO模式

PATHINFO模式是系统的默认URL模式,提供了最好的SEO支持,系统内部已经做了环境的兼容处理,所以能够支持大多数的主机环境。对应上面的URL模式,PATHINFO模式下面的URL访问地址是:http://localhost/index.php/home/user/login/var/value/

PATHINFO地址的前三个参数分别表示模块/控制器/操作。

不过,PATHINFO模式下面,依然可以采用普通URL模式的参数方式,例如:http://localhost/index.php/home/user/login?var=value 依然是有效的

PATHINFO模式下面,URL是可定制的,例如,通过下面的配置:

 
  
  1. // 更改PATHINFO参数分隔符
  2. 'URL_PATHINFO_DEPR'=>'-',
我们还可以支持下面的URL访问:  http://localhost/index.php/home-user-login-var-value

REWRITE模式

REWRITE模式是在PATHINFO模式的基础上添加了重写规则的支持,可以去掉URL地址里面的入口文件index.php,但是需要额外配置WEB服务器的重写规则。

如果是Apache则需要在入口文件的同级添加.htaccess文件,内容如下:

 
  
  1. <IfModule mod_rewrite.c>
  2. RewriteEngine on
  3. RewriteCond %{REQUEST_FILENAME} !-d
  4. RewriteCond %{REQUEST_FILENAME} !-f
  5. RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L]
  6. </IfModule>

接下来,就可以用下面的URL地址访问了: http://localhost/home/user/login/var/value

更多环境的URL重写支持参考部署部分的URL重写。

兼容模式

兼容模式是用于不支持PATHINFO的特殊环境,URL地址是: http://localhost/?s=/home/user/login/var/value

可以更改兼容模式变量的名称定义,例如:

 
  
  1. 'VAR_PATHINFO' => 'path'

PATHINFO参数分隔符对兼容模式依然有效,例如:

 
  
  1. // 更改PATHINFO参数分隔符
  2. 'URL_PATHINFO_DEPR'=>'-',

使用以上配置的话,URL访问地址可以变成: http://localhost/?path=/home-user-login-var-value

兼容模式配合Web服务器重写规则的定义,可以达到和REWRITE模式一样的URL效果。

例如,我们在Apache下面的话,.htaccess文件改成如下内容:

 
  
  1. <IfModule mod_rewrite.c>
  2. RewriteEngine on
  3. RewriteCond %{REQUEST_FILENAME} !-d
  4. RewriteCond %{REQUEST_FILENAME} !-f
  5. RewriteRule ^(.*)$ index.php?s=/$1 [QSA,PT,L]
  6. </IfModule>
复制代码
就可以和REWRITE模式一样访问下面的URL地址访问了:  http://localhost/home/user/login/var/value
3)、多层MVC

模型(Model)层

默认的模型层由Model类构成,但是随着项目的增大和业务体系的复杂化,单一的模型层很难解决要求,从3.1开始推出了多层Model的支持,设计思路很简单,不同的模型层仍然都继承自系统的Model类,但是在目录结构和命名规范上做了区分。

例如在某个项目设计中需要区分数据层、逻辑层、服务层等不同的模型层,我们可以在模块目录下面创建ModelLogicService目录,把对用户表的所有模型操作分成三层:

  1. 数据层:Model/UserModel 用于定义数据相关的自动验证和自动完成和数据存取接口
  2. 逻辑层:Logic/UserLogic 用于定义用户相关的业务逻辑
  3. 服务层:Service/UserService 用于定义用户相关的服务接口等

而这三个模型操作类统一都继承Model类即可,例如: 数据层:Home/Model/UserModel.class.php

 
  
  1. namespace Home\Model;
  2. use Think\Model;
  3. class UserModel extends Model{
  4. }

逻辑层:Home/Logic/UserLogic.class.php

 
  
  1. namespace Home\Logic;
  2. use Think\Model;
  3. class UserLogic extends Model{
  4. }

服务层:Home/Service/UserService.class.php

 
  
  1. namespace Home\Service;
  2. use Think\Model;
  3. class UserService extends Model{
  4. }

这样区分不同的模型层之后对用户数据的操作就非常清晰,在调用的时候,我们也可以用内置的D方法很方便的调用:

 
  
  1. D('User') //实例化UserModel
  2. D('User','Logic') //实例化UserLogic
  3. D('User','Service') //实例化UserService

默认的模型层是Model,我们也可以更改设置,例如:

 
  
  1. 'DEFAULT_M_LAYER' => 'Logic', // 更改默认的模型层名称为Logic

更改之后,实例化的时候需要改成:

 
  
  1. D('User') //实例化UserLogic
  2. D('User','Model') //实例化UserModel
  3. D('User','Service') //实例化UserService
对模型层的分层划分是很灵活的,开发人员可以根据项目的需要自由定义和增加模型分层,你也完全可以只使用Model层。

视图(View)层

视图层由模板和模板引擎组成,在模板中可以直接使用PHP代码,模板引擎的设计会在后面讲述,通过驱动也可以支持其他第三方的模板引擎。视图的多层可以简单的通过目录(也就是模板主题)区分,例如:

 
  
  1. View/default/User/add.html
  2. View/blue/User/add.html

复杂一点的多层视图还可以更进一步,采用不同的视图目录来完成,例如:

 
  
  1. view 普通视图层目录
  2. mobile 手机端访问视图层目录

这样做的好处是每个不同的视图层都可以支持不同的模板主题功能。

默认的视图层是View目录,我们可以调整设置如下:

 
  
  1. 'DEFAULT_V_LAYER' => 'Mobile', // 默认的视图层名称更改为Mobile
非默认视图层目录的模板获取需要使用T函数,后面会讲到。

控制器(Controller)层

ThinkPHP的控制器层由核心控制器和业务控制器组成,核心控制器由系统内部的App类完成,负责应用(包括模块、控制器和操作)的调度控制,包括HTTP请求拦截和转发、加载配置等。业务控制器则由用户定义的控制器类完成。多层业务控制器的实现原理和模型的分层类似,例如业务控制器和事件控制器:

 
  
  1. Controller/UserController //用于用户的业务逻辑控制和调度
  2. Event/UserEvent //用于用户的事件响应操作

访问控制器 Home/Controller/UserController.class.php 定义如下:

 
  
  1. namespace Home\Controller;
  2. use Think\Controller;
  3. class UserController extends Controller{
  4. }

事件控制器 Home/Event/UserEvent.class.php 定义如下:

 
  
  1. namespace Home\Event;
  2. use Think\Controller;
  3. class UserEvent extends Controller{
  4. }

UserController负责外部交互响应,通过URL请求响应,例如 http://serverName/User/index,而 UserEvent 负责内部的事件响应,并且只能在内部调用: A('User','Event');

默认的访问控制器层是Controller,我们可以调整设置如下:

 
  
  1. 'DEFAULT_C_LAYER' => 'Event', // 默认的控制器层名称改为Event

所以是和外部隔离的。多层控制器的划分也不是强制的,可以根据应用的需要自由分层。控制器分层里面可以根据需要调用分层模型,也可以调用不同的分层视图(主题)。

在MVC三层中,ThinkPHP并不依赖M或者V,甚至可以只有C或者只有V,这个在ThinkPHP的设计里面是一个很重要的用户体验设计,用户只需要定义视图,在没有C的情况下也能自动识别。
4)、CBD模式
Core(核心)

ThinkPHP的核心部分包括核心函数库、惯例配置、核心类库(包括基础类和内置驱动及核心行为),这些是ThinkPHP必不可少的部分。

 
  
  1. ThinkPHP/Common/functions.php // 核心函数库
  2. ThinkPHP/Conf/convention.php // 惯例配置文件
  3. ThinkPHP/Conf/debug.php // 惯例调试配置文件
  4. ThinkPHP/Mode/common.php // 普通模式定义文件
  5. ThinkPHP/Library/Think // 核心类库包
  6. ThinkPHP/Library/Behavior // 系统行为类库
  7. ThinkPHP/Library/Think/App.class.php // 核心应用类
  8. ThinkPHP/Library/Think/Behavior.class.php // 基础行为类
  9. ThinkPHP/Library/Think/Cache.class.php // 核心缓存类
  10. ThinkPHP/Library/Think/Controller.class.php // 基础控制器类
  11. ThinkPHP/Library/Think/Db.class.php // 数据库操作类
  12. ThinkPHP/Library/Think/Dispatcher.class.php // URL解析调度类
  13. ThinkPHP/Library/Think/Exception.class.php // 系统基础异常类
  14. ThinkPHP/Library/Think/Hook.class.php // 系统钩子类
  15. ThinkPHP/Library/Think/Log.class.php // 系统日志记录类
  16. ThinkPHP/Library/Think/Model.class.php // 系统基础模型类
  17. ThinkPHP/Library/Think/Route.class.php // 系统路由类
  18. ThinkPHP/Library/Think/Storage.class.php // 系统存储类
  19. ThinkPHP/Library/Think/Template.class.php // 内置模板引擎类
  20. ThinkPHP/Library/Think/Think.class.php // 系统引导类
  21. ThinkPHP/Library/Think/View.class.php // 系统视图类
Behavior目录下面是系统内置的一些行为类库,内置驱动则分布在各个不同的驱动目录下面(参考下面的驱动部分)。

Driver(驱动)

3.2在架构设计上更加强化了驱动的设计,替代了之前的引擎和模式扩展,并且改进了行为的设计,使得框架整体更加灵活,并且由于在需要写入数据的功能类库中都采用了驱动化的设计思想,所以使得新的框架能够轻松满足分布式部署的需求,对云平台的支持可以更简单的实现了。因此,在新版的扩展里面,已经取消了引擎扩展和模式扩展,改成配置不同的应用模式即可。

驱动包括

 
  
  1. ThinkPHP/Library/Think/Cache/Driver // 缓存驱动类库
  2. ThinkPHP/Library/Think/Db/Driver // 数据库驱动类库
  3. ThinkPHP/Library/Think/Log/Driver // 日志记录驱动类库
  4. ThinkPHP/Library/Think/Session/Driver // Session驱动类库
  5. ThinkPHP/Library/Think/Storage/Driver // 存储驱动类库
  6. ThinkPHP/Library/Think/Template/Driver // 第三方模板引擎驱动类库
  7. ThinkPHP/Library/Think/Template/TagLib // 内置模板引擎标签库扩展类库

Behavior(行为)

行为(Behavior)是ThinkPHP扩展机制中比较关键的一项扩展,行为既可以独立调用,也可以绑定到某个标签(位)中进行侦听。这里的行为指的是一个比较抽象的概念,你可以想象成在应用执行过程中的一个动作或者处理,在框架的执行流程中,各个位置都可以有行为产生,例如路由检测是一个行为,静态缓存是一个行为,用户权限检测也是行为,大到业务逻辑,小到浏览器检测、多语言检测等等都可以当做是一个行为,甚至说你希望给你的网站用户的第一次访问弹出Hello,world!这些都可以看成是一种行为,行为的存在让你无需改动框架和应用,而在外围通过扩展或者配置来改变或者增加一些功能。

而不同的行为之间也具有位置共同性,比如,有些行为的作用位置都是在应用执行前,有些行为都是在模板输出之后,我们把这些行为发生作用的位置称之为标签(位),也可以称之为钩子,当应用程序运行到这个标签的时候,就会被拦截下来,统一执行相关的行为,类似于AOP编程中的“切面”的概念,给某一个标签绑定相关行为就成了一种类AOP编程的思想。

系统标签位

系统核心提供的标签位置包括(按照执行顺序排列):

  • app_init 应用初始化标签位
  • module_check 模块检测标签位(3.2.1版本新增
  • path_info PATH_INFO检测标签位
  • app_begin 应用开始标签位
  • action_name 操作方法名标签位
  • action_begin 控制器开始标签位
  • view_begin 视图输出开始标签位
  • view_template 视图模板解析标签位
  • view_parse 视图解析标签位
  • template_filter 模板解析过滤标签位
  • view_filter 视图输出过滤标签位
  • view_end 视图输出结束标签位
  • action_end 控制器结束标签位
  • app_end 应用结束标签位

在每个标签位置,可以配置多个行为,行为的执行顺序按照定义的顺序依次执行。除非前面的行为里面中断执行了(某些行为可能需要中断执行,例如检测机器人或者非法执行行为),否则会继续下一个行为的执行。

除了这些系统内置标签之外,开发人员还可以在应用中添加自己的应用标签,在任何需要拦截的位置添加如下代码即可:

 
  
  1. tag('my_tag'); // 添加my_tag 标签侦听
  2. // 下面的写法作用一致
  3. \Think\Hook::listen('my_tag');

tag函数用于设置某个标签位,可以传入并且只接受一个参数,如果需要传入多个参数,请使用数组,

 
  
  1. tag('my_tag',$params); // 添加my_tag 标签侦听

该参数为引用传值,所以只能传入变量,因此下面的传值是错误的:

 
  
  1. tag('my_tag','param'); // 添加my_tag 标签侦听
核心行为

系统的很多核心功能也是采用行为扩展组装的,对于满足项目日益纷繁复杂的需求和定制底层框架提供了更多的方便和可能性。

核心行为位于 ThinkPHP/Behavior/ 目录下面,框架核心内置的行为包括如下:

行为名称 说明 对应标签位置
BuildLite 生成Lite文件(3.2.1版本新增) app_init
ParseTemplate 模板文件解析,并支持第三方模板引擎驱动 view_parse
ShowPageTrace 页面Trace功能行为,完成页面Trace功能 view_end
ShowRuntime 运行时间显示行为,完成运行时间显示 view_filter
TokenBuild 令牌生成行为,完成表单令牌的自动生成 view_filter
ReadHtmlCache 读取静态缓存行为 app_init
WriteHtmlCache 生成静态缓存行为 view_filter
行为定义

自定义的扩展行为可以放在核心或者应用目录,只要遵循命名空间的定义规则即可。 行为类的命名采用:行为名称(驼峰法,首字母大写)+Behavior 行为类的定义方式如下:

 
  
  1. namespace Home\Behavior;
  2. use Think\Behavior;
  3. class TestBehavior extends Behavior {
  4. // 行为扩展的执行入口必须是run
  5. public function run(&$params){
  6. if(C('TEST_PARAM')) {
  7. echo 'RUNTEST BEHAVIOR '.$params;
  8. }
  9. }
  10. }

3.2.1版本开始,行为类的定义无需继承Think\Behavior类,所以,上面的定义可以简化为:

 
  
  1. namespace Home\Behavior;
  2. class TestBehavior {
  3. // 行为扩展的执行入口必须是run
  4. public function run(&$params){
  5. if(C('TEST_PARAM')) {
  6. echo 'RUNTEST BEHAVIOR '.$params;
  7. }
  8. }
  9. }

行为类必须定义执行入口方法run,由于行为的调用机制影响,run方法不需要任何返回值,所有返回都通过引用返回。

run方法的参数只允许一个,但可以传入数组。

行为绑定

行为定义完成后,就需要绑定到某个标签位置才能生效,否则是不会执行的。

我们需要在应用的行为定义文件tags.php文件中进行行为和标签的位置定义,格式如下:

 
  
  1. return array(
  2. '标签名称1'=>array('行为名1','行为名2',...),
  3. '标签名称2'=>array('行为名1','行为名2',...),
  4. );

标签名称包括我们前面列出的系统标签和应用中自己定义的标签名称,比如你需要在app_init标签位置定义一个CheckLangBehavior行为类的话,可以使用:

 
  
  1. return array(
  2. 'app_init'=>array('Home\Behavior\CheckLang'),
  3. // **如果是3.2.1版本 需要改成(后面不再重复说明)**
  4. // 'app_init'=>array('Home\Behavior\CheckLangBehavior'),
  5. );

可以给一个标签位定义多个行为,行为的执行顺序就是定义的先后顺序,例如:

 
  
  1. return array(
  2. 'app_init'=>array(
  3. 'Home\Behavior\CheckLang',
  4. 'Home\Behavior\CronRun'
  5. ),
  6. );

默认情况下tags.php中定义的行为会并入系统行为一起执行,也就是说如果系统的行为定义中app_init标签中已经定义了其他行为,则会首先执行系统行为扩展中定义的行为,然后再执行项目行为中定义的行为。例如: 系统行为定义文件中定义了:

 
  
  1. 'app_begin' => array(
  2. 'Behavior\ReadHtmlCache', // 读取静态缓存
  3. ),

而应用行为定义文件有定义:

 
  
  1. 'app_begin' => array(
  2. 'Home\Behavior\CheckModule',
  3. 'Home\Behavior\CheckLang',
  4. ),

则最终执行到app_begin标签(位)的时候,会依次执行:

 
  
  1. Library\Behavior\ReadHtmlCache
  2. Home\Behavior\CheckModule
  3. Home\Behavior\CheckLang

三个行为(除非中间某个行为有中止执行的操作)。

如果希望应用的行为配置文件中的定义覆盖系统的行为定义,可以改为为如下方式:

 
  
  1. 'app_begin' => array(
  2. 'Home\Behavior\CheckModule',
  3. 'Home\Behavior\CheckLang',
  4. '_overlay' => true,
  5. ),

则最终执行到app_begin标签(位)的时候,会依次执行下面两个行为:

 
  
  1. Home\Behavior\CheckModule
  2. Home\Behavior\CheckLang

应用行为的定义没有限制,你可以把一个行为绑定到多个标签位置执行,例如:

 
  
  1. return array(
  2. 'app_begin'=>array('Home\Behavior\Test'), // 在app_begin 标签位添加Test行为
  3. 'app_end'=>array('Home\Behavior\Test'), // 在app_end 标签位添加Test行为
  4. );
单独执行

行为的调用不一定要放到标签才能调用,如果需要的话,我们可以在控制器中或者其他地方直接调用行为。例如,我们可以把用户权限检测封装成一个行为类,例如:

 
  
  1. namespace Home\Behavior;
  2. use Think\Behavior;
  3. class AuthCheckBehavior extends Behavior {
  4. // 行为扩展的执行入口必须是run
  5. public function run(&$return){
  6. if(C('USER_AUTH_ON')) {
  7. // 进行权限认证逻辑 如果认证通过 $return = true;
  8. // 否则用halt输出错误信息
  9. }
  10. }
  11. }
复制代码

定义了AuthCheck行为后,我们可以在控制器的_initialize方法中直接用下面的方式调用:

 
  
  1. B('Home\Behavior\AuthCheck');
5)、命名空间

Org\Util\File类的定义为:

 
  
  1. namespace Org\Util;
  2. class File {
  3. }

其所在的路径是 ThinkPHP/Library/Org/Util/File.class.php,因此,如果我们实例化该类的话:

 
  
  1. $class = new \Org\Util\File();

系统会自动加载 ThinkPHP/Library/Org/Util/File.class.php 文件。

注意:和3.1不同,我们无需在实例化命名空间定义的类之前导入类库文件了。
根命名空间
以上面的 Org\Util\File 类为例, Org 就是一个根命名空间,其对应的初始命名空间目录就是系统的类库目录( ThinkPHP/Library ),Library目录下面的子目录会自动识别为根命名空间,这些命名空间无需注册即可使用。

以上面的Org\Util\File类为例,Org就是一个根命名空间,其对应的初始命名空间目录就是系统的类库目录(ThinkPHP/Library),Library目录下面的子目录会自动识别为根命名空间,这些命名空间无需注册即可使用。

例如,我们在Library目录下面新增一个My根命名空间目录,然后定义一个Test类如下:

 
  
  1. namespace My;
  2. class Test {
  3. public function sayHello(){
  4. echo 'hello';
  5. }
  6. }

Test类保存在 ThinkPHP/Library/My/Test.class.php,我们就可以直接实例化和调用:

 
  
  1. $Test = new \My\Test();
  2. $Test->sayHello();

3.2.1版本以上的话,允许设置对应用类库不使用命名空间,你在配置文件中进行如下设置:

 
  
  1. 'APP_USE_NAMESPACE' => false,

那么,所有的应用类库不再需要使用命名空间定义,但继承和调用核心类和系统类的时候,仍然需要使用命名空间,例如:

 
  
  1. class UserModel extends \Think\Model {
  2. }

特别注意:如果你需要在3.2版本中实例化PHP内置的类库或者第三方的没有使用命名空间定义的类,需要采用下面的方式:

 
   
  1. $class = new \stdClass();
  2. $sxml = new \SimpleXmlElement($xmlstr);
6)、自动加载
框架的Library目录下面的命名空间都可以自动识别和定位,例如
├─Library 框架类库目录
 ├─Think 核心Think类库包目录
 ├─Org Org类库包目录
 ├─ ... 更多类库目录

Library目录下面的子目录都是一个根命名空间,也就是说以Think、Org为根命名空间的类都可以自动加载:

 
  
  1. new Think\Cache\Driver\File();
  2. new Org\Util\Auth();
  3. new Org\Io\File();

都可以自动加载对应的类库文件。

你可以在Library目录下面任意增加新的目录,就会自动注册成为一个新的根命名空间。
注册新的命名空间

除了Library目录下面的命名空间之外,我们还可以注册其他的根命名空间,例如:

 
  
  1. 'AUTOLOAD_NAMESPACE' => array(
  2. 'My' => THINK_PATH.'My',
  3. 'One' => THINK_PATH.'One',
  4. )

配置了上面的AUTOLOAD_NAMESPACE后,如果我们实例化下面的类库

 
  
  1. new My\Net\IpLocation();
  2. new One\Util\Log();

会自动加载对应的类库文件

 
  
  1. ThinkPHP/My/Net/IpLocation.class.php
  2. ThinkPHP/One/Util/Log.class.php

如果命名空间不在Library目录下面,并且没有定义对应的AUTOLOAD_NAMESPACE参数的话,则会当作模块的命名空间进行自动加载,例如:

 
  
  1. new Home\Model\UserModel();
  2. new Home\Event\UserEvent();

由于ThinkPHP/Library目录下面不存在Home目录,也没在AUTOLOAD_NAMESPACE参数定义Home命名空间,所以就把Home当成模块命名空间来识别,所以会自动加载:

 
  
  1. Application/Home/Model/UserModel.class.php
  2. Application/Home/Event/UserEvent.class.php
注意:命名空间的大小写需要和目录名的大小写对应,否则可能会自动加载失败。
类库映射

遵循我们上面的命名空间定义规范的话,基本上可以完成类库的自动加载了,但是如果定义了较多的命名空间的话,效率会有所下降,所以,我们可以给常用的类库定义类库映射。命名类库映射相当于给类文件定义了一个别名,效率会比命名空间定位更高效,例如:

 
  
  1. Think\Think::addMap('Think\Log',THINK_PATH.'Think\Log.php');
  2. Think\Think::addMap('Org\Util\Array',THINK_PATH.'Org\Util\Array.php');

也可以利用addMap方法批量导入类库映射定义,例如:

 
  
  1. $map = array('Think\Log'=>THINK_PATH.'Think\Log.php','Org\Util\Array'=>THINK_PATH.'Org\Util\Array.php');
  2. Think\Think::addMap($map);

当然,比较方便的方式是我们可以在模块配置目录下面创建alias.php文件用于定义类库映射,该文件会自动加载,定义方式如下:

 
  
  1. return array(
  2. 'Think\Log' => THINK_PATH.'Think\Log.php',
  3. 'Org\Util\Array' => THINK_PATH.'Org\Util\Array.php'
  4. );
自动加载的优先级

在实际的应用类库加载过程中,往往会涉及到自动加载的优先级问题,以Test\MyClass类为例,自动加载的优先顺序如下:

  1. 判断是否有注册了Test\MyClass类库映射,如果有则自动加载类库映射定义的文件;
  2. 判断是否存在Library/Test目录,有则以该目录为初始目录加载;
  3. 判断是否有注册Test根命名空间,有则以注册的目录为初始目录加载;
  4. 如果以上都不成立,则以Test为模块目录进行初始目录加载;
以上面获取到的初始目录加载命名空间对应路径的文件;
手动加载第三方类库

我们可以使用import方法导入任何类库,用法如下:

 
  
  1. // 导入Org类库包 Library/Org/Util/Date.class.php类库
  2. import("Org.Util.Date");
  3. // 导入Home模块下面的 Application/Home/Util/UserUtil.class.php类库
  4. import("Home.Util.UserUtil");
  5. // 导入当前模块下面的类库
  6. import("@.Util.Array");
  7. // 导入Vendor类库包 Library/Vendor/Zend/Server.class.php
  8. import('Vendor.Zend.Server');

对于import方法,系统会自动识别导入类库文件的位置,ThinkPHP可以自动识别的类库包包括Think、Org、Com、Behavior和Vendor包,以及Library目录下面的子目录,如果你在Library目录下面创建了一个Test子目录,并且创建了一个UserTest.class.php类库,那么可以这样导入:

 
  
  1. import('Test.UserTest');

其他的就认为是应用类库导入。

注意,如果你的类库没有使用命名空间定义的话,实例化的时候需要加上根命名空间,例如:

 
  
  1. import('Test.UserTest');
  2. $test = new \UserTest();

按照系统的规则,import方法是无法导入具有点号的类库文件的,因为点号会直接转化成斜线,例如我们定义了一个名称为User.Info.class.php 的文件的话,采用:

 
  
  1. import("Org.User.Info");

方式加载的话就会出现错误,导致加载的文件不是Org/User.Info.class.php 文件,而是Org/User/Info.class.php 文件,这种情况下,我们可以使用:

 
  
  1. import("Org.User#Info");

来导入。

大多数情况下,import方法都能够自动识别导入类库文件的位置,如果是特殊情况的导入,需要指定import方法的第二个参数作为起始导入路径。例如,要导入当前文件所在目录下面的 RBAC/AccessDecisionManager.class.php 文件,可以使用:

 
  
  1. import("RBAC.AccessDecisionManager",dirname(__FILE__));

如果你要导入的类库文件名的后缀不是class.php而是php,那么可以使用import方法的第三个参数指定后缀:

 
  
  1. import("RBAC.AccessDecisionManager",dirname(__FILE__),".php");
注意:在Unix或者Linux主机下面是区别大小写的,所以在使用import方法的时候要注意目录名和类库名称的大小写,否则会导入失败。

如果你的第三方类库都放在Vendor目录下面,并且都以.php为类文件后缀,也没用采用命名空间的话,那么可以使用系统内置的Vendor函数简化导入。 例如,我们把 Zend 的 Filter\Dir.php 放到 Vendor 目录下面,这个时候 Dir 文件的路径就是 Vendor\Zend\Filter\Dir.php,我们使用vendor 方法导入只需要使用:

 
  
  1. Vendor('Zend.Filter.Dir');

就可以导入Dir类库了。

Vendor方法也可以支持和import方法一样的基础路径和文件名后缀参数,例如:

 
  
  1. Vendor('Zend.Filter.Dir',dirname(__FILE__),'.class.php');
7)、应用模式

3.2版本的应用模式可以替代之前的引擎扩展和模式扩展,支持应用模式定义。

每个应用模式有自己的定义文件,用于配置当前模式需要加载的核心文件和配置文件,以及别名定义、行为扩展定义等等。除了模式定义外,应用自身也可以独立定义模式文件。

如果应用模式涉及到不同的存储类型,例如采用分布式存储等,就需要另外设置存储类型(STORAGE_TYPE)。不同的存储类型由Think\Storage类及相关驱动进行支持。

默认情况下的应用模式是普通模式(common),如果要采用其他的应用模式(当然,前提是已经有定义),必须在入口文件中定义,设置APP_MODE常量即可,例如:

 
  
  1. // 定义存储类型和应用模式为SAE(用于支持SAE平台)
  2. define('STORAGE_TYPE','sae');
  3. define('APP_MODE','sae');

应用模式的一个典型应用是对分布式平台的支持,对不同的平台定义不同的应用模式就可以支持。

每个入口文件仅能定义一个应用模式,所以,如果需要对相同的应用模块设置不同的应用模式访问,就可以通过增加入口文件的方式来解决。

每个应用模式可以定义单独的配置文件,一般是config_模式名称,例如,sae模式下面可以定义:

 
  
  1. // 应用配置文件
  2. Application/Common/Conf/config_sae.php
复制代码

或者

 
  
  1. // 模块配置文件
  2. Application/Home/Conf/config_sae.php
8)、项目编译

应用编译缓存

编译缓存的基础原理是第一次运行的时候把核心需要加载的文件去掉空白和注释后合并到一个文件中,第二次运行的时候就直接载入编译缓存而无需载入众多的核心文件。当第二次执行的时候就会根据当前的应用模式直接载入编译过的缓存文件,从而省去很多IO开销,加快执行速度。

项目编译机制对运行没有任何影响,预编译机制只会执行一次,因此无论在预编译过程中做了多少复杂的操作,对后面的执行没有任何效率的缺失。

编译缓存文件默认生成在应用目录的Runtime目录下面,我们可以在Application/Runtime目录下面看到有一个common~runtime.php文件,这个就是普通模式的编译缓存文件。如果你当前运行在其他的应用模式下面,那么编译缓存文件就是:应用模式~runtime.php

例如,如果你当前用的是SAE模式,那么生成的编译缓存文件则会变成sae~runtime.php

普通模式的编译缓存的内容包括:系统函数库、系统基础核心类库、核心行为类库、项目函数文件,当然这些是可以改变的。

运行Lite文件

这是3.2.1版本的新特性。 运行Lite文件的作用是替换框架的入口文件或者替换应用入口文件,提高运行效率。因为默认生成的文件名为lite.php,并且是运行时动态生成,因此称之为运行Lite文件

Lite文件的特点包括:

  • 运行时动态生成;
  • 常量定义为针对当前环境;
  • 支持定义需要编译的文件列表;
  • 支持生成Lite文件的名称;
如何生成Lite文件,请参考部署部分的 替换入口。
9)、系统流程

我们来系统的了解下ThinkPHP框架开发的应用的标准执行流程:

 
  
  1. 用户URL请求
  2. 调用应用入口文件(通常是网站的index.php
  3. 载入框架入口文件(ThinkPHP.php
  4. 记录初始运行时间和内存开销
  5. 系统常量判断及定义
  6. 载入框架引导类(Think\Think)并执行Think::start方法进行应用初始化
  7. 设置错误处理机制和自动加载机制
  8. 调用Think\Storage类进行存储初始化(由STORAGE_TYPE常量定义存储类型)
  9. 部署模式下如果存在应用编译缓存文件则直接加载(直接跳转到步骤22
  10. 读取应用模式(由APP_MODE常量定义)的定义文件(以下以普通模式为例说明)
  11. 加载当前应用模式定义的核心文件(普通模式是 ThinkPHP/Mode/common.php
  12. 加载惯例配置文件(普通模式是 ThinkPHP/Conf/convention.php
  13. 加载应用配置文件(普通模式是 Application/Common/Conf/config.php
  14. 加载系统别名定义
  15. 判断并读取应用别名定义文件(普通模式是 Application/Common/Conf/alias.php
  16. 加载系统行为定义
  17. 判断并读取应用行为定义文件(普通模式是 Application/Common/Conf/tags.php
  18. 加载框架底层语言包(普通模式是 ThinkPHP/Lang/zh-cn.php
  19. 如果是部署模式则生成应用编译缓存文件
  20. 加载调试模式系统配置文件(ThinkPHP/Conf/debug.php
  21. 判断并读取应用的调试配置文件(默认是 Application/Common/Conf/debug.php
  22. 判断应用状态并读取状态配置文件(如果APP_STATUS常量定义不为空的话)
  23. 检测应用目录结构并自动生成(如果CHECK_APP_DIR配置开启并且RUNTIME_PATH目录不存在的情况下)
  24. 调用Think\App类的run方法启动应用
  25. 应用初始化(app_init)标签位侦听并执行绑定行为
  26. 判断并加载动态配置和函数文件
  27. 调用Think\Dispatcher::dispatch方法进行URL请求调度
  28. 自动识别兼容URL模式和命令行模式下面的$_SERVER['PATH_INFO']参数
  29. 检测域名部署以及完成模块和控制器的绑定操作(APP_SUB_DOMAIN_DEPLOY参数开启)
  30. 分析URL地址中的PATH_INFO信息
  31. 获取请求的模块信息
  32. 检测模块是否存在和允许访问
  33. 判断并加载模块配置文件、别名定义、行为定义及函数文件
  34. 判断并加载模块的动态配置和函数文件
  35. 模块的URL模式判断
  36. 模块的路由检测(URL_ROUTER_ON开启)
  37. PATH_INFO处理(path_info)标签位侦听并执行绑定行为
  38. URL后缀检测(URL_DENY_SUFFIX以及URL_HTML_SUFFIX处理)
  39. 获取当前控制器和操作,以及URL其他参数
  40. URL请求调度完成(url_dispatch)标签位侦听并执行绑定行为
  41. 应用开始(app_begin)标签位侦听并执行绑定行为
  42. 调用SESSION_OPTIONS配置参数进行Session初始化(如果不是命令行模式)
  43. 根据请求执行控制器方法
  44. 如果控制器不存在则检测空控制器是否存在
  45. 控制器开始(action_begin)标签位侦听并执行绑定行为
  46. 默认调用系统的ReadHtmlCache行为读取静态缓存(HTML_CACHE_ON参数开启)
  47. 判断并调用控制器的_initialize初始化方法
  48. 判断操作方法是否存在,如果不存在则检测是否定义空操作方法
  49. 判断前置操作方法是否定义,有的话执行
  50. Action参数绑定检测,自动匹配操作方法的参数
  51. 如果有模版渲染(调用控制器display方法)
  52. 视图开始(view_begin)标签位侦听并执行绑定行为
  53. 调用Think\Viewfetch方法解析并获取模版内容
  54. 自动识别当前主题以及定位模版文件
  55. 视图解析(view_parse)标签位侦听并执行绑定行为
  56. 默认调用内置ParseTemplate行为解析模版(普通模式下面)
  57. 模版引擎解析模版内容后生成模版缓存
  58. 模版过滤替换(template_filter)标签位侦听并执行绑定行为
  59. 默认调用系统的ContentReplace行为进行模版替换
  60. 输出内容过滤(view_filter)标签位侦听并执行绑定行为
  61. 默认调用系统的WriteHtmlCache行为写入静态缓存(HTML_CACHE_ON参数开启)
  62. 调用Think\View类的render方法输出渲染内容
  63. 视图结束(view_end)标签位侦听并执行绑定行为
  64. 判断后置操作方法是否定义,有的话执行
  65. 控制器结束(action_end)标签位侦听并执行绑定行为
  66. 应用结束(app_end)标签位侦听并执行绑定行为
  67. 执行系统的ShowPageTrace行为(SHOW_PAGE_TRACE参数开启并且不是AJAX请求)
  68. 日志信息存储写入
复制代码

如果你绑定了更多的应用行为的话,流程可能会更加复杂。

如果是部署模式下面的第二次请求的话,上面的流程中的步骤10~21是可以省略的。















你可能感兴趣的:(架构,thinkphp)