主要特性
-
引入容器和
Facade
支持 -
依赖注入完善和支持更多场景
-
重构的(对象化)路由
-
支持注解路由
-
跨域请求支持
-
配置和路由目录独立
-
取消系统常量
-
助手函数增强
-
类库别名机制
-
模型和数据库增强
-
验证类增强
-
模板引擎改进
-
支持
PSR-3
日志规范 -
中间件支持(
V5.1.6+
) -
支持
Swoole
/Workerman
运行(V5.1.18+
)
安装
环境要求
- PHP >= 5.6.0
- PDO PHP Extension
- MBstring PHP Extension
Composer安装
第一次安装
composer create-project topthink/think=5.1.* tp5
已经安装过(切换到应用根目录下面)
composer update topthink/framework
* 修改composer.json中"topthink/framework": "5.1.*",再执行,否则报Nothing to modify in lock file,无法更新
命名规范
ThinkPHP5.1
遵循PSR-2
命名规范和PSR-4
自动加载规范
PSR(PHP Standard Recommendations),由PHP FIG(Framework Interoperability Group)组织制定的 PHP 规范,是 PHP 开发的实践标准。
PSR-2
-
代码必须遵循 PSR-1 中的编码规范 。
-
代码必须使用4个空格符而不是 tab键 进行缩进。
所有PHP文件必须以一个空白行作为结束。
纯PHP代码文件必须省略最后的
?>
结束标签。 -
每行的字符数应该软性保持在80个之内, 理论上一定不可多于120个, 但一定不能有硬性限制。
PHP所有 关键字必须全部小写。
常量
true
、false
和null
也必须全部小写。 -
每个
namespace
命名空间声明语句和use
声明语句块后面,必须插入一个空白行。 -
类的开始花括号(
{
)必须写在函数声明后自成一行,结束花括号(}
)也必须写在函数主体后自成一行。 -
方法的开始花括号(
{
)必须写在函数声明后自成一行,结束花括号(}
)也必须写在函数主体后自成一行。方法名称后一定不能有空格符,参数左括号后和右括号前一定不能有空格。
参数列表中,每个参数后面必须要有一个空格,而前面一定不能有空格。
有默认值的参数,必须放到参数列表的末尾。
-
类的属性和方法必须添加访问修饰符(
private
、protected
以及public
),abstract
以及final
必须声明在访问修饰符之前,而static
必须声明在访问修饰符之后。 -
控制结构的关键字后必须要有一个空格符,而调用方法或函数时则一定不能有。
-
控制结构的开始花括号(
{
)必须写在声明的同一行,而结束花括号(}
)必须写在主体后自成一行。 -
控制结构的开始左括号后和结束右括号前,都一定不能有空格符。
PSR-4
-
完整的类名必须要有一个顶级命名空间,被称为 "vendor namespace";
-
完整的类名可以有一个或多个子命名空间;
-
完整的类名必须有一个最终的类名;
-
完整的类名中任意一部分中的下滑线都是没有特殊含义的;
-
完整的类名可以由任意大小写字母组成;
-
所有类名都必须是大小写敏感的。
-
当根据完整的类名载入相应的文件……
-
完整的类名中,去掉最前面的命名空间分隔符,前面连续的一个或多个命名空间和子命名空间,作为“命名空间前缀”,其必须与至少一个“文件基目录”相对应;
-
紧接命名空间前缀后的子命名空间必须与相应的”文件基目录“相匹配,其中的命名空间分隔符将作为目录分隔符。
-
末尾的类名必须与对应的以
.php
为后缀的文件同名。 -
自动加载器(autoloader)的实现一定不能抛出异常、一定不能触发任一级别的错误信息以及不应该有返回值。
目录和文件
- 目录使用小写+下划线;
- 类库、函数文件统一以
.php
为后缀; - 类的文件名均以命名空间定义,并且命名空间的路径和类库文件所在路径一致;
- 类文件采用驼峰法命名(首字母大写),其它文件采用小写+下划线命名;
- 类名和类文件名保持一致,统一采用驼峰法命名(首字母大写);
函数和类、属性命名
- 类的命名采用驼峰法(首字母大写),例如
User
、UserType
,默认不需要添加后缀,例如UserController
应该直接命名为User
; - 函数的命名使用小写字母和下划线(小写字母开头)的方式,例如
get_client_ip
; - 方法的命名使用驼峰法(首字母小写),例如
getUserName
; - 属性的命名使用驼峰法(首字母小写),例如
tableName
、instance
; - 特例:以双下划线
__
打头的函数或方法作为魔术方法,例如__call
和__autoload
;
常量和配置
- 常量以大写字母和下划线命名,例如
APP_PATH
; - 配置参数以小写字母和下划线命名,例如
url_route_on
和url_convert
; - 环境变量定义使用大写字母和下划线命名,例如
APP_DEBUG
;
数据表和字段
- 数据表和字段采用小写加下划线方式命名,并注意字段名不要以下划线开头,例如
think_user
表和user_name
字段,不建议使用驼峰和中文作为数据表及字段命名。
目录结构
5.1
版本目录结构的主要变化是配置目录和路由定义目录独立出来,不再放入应用类库目录(并且不可更改)。
www WEB部署目录(或者子目录)
├─application 应用目录
│ ├─common 公共模块目录(可以更改)
│ ├─module_name 模块目录
│ │ ├─common.php 模块函数文件
│ │ ├─controller 控制器目录
│ │ ├─model 模型目录
│ │ ├─view 视图目录
│ │ ├─config 配置目录
│ │ └─ ... 更多类库目录
│ │
│ ├─command.php 命令行定义文件
│ ├─common.php 公共函数文件
│ └─tags.php 应用行为扩展定义文件
│
├─config 应用配置目录
│ ├─module_name 模块配置目录
│ │ ├─database.php 数据库配置
│ │ ├─cache 缓存配置
│ │ └─ ...
│ │
│ ├─app.php 应用配置
│ ├─cache.php 缓存配置
│ ├─cookie.php Cookie配置
│ ├─database.php 数据库配置
│ ├─log.php 日志配置
│ ├─session.php Session配置
│ ├─template.php 模板引擎配置
│ └─trace.php Trace配置
│
├─route 路由定义目录
│ ├─route.php 路由定义
│ └─... 更多
│
├─public WEB目录(对外访问目录)
│ ├─index.php 入口文件
│ ├─router.php 快速测试文件
│ └─.htaccess 用于apache的重写
│
├─thinkphp 框架系统目录
│ ├─lang 语言文件目录
│ ├─library 框架类库目录
│ │ ├─think Think类库包目录
│ │ └─traits 系统Trait目录
│ │
│ ├─tpl 系统模板目录
│ ├─base.php 基础定义文件
│ ├─convention.php 框架惯例配置文件
│ ├─helper.php 助手函数文件
│ └─logo.png 框架LOGO文件
│
├─extend 扩展类库目录
├─runtime 应用的运行时目录(可写,可定制)
├─vendor 第三方类库目录(Composer依赖库)
├─build.php 自动生成定义文件(参考)
├─composer.json composer 定义文件
├─LICENSE.txt 授权说明文件
├─README.md README 文件
├─think 命令行入口文件
* 由于5.1
版本取消了系统路径的常量定义,因此系统的目录名称不可更改。如果需要更改应用目录或者入口文件位置,参考架构章节的入口文件部分。
配置
配置基础
ThinkPHP
遵循惯例重于配置的原则,系统会按照下面的顺序来加载配置文件(配置的优先顺序从右到左)。
├─config(应用配置目录)
│ ├─app.php 应用配置
│ ├─cache.php 缓存配置
│ ├─cookie.php Cookie配置
│ ├─database.php 数据库配置
│ ├─log.php 日志配置
│ ├─session.php Session配置
│ ├─template.php 模板引擎配置
│ ├─trace.php Trace配置
│ └─ ... 更多配置文件
│
├─route(路由目录)
│ ├─route.php 路由定义文件
│ └─ ... 更多路由定义文件
│
├─application (应用目录)
│ └─module (模块目录)
│ └─config(模块配置目录)
│ ├─app.php 应用配置
│ ├─cache.php 缓存配置
│ ├─cookie.php Cookie配置
│ ├─database.php 数据库配置
│ ├─log.php 日志配置
│ ├─session.php Session配置
│ ├─template.php 模板引擎配置
│ ├─trace.php Trace配置
│ └─ ... 更多配置文件
│
* 5.1没有config.php
配置文件,默认配置都在app.php
配置文件,并且配置参数区分大小写
想要统一管理所有的配置文件,可以把模块目录下面的
config
目录移动到应用配置目录下面改为模块子目录的方式,调整后的配置目录的结构如下:
├─application(应用目录)
├─config(配置目录)
│ ├─module (模块配置目录)
│ │ ├─database.php 数据库配置
│ │ ├─cache 缓存配置
│ │ └─ ...
│ │
│ ├─app.php 应用配置
│ ├─cache.php 缓存配置
│ ├─cookie.php Cookie配置
│ ├─database.php 数据库配置
│ ├─log.php 日志配置
│ ├─session.php Session配置
│ ├─template.php 模板引擎配置
│ └─trace.php Trace配置
│
├─route(路由配置目录)
│ ├─route.php 路由定义文件
│ └─ ... 更多路由定义文件
配置定义
可以直接在相应的应用或模块配置文件中修改或者增加配置参数,如果你要增加额外的配置文件,直接放入应用或模块配置目录即可(文件名小写)。
另外涉及到配置参数的定义有效性问题,下列配置参数在模块配置中定义(包括动态配置)无效,而必须在应用配置中设置:
配置参数 | 描述 |
---|---|
app_debug | 应用调试模式(支持环境变量配置) |
app_trace | 应用trace(支持环境变量配置) |
class_suffix | 类后缀 |
default_filter | 默认过滤机制 |
root_namespace | 根命名空间 |
pathinfo_depr | PATH_INFO分隔符 |
url_route_must | 路由强制模式 |
auto_bind_module | 自动绑定模块 |
default_lang | 默认语言 |
lang_switch_on | 多语言切换 |
由于架构设计原因,下面的配置只能在环境变量中修改。
配置参数 | 描述 |
---|---|
app_namespace | 应用命名空间 |
config_ext | 配置文件后缀 |
环境变量定义
在应用的根目录下定义一个特殊的.env
环境变量文件,用于在开发过程中模拟环境变量配置(该文件建议在服务器部署的时候忽略),.env
文件中的配置参数定义格式采用ini
方式
APP_DEBUG = true
APP_TRACE = true
* 如果部署环境单独配置了环境变量( 环境变量的前缀使用PHP_
),需要删除.env
配置文件,避免冲突。
环境变量配置的参数会全部转换为大写,值为
null
,no
和false
等效于""
,值为yes
和true
等效于"1"
。
环境变量不支持数组参数,如果需要使用数组参数可以,使用下划线分割定义配置参数名:
DATABASE_USERNAME = root
DATABASE_PASSWORD = 123456
获取环境变量的值使用下面的方式:
Env::get('database_username');
Env::get('database_password');
如果使用
[DATABASE]
USERNAME = root
PASSWORD = 123456
获取环境变量的值可以使用下面的方式获取:
Env::get('database.username');
Env::get('database.password');
* 要使用Env
类,必须先引入think\facade\Env
或者\Env
。环境变量的获取不区分大小写。
支持默认值,例如:
// 获取环境变量 如果不存在则使用默认值root
Env::get('database.username','root');
可以直接在配置文件中使用环境变量进行本地环境和服务器的自动配置,例如:
return [
'hostname' => Env::get('hostname','127.0.0.1'),
];
环境变量中设置的
APP_DEBUG
和APP_TRACE
参数会自动生效(优先于应用的配置文件),其它参数则必须通过Env::get
方法才能读取。
支持获取的系统路径变量包括:
系统路径 | Env参数名称 |
---|---|
应用根目录 | root_path |
应用目录 | app_path |
框架目录 | think_path |
配置目录 | config_path |
扩展目录 | extend_path |
composer目录 | vendor_path |
运行缓存目录 | runtime_path |
路由目录 | route_path |
当前模块目录 | module_path |
配置获取
要使用Config
类,首先需要在你的类文件中引入
use think\facade\Config;
或者(因为系统做了类库别名,其实就是调用think\facade\Config
)
use Config;
然后就可以使用下面的方法读取某个配置参数的值:
echo Config::get('配置参数1');
如果你需要读取某个一级配置的所有配置参数,可以使用
Config::pull('app');
或者使用
Config::get('app.');
读取所有的配置参数:
dump(Config::get());
判断是否存在某个设置参数:
Config::has('配置参数2');
助手函数
系统定义了一个助手函数config
,以上可以简化为:
echo config('配置参数1');
5.1
的配置参数全部采用二级配置的方式(默认一级配置为app),所以当你使用config('name')
的时候其实相当于使用:
config('app.name')
支持获取多级配置参数值,直接使用(必须从一级开始写)
config('app.name1.name2')
获取某个一级配置的所有参数可以使用
config('app.');
读取所有的配置参数:
dump(config());
或者你需要判断是否存在某个设置参数:
config('?配置参数2');
架构
入口文件
5.1
默认的应用入口文件位于public/index.php
// [ 应用入口文件 ]
namespace think;
// 加载基础文件
require __DIR__ . '/../thinkphp/base.php';
// 执行应用并响应
Container::get('app')->run()->send();
更改应用目录和入口位置
新版框架默认不再支持改变应用目录(application
)和入口文件位置,如果你需要更改,需要自己重新定义入口文件。
<?php
namespace think;
// 定义应用目录
define('APP_PATH', __DIR__ . '/app/');
// 加载框架基础引导文件
require __DIR__ . '/thinkphp/base.php';
// 添加额外的代码
// ...
// 执行应用并响应
Container::get('app', [APP_PATH])->run()->send();
如果是V5.1.2+
版本,上面的最后一行代码可以使用下面的替代:
Container::get('app')->path(APP_PATH)->run()->send();
* 更改应用目录名称和位置可能导致默认的命令行操作失效,你需要同步自定义根目录下面的think
文件。
模块设计
├─application 应用目录(可设置)
│ ├─common 公共模块目录(可选)
│ ├─module1 模块1目录
│ │ ├─common.php 模块函数文件
│ │ ├─config 模块配置目录(可选)
│ │ ├─controller 控制器目录
│ │ ├─model 模型目录(可选)
│ │ ├─view 视图目录(可选)
│ │ └─ ... 更多类库目录
│ │
│ ├─module2 模块2目录
│ │ ├─common.php 模块函数文件
│ │ ├─config 模块配置目录(可选)
│ │ ├─controller 控制器目录
│ │ ├─model 模型目录(可选)
│ │ ├─view 视图目录(可选)
│ │ └─ ... 更多类库目录
模块的配置目录也可以放到外面的config目录的模块子目录下面。
* 其中common
模块是一个特殊的模块,默认是禁止直接访问的,一般用于放置一些公共的类库用于其他模块的继承。
命名空间
如果需要调用PHP内置的类库,或者第三方没有使用命名空间的类库,记得在实例化类库的时候加上 \
根命名空间(类库包)
系统内置的几个根命名空间(类库包)如下:
名称 | 描述 | 类库目录 |
---|---|---|
think | 系统核心类库 | thinkphp/library/think |
traits | 系统Trait类库 | thinkphp/library/traits |
app | 应用类库 | application |
应用类库包
为了避免和Composer
自动加载的类库存在冲突 ,应用类库的命名空间的根都统一以app
命名
如果觉得
app
根命名空间不合适或者有冲突,可以更改环境变量APP_NAMESPACE
,如果你定义了.env
文件的话,可以在里面添加:
APP_NAMESPACE = application
定义后,应用类库的命名空间改为:
<?php
namespace application\index\model;
class User extends \think\Model
{
}
容器和依赖注入
容器类的工作由think\Container
类完成,但大多数情况我们只需要通过app
助手函数即可完成大部分操作。
依赖注入其实本质上是指对类的依赖通过构造器完成自动注入,例如在控制器架构方法和操作方法中一旦对参数进行对象类型约束则会自动触发依赖注入,由于访问控制器的参数都来自于URL请求,普通变量就是通过参数绑定自动获取,对象变量则是通过依赖注入生成。
依赖注入的对象参数支持多个,并且和顺序无关。
支持使用依赖注入的场景包括(但不限于):
- 控制器架构方法;
- 控制器操作方法;
- 数据库和模型事件方法;
- 路由的闭包定义;
- 行为类的方法;
在ThinkPHP的设计中,
think\App
类虽然自身不是容器,但却是一个容器管理类,可以完成容器的所有操作。
V5.1.14+
版本开始,应用类自身就是一个容器实例。
绑定
依赖注入的类统一由容器进行管理,大多数情况下是在自动绑定并且实例化的。不过你可以随时进行手动绑定类到容器中,支持多种绑定方式。
可以对已有的类库绑定一个标识(唯一),便于快速调用。
// 绑定类库标识
bind('cache','think\Cache');
// 快速调用(自动实例化)
$cache = app('cache');
调用和绑定的标识必须保持一致(包括大小写)
系统已经内置绑定了核心常用类库,无需重复绑定,内置绑定到容器中的类库包括
系统类库 | 容器绑定标识 |
---|---|
think\Build | build |
think\Cache | cache |
think\Config | config |
think\Cookie | cookie |
think\Debug | debug |
think\Env | env |
think\Hook | hook |
think\Lang | lang |
think\Log | log |
think\Request | request |
think\Response | response |
think\Route | route |
think\Session | session |
think\Url | url |
think\Validate | validate |
think\View | view |
解析
使用app
助手函数进行容器中的类解析调用,对于已经绑定的类标识,会自动快速实例化
app('cache');
上面的app助手函数相当于调用了
Container::get('cache');
带参数实例化调用
app('cache',['file']);
对于没有绑定的类,也可以直接解析
app('org\utils\ArrayItem');
使用app
助手函数获取容器中的对象实例(支持依赖注入)。
$app = app();
// 判断对象实例是否存在
isset($app->cache);
// 注册容器对象实例
$app->cache = think\Cache::class;
// 获取容器中的对象实例
$cache = $app->cache;
不带任何参数调用app
助手函数其实是实例化think\App
类,可以方便的操作容器、绑定和调用对象实例。
// 绑定类到容器
app()->test = new Test;
// 实例调用
$test = app()->test;
也就是说,你可以在任何地方使用app()
方法调用容器中的任何类。
// 调用配置类
app()->config->get('app_name');
// 调用session类
app()->session->get('user_name');
门面(Facade
)
门面为容器中的类提供了一个静态调用接口,相比于传统的静态方法调用, 带来了更好的可测试性和扩展性,你可以为任何的非静态类库定义一个facade
类。\
说的直白一点,Facade功能可以让类无需实例化而直接进行静态方式调用。
核心Facade
类库
系统给内置的常用类库定义了Facade
类库,还给这些常用的核心类库的Facade
类注册了类库别名,当进行静态调用的时候可以直接使用简化的别名进行调用。
(动态)类库 | Facade类 | 别名类 |
---|---|---|
think\App | think\facade\App | App |
think\Build | think\facade\Build | Build |
think\Cache | think\facade\Cache | Cache |
think\Config | think\facade\Config | Config |
think\Cookie | think\facade\Cookie | Cookie |
think\Db | Db | |
think\Debug | think\facade\Debug | Debug |
think\Env | think\facade\Env | Env |
think\Hook | think\facade\Hook | Hook |
think\Lang | think\facade\Lang | Lang |
think\Log | think\facade\Log | Log |
think\Middleware | think\facade\Middleware | Middleware |
think\Request | think\facade\Request | Request |
think\Response | think\facade\Response | Response |
think\Route | think\facade\Route | Route |
think\Session | think\facade\Session | Session |
think\Url | think\facade\Url | Url |
think\Validate | think\facade\Validate | Validate |
think\View | think\facade\View | View |
无需进行实例化就可以很方便的进行方法调用,例如:
use think\facade\Cache;
Cache::set('name','value');
echo Cache::get('name');
前面的代码可以改成
\Cache::set('name','value');
echo \Cache::get('name');
Facade类定义了一个实例化的
instance
方法,如果你的类也有定义的话将会失效。
think\Db
类的实现本来就类似于Facade
机制,所以不需要再进行静态代理就可以使用静态方法调用(确切的说Db
类是没有方法的,都是调用的Query
类的方法)。
在进行依赖注入的时候,请不要使用Facade
类作为类型约束,而是建议使用原来的动态类。
钩子和行为
ThinkPHP中的行为是一个比较抽象的概念,你可以把行为想象成在应用执行过程中的一个动作。在框架的执行流程中,例如路由检测是一个行为,静态缓存是一个行为,用户权限检测也是行为,大到业务逻辑,小到浏览器检测、多语言检测等等都可以当做是一个行为,甚至说你希望给你的网站用户的第一次访问弹出Hello,world!
这些都可以看成是一种行为,把这些行为抽离出来的目的是为了让你无需改动框架和应用,而在外围通过扩展或者配置来改变或者增加一些功能。
而不同的行为之间也具有位置共同性,比如,有些行为的作用位置都是在应用执行前,有些行为都是在模板输出之后,我们把这些行为发生作用的位置称之为钩子,当应用程序运行到这个钩子的时候,就会被拦截下来,统一执行相关的行为,类似于AOP
编程中的“切面”的概念,给某一个钩子绑定相关行为就成了一种类AOP
编程的思想。
一个钩子可以注册多个行为,执行到某个钩子位置后,会按照注册的顺序依次执行相关的行为。但在某些特殊的情况下,你可以设置某个钩子只能执行一次行为,又或者你可以在一个钩子的某个行为中返回false
来强制终止后续的行为执行;一个行为可以同时注册到多个不同的钩子上,完全看应用的需求来设计。
钩子的位置必须是事先设计好的,无论是框架还是应用的,要设置一个钩子,只需要在相关的位置添加一行代码(事先需要引入think\facade\Hook
类):
除了钩子名称之外,其它参数都是可选的,注意5.1
版本第二个参数不支持引用传值。
系统核心设计提供了一些可能会需要的钩子(位置),尽可能的方便应用的扩展而不必改动框架核心,按照执行顺序依次如下:
钩子 | 描述 | 参数 |
---|---|---|
app_init | 应用初始化标签位 | 无 |
app_dispatch | 应用调度标签位 | 无 |
app_begin | 应用开始标签位 | 无 |
module_init | 模块初始化标签位 | 无 |
action_begin | 控制器开始标签位 | 当前的callback参数 |
view_filter | 视图输出过滤标签位 | 当前模板渲染输出内容 |
app_end | 应用结束标签位 | 当前响应对象实例 |
log_write | 日志write方法标签位 | 当前写入的日志信息 |
log_level | 日志写入标签位 | 包含日志类型和日志信息的数组(V5.1.25+ ) |
response_send | 响应发送标签位 | 当前响应对象 |
response_end | 输出结束标签位 | 当前响应对象实例 |
其中
log_write
钩子仅在调用Log::write
方法的时候执行。
view_filter
钩子v5.1.3+
版本中已经废除,改用视图类的filter
方法过滤。
行为定义
行为类的定义很简单,一般来说只需要定义一个行为入口方法run
即可,例如:
namespace app\index\behavior;
class Test
{
public function run($params)
{
// 行为逻辑
}
}
可以在行为方法中使用依赖注入,例如:
namespace app\index\behavior;
use think\Request;
class Test
{
public function run(Request $request, $params)
{
// 行为逻辑
}
}
行为的入口方法名称支持自定义,如果需要更改在应用公共文件中添加下面的代码即可:
Hook::portal('portal');
入口方法名称就变成了portal
。
行为类并不需要继承任何类,相对比较灵活。如果行为类需要绑定到多个钩子,可以采用如下定义:
namespace app\index\behavior;
class Test
{
public function appInit($params)
{
}
public function appEnd($params)
{
}
}
该行为绑定到app_init
和app_end
钩子后 就会调用相关的方法,方法名就是钩子名称的驼峰命名(首字母小写)。
行为绑定
行为定义完成后,就需要绑定到某个标签位置才能生效,否则是不会执行的。
使用think\facade\Hook
类的add方法注册行为,例如:
// 注册 app\index\behavior\CheckLang行为类到app_init标签位
Hook::add('app_init','app\\index\\behavior\\CheckLang');
//注册 app\admin\behavior\CronRun行为类到app_init标签位
Hook::add('app_init','app\\admin\\behavior\\CronRun');
如果要批量注册行为的话,可以使用:
Hook::add('app_init',['app\\index\\behavior\\CheckAuth','app\\index\\behavior\\CheckLang','app\\admin\\behavior\\CronRun']);
当应用运行到app_init
标签位的时候,就会依次调用app\index\behavior\CheckAuth
、app\index\behavior\CheckLang
和app\admin\behavior\CronRun
行为。如果其中一个行为中有中止代码的话则后续不会执行,如果返回false
则当前标签位的后续行为将不会执行,但应用将继续运行。
我们也可以直接在应用目录下面或者模块的目录下面定义tags.php
文件来统一定义行为,定义格式如下:
return [
'app_init'=> [
'app\\index\\behavior\\CheckAuth',
'app\\index\\behavior\\CheckLang'
],
'app_end'=> [
'app\\admin\\behavior\\CronRun'
]
]
如果应用目录下面和模块目录下面的tags.php
都定义了app_init
的行为绑定的话,会采用合并模式,如果希望覆盖,那么可以在模块目录下面的tags.php
中定义如下:
return [
'app_init'=> [
'app\\index\\behavior\\CheckAuth',
'_overlay'=>true
],
'app_end'=> [
'app\\admin\\behavior\\CronRun'
]
]
如果某个行为标签定义了'_overlay' =>true
就表示覆盖之前的相同标签下面的行为定义。
中间件
中间件主要用于拦截或过滤应用的HTTP
请求,并进行必要的业务处理。
定义中间件
可以通过命令行指令快速生成中间件
php think make:middleware Check
这个指令会 application/http/middleware
目录下面生成一个Check
中间件。
<?php
namespace app\http\middleware;
class Check
{
public function handle($request, \Closure $next)
{
if ($request->param('name') == 'think') {
return redirect('index/think');
}
return $next($request);
}
}
中间件的入口执行方法必须是handle
方法,而且第一个参数是Request
对象,第二个参数是一个闭包。
中间件
handle
方法的返回值必须是一个Response
对象。
在这个中间件中我们判断当前请求的name
参数等于think
的时候进行重定向处理。否则,请求将进一步传递到应用中。要让请求继续传递到应用程序中,只需使用 $request
作为参数去调用回调函数 $next
。
在某些需求下,可以使用第三个参数传入额外的参数。
<?php
namespace app\http\middleware;
class Check
{
public function handle($request, \Closure $next, $name)
{
if ($name == 'think') {
return redirect('index/think');
}
return $next($request);
}
}
前置/后置中间件
中间件是在请求具体的操作之前还是之后执行,完全取决于中间件的定义本身。
下面是一个前置行为的中间件
<?php
namespace app\http\middleware;
class Before
{
public function handle($request, \Closure $next)
{
// 添加中间件执行代码
return $next($request);
}
}
下面是一个后置行为的中间件
<?php
namespace app\http\middleware;
class After
{
public function handle($request, \Closure $next)
{
$response = $next($request);
// 添加中间件执行代码
return $response;
}
}
来个比较实际的例子,我们需要判断当前浏览器环境是在微信或支付宝
namespace app\http\middleware;
/**
* 访问环境检查,是否是微信或支付宝等
*/
class InAppCheck
{
public function handle($request, \Closure $next)
{
if (preg_match('~micromessenger~i', $request->header('user-agent'))) {
$request->InApp = 'WeChat';
} else if (preg_match('~alipay~i', $request->header('user-agent'))) {
$request->InApp = 'Alipay';
}
return $next($request);
}
}
然后在你的移动版的module
里添加一个middleware.php
文件
例如:/path/application/mobile/middleware.php
return [
app\http\middleware\InAppCheck::class,
];
然后在你的controller
中可以通过$this->request->InApp
获取相关的值
注册中间件
路由中间件
最常用的中间件注册方式是注册路由中间件
使用闭包定义中间件
你不一定要使用中间件类,在某些简单的场合你可以使用闭包定义中间件,但闭包函数必须返回Response
对象实例。
Route::group('hello', function(){
Route::rule('hello/:name','hello');
})->middleware(function($request,\Closure $next){
if ($request->param('name') == 'think') {
return redirect('index/think');
}
return $next($request);
});
全局中间件
你可以在应用目录下面定义middleware.php
文件,使用下面的方式:
<?php
return [
\app\http\middleware\Auth::class,
'Check',
'Hello',
];
中间件的注册应该使用完整的类名,如果没有指定命名空间则使用app\http\middleware
作为命名空间。
全局中间件的执行顺序就是定义顺序。可以在定义全局中间件的时候传入中间件参数,支持两种方式传入。
<?php
return [
[\app\http\middleware\Auth::class, 'admin'],
'Check',
'Hello:thinkphp',
];
上面的定义表示 给Auth
中间件传入admin
参数,给Hello
中间件传入thinkphp
参数。
模块中间件
V5.1.8+
版本开始,支持模块中间件定义,你可以直接在模块目录下面增加middleware.php
文件,定义方式和应用中间件定义一样,只是只会在该模块下面生效。
控制器中间件
V5.1.17+
版本开始,支持为控制器定义中间件。首先你的控制器需要继承系统的think\Controller
类,然后在控制器中定义middleware
属性,如果需要设置控制器中间的生效操作,可以如下定义:
<?php
namespace app\index\controller;
use think\Controller;
class Index extends Controller
{
protected $middleware = [
'Auth' => ['except' => ['hello'] ],
'Hello' => ['only' => ['hello'] ],
];
public function index()
{
return 'index';
}
public function hello()
{
return 'hello';
}
}
中间件向控制器传参
可以通过给请求对象赋值的方式传参给控制器(或者其它地方),例如
<?php
namespace app\http\middleware;
class Hello
{
public function handle($request, \Closure $next)
{
$request->hello = 'ThinkPHP';
return $next($request);
}
}
注意,传递的变量名称不要和
param
变量有冲突。
然后在控制器的方法里面可以直接使用
public function index(Request $request)
{
return $request->hello; // ThinkPHP
}
控制器
控制器文件通常放在application/module/controller
下面,类名和文件名保持大小写一致,并采用驼峰命名(首字母大写)。
定义
单一模块控制器
在应用配置文件app.php
中设置
// 是否支持多模块
'app_multi_module' => false,
输出转换
默认情况下,控制器的返回输出不会做任何的数据处理,但可以设置输出格式,并进行自动的数据转换处理,前提是控制器的输出数据必须采用return
的方式返回。
// 默认输出类型
'default_return_type' => 'json',
多级控制器
支持任意层次级别的控制器,并且支持路由。
控制器初始化
如果你的控制器类继承了系统控制器基类(\think\Controller
)的话,可以定义控制器初始化方法initialize
,该方法会在调用控制器的方法之前首先执行,如非必要,不建议直接修改控制器的架构函数。
前置操作
可以为某个或者某些操作指定前置执行的操作方法,设置 beforeActionList
属性可以指定某个方法为其他方法的前置操作,数组键名为需要调用的前置方法名,无值的话为当前控制器下所有方法的前置方法。
['except' => '方法名,方法名']
表示这些方法不使用前置方法,
['only' => '方法名,方法名']
表示只有这些方法使用前置方法。
<?php
namespace app\index\controller;
use think\Controller;
class Index extends Controller
{
protected $beforeActionList = [
'first',
'second' => ['except'=>'hello'],
'three' => ['only'=>'hello,data'],
];
protected function first()
{
echo 'first<br/>';
}
protected function second()
{
echo 'second<br/>';
}
protected function three()
{
echo 'three<br/>';
}
public function hello()
{
return 'hello';
}
public function data()
{
return 'data';
}
}
分层控制器
<?php
namespace app\index\event;
class Blog
{
public function insert()
{
return 'insert';
}
public function update($id)
{
return 'update:'.$id;
}
public function delete($id)
{
return 'delete:'.$id;
}
}
调用
$event = \think\facade\App::controller('Blog', 'event');
echo $event->update(5); // 输出 update:5
controller
助手函数直接实例化多层控制器
$event = controller('Blog', 'event');
echo $event->update(5); // 输出 update:5
直接调用分层控制器类的某个方法
echo \think\facade\App::action('Blog/update', ['id' => 5], 'event'); // 输出 update:5
助手函数action
echo action('Blog/update', ['id' => 5], 'event'); // 输出 update:5
控制器中间件
V5.1.17+
版本开始,支持为控制器定义中间件。首先你的控制器需要继承系统的think\Controller
类,然后在控制器中定义middleware
属性,例如:
<?php
namespace app\index\controller;
use think\Controller;
class Index extends Controller
{
protected $middleware = [
'Auth' => ['except' => ['hello'] ],
'Hello' => ['only' => ['hello'] ],
];
public function index()
{
return 'index';
}
public function hello()
{
return 'hello';
}
}
控制器传参
可以通过给请求对象赋值的方式传参给控制器(或者其它地方),例如
<?php
namespace app\http\middleware;
class Hello
{
public function handle($request, \Closure $next)
{
$request->hello = 'ThinkPHP';
return $next($request);
}
}
注意,传递的变量名称不要和
param
变量有冲突。
然后在控制器的方法里面可以直接使用
public function index(Request $request)
{
return $request->hello; // ThinkPHP
}
请求对象
构造方法注入
<?php
namespace app\index\controller;
use think\Request;
class Index
{
/**
* @var \think\Request Request实例
*/
protected $request;
/**
* 构造方法
* @param Request $request Request对象
* @access public
*/
public function __construct(Request $request)
{
$this->request = $request;
}
public function index()
{
return $this->request->param('name');
}
}
如果你继承了系统的控制器基类
think\Controller
的话,系统已经自动完成了请求对象的构造方法注入了,你可以直接使用$this->request
属性调用当前的请求对象。
<?php
namespace app\index\controller;
use think\Controller;
class Index extends Controller
{
public function index()
{
return $this->request->param('name');
}
}
操作方法注入
另外一种选择是在每个方法中使用依赖注入。
<?php
namespace app\index\controller;
use think\Controller;
use think\Request;
class Index extends Controller
{
public function index(Request $request)
{
return $request->param('name');
}
}
无论是否继承系统的控制器基类,都可以使用操作方法注入。
Facade调用
在没有使用依赖注入的场合,可以通过Facade
机制来静态调用请求对象的方法(注意use
引入的类库区别)。
<?php
namespace app\index\controller;
use think\Controller;
use think\facade\Request;
class Index extends Controller
{
public function index()
{
return Request::param('name');
}
}
该方法也同样适用于依赖注入无法使用的场合。
助手函数
为了简化调用,系统还提供了request
助手函数,可以在任何需要的时候直接调用当前请求对象。
<?php
namespace app\index\controller;
use think\Controller;
class Index extends Controller
{
public function index()
{
return request()->param('name');
}
}
无论是否继承系统的控制器基类,都可以使用
请求信息
方法 | 含义 |
---|---|
host | 当前访问域名或者IP |
scheme | 当前访问协议 |
port | 当前访问的端口 |
remotePort | 当前请求的REMOTE_PORT |
protocol | 当前请求的SERVER_PROTOCOL |
contentType | 当前请求的CONTENT_TYPE |
domain | 当前包含协议的域名 |
subDomain | 当前访问的子域名 |
panDomain | 当前访问的泛域名 |
rootDomain | 当前访问的根域名(V5.1.6+ ) |
url | 当前完整URL |
baseUrl | 当前URL(不含QUERY_STRING) |
query | 当前请求的QUERY_STRING参数 |
baseFile | 当前执行的文件 |
root | URL访问根地址 |
rootUrl | URL访问根目录 |
pathinfo | 当前请求URL的pathinfo信息(含URL后缀) |
path | 请求URL的pathinfo信息(不含URL后缀) |
ext | 当前URL的访问后缀 |
time | 获取当前请求的时间 |
type | 当前请求的资源类型 |
method | 当前请求类型 |
输入变量
检测变量是否设置
Request::has('id','get');
Request::has('name','post');
变量获取
变量类型方法('变量名/变量修饰符','默认值','过滤方法')
方法 | 描述 |
---|---|
param | 获取当前请求的变量 |
get | 获取 $_GET 变量 |
post | 获取 $_POST 变量 |
put | 获取 PUT 变量 |
delete | 获取 DELETE 变量 |
session | 获取 $_SESSION 变量 |
cookie | 获取 $_COOKIE 变量 |
request | 获取 $_REQUEST 变量 |
server | 获取 $_SERVER 变量 |
env | 获取 $_ENV 变量 |
route | 获取 路由(包括PATHINFO) 变量 |
file | 获取 $_FILES 变量 |
获取部分变量
// 只获取当前请求的id和name变量
Request::only('id,name');
或者使用数组方式
// 只获取当前请求的id和name变量
Request::only(['id','name']);
V5.1.3+
版本开始,only方法可以支持批量设置默认值,如下
// 设置默认值
Request::only(['id'=>0,'name'=>'']);
默认获取的是当前请求参数(PARAM
类型变量),如果需要获取其它类型的参数,可以在第二个参数传入,例如:
// 只获取GET请求的id和name变量
Request::only(['id','name'], 'get');
// 只获取POST请求的id和name变量
Request::only(['id','name'], 'post');
支持排除某些变量后获取,例如
// 排除id和name变量
Request::except('id,name');
或者使用数组方式
// 排除id和name变量
Request::except(['id','name']);
同样支持指定变量类型获取:
// 排除GET请求的id和name变量
Request::except(['id','name'], 'get');
// 排除POST请求的id和name变量
Request::except(['id','name'], 'post');
变量修饰符
Request::变量类型('变量名/修饰符');
修饰符 | 作用 |
---|---|
s | 强制转换为字符串类型 |
d | 强制转换为整型类型 |
b | 强制转换为布尔类型 |
a | 强制转换为数组类型 |
f | 强制转换为浮点类型 |
修改变量
V5.1.12+
版本开始,可以(通常是在中间件里面)设置请求变量的值。
<?php
namespace app\http\middleware;
class Check
{
public function handle($request, \Closure $next)
{
if ('think' == $request->name) {
$request->name = 'ThinkPHP';
}
return $next($request);
}
}
助手函数
为了简化使用,还可以使用系统提供的input
助手函数完成上述大部分功能。
判断变量是否定义
input('?get.id');
input('?post.name');
获取PARAM参数
input('param.name'); // 获取单个参数
input('param.'); // 获取全部参数
// 下面是等效的
input('name');
input('');
使用变量修饰符
input('get.id/d');
input('post.name/s');
input('post.ids/a');
获取请求类型
用途 | 方法 |
---|---|
获取当前请求类型 | method |
判断是否GET请求 | isGet |
判断是否POST请求 | isPost |
判断是否PUT请求 | isPut |
判断是否DELETE请求 | isDelete |
判断是否AJAX请求 | isAjax |
判断是否PJAX请求 | isPjax |
判断是否为JSON请求 | isJson(V5.1.38+ ) |
判断是否手机访问 | isMobile |
判断是否HEAD请求 | isHead |
判断是否PATCH请求 | isPatch |
判断是否OPTIONS请求 | isOptions |
判断是否为CLI执行 | isCli |
判断是否为CGI模式 | isCgi |
HTTP头信息
可以使用Request
对象的header
方法获取当前请求的HTTP
请求头信息,例如:
$info = Request::header();
echo $info['accept'];
echo $info['accept-encoding'];
echo $info['user-agent'];
也可以直接获取某个请求头信息,例如:
$agent = Request::header('user-agent');
HTTP
请求头信息的名称不区分大小写,并且_
会自动转换为-
,所以下面的写法都是等效的:
$agent = Request::header('user-agent');
$agent = Request::header('User-Agent');
$agent = Request::header('User_Agent');
$agent = Request::header('USER_AGENT');
响应
设置数据
不需要关注Response
对象本身,只需要在控制器的操作方法中返回数据即可。
Response
基类提供了data
方法用于设置响应数据。
response()->data($data);
json()->data($data);
如果要获取当前响应对象实例的实际输出数据可以使用
getContent
方法。
设置状态码
Response
基类提供了code
方法用于设置响应数据,但大部分情况一般我们是直接在调用助手函数的时候直接传入状态码,例如:
json($data,201);
view($data,401);
或者在后面链式调用code
方法是等效的:
json($data)->code(201);
除了
redirect
函数的默认返回状态码是302
之外,其它方法没有指定状态码都是返回200
状态码。
如果要获取当前响应对象实例的状态码的值,可以使用
getCode
方法。
设置头信息
可以使用Response
类的header
设置响应的头信息
json($data)->code(201)->header(['Cache-control' => 'no-cache,must-revalidate']);
header
方法支持两种方式设置,如果传入数组,则表示批量设置,如果传入两个参数,第一个参数表示头信息名,第二个参数表示头信息的值,例如:
// 单个设置
header('Cache-control', 'no-cache,must-revalidate');
// 批量设置
header([
'Cache-control' => 'no-cache,must-revalidate',
'Last-Modified' => gmdate('D, d M Y H:i:s') . ' GMT',
]);
除了header
方法之外,Response
基类还提供了常用头信息的快捷设置方法:
方法名 | 作用 |
---|---|
lastModified | 设置Last-Modified 头信息 |
expires | 设置Expires 头信息 |
eTag | 设置ETag 头信息 |
cacheControl | 设置Cache-control 头信息 |
contentType | 设置Content-Type 头信息 |
你可以使用getHeader
方法获取当前响应对象实例的头信息。