手册网

本手册由手册网整理收藏

手册网（www.shouce.ren）提供最全最新的开发手册

和教程，免费提供在线手册阅读和手册下载。


安装ThinkPHP

来源：ThinkPHP5.0完全开发手册

ThinkPHP5的环境要求如下：

  • PHP >= 5.4.0
  • PDO PHP Extension
  • MBstring PHP Extension
  • CURL PHP Extension

严格来说，ThinkPHP无需安装过程，这里所说的安装其实就是把ThinkPHP框架放入WEB运行环境（前提是你的WEB运行环境已经OK），可以通过下面几种方式获取和安装ThinkPHP。

一、官网下载安装

获取ThinkPHP的方式很多，官方网站（ http://thinkphp.cn）提供了稳定版本或者带扩展完整版本的下载。

官网的下载版本不一定是最新版本，GIT版本获取的才是保持更新的版本。

二、Composer安装

ThinkPHP5支持使用Composer安装，如果还没有安装 Composer，你可以按 Composer安装 中的方法安装。在 Linux 和 Mac OS X 中可以运行如下命令：

curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer

在 Windows 中，你需要下载并运行 Composer-Setup.exe。

如果遇到任何问题或者想更深入地学习 Composer，请参考 Composer 文档（英文），Composer 中文。

如果你已经安装有 Composer 请确保使用的是最新版本，你可以用 composer self-update 命令更新 Composer 为最新版本。

然后在命令行下面，切换到你的web根目录下面并执行下面的命令：

composer create-project topthink/think tp5  --prefer-dist

如果出现错误提示，请根据提示操作或者参考Composer中文文档。

如果国内访问composer的速度比较慢，可以参考这里的说明使用国内镜像

三、Git安装

如果你不太了解Composer或者觉得Composer太慢，也可以使用git版本库安装和更新，ThinkPHP5.0拆分为多个仓库，主要包括：

  • 应用项目：https://github.com/top-think/think
  • 核心框架：https://github.com/top-think/framework

如果你访问github速度比较慢，可以考虑下面两个国内GIT仓库：

[ 码云 ]

  • 应用项目：https://git.oschina.net/liu21st/thinkphp5.git
  • 核心框架：https://git.oschina.net/liu21st/framework.git

[ Coding ]

  • 应用项目：https://git.coding.net/liu21st/thinkphp5.git
  • 核心框架：https://git.coding.net/liu21st/framework.git

之所以设计为应用和核心仓库分离，是为了支持Composer单独更新核心框架。

首先克隆下载应用项目仓库

git clone https://github.com/top-think/think tp5

然后切换到tp5目录下面，再克隆核心框架仓库：

git clone https://github.com/top-think/framework thinkphp

两个仓库克隆完成后，就完成了ThinkPHP5.0的Git方式下载，如果需要更新核心框架的时候，只需要切换到thinkphp核心目录下面，然后执行：

git pull https://github.com/top-think/framework

如果不熟悉git命令行，可以使用任何一个GIT客户端进行操作，在此不再详细说明。

无论你采用什么方式获取的ThinkPHP框架，现在只需要做最后一步来验证是否正常运行。

在浏览器中输入地址：

http://localhost/tp5/public/

如果浏览器输出如图所示：

恭喜你，现在已经完成ThinkPHP5的安装！

如果你无法正常运行并显示ThinkPHP的欢迎页面，那么请检查下你的服务器环境：

  • PHP5.4以上版本（注意：PHP5.4dev版本和PHP6均不支持）
  • WEB服务器是否正常启动

手册网


开发规范

命名规范

ThinkPHP5遵循PSR-2命名规范和PSR-4自动加载规范，并且注意如下规范：

目录和文件

  • 目录使用小写+下划线；
  • 类库、函数文件统一以.php为后缀；
  • 类的文件名均以命名空间定义，并且命名空间的路径和类库文件所在路径一致；
  • 类文件采用驼峰法命名（首字母大写），其它文件采用小写+下划线命名；
  • 类名和类文件名保持一致，统一采用驼峰法命名（首字母大写）；

函数和类、属性命名

  • 类的命名采用驼峰法（首字母大写），例如 User、UserType，默认不需要添加后缀，例如UserController应该直接命名为User；
  • 函数的命名使用小写字母和下划线（小写字母开头）的方式，例如 get_client_ip；
  • 方法的命名使用驼峰法（首字母小写），例如 getUserName；
  • 属性的命名使用驼峰法（首字母小写），例如 tableName、instance；
  • 以双下划线“”打头的函数或方法作为魔法方法，例如 `call和__autoload`；

常量和配置

  • 常量以大写字母和下划线命名，例如 APP_PATH和 THINK_PATH；
  • 配置参数以小写字母和下划线命名，例如 url_route_on 和url_convert；

数据表和字段

  • 数据表和字段采用小写加下划线方式命名，并注意字段名不要以下划线开头，例如 think_user 表和 user_name字段，不建议使用驼峰和中文作为数据表字段命名。

应用类库命名空间规范

应用类库的根命名空间统一为app（可以设置app_namespace配置参数更改）；
例如：app\index\controller\Index和app\index\model\User。

请避免使用PHP保留字（保留字列表参见 http://php.net/manual/zh/reserved.keywords.php ）作为常量、类名和方法名，以及命名空间的命名，否则会造成系统错误。


目录结构

下载最新版框架后，解压缩到web目录下面，可以看到初始的目录结构如下：

project  应用部署目录
├─application           应用目录（可设置）
│  ├─common             公共模块目录（可更改）
│  ├─index              模块目录(可更改)
│  │  ├─config.php      模块配置文件
│  │  ├─common.php      模块函数文件
│  │  ├─controller      控制器目录
│  │  ├─model           模型目录
│  │  ├─view            视图目录
│  │  └─ ...            更多类库目录
│  ├─command.php        命令行工具配置文件
│  ├─common.php         应用公共（函数）文件
│  ├─config.php         应用（公共）配置文件
│  ├─database.php       数据库配置文件
│  ├─tags.php           应用行为扩展定义文件
│  └─route.php          路由配置文件
├─extend                扩展类库目录（可定义）
├─public                WEB 部署目录（对外访问目录）
│  ├─static             静态资源存放目录(css,js,image)
│  ├─index.php          应用入口文件
│  ├─router.php         快速测试文件
│  └─.htaccess          用于 apache 的重写
├─runtime               应用的运行时目录（可写，可设置）
├─vendor                第三方类库目录（Composer）
├─thinkphp              框架系统目录
│  ├─lang               语言包目录
│  ├─library            框架核心类库目录
│  │  ├─think           Think 类库包目录
│  │  └─traits          系统 Traits 目录
│  ├─tpl                系统模板目录
│  ├─.htaccess          用于 apache 的重写
│  ├─.travis.yml        CI 定义文件
│  ├─base.php           基础定义文件
│  ├─composer.json      composer 定义文件
│  ├─console.php        控制台入口文件
│  ├─convention.php     惯例配置文件
│  ├─helper.php         助手函数文件（可选）
│  ├─LICENSE.txt        授权说明文件
│  ├─phpunit.xml        单元测试配置文件
│  ├─README.md          README 文件
│  └─start.php          框架引导文件
├─build.php             自动生成定义文件（参考）
├─composer.json         composer 定义文件
├─LICENSE.txt           授权说明文件
├─README.md             README 文件
├─think                 命令行入口文件

5.0的部署建议是public目录作为web目录访问内容，其它都是web目录之外，当然，你必须要修改public/index.php中的相关路径。如果没法做到这点，请记得设置目录的访问权限或者添加目录列表的保护文件。

router.php用于php自带webserver支持，可用于快速测试
启动命令：php -S localhost:8888 router.php

5.0版本自带了一个完整的应用目录结构和默认的应用入口文件，开发人员可以在这个基础之上灵活调整。

上面的目录结构和名称是可以改变的，尤其是应用的目录结构，这取决于你的入口文件和配置参数。

由于ThinkPHP5.0的架构设计对模块的目录结构保留了很多的灵活性，尤其是对于用于存储的目录具有高度的定制化，因此上述的目录结构仅供建议参考。


架构总览

ThinkPHP5.0应用基于MVC（模型-视图-控制器）的方式来组织。

MVC是一个设计模式，它强制性的使应用程序的输入、处理和输出分开。使用MVC应用程序被分成三个核心部件：模型（M）、视图（V）、控制器（C），它们各自处理自己的任务。

5.0的URL访问受路由决定，如果关闭路由或者没有匹配路由的情况下，则是基于：

http://serverName/index.php（或者其它应用入口文件）/模块/控制器/操作/参数/值...

下面的一些概念有必要做下了解，可能在后面的内容中经常会被提及。

入口文件

用户请求的PHP文件，负责处理一个请求（注意，不一定是URL请求）的生命周期，最常见的入口文件就是index.php，有时候也会为了某些特殊的需求而增加新的入口文件，例如给后台模块单独设置的一个入口文件admin.php或者一个控制器程序入口think都属于入口文件。

应用

应用在ThinkPHP中是一个管理系统架构及生命周期的对象，由系统的 \think\App类完成，应用通常在入口文件中被调用和执行，具有相同的应用目录（APP_PATH）的应用我们认为是同一个应用，但一个应用可能存在多个入口文件。

应用具有自己独立的配置文件、公共（函数）文件。

模块

一个典型的应用是由多个模块组成的，这些模块通常都是应用目录下面的一个子目录，每个模块都有自己独立的配置文件、公共文件和类库文件。

5.0支持单一模块架构设计，如果你的应用下面只有一个模块，那么这个模块的子目录可以省略，并且在应用配置文件中修改：

'app_multi_module' => false,

控制器

每个模块拥有独立的MVC类库及配置文件，一个模块下面有多个控制器负责响应请求，而每个控制器其实就是一个独立的控制器类。

控制器主要负责请求的接收，并调用相关的模型处理，并最终通过视图输出。严格来说，控制器不应该过多的介入业务逻辑处理。

事实上，5.0中控制器是可以被跳过的，通过路由我们可以直接把请求调度到某个模型或者其他的类进行处理。

5.0的控制器类比较灵活，可以无需继承任何基础类库。

一个典型的Index控制器类如下：

namespace app\index\controller;

class Index
{
    public function index()
    {
        return 'hello,thinkphp!';
    }
}

操作

一个控制器包含多个操作（方法），操作方法是一个URL访问的最小单元。

下面是一个典型的Index控制器的操作方法定义，包含了两个操作方法：

namespace app\index\controller;

class Index
{
    public function index()
    {
        return 'index';
    }

    public function hello($name)
    {
        return 'Hello,'.$name;
    }
}

操作方法可以不使用任何参数，如果定义了一个非可选参数，则该参数必须通过用户请求传入，如果是URL请求，则通常是$_GET或者$_POST方式传入。

模型

模型类通常完成实际的业务逻辑和数据封装，并返回和格式无关的数据。

模型类并不一定要访问数据库，而且在5.0的架构设计中，只有进行实际的数据库查询操作的时候，才会进行数据库的连接，是真正的惰性连接。
ThinkPHP的模型层支持多层设计，你可以对模型层进行更细化的设计和分工，例如把模型层分为逻辑层/服务层/事件层等等。

视图

控制器调用模型类后返回的数据通过视图组装成不同格式的输出。视图根据不同的需求，来决定调用模板引擎进行内容解析后输出还是直接输出。

视图通常会有一系列的模板文件对应不同的控制器和操作方法，并且支持动态设置模板目录。

驱动

系统很多的组件都采用驱动式设计，从而可以更灵活的扩展，驱动类的位置默认是放入核心类库目录下面，也可以重新定义驱动类库的命名空间而改变驱动的文件位置。

行为

行为（Behavior）是在预先定义好的一个应用位置执行的一些操作。类似于AOP编程中的“切面”的概念，给某一个切面绑定相关行为就成了一种类AOP编程的思想。所以，行为通常是和某个位置相关，行为的执行时间依赖于绑定到了哪个位置上。

要执行行为，首先要在应用程序中进行行为侦听，例如：

// 在app_init位置侦听行为
\think\Hook::listen('app_init');

然后对某个位置进行行为绑定：

// 绑定行为到app_init位置
\think\Hook::add('app_init','\app\index\behavior\Test');

一个位置上如果绑定了多个行为的，按照绑定的顺序依次执行，除非遇到中断。

命名空间

ThinkPHP5采用了PHP的命名空间进行类库文件的设计和规划，并且符合PSR-4的自动加载规范。


生命周期

本篇内容我们对ThinkPHP5.0的应用请求的生命周期做大致的介绍，以便于开发者了解整个执行流程。

1、入口文件

用户发起的请求都会经过应用的入口文件，通常是 public/index.php文件。当然，你也可以更改或者增加新的入口文件。

通常入口文件的代码都比较简单，一个普通的入口文件代码如下：

// 应用入口文件

// 定义项目路径
define('APP_PATH', __DIR__ . '/../application/');
// 加载框架引导文件
require __DIR__ . '/../thinkphp/start.php';

一般入口文件以定义一些常量为主，支持的常量请参考后续的内容或者附录部分。

通常，我们不建议在应用入口文件中加入过多的代码，尤其是和业务逻辑相关的代码。

2、引导文件

接下来就是执行框架的引导文件，start.php文件就是系统默认的一个引导文件。在引导文件中，会依次执行下面操作：

  • 加载系统常量定义；
  • 加载环境变量定义文件；
  • 注册自动加载机制；
  • 注册错误和异常处理机制；
  • 加载惯例配置文件；
  • 执行应用；

start.php引导文件首先会调用base.php基础引导文件，某些特殊需求下面可能直接在入口文件中引入基础引导文件。

如果在你的应用入口文件中更改了默认的引导文件，则上述执行流程可能会跟随发生变化。

3、注册自动加载

系统会调用Loader::register()方法注册自动加载，在这一步完成后，所有符合规范的类库（包括Composer依赖加载的第三方类库）都将自动加载。

系统的自动加载由下面主要部分组成：

  1. 注册系统的自动加载方法 \think\Loader::autoload
  2. 注册系统命名空间定义
  3. 加载类库映射文件（如果存在）
  4. 如果存在Composer安装，则注册Composer自动加载
  5. 注册extend扩展目录

一个类库的自动加载检测顺序为：

  1. 是否定义类库映射；
  2. PSR-4自动加载检测；
  3. PSR-0自动加载检测；

可以看到，定义类库映射的方式是最高效的。

4、注册错误和异常机制

执行Error::register()注册错误和异常处理机制。

由三部分组成：

  • 应用关闭方法：think\Error::appShutdown
  • 错误处理方法：think\Error::appError
  • 异常处理方法：think\Error::appException

注册应用关闭方法是为了便于拦截一些系统错误。

在整个应用请求的生命周期过程中，如果抛出了异常或者严重错误，均会导致应用提前结束，并响应输出异常和错误信息。

5、应用初始化

执行应用的第一步操作就是对应用进行初始化，包括：

  • 加载应用（公共）配置；
  • 加载扩展配置文件（由extra_config_list定义）；
  • 加载应用状态配置；
  • 加载别名定义；
  • 加载行为定义；
  • 加载公共（函数）文件；
  • 注册应用命名空间；
  • 加载扩展函数文件（由extra_file_list定义）；
  • 设置默认时区；
  • 加载系统语言包；

6、URL访问检测

应用初始化完成后，就会进行URL的访问检测，包括PATH_INFO检测和URL后缀检测。

5.0的URL访问必须是PATH_INFO方式（包括兼容方式）的URL地址，例如：

http://serverName/index.php/index/index/hello/val/value

所以，如果你的环境只能支持普通方式的URL参数访问，那么必须使用

http://serverName/index.php?s=/index/index/hello&val=value

如果是命令行下面访问入口文件的话，则通过

$php index.php index/index/hello/val/value...

获取到正常的$_SERVER['PATH_INFO']参数后才能继续。

7、路由检测

如果开启了url_route_on参数的话，会首先进行URL的路由检测。

如果一旦检测到匹配的路由，根据定义的路由地址会注册到相应的URL调度。
5.0的路由地址支持如下方式：

  • 路由到模块/控制器/操作；
  • 路由到外部重定向地址；
  • 路由到控制器方法；
  • 路由到闭包函数；
  • 路由到类的方法；

路由地址可能会受域名绑定的影响。

如果关闭路由或者路由检测无效则进行默认的模块/控制器/操作的分析识别。

如果在应用初始化的时候指定了应用调度方式，那么路由检测是可选的。
可以使用 \think\App::dispatch() 进行应用调度，例如：
App::dispatch(['type' => 'module', 'module' => 'index/index']);

8、分发请求

在完成了URL检测和路由检测之后，路由器会分发请求到对应的路由地址，这也是应用请求的生命周期中最重要的一个环节。

在这一步骤中，完成应用的业务逻辑及数据返回。

建议统一使用return返回数据，而不是echo输出，如非必要，请不要使用exit或者die中断执行。

直接echo输出的数据将无法进行自动转换响应输出的便利。

下面是系统支持的分发请求机制，可以根据情况选择：

模块/控制器/操作

这是默认的分发请求机制，系统会根据URL或者路由地址来判断当前请求的模块、控制器和操作名，并自动调用相应的访问控制器类，执行操作对应的方法。
该机制下面，首先会判断当前模块，并进行模块的初始化操作（和应用的初始化操作类似），模块的配置参数会覆盖应用的尚未生效的配置参数。

支持模块映射、URL参数绑定到方法，以及操作绑定到类等一些功能。

控制器方法

和前一种方式类似，只是无需判断模块、控制器和操作，直接分发请求到一个指定的控制器类的方法，因此没有进行模块的初始化操作。

外部重定向

可以直接分发请求到一个外部的重定向地址，支持指定重定向代码，默认为301重定向。

闭包函数

路由地址定义的时候可以直接采用闭包函数，完成一些相对简单的逻辑操作和输出。

类的方法

除了以上方式外，还支持分发请求到类的方法，包括：
静态方法： 'blog/:id'=>'\org\util\Blog::read'
类的方法：'blog/:id'=>'\app\index\controller\Blog@read'

9、响应输出

控制器的所有操作方法都是return返回而不是直接输出，系统会调用Response::send方法将最终的应用返回的数据输出到页面或者客户端，并自动转换成default_return_type参数配置的格式。所以，应用执行的数据输出只需要返回一个正常的PHP数据即可。

10、应用结束

事实上，在应用的数据响应输出之后，应用并没真正的结束，系统会在应用输出或者中断后进行日志保存写入操作。

系统的日志包括用户调试输出的和系统自动生成的日志，统一会在应用结束的时候进行写入操作。

而日志的写入操作受日志初始化的影响。


入口文件

ThinkPHP采用单一入口模式进行项目部署和访问，无论完成什么功能，一个应用都有一个统一（但不一定是唯一）的入口。

应该说，所有应用都是从入口文件开始的，并且不同应用的入口文件是类似的。

入口文件定义

入口文件主要完成：

  • 定义框架路径、项目路径（可选）
  • 定义系统相关常量（可选）
  • 载入框架入口文件（必须）

5.0默认的应用入口文件位于public/index.php，内容如下：

// 定义应用目录
define('APP_PATH', __DIR__ . '/../application/');
// 加载框架引导文件
require __DIR__ . '/../thinkphp/start.php';

入口文件位置的设计是为了让应用部署更安全，public目录为web可访问目录，其他的文件都可以放到非WEB访问目录下面。

修改入口文件位置请查看章节<部署-虚拟主机环境>

入口文件中还可以定义一些系统变量，用于相关的绑定操作（通常用于多个入口的情况），这个会在后面涉及，暂且不提。

给APP_PATH定义绝对路径会提高系统的加载效率。

在有些情况下，你可能需要加载框架的基础引导文件base.php，该引导文件和start.php的区别是不会主动执行应用，而是需要自己进行应用执行，下面是一个例子：

// 定义应用目录
define('APP_PATH', __DIR__ . '/../application/');
// 加载框架基础引导文件
require __DIR__ . '/../thinkphp/base.php';
// 添加额外的代码
// ...
// 执行应用
\think\App::run()->send();


URL访问

URL设计

ThinkPHP5.0在没有启用路由的情况下典型的URL访问规则是：

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

支持切换到命令行访问，如果切换到命令行模式下面的访问规则是：

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

可以看到，无论是URL访问还是命令行访问，都采用PATH_INFO访问地址，其中PATH_INFO的分隔符是可以设置的。

注意：5.0取消了URL模式的概念，并且普通模式的URL访问不再支持，但参数可以支持普通方式传值，例如：

>php.exe index.php(或者其它应用入口文件） 模块/控制器/操作?参数名=参数值&...

如果不支持PATHINFO的服务器可以使用兼容模式访问如下：

http://serverName/index.php（或者其它应用入口文件）?s=/模块/控制器/操作/[参数名/参数值...]

必要的时候，我们可以通过某种方式，省略URL里面的模块和控制器。

URL大小写

默认情况下，URL是不区分大小写的，也就是说 URL里面的模块/控制器/操作名会自动转换为小写，控制器在最后调用的时候会转换为驼峰法处理。

例如：

http://localhost/index.php/Index/Blog/read
// 和下面的访问是等效的
http://localhost/index.php/index/blog/read

如果访问下面的地址

http://localhost/index.php/Index/BlogTest/read
// 和下面的访问是等效的
http://localhost/index.php/index/blogtest/read

在这种URL不区分大小写情况下，如果要访问驼峰法的控制器类，则需要使用：

http://localhost/index.php/Index/blog_test/read

模块名和操作名会直接转换为小写处理。

如果希望URL访问严格区分大小写，可以在应用配置文件中设置：

// 关闭URL中控制器和操作名的自动转换
'url_convert'    =>  false,

一旦关闭自动转换，URL地址中的控制器名就变成大小写敏感了，例如前面的访问地址就要写成：

http://localhost/index.php/Index/BlogTest/read

但是下面的URL访问依然是有效的：

http://localhost/index.php/Index/blog_test/read

下面的URL访问则无效：

http://localhost/index.php/Index/blogtest/read

需要注意：路由规则中定义的路由地址是按照控制器名的实际名称定义（区分大小写）。

隐藏入口文件

在ThinkPHP5.0中，出于优化的URL访问原则，还支持通过URL重写隐藏入口文件，下面以Apache为例说明隐藏应用入口文件index.php的设置。

下面是Apache的配置过程，可以参考下：
1、httpd.conf配置文件中加载了mod_rewrite.so模块
2、AllowOverride None 将None改为 All
3、在应用入口文件同级目录添加.htaccess文件，内容如下：

<IfModule mod_rewrite.c>
Options +FollowSymlinks -Multiviews
RewriteEngine on

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L]
</IfModule>

更多其它环境的隐藏入口文件参考后面的 URL重写


模块设计

5.0版本对模块的功能做了灵活设计，默认采用多模块的架构，并且支持单一模块设计，所有模块的命名空间均以app作为根命名空间（可配置更改）。

目录结构

标准的应用和模块目录结构如下：

├─application           应用目录（可设置）
│  ├─common             公共模块目录（可选）
│  ├─common.php         公共函数文件
│  ├─route.php          路由配置文件
│  ├─database.php       数据库配置文件
│  ├─config.php         应用配置文件
│  ├─module1            模块1目录
│  │  ├─config.php      模块配置文件
│  │  ├─common.php      模块函数文件
│  │  ├─controller      控制器目录
│  │  ├─model           模型目录（可选）
│  │  ├─view            视图目录（可选）
│  │  └─ ...            更多类库目录
│  │
│  ├─module2            模块2目录
│  │  ├─config.php      模块配置文件
│  │  ├─common.php      模块函数文件
│  │  ├─controller      控制器目录
│  │  ├─model           模型目录（可选）
│  │  ├─view            视图目录（可选）
│  │  └─ ...            更多类库目录

遵循ThinkPHP5.0的命名规范，模块目录全部采用小写和下划线命名。

模块名称请避免使用PHP保留关键字（保留字列表参见 http://php.net/manual/zh/reserved.keywords.php ），否则会造成系统错误。

其中common模块是一个特殊的模块，默认是禁止直接访问的，一般用于放置一些公共的类库用于其他模块的继承。

模块类库

一个模块下面的类库文件的命名空间统一以app\模块名开头，例如：

// index模块的Index控制器类
app\index\controller\Index
// index模块的User模型类
app\index\model\User

其中app可以通过定义的方式更改，例如我们在应用配置文件中修改：

'app_namespace' => 'application',

那么，index模块的类库命名空间则变成：

// index模块的Index控制器类
application\index\controller\Index
// index模块的User模型类
application\index\model\User

更多的关于类库和命名空间的关系可以参考下一章节：命名空间。

模块和控制器隐藏

由于默认是采用多模块的支持，所以多个模块的情况下必须在URL地址中标识当前模块，如果只有一个模块的话，可以进行模块绑定，方法是应用的入口文件中添加如下代码：

// 绑定当前访问到index模块
define('BIND_MODULE','index');

绑定后，我们的URL访问地址则变成：

http://serverName/index.php/控制器/操作/[参数名/参数值...]

访问的模块是index模块。

如果你的应用比较简单，模块和控制器都只有一个，那么可以在应用公共文件中绑定模块和控制器，如下：

// 绑定当前访问到index模块的index控制器
define('BIND_MODULE','index/index');

设置后，我们的URL访问地址则变成：

http://serverName/index.php/操作/[参数名/参数值...]

访问的模块是index模块，控制器是Index控制器。

单一模块

如果你的应用比较简单，只有唯一一个模块，那么可以进一步简化成使用单一模块结构，方法如下：

首先在应用配置文件中定义：

// 关闭多模块设计
'app_multi_module'  =>  false,

然后，调整应用目录的结构为如下：

├─application        应用目录（可设置）
│  ├─controller      控制器目录
│  ├─model           模型目录
│  ├─view            视图目录
│  ├─ ...            更多类库目录
│  ├─common.php      函数文件
│  ├─route.php       路由配置文件
│  ├─database.php    数据库配置文件
│  └─config.php      配置文件

URL访问地址变成

http://serverName/index.php（或者其它应用入口）/控制器/操作/[参数名/参数值...]

同时，单一模块设计下的应用类库的命名空间也有所调整，例如：

原来的

app\index\controller\Index
app\index\model\User

变成

app\controller\Index
app\model\User

更多的URL简化和定制还可以通过URL路由功能实现。


命名空间

命名空间

ThinkPHP5采用命名空间方式定义和自动加载类库文件，有效的解决了多模块和Composer类库之间的命名空间冲突问题，并且实现了更加高效的类库自动加载机制。

如果不清楚命名空间的基本概念，可以参考PHP手册：PHP命名空间

特别注意的是，如果你需要调用PHP内置的类库，或者第三方没有使用命名空间的类库，记得在实例化类库的时候加上 \，例如：

// 错误的用法
$class = new stdClass();
$xml  =  new SimpleXmlElement($xmlstr);
// 正确的用法
$class = new \stdClass();
$xml  =  new \SimpleXmlElement($xmlstr);

在ThinkPHP5.0中，只需要给类库正确定义所在的命名空间，并且命名空间的路径与类库文件的目录一致，那么就可以实现类的自动加载，从而实现真正的惰性加载。

例如，\think\cache\driver\File类的定义为：

namespace think\cache\driver;

class File
{
}

如果我们实例化该类的话，应该是：

$class = new \think\cache\driver\File();

系统会自动加载该类对应路径的类文件，其所在的路径是 thinkphp/library/think/cache/driver/File.php。

5.0默认的目录规范是小写，类文件命名是驼峰法，并且首字母大写。

原则上，可以支持驼峰法命名的目录，只要命名空间定义和目录一致即可，例如：

我们实例化

$class = new \Think\Cache\Driver\File();

系统则会自动加载 thinkphp/library/Think/Cache/Driver/File.php文件。

根命名空间（类库包）

根命名空间是一个关键的概念，以上面的\think\cache\driver\File类为例，think就是一个根命名空间，其对应的初始命名空间目录就是系统的类库目录（thinkphp/library/think），我们可以简单的理解一个根命名空间对应了一个类库包。

系统内置的几个根命名空间（类库包）如下： | 名称 | 描述 | 类库目录
think | 系统核心类库 | thinkphp/library/think
traits | 系统Trait类库 | thinkphp/library/traits
app | 应用类库 | application

如果需要增加新的根命名空间，有两种方式：注册新的根命名空间或者放入EXTEND_PATH目录（自动注册）。

请注意本手册中的示例代码为了简洁，如无指定类库的命名空间的话，都表示指的是think命名空间，例如下面的代码：

Route::get('hello','index/hello');

请自行添加 use think\Route或者使用

\think\Route::get('hello','index/hello');

自动注册

我们只需要把自己的类库包目录放入EXTEND_PATH目录（默认为extend，可配置），就可以自动注册对应的命名空间，例如：

我们在extend目录下面新增一个my目录，然后定义一个\my\Test类（ 类文件位于extend/my/Test.php）如下：

namespace my;

class Test
{
    public function sayHello()
    {
        echo 'hello';
    }
}

我们就可以直接实例化和调用：

$Test = new \my\Test();
$Test->sayHello();

如果我们在应用入口文件中重新定义了EXTEND_PATH常量的话，还可以改变\my\Test类文件的位置，例如：

define('EXTEND_PATH','../vendor/');

那么\my\Test类文件的位置就变成了/vendor/my/File.php。

手动注册

也可以通过手动注册的方式注册新的根命名空间，例如：

在应用入口文件中添加下面的代码：

\think\Loader::addNamespace('my','../application/extend/my/');

如果要同时注册多个根命名空间，可以使用：

\think\Loader::addNamespace([
    'my'  => '../application/extend/my/',
    'org' => '../application/extend/org/',
]);

也可以直接在应用的配置文件中添加配置，系统会在应用执行的时候自动注册。

'root_namespace' => [
    'my'  => '../application/extend/my/',
    'org' => '../application/extend/org/',
]

应用类库包

为了避免和Composer自动加载的类库存在冲突 ，应用类库的命名空间的根都统一以app命名，例如：

namespace app\index\model;

class User extends \think\Model
{
}

其类文件位于 application/index/model/User.php。

namespace app\admin\event;

class User
{
}

其类文件位于 application/admin/event/User.php。

如果觉得app根命名空间不合适或者有冲突，可以在应用配置文件中修改：

'app_namespace' =>  'application',

定义后，应用类库的命名空间改为：

namespace application\index\model;

class User extends \think\Model
{
}

命名空间别名

框架允许给命名空间定义别名，例如：

namespace app\index\model;

use think\Model;

class User extends Model
{
}

原来在控制器里面调用方式为：

namespace app\index\controller;

use app\index\model\User;

class Index
{
    public function index()
    {
        $user = new User();
    }
}

如果我们在应用公共文件中注册命名空间别名如下：

\think\Loader::addNamespaceAlias('model','app\index\model');

那么，上面的控制器代码就可以更改为：

namespace app\index\controller;

use model\User;

class Index
{
    public function index()
    {
        $user = new User();
    }
}

本手册后续的章节，均建立在你已经了解PHP命名空间的基础之上，如果不掌握请自行补充PHP基础，否则你在后续的文档和ThinkPHP5.0的学习过程中，对命名空间的缺乏理解会成为你最大的学习障碍。

手册网


自动加载

概述

ThinkPHP5.0真正实现了按需加载，所有类库采用自动加载机制，并且支持类库映射和composer类库的自动加载。

自动加载的实现由think\Loader类库完成，自动加载规范符合PHP的PSR-4。

自动加载

由于新版ThinkPHP完全采用了命名空间的特性，因此只需要给类库正确定义所在的命名空间，而命名空间的路径与类库文件的目录一致，那么就可以实现类的自动加载。

类库的自动加载检测顺序如下：

1、类库映射检测；
2、PSR-4自动加载检测；
3、PSR-0自动加载检测；

系统会按顺序检测，一旦检测生效的话，就会自动载入对应的类库文件。

类库映射

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

Loader::addClassMap('think\Log',LIB_PATH.'think\Log.php');
Loader::addClassMap('org\util\Array',LIB_PATH.'org\util\Array.php');

也可以利用addClassMap方法批量导入类库映射定义，例如：

$map = [
    'think\Log'     =>  LIB_PATH.'think\Log.php',
    'org\util\array'=>  LIB_PATH.'org\util\Array.php'
];
Loader::addClassMap($map);

虽然通过类库映射的方式注册的类可以不强制要求对应命名空间目录，但是仍然建议遵循PSR-4规范定义类库和目录。

类库导入

如果你不需要系统的自动加载功能，又或者没有使用命名空间的话，那么也可以使用think\Loader类的import方法手动加载类库文件，例如：

Loader::import('org.util.array');
Loader::import('@.util.upload');

示例

// 引入 extend/qrcode.php
Loader::import('qrcode', EXTEND_PATH);
// 助手函数
import('qrcode', EXTEND_PATH);

// 引入 extend/wechat-sdk/wechat.class.php
Loader::import('wechat-sdk.wechat', EXTEND_PATH, '.class.php');
// 助手函数
import('wechat-sdk.wechat', EXTEND_PATH, '.class.php');
类库导入也采用类似命名空间的概念（但不需要实际的命名空间支持），支持的“根命名空间”包括： | 目录 | 说明
behavior | 系统行为类库
think | 核心基类库
traits | 系统Traits类库
app | 应用类库
@ | 表示当前模块类库包

如果完全遵从系统的命名空间定义的话，一般来说无需手动加载类库文件，直接实例化即可。

`ThinkPHP5.0`不推荐使用`import`方法。

## Composer自动加载
5.0版本支持`Composer`安装的类库的自动加载，你可以直接按照`Composer`依赖库中的命名空间直接调用。


Traits引入

ThinkPHP 5.0开始采用trait功能（PHP5.4+）来作为一种扩展机制，可以方便的实现一个类库的多继承问题。

trait是一种为类似 PHP 的单继承语言而准备的代码复用机制。trait为了减少单继承语言的限制，使开发人员能够自由地在不同层次结构内独立的类中复用方法集。trait和类组合的语义是定义了一种方式来减少复杂性，避免传统多继承和混入类（Mixin）相关的典型问题。

但由于PHP5.4版本不支持trait的自动加载，因此如果是PHP5.4版本，必须手动导入trait类库，系统提供了一个助手函数load_trait，用于自动加载trait类库，例如，可以这样正确引入trait类库。

namespace app\index\controller;

load_trait('controller/Jump');  // 引入traits\controller\Jump

class index
{
     use \traits\controller\Jump;

     public function index()
     {
         $this->assign('name','value');
         $this->show('index');
     }
}

如果你的PHP版本大于5.5的话，则可以省略load_trait函数引入trait。

namespace app\index\controller;

class index
{
     use \traits\controller\Jump;

     public function index()
     {

     }
}

可以支持同时引入多个trait类库，例如：

namespace app\index\controller;

load_trait('controller/Other');
load_trait('controller/Jump');

class index
{
     use \traits\controller\Other;
     use \traits\controller\Jump;

     public function index()
     {

     }
}

或者使用

namespace app\index\controller;

load_trait('controller/Other');
load_trait('controller/Jump');

class index
{
     use \traits\controller\Other,\traits\controller\Jump;

     public function index()
     {

     }
}

系统提供了一些封装好的trait类库，主要是用于控制器和模型类的扩展。这些系统内置的trait类库的根命名空间采用traits而不是trait，是为了避免和系统的关键字冲突。

trait方式引入的类库需要注意优先级，从基类继承的成员将被 trait 插入的成员所覆盖。优先顺序是来自当前类的成员覆盖了 trait的方法，而 trait则覆盖了被继承的方法。

trait类中不支持定义类的常量，在trait中定义的属性将不能在当前类中或者继承的类中重新定义。

冲突的解决

我们可以在一个类库中引入多个trait类库，如果两个trait都定义了一个同名的方法，如果没有明确解决冲突将会产生一个致命错误。

为了解决多个trait在同一个类中的命名冲突，需要使用 insteadof操作符来明确指定使用冲突方法中的哪一个。

以上方式仅允许排除掉其它方法，as操作符可以将其中一个冲突的方法以另一个名称来引入。

更多的关于trait内容可以参考PHP官方手册。


API友好

新版ThinkPHP针对API开发做了很多的优化，并且不依赖原来的API模式扩展。

数据输出

新版的控制器输出采用Response类统一处理，而不是直接在控制器中进行输出，通过设置default_return_type或者动态设置不同类型的Response输出就可以自动进行数据转换处理，一般来说，你只需要在控制器中返回字符串或者数组即可，例如如果我们配置：

'default_return_type'=>'json'

那么下面的控制器方法返回值会自动转换为json格式并返回。

namespace app\index\controller;

class Index
{
    public function index()
    {
        $data = ['name'=>'thinkphp','url'=>'thinkphp.cn'];
        return ['data'=>$data,'code'=>1,'message'=>'操作完成'];
    }
}

访问该请求URL地址后，最终可以在浏览器中看到输出结果如下：

{"data":{"name":"thinkphp","url":"thinkphp.cn"},"code":1,"message":"\u64cd\u4f5c\u5b8c\u6210"}

如果你需要返回其他的数据格式的话，控制器本身的代码无需做任何改变。

支持明确指定输出类型的方式输出，例如下面指定JSON数据输出：

namespace app\index\controller;

class Index
{
    public function index()
    {
        $data = ['name'=>'thinkphp','url'=>'thinkphp.cn'];
        // 指定json数据输出
        return json(['data'=>$data,'code'=>1,'message'=>'操作完成']);
    }
}

或者指定输出XML类型数据：

namespace app\index\controller;

class Index
{
    public function index()
    {
        $data = ['name'=>'thinkphp','url'=>'thinkphp.cn'];
        // 指定xml数据输出
        return xml(['data'=>$data,'code'=>1,'message'=>'操作完成']);
    }
}

核心支持的数据类型包括view、xml、json和jsonp，其他类型的需要自己扩展。

错误调试

由于API开发不方便在客户端进行开发调试，但ThinkPHP5的Trace调试功能支持Socket在内的方式，可以实现远程的开发调试。

设置方式：

'app_trace' => true,
'trace'     => [
    'type'             => 'socket',
    // socket服务器
    'host'             => 'slog.thinkphp.cn',
],

然后安装chrome浏览器插件后即可进行远程调试，详细参考调试部分。


配置目录

配置目录

系统默认的配置文件目录就是应用目录（APP_PATH），也就是默认的application下面，并分为应用配置（整个应用有效）和模块配置（仅针对该模块有效）。

├─application         应用目录
│  ├─config.php       应用配置文件
│  ├─database.php     数据库配置文件
│  ├─route.php        路由配置文件
│  ├─index            index模块配置文件目录
│  │  ├─config.php    index模块配置文件
│  │  └─database.php  index模块数据库配置文件

如果不希望配置文件放到应用目录下面，可以在入口文件中定义独立的配置目录，添加CONF_PATH常量定义即可，例如：

// 定义配置文件目录和应用目录同级
define('CONF_PATH', __DIR__.'/../config/');

配置目录下面的结构类似如下：

├─application         应用目录
├─config              配置目录
│  ├─config.php       应用配置文件
│  ├─database.php     数据库配置文件
│  ├─route.php        路由配置文件
│  ├─index            index模块配置文件目录
│  │  ├─config.php    index模块配置文件
│  │  └─database.php  index模块数据库配置文件

扩展配置目录（V5.0.1）

5.0.1开始增加了扩展配置目录的概念，在应用配置目录或者模块配置目录下面增加extra子目录，下面的配置文件都会自动加载，无需任何配置。

如果你定义了CONF_PATH常量为config目录为例，扩展配置目录如下：

├─application         应用目录
├─config              配置目录
│  ├─config.php       应用配置文件
│  ├─database.php     数据库配置文件
│  ├─route.php        路由配置文件
│  ├─extra            应用扩展配置目录
│  ├─index            index模块配置文件目录
│  │  ├─extra         index模块扩展配置目录
│  │  ├─config.php    index模块配置文件
│  │  └─database.php  index模块数据库配置文件

扩展配置文件的文件名（不含后缀）就是配置参数名，并且会和应用配置文件中的参数进行合并。


配置格式

ThinkPHP支持多种格式的配置格式，但最终都是解析为PHP数组的方式。

PHP数组定义

返回PHP数组的方式是默认的配置定义格式，例如：

//项目配置文件
return [
    // 默认模块名
    'default_module'        => 'index',
    // 默认控制器名
    'default_controller'    => 'Index',
    // 默认操作名
    'default_action'        => 'index',
    //更多配置参数
    //...
];

配置参数名不区分大小写（因为无论大小写定义都会转换成小写），新版的建议是使用小写定义配置参数的规范。

还可以在配置文件中可以使用二维数组来配置更多的信息，例如：

//项目配置文件
return [
    'cache'                 => [
        'type'   => 'File',
        'path'   => CACHE_PATH,
        'prefix' => '',
        'expire' => 0,
    ],
];

其他配置格式支持

默认方式为PHP数组方式定义配置文件，你可以在入口文件定义CONF_EXT常量来更改为其它的配置类型：

// 更改配置格式为ini格式
define('CONF_EXT', '.ini');

配置后，会自动解析支持的配置类型，包括.ini、.xml、.json 和 .php 在内的格式支持。

5.0.2版本开始支持yaml配置格式支持

ini格式配置示例：

default_module=Index ;默认模块
default_controller=index ;默认控制器
default_action=index ;默认操作

xml格式配置示例：

<config>
<default_module>Index</default_module>
<default_controller>index</default_controller>
<default_action>index</default_action>
</config>

json格式配置示例：

{
"default_module":"Index",
"default_controller":"index",
"default_action":"index"
}

二级配置

配置参数支持二级，例如，下面是一个二级配置的设置和读取示例：

$config = [
    'user'  =>  [
        'type'  =>  1,
        'name'  =>  'thinkphp',
    ],
    'db'    =>  [
        'type'      =>  'mysql',
        'user'      =>  'root',
        'password'  =>  '',
    ],
];
// 设置配置参数
Config::set($config);
// 读取二级配置参数
echo Config::get('user.type');
// 或者使用助手函数
echo config('user.type');

系统不支持二级以上的配置参数读取，需要手动分步骤读取。
有作用域的情况下，仍然支持二级配置的操作。

如果采用其他格式的配置文件的话，二级配置定义方式如下（以ini和xml为例）：

[user]
type=1
name=thinkphp
 [db]
type=mysql
user=rot
password=''

标准的xml格式文件定义：

<config>
<user>
<type>1</type>
<name>thinkphp</name>
</user>
<db>
<type>mysql</type>
<user>root</user>
<password></password>
</db>
</config>

set方法也支持二级配置，例如：

Config::set([
    'type'      =>  'file',
    'prefix'    =>  'think'
],'cache');


配置加载

在ThinkPHP中，一般来说应用的配置文件是自动加载的，加载的顺序是：

惯例配置->应用配置->扩展配置->场景配置->模块配置->动态配置

以上是配置文件的加载顺序，因为后面的配置会覆盖之前的同名配置（在没有生效的前提下），所以配置的优先顺序从右到左。

下面说明下不同的配置文件的区别和位置：

惯例配置

惯例重于配置是系统遵循的一个重要思想，框架内置有一个惯例配置文件（位于thinkphp/convention.php），按照大多数的使用对常用参数进行了默认配置。所以，对于应用的配置文件，往往只需要配置和惯例配置不同的或者新增的配置参数，如果你完全采用默认配置，甚至可以不需要定义任何配置文件。

建议仔细阅读下系统的惯例配置文件中的相关配置参数，了解下系统默认的配置参数。

应用配置

应用配置文件是应用初始化的时候首先加载的公共配置文件，默认位于application/config.php。

扩展配置

扩展配置文件是由extra_config_list配置参数定义的额外的配置文件，默认会加载database和validate两个扩展配置文件。

V5.0.1开始，取消了该配置参数，扩展配置文件直接放入application/extra目录会自动加载。

场景配置

每个应用都可以在不同的情况下设置自己的状态（或者称之为应用场景），并且加载不同的配置文件。

举个例子，你需要在公司和家里分别设置不同的数据库测试环境。那么可以这样处理，在公司环境中，我们在应用配置文件中配置：

'app_status'=>'office'

那么就会自动加载该状态对应的配置文件（默认位于application/office.php）。

如果我们回家后，我们修改定义为：

'app_status'=>'home'

那么就会自动加载该状态对应的配置文件（位于application/home.php）。

状态配置文件是可选的

模块配置

每个模块会自动加载自己的配置文件（位于application/当前模块名/config.php）。

模块还可以支持独立的状态配置文件，命名规范为：application/当前模块名/应用状态.php。

模块配置文件是可选的

如果你的应用的配置文件比较大，想分成几个单独的配置文件或者需要加载额外的配置文件的话，可以考虑采用扩展配置或者动态配置（参考后面的描述）。

加载配置文件

Config::load('配置文件名');

配置文件一般位于APP_PATH目录下面，如果需要加载其它位置的配置文件，需要使用完整路径，例如：

Config::load(APP_PATH.'config/config.php');

系统默认的配置定义格式是PHP返回数组的方式，例如：

return [
    '配置参数1'=>'配置值',
    '配置参数1'=>'配置值',
    // ... 更多配置
 ];

如果你定义格式是其他格式的话，可以使用parse方法来导入，例如：

Config::parse(APP_PATH.'my_config.ini','ini');
Config::parse(APP_PATH.'my_config.xml','xml');

parse方法的第一个参数需要传入完整的文件名或者配置内容。

如果不传入第二个参数的话，系统会根据配置文件名自动识别配置类型，所以下面的写法仍然是支持的：

Config::parse('my_config.ini');

parse方法除了支持读取配置文件外，也支持直接传入配置内容，例如：

$config = 'var1=val
var2=val';
Config::parse($config,'ini');

支持传入配置文件内容的时候 第二个参数必须显式指定。

标准的ini格式文件定义：

配置参数1=配置值
配置参数2=配置值

标准的xml格式文件定义：

<config>
 <var1>val1</var1>
 <var2>val2</var2>
 </config>

配置类采用驱动方式支持各种不同的配置文件类型，因此可以根据需要随意扩展。


读取配置

读取配置参数

设置完配置参数后，就可以使用get方法读取配置了，例如：

echo Config::get('配置参数1');

系统定义了一个助手函数config，以上可以简化为：

echo config('配置参数1');

读取所有的配置参数：

dump(Config::get());
 // 或者 dump(config());

或者你需要判断是否存在某个设置参数：

Config::has('配置参数2');
// 或者 config('?配置参数2');

如果需要读取二级配置，可以使用：

echo Config::get('配置参数.二级参数');
echo config('配置参数.二级参数');


动态配置

设置配置参数

使用set方法动态设置参数，例如：

Config::set('配置参数','配置值');
// 或者使用助手函数
config('配置参数','配置值');

也可以批量设置，例如：

Config::set([
    '配置参数1'=>'配置值',
    '配置参数2'=>'配置值'
]);
// 或者使用助手函数
config([
    '配置参数1'=>'配置值',
    '配置参数2'=>'配置值'
]);


独立配置

独立配置文件

配置文件支持分离（也称为扩展配置），只需要在公共配置文件配置extra_config_list参数(V5.0.1版本已经废除该写法）。

例如，不使用独立配置文件的话，数据库配置信息应该是在config.php中配置如下：

/* 数据库设置 */
'database'              => [
    // 数据库类型
    'type'        => 'mysql',
    // 服务器地址
    'hostname'    => '127.0.0.1',
    // 数据库名
    'database'    => 'thinkphp',
    // 数据库用户名
    'username'    => 'root',
    // 数据库密码
    'password'    => '',
    // 数据库连接端口
    'hostport'    => '',
    // 数据库连接参数
    'params'      => [],
    // 数据库编码默认采用utf8
    'charset'     => 'utf8',
    // 数据库表前缀
    'prefix'      => '',
    // 数据库调试模式
    'debug'       => false,
],

如果需要使用独立配置文件的话，则首先在config.php中添加配置：

'extra_config_list'     => ['database'],

定义之后，数据库配置就可以独立使用database.php文件，配置内容如下：

/* 数据库设置 */
return [
    // 数据库类型
    'type'        => 'mysql',
    // 服务器地址
    'hostname'    => '127.0.0.1',
    // 数据库名
    'database'    => 'thinkphp',
    // 数据库用户名
    'username'    => 'root',
    // 数据库密码
    'password'    => '',
    // 数据库连接端口
    'hostport'    => '',
    // 数据库连接参数
    'params'      => [],
    // 数据库编码默认采用utf8
    'charset'     => 'utf8',
    // 数据库表前缀
    'prefix'      => '',
    // 数据库调试模式
    'debug'       => false,
],

如果配置了extra_config_list参数，并同时在config.php和database.php文件中都配置的话，则database.php文件的配置会覆盖config.php中的设置。

独立配置文件的参数获取都是二维配置方式，例如，要获取database独立配置文件的type参数，应该是：

Config::get('database.type');

要获取完整的独立配置文件的参数，则使用：

Config::get('database');

系统默认设置了2个独立配置文件，包括database和validate，分别用于设置数据库配置和验证规则定义。

自动读取扩展配置

版本要求V5.0.1

5.0.1以上版本支持自动读取扩展配置文件（extra_config_list配置参数废弃），只需要将扩展配置文件放入application/extra目录，即可自动读取。

自动读取的配置文件都是二级配置参数，一级配置名称就是扩展配置的文件名。

模块也可以支持自己的扩展配置文件，只需要放入 application/模块名/extra下面就可以自动加载。

系统默认加载的独立配置文件不在此列，包括：

文件名 | 描述
config | 应用或者模块配置文件
database | 数据库配置文件
tags | 行为定义文件
场景名 | 应用场景配置文件

如果你更改了CONF_PATH，那么扩展配置文件目录应该是CONF_PATH/extra，模块配置目录则变成 CONF_PATH/module/，模块的扩展配置目录则变成CONF_PATH/module/extra。


配置作用域

作用域

配置参数支持作用域的概念，默认情况下，所有参数都在同一个系统默认作用域下面。如果你的配置参数需要用于不同的项目或者相互隔离，那么就可以使用作用域功能，作用域的作用好比是配置参数的命名空间一样。

// 导入my_config.php中的配置参数，并纳入user作用域
Config::load('my_config.php','','user');
// 解析并导入my_config.ini 中的配置参数，读入test作用域
Config::parse('my_config.ini','ini','test');
// 设置user_type参数，并纳入user作用域
Config::set('user_type',1,'user');
// 批量设置配置参数，并纳入test作用域
Config::set($config,'test');
// 读取user作用域的user_type配置参数
echo Config::get('user_type','user');
// 读取user作用域下面的所有配置参数
dump(Config::get('','user'));
dump(config('',null,'user')); // 同上
// 判断在test作用域下面是否存在user_type参数
Config::has('user_type','test');

可以使用range方法切换当前配置文件的作用域，例如：

Config::range('test');


环境变量配置

环境变量配置

ThinkPHP5.0支持使用环境变量配置。

在开发过程中，可以在应用根目录下面的.env来模拟环境变量配置，.env文件中的配置参数定义格式采用ini方式，例如：

app_debug =  true
app_trace =  true

如果你的部署环境单独配置了环境变量，那么请删除.env配置文件，避免冲突。

环境变量配置的参数会全部转换为大写，值为 null，no 和 false 等效于 ""，值为 yes 和 true 等效于 "1"。

ThinkPHP5.0默认的环境变量前缀是PHP_，也可以通过改变ENV_PREFIX常量来重新设置。

注意，环境变量不支持数组参数，如果需要使用数组参数可以，使用下划线分割定义配置参数名：

database_username =  root
database_password =  123456

或者使用

[database]
username =  root
password =  123456

获取环境变量的值可以使用下面的两种方式获取：

Env::get('database.username');
Env::get('database.password');
// 同时下面的方式也可以获取
Env::get('database_username');
Env::get('database_password');

可以支持默认值，例如：

// 获取环境变量 如果不存在则使用默认值root
Env::get('database.username','root');

可以直接在应用配置中使用环境变量，例如：

return [
    'hostname'  =>  Env::get('hostname','127.0.0.1'),
];

环境变量中设置的app_debug和app_trace参数会自动生效（优先于应用的配置文件），其它参数则必须通过Env::get方法才能读取。


路由模式

ThinkPHP5.0的路由比较灵活，并且不需要强制定义，可以总结归纳为如下三种方式：

一、普通模式

关闭路由，完全使用默认的PATH_INFO方式URL：

'url_route_on'  =>  false,

路由关闭后，不会解析任何路由规则，采用默认的PATH_INFO 模式访问URL：

http://serverName/index.php/module/controller/action/param/value/...

但仍然可以通过操作方法的参数绑定、空控制器和空操作等特性实现URL地址的简化。

可以设置url_param_type配置参数来改变pathinfo模式下面的参数获取方式，默认是按名称成对解析，支持按照顺序解析变量，只需要更改为：

// 按照顺序解析变量
'url_param_type'    =>  1,

二、混合模式

开启路由，并使用路由定义+默认PATH_INFO方式的混合：

'url_route_on'  =>  true,
'url_route_must'=>  false,

该方式下面，只需要对需要定义路由规则的访问地址定义路由规则，其它的仍然按照第一种普通模式的PATH_INFO模式访问URL。

三、强制模式

开启路由，并设置必须定义路由才能访问：

'url_route_on'          =>  true,
'url_route_must'        =>  true,

这种方式下面必须严格给每一个访问地址定义路由规则（包括首页），否则将抛出异常。

首页的路由规则采用/定义即可，例如下面把网站首页路由输出Hello,world!

Route::get('/',function(){
    return 'Hello,world!';
});


路由定义

注册路由规则

路由注册可以采用方法动态单个和批量注册，也可以直接定义路由定义文件的方式进行集中注册。

动态注册

路由定义采用\think\Route类的rule方法注册，通常是在应用的路由配置文件application/route.php进行注册，格式是：

Route::rule('路由表达式','路由地址','请求类型','路由参数（数组）','变量规则（数组）');

例如注册如下路由规则：

use think\Route;
// 注册路由到index模块的News控制器的read操作
Route::rule('new/:id','index/News/read');

我们访问：

http://serverName/new/5

ThinkPHP5.0的路由规则定义是从根目录开始，而不是基于模块名的。

会自动路由到：

http://serverName/index/news/read/id/5

并且原来的访问地址会自动失效。

路由表达式（第一个参数）支持定义命名标识，例如：

// 定义new路由命名标识
Route::rule(['new','new/:id'],'index/News/read');

注意，路由命名标识必须唯一，定义后可以用于URL的快速生成。

可以在rule方法中指定请求类型，不指定的话默认为任何请求类型，例如：

Route::rule('new/:id','News/update','POST');

表示定义的路由规则在POST请求下才有效。

请求类型包括： | 类型 | 描述
GET | GET请求
POST | POST请求
PUT | PUT请求
DELETE | DELETE请求
* | 任何请求类型

注意：请求类型参数必须大写。

系统提供了为不同的请求类型定义路由规则的简化方法，例如：

Route::get('new/:id','News/read'); // 定义GET请求路由规则
Route::post('new/:id','News/update'); // 定义POST请求路由规则
Route::put('new/:id','News/update'); // 定义PUT请求路由规则
Route::delete('new/:id','News/delete'); // 定义DELETE请求路由规则
Route::any('new/:id','News/read'); // 所有请求都支持的路由规则

如果要定义get和post请求支持的路由规则，也可以用：

Route::rule('new/:id','News/read','GET|POST');

我们也可以批量注册路由规则，例如：

Route::rule(['new/:id'=>'News/read','blog/:name'=>'Blog/detail']);
Route::get(['new/:id'=>'News/read','blog/:name'=>'Blog/detail']);
Route::post(['new/:id'=>'News/update','blog/:name'=>'Blog/detail']);

注册多个路由规则后，系统会依次遍历注册过的满足请求类型的路由规则，一旦匹配到正确的路由规则后则开始调用控制器的操作方法，后续规则就不再检测。

路由表达式

路由表达式统一使字符串定义，采用规则定义的方式。

正则路由定义功能已经废除，改由变量规则定义完成。

规则表达式

规则表达式通常包含静态地址和动态地址，或者两种地址的结合，例如下面都属于有效的规则表达式：

'/' => 'index', // 首页访问路由
'my'        =>  'Member/myinfo', // 静态地址路由
'blog/:id'  =>  'Blog/read', // 静态地址和动态地址结合
'new/:year/:month/:day'=>'News/read', // 静态地址和动态地址结合
':user/:blog_id'=>'Blog/read',// 全动态地址

规则表达式的定义以/为参数分割符（无论你的PATH_INFO分隔符设置是什么，请确保在定义路由规则表达式的时候统一使用/进行URL参数分割）。

每个参数中以“:”开头的参数都表示动态变量，并且会自动绑定到操作方法的对应参数。

可选定义

支持对路由参数的可选定义，例如：

'blog/:year/[:month]'=>'Blog/archive',

[:month]变量用[ ]包含起来后就表示该变量是路由匹配的可选变量。

以上定义路由规则后，下面的URL访问地址都可以被正确的路由匹配：

http://serverName/index.php/blog/2015
http://serverName/index.php/blog/2015/12

采用可选变量定义后，之前需要定义两个或者多个路由规则才能处理的情况可以合并为一个路由规则。

可选参数只能放到路由规则的最后，如果在中间使用了可选参数的话，后面的变量都会变成可选参数。

完全匹配

规则匹配检测的时候只是对URL从头开始匹配，只要URL地址包含了定义的路由规则就会匹配成功，如果希望完全匹配，可以在路由表达式最后使用$符号，例如：

'new/:cate$'=> 'News/category',
http://serverName/index.php/new/info

会匹配成功,而

http://serverName/index.php/new/info/2

则不会匹配成功。

如果是采用

'new/:cate'=> 'News/category',

方式定义的话，则两种方式的URL访问都可以匹配成功。

如果你希望所有的路由定义都是完全匹配的话，可以直接配置

// 开启路由定义的全局完全匹配
'route_complete_match'  =>  true,

当开启全局完全匹配的时候，如果个别路由不需要使用完整匹配，可以添加路由参数覆盖定义：

Route::rule('new/:id','News/read','GET|POST',['complete_match' => false]);

额外参数

在路由跳转的时候支持额外传入参数对（额外参数指的是不在URL里面的参数，隐式传入需要的操作中，有时候能够起到一定的安全防护作用，后面我们会提到）。例如：

'blog/:id'=>'blog/read?status=1&app_id=5',

上面的路由规则定义中额外参数的传值方式都是等效的。status和app_id参数都是URL里面不存在的，属于隐式传值，当然并不一定需要用到，只是在需要的时候可以使用。


MISS路由

全局MISS路由

如果希望在没有匹配到所有的路由规则后执行一条设定的路由，可以使用MISS路由功能，只需要在路由配置文件中定义：

return [
    'new/:id'   => 'News/read',
    'blog/:id'  => ['Blog/update',['method' => 'post|put'], ['id' => '\d+']],
    '__miss__'  => 'public/miss',
];

或者使用miss方法注册路由

Route::miss('public/miss');

当没有匹配到所有的路由规则后，会路由到 public/miss路由地址。

分组MISS路由

分组支持独立的MISS路由，例如如下定义：

return [
    '[blog]' =>  [
        'edit/:id'  => ['Blog/edit',['method' => 'get'], ['id' => '\d+']],
        ':id'       => ['Blog/read',['method' => 'get'], ['id' => '\d+']],
        '__miss__'  => 'blog/miss',
    ],
    'new/:id'   => 'News/read',
    '__miss__'  => 'public/miss',
];

如果使用group方法注册路由的话，可以使用下面的方式：

Route::group('blog',function(){
    Route::rule(':id','blog/read',[],['id'=>'\d+']);
    Route::rule(':name','blog/read',[],['name'=>'\w+']);
    Route::miss('blog/miss');
},['method'=>'get','ext'=>'html']);


URL生成

ThinkPHP5.0支持路由URL地址的统一生成，并且支持所有的路由方式，以及完美解决了路由地址的反转解析，无需再为路由定义和变化而改变URL生成。

URL生成使用 \think\Url::build() 方法或者使用系统提供的助手函数url()，参数一致：

Url::build('地址表达式',['参数'],['URL后缀'],['域名'])

url('地址表达式',['参数'],['URL后缀'],['域名'])

地址表达式和参数

对使用不同的路由地址方式，地址表达式的定义有所区别。参数单独通过第二个参数传入，假设我们定义了一个路由规则如下：

Route::rule('blog/:id','index/blog/read');

就可以使用下面的方式来生成URL地址：

Url::build('index/blog/read','id=5&name=thinkphp');
Url::build('index/blog/read',['id'=>5,'name'=>'thinkphp']);
url('index/blog/read','id=5&name=thinkphp');
url('index/blog/read',['id'=>5,'name'=>'thinkphp']);

下面我们统一使用第一种方式讲解。

使用模块/控制器/操作生成

如果你的路由方式是路由到模块/控制器/操作，那么可以直接写

// 生成index模块 blog控制器的read操作 URL访问地址
Url::build('index/blog/read','id=5&name=thinkphp');
// 使用助手函数
url('index/blog/read','id=5&name=thinkphp');

以上方法都会生成下面的URL地址：

/index.php/blog/5/name/thinkphp.html

注意，生成方法的第一个参数必须和路由定义的路由地址保持一致，如果写成下面的方式可能无法正确生成URL地址：

Url::build('blog/read','id=5&name=thinkphp');

如果你的环境支持REWRITE，那么生成的URL地址会变为：

/blog/5/name/thinkphp.html

如果你配置了：

'url_common_param'=>true

那么生成的URL地址变为：

/index.php/blog/5.html?name=thinkphp

不在路由规则里面的变量会直接使用普通URL参数的方式。

需要注意的是，URL地址生成不会检测路由的有效性，只是按照给定的路由地址和参数生成符合条件的路由规则。

使用控制器的方法生成

如果你的路由地址是采用控制器的方法，并且路由定义如下：

// 这里采用配置方式定义路由 动态注册的方式一样有效
'blog/:id'  => '@index/blog/read'

那么可以使用如下方式生成：

// 生成index模块 blog控制器的read操作 URL访问地址
Url::build('@index/blog/read','id=5');
// 使用助手函数
url('@index/blog/read','id=5');

那么自动生成的URL地址变为：

/index.php/blog/5.html

使用类的方法生成

如果你的路由地址是路由到类的方法，并且做了如下路由规则定义：

// 这里采用配置方式定义路由 动态注册的方式一样有效
Route::rule(['blog','blog/:id'],'\app\index\controller\blog@read');

如果路由地址是到类的方法，需要首先给路由定义命名标识，然后使用标识快速生成URL地址。

那么可以使用如下方式生成：

// 生成index模块 blog控制器的read操作 URL访问地址
Url::build('blog?id=5');
url('blog?id=5');

那么自动生成的URL地址变为：

/index.php/blog/5.html

直接使用路由地址

我们也可以直接使用路由地址来生成URL，例如：

我们定义了路由规则如下：

'blog/:id' => 'index/blog/read'

可以使用下面的方式直接使用路由规则生成URL地址：

Url::build('/blog/5');

那么自动生成的URL地址变为：

/index.php/blog/5.html

URL后缀

默认情况下，系统会自动读取url_html_suffix配置参数作为URL后缀（默认为html），如果我们设置了：

'url_html_suffix'   => 'shtml'

那么自动生成的URL地址变为：

/index.php/blog/5.shtml

如果我们设置了多个URL后缀支持

'url_html_suffix'   => 'html|shtml'

则会取第一个后缀来生成URL地址，所以自动生成的URL地址还是：

/index.php/blog/5.html

如果你希望指定URL后缀生成，则可以使用：

Url::build('index/blog/read','id=5','shtml');
url('index/blog/read','id=5','shtml');

域名生成

默认生成的URL地址是不带域名的，如果你采用了多域名部署或者希望生成带有域名的URL地址的话，就需要传入第四个参数，该参数有两种用法：

自动生成域名

Url::build('index/blog/read','id=5','shtml',true);
url('index/blog/read','id=5','shtml',true);

第四个参数传入true的话，表示自动生成域名，如果你开启了url_domain_deploy还会自动识别匹配当前URL规则的域名。

例如，我们注册了域名路由信息如下：

Route::domain('blog','index/blog');

那么上面的URL地址生成为：

http://blog.thinkphp.cn/read/id/5.shtml

指定域名

你也可以显式传入需要生成地址的域名，例如：

Url::build('index/blog/read','id=5','shtml','blog');
url('index/blog/read','id=5','shtml','blog');

或者传入完整的域名

Url::build('index/blog/read','id=5','shtml','blog.thinkphp.cn');
url('index/blog/read','id=5','shtml','blog.thinkphp.cn');

生成的URL地址为：

http://blog.thinkphp.cn/read/id/5.shtml

也可以直接在第一个参数里面传入域名，例如：

Url::build('index/blog/read@blog','id=5');
url('index/blog/read@blog','id=5');
url('index/blog/read@blog.thinkphp.cn','id=5');

生成锚点

支持生成URL的锚点，可以直接在URL地址参数中使用：

Url::build('index/blog/read#anchor@blog','id=5');
url('index/blog/read#anchor@blog','id=5');

锚点和域名一起使用的时候，注意锚点在前面，域名在后面。

生成的URL地址为：

http://blog.thinkphp.cn/read/id/5.html#anchor

隐藏或者加上入口文件

有时候我们生成的URL地址可能需要加上index.php或者去掉index.php，大多数时候系统会自动判断，如果发现自动生成的地址有问题，可以直接在调用build方法之前调用root方法，例如加上index.php：

Url::root('/index.php');
Url::build('index/blog/read','id=5');

或者隐藏index.php：

Url::root('/');
Url::build('index/blog/read','id=5');

root方法只需要调用一次即可。


绑定模型

模型绑定（V5.0.1）

路由规则和分组支持绑定模型数据，例如：

Route::rule('hello/:id','index/index/hello','GET',[
    'ext'                   =>  'html',
    'bind_model'    =>  [
        'user'  =>  '\app\index\model\User',
    ],
]);

会自动给当前路由绑定 id为 当前路由变量值的User模型数据。

可以定义模型数据的查询条件，例如：

Route::rule('hello/:name/:id','index/index/hello','GET',[
    'ext'                   =>  'html',
    'bind_model'    =>  [
        'user'  =>  ['\app\index\model\User','id&name']
    ],
]);

表示查询id和name的值等于当前路由变量的模型数据。

也可以使用闭包来返回模型对象数据

Route::rule('hello/:id','index/index/hello','GET',[
    'ext'                   =>  'html',
    'bind_model'    =>  [
        'user'  =>  function($param){
            $model = new \app\index\model\User;
            return $model->where($param)->find();
        }
    ],
]);

闭包函数的参数就是当前请求的URL变量信息。

在控制器中可以通过下面的代码或者使用依赖注入获取：

request()->user;

绑定的模型可以直接在控制器的架构方法或者操作方法中自动注入，具体可以参考请求章节的依赖注入。


闭包支持

闭包定义

我们可以使用闭包的方式定义一些特殊需求的路由，而不需要执行控制器的操作方法了，例如：

Route::get('hello',function(){
    return 'hello,world!';
});

参数传递

闭包定义的时候支持参数传递，例如：

Route::get('hello/:name',function($name){
    return 'Hello,'.$name;
});

规则路由中定义的动态变量的名称 就是闭包函数中的参数名称，不分次序。

因此，如果我们访问的URL地址是：

http://serverName/hello/thinkphp

则浏览器输出的结果是：

Hello,thinkphp


变量规则

变量规则

ThinkPHP5.0支持在规则路由中为变量用正则的方式指定变量规则，弥补了动态变量无法限制具体的类型问题，并且支持全局规则设置。使用方式如下：

全局变量规则

设置全局变量规则，全部路由有效：

// 设置name变量规则（采用正则定义）
Route::pattern('name','\w+');
// 支持批量添加
Route::pattern([
    'name'  =>  '\w+',
    'id'    =>  '\d+',
]);

局部变量规则

局部变量规则，仅在当前路由有效：

// 定义GET请求路由规则 并设置name变量规则
Route::get('new/:name','News/read',[],['name'=>'\w+']);

如果一个变量同时定义了全局规则和局部规则，局部规则会覆盖全局变量的定义。

完整URL规则

如果要对整个URL进行规则检查，可以进行__url__ 变量规则，例如：

// 定义GET请求路由规则 并设置完整URL变量规则
Route::get('new/:id','News/read',[],['__url__'=>'new\/\w+$']);


快捷路由

快捷路由允许你快速给控制器注册路由，并且针对不同的请求类型可以设置方法前缀，例如：

// 给User控制器设置快捷路由
Route::controller('user','index/User');

User控制器定义如下：

namespace app\index\controller;

class User {
    public function getInfo()
    {
    }

    public function getPhone()
    {
    }

    public function postInfo()
    {
    }

    public function putInfo()
    {
    }

    public function deleteInfo()
    {
    }
}

我们可以通过下面的URL访问

get http://localhost/user/info
get http://localhost/user/phone
post http://localhost/user/info
put http://localhost/user/info
delete http://localhost/user/info


路由绑定

可以使用路由绑定简化URL或者路由规则的定义，绑定支持如下方式：

绑定到模块/控制器/操作

把当前的URL绑定到模块/控制器/操作，最多支持绑定到操作级别，例如在路由配置文件中添加：

// 绑定当前的URL到 index模块
Route::bind('index');
// 绑定当前的URL到 index模块的blog控制器
Route::bind('index/blog');
// 绑定当前的URL到 index模块的blog控制器的read操作
Route::bind('index/blog/read');

该方式针对路由到模块/控制器/操作有效，假如我们绑定到了index模块的blog控制器，那么原来的访问URL从

http://serverName/index/blog/read/id/5

可以简化成

http://serverName/read/id/5

如果定义了路由

Route::get('index/blog/:id','index/blog/read');

那么访问URL就变成了

http://serverName/5

绑定到命名空间

把当前的URL绑定到某个指定的命名空间，例如：

// 绑定命名空间
Route::bind('\app\index\controller','namespace');

那么，我们接下来只需要通过

http://serverName/blog/read/id/5

就可以直接访问 \app\index\controller\Blog类的read方法。

绑定到类

把当前的URL直接绑定到某个指定的类，例如：

// 绑定到类
Route::bind('\app\index\controller\Blog','class');

那么，我们接下来只需要通过

http://serverName/read/id/5

就可以直接访问 \app\index\controller\Blog类的read方法。

注意：绑定到命名空间和类之后，不会进行模块的初始化工作。

入口文件绑定

如果我们需要给某个入口文件绑定模块，可以使用下面两种方式：

常量定义

只需要入口文件添加BIND_MODULE常量，即可把当前入口文件绑定到指定的模块或者控制器，例如：

// 定义应用目录
define('APP_PATH', __DIR__ . '/../application/');
// 绑定到index模块
define('BIND_MODULE','index');
// 加载框架引导文件
require __DIR__ . '/../thinkphp/start.php';

自动入口绑定

如果你的入口文件都是对应实际的模块名，那么可以使用入口文件自动绑定模块的功能，只需要在应用配置文件中添加：

// 开启入口文件自动绑定模块
'auto_bind_module'  =>  true,

当我们重新添加一个 public/demo.php入口文件，内容和public/index.php一样：

// 定义应用目录
define('APP_PATH', __DIR__ . '/../application/');
// 加载框架引导文件
require __DIR__ . '/../thinkphp/start.php';

但其实访问 demo.php的时候，其实已经自动绑定到了demo模块。


路由别名

路由别名功能可以使用一条规则，批量定义一系列的路由规则。

例如，我们希望使用user可以访问index模块的User控制器的所有操作，可以使用：

// user 别名路由到 index/User 控制器
Route::alias('user','index/User');

如果在路由配置文件route.php中定义的话，使用：

return [
    '__alias__' =>  [
        'user'  =>  'index/User',
    ],
];

和前面的方式是等效的。

然后可以直接通过URL地址访问User控制器的操作，例如：

http://serverName/index.php/user/add
http://serverName/index.php/user/edit/id/5
http://serverName/index.php/user/read/id/5

如果URL参数绑定方式使用按顺序绑定的话，URL地址可以进一步简化，参考请求->方法参数绑定。

路由别名可以指向任意一个有效的路由地址，例如下面指向一个类

// user 路由别名指向 User控制器类
Route::alias('user','\app\index\controller\User');

路由别名不支持变量类型和路由条件判断，单纯只是为了缩短URL地址，并且在定义的时候需要注意避免和路由规则产生混淆。

支持给路由别名设置路由条件，例如：

// user 别名路由到 index/user 控制器
Route::alias('user','index/user',['ext'=>'html']);

或者在路由配置文件中使用：

return [
    '__alias__' =>  [
        'user'  =>  ['index/user',['ext'=>'html']],
    ],
];

操作方法黑白名单（v5.0.2+）

路由别名的操作方法支持白名单或者黑名单机制，例如：

// user 别名路由到 index/user 控制器
Route::alias('user','index/user',[
    'ext'=>'html'，
    'allow'=>'index,read,edit,delete',
]);

或者使用黑名单机制

// user 别名路由到 index/user 控制器
Route::alias('user','index/user',[
    'ext'=>'html'，
    'except'=>'save,delete',
]);

并且支持设置操作方法的请求类型，例如：

// user 别名路由到 index/user 控制器
Route::alias('user','index/user',[
    'ext'=>'html'，
    'allow'=>'index,save,delete',
    'method'=>['index'=>'GET','save'=>'POST','delete'=>'DELETE'],
]);


路由参数

路由参数

路由参数是指可以设置一些路由匹配的条件参数，主要用于验证当前的路由规则是否有效，主要包括：

参数 | 说明
method | 请求类型检测，支持多个请求类型
ext | URL后缀检测，支持匹配多个后缀
deny_ext | URL禁止后缀检测，支持匹配多个后缀
https | 检测是否https请求
domain | 域名检测
before_behavior | 前置行为（检测）
after_behavior | 后置行为（执行）
callback | 自定义检测方法
merge_extra_vars | 合并额外参数
bind_model | 绑定模型（V5.0.1+）
cache | 请求缓存（V5.0.1+）
param_depr | 路由参数分隔符（V5.0.2+）
ajax | Ajax检测（V5.0.2+）
pjax | Pjax检测（V5.0.2+）

V5.0.3+版本对ajax/pjax/https条件支持false。

这些路由参数可以混合使用，只要有任何一条参数检查不通过，当前路由就不会生效，继续检测后面的路由规则。

请求类型

如果指定请求类型注册路由的话，无需设置method请求类型参数。如果使用了rule或者any方法注册路由，或者使用路由配置定义文件的话，可以单独使用method参数进行请求类型检测。

使用方法：

// 检测路由规则仅GET请求有效
Route::any('new/:id','News/read',['method'=>'get']);
// 检测路由规则仅GET和POST请求有效
Route::any('new/:id','News/read',['method'=>'get|post']);

URL后缀

// 定义GET请求路由规则 并设置URL后缀为html的时候有效
Route::get('new/:id','News/read',['ext'=>'html']);

支持匹配多个后缀，例如：

Route::get('new/:id','News/read',['ext'=>'shtml|html']);

可以设置禁止访问的URL后缀，例如：

// 定义GET请求路由规则 并设置禁止URL后缀为png、jpg和gif的访问
Route::get('new/:id','News/read',['deny_ext'=>'jpg|png|gif']);

域名检测

支持使用完整域名或者子域名进行检测，例如：

// 完整域名检测 只在news.thinkphp.cn访问时路由有效
Route::get('new/:id','News/read',['domain'=>'news.thinkphp.cn']);
// 子域名检测
Route::get('new/:id','News/read',['domain'=>'news']);

HTTPS检测

支持检测当前是否HTTPS访问

Route::get('new/:id','News/read',['https'=>true]);

前置行为检测

支持使用行为对路由进行检测是否匹配，如果行为方法返回false表示当前路由规则无效。

Route::get('user/:id','index/User/read',['before_behavior'=>'\app\index\behavior\UserCheck']);

行为类定义如下：

namespace app\index\behavior;

class UserCheck
{
    public function run()
    {
        if('user/0'==request()->url()){
            return false;
        }
    }
}

后置行为执行

可以为某个路由或者某个分组路由定义后置行为执行，表示当路由匹配成功后，执行的行为，例如：

Route::get('user/:id','User/read',['after_behavior'=>'\app\index\behavior\ReadInfo']);

其中\app\index\behavior\ReadInit 行为类定义如下：

namespace app\index\behavior;

use app\index\model\User;

class ReadInfo {
    public function run(){
        $id = request()->route('id');
        request()->user = User::get($id);
    }
}

如果成功匹配到new/:id路由后，就会执行行为类的run方法，参数是路由地址，可以动态改变。

Callback检测

也可以支持使用函数检测路由，如果函数返回false则表示当前路由规则无效，例如：

Route::get('new/:id','News/read',['callback'=>'my_check_fun']);

合并额外参数

通常用于完整匹配的情况，如果有额外的参数则合并作为变量值，例如：

Route::get('new/:name$','News/read',['merge_extra_vars'=>true]);

http://serverName/new/thinkphp/hello

会被匹配到，并且name变量的值为 thinkphp/hello。

配置文件中添加路由参数

如果使用配置文件的话，可以使用：

return [
    'blog/:id'   => ['Blog/update',['method' => 'post','ext'=>'html|shtml']],
];

路由绑定模型（V5.0.1）

可以在当前路由匹配后绑定模型，后面则同过方法的对象自动注入来获取。

Route::get('new/:name$','News/read',['bind_model'=>['User','name']]);

更多细节可以参考请求->依赖注入。

缓存路由请求

可以对当前的路由请求进行缓存处理，例如：

Route::get('new/:name$','News/read',['cache'=>3600]);

表示对当前路由请求缓存3600秒。


路由地址

路由地址定义

版本 | 新增功能
5.0.4 | 路由到类和控制器的方法支持传入额外参数

路由地址表示定义的路由表达式最终需要路由到的地址以及一些需要的额外参数，支持下面5种方式定义：

定义方式 | 定义格式
方式1：路由到模块/控制器 | '[模块/控制器/操作]?额外参数1=值1&额外参数2=值2...'
方式2：路由到重定向地址 | '外部地址'（默认301重定向） 或者 ['外部地址','重定向代码']
方式3：路由到控制器的方法 | '@[模块/控制器/]操作'
方式4：路由到类的方法 | '\完整的命名空间类::静态方法' 或者 '\完整的命名空间类@动态方法'
方式5：路由到闭包函数 | 闭包函数定义（支持参数传入）

其中方式5我们将会在下一个章节闭包支持中详细描述。

路由到模块/控制器/操作

这是最常用的一种路由方式，把满足条件的路由规则路由到相关的模块、控制器和操作，然后由App类调度执行相关的操作。

同时会进行模块的初始化操作（包括配置读取、公共文件载入、行为定义载入、语言包载入等等）。

路由地址的格式为：

[模块/控制器/]操作?参数1=值1&参数2=值2...

解析规则是从操作开始解析，然后解析控制器，最后解析模块，例如：

// 路由到默认或者绑定模块
'blog/:id'=>'blog/read',
// 路由到index模块
'blog/:id'=>'index/blog/read',

Blog类定义如下：

namespace app\index\controller;

class Blog {
    public function read($id){
        return 'read:'.$id;
    }
}

路由地址中支持多级控制器，使用下面的方式进行设置：

'blog/:id'=>'index/group.blog/read'

表示路由到下面的控制器类，

index/controller/group/Blog

Blog类定义如下：

namespace app\index\controller\group;

class Blog {
    public function read($id){
        return 'read:'.$id;
    }
}

还可以支持路由到动态的模块、控制器或者操作，例如：

// action变量的值作为操作方法传入
':action/blog/:id' => 'index/blog/:action'
// 变量传入index模块的控制器和操作方法
':c/:a'=> 'index/:c/:a'

如果关闭路由功能的话，默认也会按照该规则对URL进行解析调度。

额外参数

在这种方式路由跳转的时候支持额外传入参数对（额外参数指的是不在URL里面的参数，隐式传入需要的操作中，有时候能够起到一定的安全防护作用，后面我们会提到）。例如：

'blog/:id'=>'blog/read?status=1&app_id=5',

上面的路由规则定义中额外参数status和app_id参数都是URL里面不存在的，属于隐式传值，当然并不一定需要用到，只是在需要的时候可以使用。

路由到操作方法

路由地址的格式为：

@[模块/控制器/]操作

这种方式看起来似乎和第一种是一样的，本质的区别是直接执行某个控制器类的方法，而不需要去解析 模块/控制器/操作这些，同时也不会去初始化模块。
例如，定义如下路由后：

'blog/:id'=>'@index/blog/read',

系统会直接执行

Loader::action('index/blog/read');

相当于直接调用 \app\index\controller\blog类的read方法。

Blog类定义如下：

namespace app\index\controller;

class Blog {
    public function read($id){
        return 'read:'.$id;
    }
}

通常这种方式下面，由于没有定义当前模块名、当前控制器名和当前方法名 ，从而导致视图的默认模板规则失效，所以这种情况下面，如果使用了视图模板渲染，则必须传入明确的参数。

路由到类的方法

路由地址的格式为（动态方法）：

\类的命名空间\类名@方法名

或者（静态方法）

\类的命名空间\类名::方法名

这种方式更进一步，可以支持执行任何类的方法，而不仅仅是执行控制器的操作方法，例如：

'blog/:id'=>'\app\index\service\Blog@read',

执行的是 \app\index\service\Blog类的read方法。
也支持执行某个静态方法，例如：

'blog/:id'=>'\app\index\service\Blog::read',

V5.0.4+版本开始，支持传入额外的参数作为方法的参数调用（用于参数绑定），例如：

'blog/:id'=>'\app\index\service\Blog::read?status=1',

路由到重定向地址

重定向的外部地址必须以“/”或者http开头的地址。

如果路由地址以“/”或者“http”开头则会认为是一个重定向地址或者外部地址，例如：

'blog/:id'=>'/blog/read/id/:id'

和

'blog/:id'=>'blog/read'

虽然都是路由到同一个地址，但是前者采用的是301重定向的方式路由跳转，这种方式的好处是URL可以比较随意（包括可以在URL里面传入更多的非标准格式的参数），而后者只是支持模块和操作地址。举个例子，如果我们希望avatar/123重定向到
/member/avatar/id/123_small的话，只能使用：

'avatar/:id'=>'/member/avatar/id/:id_small'

路由地址采用重定向地址的话，如果要引用动态变量，直接使用动态变量即可。

采用重定向到外部地址通常对网站改版后的URL迁移过程非常有用，例如：

'blog/:id'=>'http://blog.thinkphp.cn/read/:id'

表示当前网站（可能是http://thinkphp.cn ）的 blog/123地址会直接重定向到 http://blog.thinkphp.cn/read/123。


路由分组

路由分组

路由分组功能允许把相同前缀的路由定义合并分组，这样可以提高路由匹配的效率，不必每次都去遍历完整的路由规则。

例如，我们有定义如下两个路由规则的话

'blog/:id'   => ['Blog/read', ['method' => 'get'], ['id' => '\d+']],
'blog/:name' => ['Blog/read', ['method' => 'post']],

可以合并到一个blog分组

'[blog]'     => [
    ':id'   => ['Blog/read', ['method' => 'get'], ['id' => '\d+']],
    ':name' => ['Blog/read', ['method' => 'post']],
],

可以使用Route类的group方法进行注册，如下：

Route::group('blog',[
    ':id'   => ['Blog/read', ['method' => 'get'], ['id' => '\d+']],
    ':name' => ['Blog/read', ['method' => 'post']],
]);

可以给分组路由定义一些公用的路由设置参数，例如：

Route::group('blog',[
    ':id'   => ['Blog/read', [], ['id' => '\d+']],
    ':name' => ['Blog/read', [],
],['method'=>'get','ext'=>'html']);

支持使用闭包方式注册路由分组，例如：

Route::group('blog',function(){
    Route::rule(':id','blog/read',[],['id'=>'\d+']);
    Route::rule(':name','blog/read',[],['name'=>'\w+']);
},['method'=>'get','ext'=>'html']);

如果仅仅是用于对一些路由规则设置一些公共的路由参数，也可以使用：

Route::group(['method'=>'get','ext'=>'html'],function(){
    Route::rule('blog/:id','blog/read',[],['id'=>'\d+']);
    Route::rule('blog/:name','blog/read',[],['name'=>'\w+']);
});

路由分组支持嵌套，例如：

Route::group(['method'=>'get','ext'=>'html'],function(){
    Route::group('blog',function(){
        Route::rule('blog/:id','blog/read',[],['id'=>'\d+']);
        Route::rule('blog/:name','blog/read',[],['name'=>'\w+']);
    }
});


批量注册

批量注册路由规则可以使用两种方式，包括方法注册和路由配置定义。

批量注册

如果不希望一个个注册，可以使用批量注册，规则如下：

Route::rule([
'路由规则1'=>'路由地址和参数',
'路由规则2'=>['路由地址和参数','匹配参数（数组）','变量规则（数组）']
...
],'','请求类型','匹配参数（数组）','变量规则');

如果在外面和规则里面同时传入了匹配参数和变量规则的话，路由规则定义里面的最终生效，但请求类型参数以最外层决定，例如：

Route::rule([
    'new/:id'  =>  'News/read',
    'blog/:id' =>  ['Blog/update',['ext'=>'shtml'],['id'=>'\d{4}']],
    ...
],'','GET',['ext'=>'html'],['id'=>'\d+']);

以上的路由注册，最终blog/:id只会在匹配shtml后缀的访问请求，id变量的规则则是 \d{4}。

如果不同的请求类型的路由规则是一样的，为了避免数组索引冲突的问题，请使用单独的请求方法定义路由。

同样，我们也可以使用其他几个注册方法进行批量注册。

// 批量注册GET路由
Route::get([
    'new/:id'  =>  'News/read',
    'blog/:id' =>  ['Blog/edit',[],['id'=>'\d+']]
    ...
]);
// 效果等同于
Route::rule([
    'new/:id'  =>  'News/read',
    'blog/:id' =>  ['Blog/edit',[],['id'=>'\d+']]
    ...
],'','GET');

定义路由配置文件

除了支持动态注册，也可以直接在应用目录下面的route.php 的最后通过返回数组的方式直接定义路由规则，内容示例如下：

return [
    'new/:id'   => 'News/read',
    'blog/:id'   => ['Blog/update',['method' => 'post|put'], ['id' => '\d+']],
];

路由配置文件定义的路由规则效果和使用any注册路由规则一样。

路由动态注册和配置定义的方式可以共存，例如：

use think\Route;

Route::rule('hello/:name','index/index/hello');

return [
    'new/:id'   => 'News/read',
    'blog/:id'   => ['Blog/update',['method' => 'post|put'], ['id' => '\d+']],
];

默认情况下，只会加载一个路由配置文件route.php，如果你需要定义多个路由文件，可以修改route_config_file配置参数，例如：

// 定义路由配置文件（数组）
'route_config_file' =>  ['route', 'route1', 'route2'],

如果存在相同的路由规则，一样可以参考前面的批量注册方式进行定义。

由于检测机制问题，动态注册的性能比路由配置要高一些，尤其是多种请求类型混合定义的时候。


域名路由

ThinkPHP支持完整域名、子域名和IP部署的路由和绑定功能，同时还可以起到简化URL的作用。

要启用域名部署路由功能，首先需要开启：

'url_domain_deploy' =>  true

定义域名部署规则支持两种方式：动态注册和配置定义。

动态注册

可以在应用的公共文件或者配置文件中动态注册域名部署规则，例如：

// blog子域名绑定到blog模块
Route::domain('blog','blog');
// 完整域名绑定到admin模块
Route::domain('admin.thinkphp.cn','admin');
// IP绑定到admin模块
Route::domain('114.23.4.5','admin');

blog子域名绑定后，URL访问规则变成：

// 原来的URL访问
http://www.thinkphp.cn/blog/article/read/id/5
// 绑定到blog子域名访问
http://blog.thinkphp.cn/article/read/id/5

支持绑定的时候添加默认参数，例如：

// blog子域名绑定到blog模块
Route::domain('blog','blog?var=thinkphp');

除了绑定到模块之外，还隐式传入了一个$_GET['var'] = 'thinkphp' 变量。

支持直接绑定到控制器，例如：

// blog子域名绑定到index模块的blog控制器
Route::domain('blog','index/blog');

URL访问地址变化为：

// 原来的URL访问
http://www.thinkphp.cn/index/blog/read/id/5
// 绑定到blog子域名访问
http://blog.thinkphp.cn/read/id/5

如果你的域名后缀比较特殊，例如是com.cn或者net.cn 之类的域名，需要配置：

'url_domain_root'=>'thinkphp.com.cn'

泛域名部署

可以支持泛域名部署规则，例如：

// 绑定泛二级域名域名到book模块
Route::domain('*','book?name=*');

下面的URL访问都会直接访问book模块

http://hello.thinkphp.cn
http://quickstart.thinkphp.cn

并且可以直接通过$_GET['name']变量 获取当前的泛域名。

支持三级泛域名部署，例如：

// 绑定泛三级域名到user模块
Route::domain('*.user','user?name=*');

如果我们访问如下URL地址：

http://hello.user.thinkphp.cn

的同时，除了会访问user模块之外，还会默认传入 $_GET['name'] = 'hello'

在配置传入参数的时候，如果需要使用当前的泛域名作为参数，可以直接设置为“*”即可。

目前只支持二级域名和三级域名的泛域名部署。

配置定义方式

除了动态注册之外，还支持直接在路由配置文件中定义域名部署规则，例如：

return [
    '__domain__'=>[
        'blog'      => 'blog',
        // 泛域名规则建议在最后定义
        '*.user'    =>  'user',
        '*'         => 'book',
    ],
    // 下面是路由规则定义
]

域名绑定地址

前面我们看到的域名部署规则：

// blog子域名绑定到blog模块
Route::domain('blog','blog');

其实是把域名绑定到模块的方式，其实还有其他的绑定方式。

绑定到命名空间

// blog子域名绑定命名空间
Route::domain('blog','\app\blog\controller');

绑定到类

// blog子域名绑定到类
Route::domain('blog','@\app\blog\controller\Article');

绑定到闭包函数

如果需要，你也可以直接把域名绑定到一个闭包函数，例如：

// blog子域名绑定闭包函数
Route::domain('blog',function(){
    echo 'hello';
    return ['bind'=>'module','module'=>'blog'];
});

域名绑定到闭包函数其实是一种劫持，可以在闭包函数里面动态注册其它的绑定机制或者注册新的路由，例如：

Route::domain('www', function(){
    // 动态注册域名的路由规则
    Route::rule('new/:id', 'index/news/read');
    Route::rule(':user', 'index/user/info');
});

如果你不希望继续，可以直接在闭包函数里面中止执行。

// blog子域名绑定到闭包函数
Route::domain('blog',function(){
    exit('hello');
});

绑定路由规则

可以把域名绑定到一系列指定的路由规则，例如：

Route::domain('blog',[
    // 动态注册域名的路由规则
    ':id' => ['blog/read',['method'=>'GET'],['id'=>'\d+']],
    ':name'=>'blog/read',
]);

如果使用配置文件配置的话，可以按照下面的方式：

return [
    '__domain__'=>[
        'blog'      => [
            // 动态注册域名的路由规则
            ':id' => ['blog/read',['method'=>'GET'],['id'=>'\d+']],
            ':name'=>'blog/read',
        ],
    ],
    // 下面是其它的路由规则定义
]

更详细的绑定功能请参考路由绑定一章内容。


资源路由

资源路由

5.0支持设置RESTFul请求的资源路由，方式如下：

Route::resource('blog','index/blog');

或者在路由配置文件中使用__rest__添加资源路由定义：

return [
    // 定义资源路由
    '__rest__'=>[
        // 指向index模块的blog控制器
        'blog'=>'index/blog',
    ],
    // 定义普通路由
    'hello/:id'=>'index/hello',
]

设置后会自动注册7个路由规则，如下：

标识 | 请求类型 | 生成路由规则 | 对应操作方法（默认）
index | GET | blog | index
create | GET | blog/create | create
save | POST | blog | save
read | GET | blog/:id | read
edit | GET | blog/:id/edit | edit
update | PUT | blog/:id | update
delete | DELETE | blog/:id | delete

具体指向的控制器由路由地址决定，例如上面的设置，会对应index模块的blog控制器，你只需要为Blog控制器创建以上对应的操作方法就可以支持下面的URL访问：

http://serverName/blog/
http://serverName/blog/128
http://serverName/blog/28/edit

Blog控制器中的对应方法如下：

namespace app\index\controller;
class Blog {
    public function index(){
    }

    public function read($id){
    }

    public function edit($id){
    }
}

可以改变默认的id参数名，例如：

Route::resource('blog','index/blog',['var'=>['blog'=>'blog_id']]);

控制器的方法定义需要调整如下：

namespace app\index\controller;
class Blog {
    public function index(){
    }

    public function read($blog_id){
    }

    public function edit($blog_id){
    }
}

也可以在定义资源路由的时候限定执行的方法（标识），例如：

// 只允许index read edit update 四个操作
Route::resource('blog','index/blog',['only'=>['index','read','edit','update']]);
// 排除index和delete操作
Route::resource('blog','index/blog',['except'=>['index','delete']]);

资源路由的标识不可更改，但生成的路由规则和对应操作方法可以修改。

如果需要更改某个资源路由标识的对应操作，可以使用下面方法：

Route::rest('create',['GET', '/add','add']);

设置之后，URL访问变为：

http://serverName/blog/create
变成
http://serverName/blog/add

创建blog页面的对应的操作方法也变成了add。

支持批量更改，如下：

Route::rest([
    'save'   => ['POST', '', 'store'],
    'update' => ['PUT', '/:id', 'save'],
    'delete' => ['DELETE', '/:id', 'destory'],
]);

资源嵌套

支持资源路由的嵌套，例如：

Route::resource('blog.comment','index/comment');

就可以访问如下地址：

http://serverName/blog/128/comment/32
http://serverName/blog/128/comment/32/edit

生成的路由规则分别是：

blog/:blog_id/comment/:id
blog/:blog_id/comment/:id/edit

Comment控制器对应的操作方法如下：

namespace app\index\controller;
class Comment{
    public function edit($id,$blog_id){
    }
}

edit方法中的参数顺序可以随意，但参数名称必须满足定义要求。

如果需要改变其中的变量名，可以使用：

// 更改嵌套资源路由的blog资源的资源变量名为blogId
Route::resource('blog.comment','index/comment',['var'=>['blog'=>'blogId']]);

Comment控制器对应的操作方法改变为：

namespace app\index\controller;

class Comment{
    public function edit($id,$blogId)
    {
    }
}


组合变量

如果你的路由规则比较特殊，可以在路由定义的时候使用组合变量。

例如：

Route::get('item-<name>-<id>','product/detail',[],['name'=>'\w+','id'=>'\d+']);

组合变量的优势是路由规则中没有固定的分隔符，可以随意组合需要的变量规则，例如路由规则改成如下一样可以支持：

Route::get('item<name><id>','product/detail',[],['name'=>'[a-zA-Z]+','id'=>'\d+']);
Route::get('item@<name>-<id>','product/detail',[],['name'=>'\w+','id'=>'\d+']);

如果需要使用可选变量，则可以使用：

Route::get('item-<name><id?>','product/detail',[],['name'=>'[a-zA-Z]+','id'=>'\d+']);


控制器定义

ThinkPHP V5.0的控制器定义比较灵活，可以无需继承任何的基础类，也可以继承官方封装的\think\Controller类或者其他的控制器类。

控制器定义

一个典型的控制器类定义如下：

namespace app\index\controller;

class Index
{
    public function index()
    {
        return 'index';
    }
}

控制器类文件的实际位置是

application\index\controller\Index.php

控制器类可以无需继承任何类，命名空间默认以app为根命名空间。

控制器的根命名空间可以设置，例如我们在应用配置文件中修改：

// 修改应用类库命名空间
'app_namespace' => 'application',

则实际的控制器类应该更改定义如下：

namespace application\index\controller;

class Index
{
    public function index()
    {
        return 'index';
    }
}

只是命名空间改变了，但实际的文件位置和文件名并没有改变。

使用该方式定义的控制器类，如果要在控制器里面渲染模板，可以使用

namespace app\index\controller;

use think\View;

class Index
{
    public function index()
    {
        $view = new View();
        return $view->fetch('index');
    }
}

或者直接使用view助手函数渲染模板输出，例如：

namespace app\index\controller;

class Index
{
    public function index()
    {
        return view('index');
    }
}

如果继承了think\Controller类的话，可以直接调用think\View及think\Request类的方法，例如：

namespace app\index\controller;

use think\Controller;

class Index extends Controller
{
    public function index()
    {
        // 获取包含域名的完整URL地址
        $this->assign('domain',$this->request->url(true));
        return $this->fetch('index');
    }
}

渲染输出

默认情况下，控制器的输出全部采用return的方式，无需进行任何的手动输出，系统会自动完成渲染内容的输出。

下面都是有效的输出方式：

namespace app\index\controller;

class Index
{
    public function hello()
    {
        return 'hello,world!';
    }

    public function json()
    {
        return json_encode($data);
    }

    public function read()
    {
        return view();
    }

}

控制器一般不需要任何输出，直接return即可。

输出转换

默认情况下，控制器的返回输出不会做任何的数据处理，但可以设置输出格式，并进行自动的数据转换处理，前提是控制器的输出数据必须采用return的方式返回。

如果控制器定义为：

namespace app\index\controller;

class Index
{
    public function hello()
    {
        return 'hello,world!';
    }

    public function data()
    {
        return ['name'=>'thinkphp','status'=>1];
    }

}

当我们设置输出数据格式为JSON：

// 默认输出类型
'default_return_type'   => 'json',

我们访问

http://localhost/index.php/index/Index/hello
http://localhost/index.php/index/Index/data

输出的结果变成：

"hello,world!"
{"name":"thinkphp","status":1}

默认情况下，控制器在ajax请求会对返回类型自动转换，默认为json

如果我们控制器定义

namespace app\index\controller;

class Index
{
    public function data()
    {
        return ['name'=>'thinkphp','status'=>1];
    }

}

我们访问

http://localhost/index.php/index/Index/data

输出的结果变成：

{"name":"thinkphp","status":1}

当我们设置输出数据格式为html：

// 默认输出类型
'default_ajax_return'   => 'html',

这种情况下ajax请求不会对返回内容进行转换


控制器初始化

如果你的控制器类继承了\think\Controller类的话，可以定义控制器初始化方法_initialize，在该控制器的方法调用之前首先执行。

例如：

namespace app\index\controller;

use think\Controller;

class Index extends Controller
{

    public function _initialize()
    {
        echo 'init<br/>';
    }

    public function hello()
    {
        return 'hello';
    }

    public function data()
    {
        return 'data';
    }
}

如果访问
http://localhost/index.php/index/Index/hello

会输出

init
hello

如果访问
http://localhost/index.php/index/Index/data

会输出

init
data


前置操作

可以为某个或者某些操作指定前置执行的操作方法，设置 beforeActionList属性可以指定某个方法为其他方法的前置操作，数组键名为需要调用的前置方法名，无值的话为当前控制器下所有方法的前置方法。

['except' => '方法名,方法名']

表示这些方法不使用前置方法，

['only' => '方法名,方法名']

表示只有这些方法使用前置方法。

示例如下:

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';
    }
}

访问

http://localhost/index.php/index/Index/hello

最后的输出结果是

first
three
hello

访问

http://localhost/index.php/index/Index/data

的输出结果是：

first
second
three
data


跳转和重定向

页面跳转

在应用开发中，经常会遇到一些带有提示信息的跳转页面，例如操作成功或者操作错误页面，并且自动跳转到另外一个目标页面。系统的\think\Controller类内置了两个跳转方法success和error，用于页面跳转提示。

使用方法很简单，举例如下：

namespace app\index\controller;

use think\Controller;
use app\index\model\User;

class Index extends Controller
{
    public function index()
    {
        $User = new User; //实例化User对象
        $result = $User->save($data);
        if($result){
            //设置成功后跳转页面的地址，默认的返回页面是$_SERVER['HTTP_REFERER']
            $this->success('新增成功', 'User/list');
        } else {
            //错误页面的默认跳转页面是返回前一页，通常不需要设置
            $this->error('新增失败');
        }
    }
}

跳转地址是可选的，success方法的默认跳转地址是$_SERVER["HTTP_REFERER"]，error方法的默认跳转地址是javascript:history.back(-1);。

默认的等待时间都是3秒

success和error方法都可以对应的模板，默认的设置是两个方法对应的模板都是：

THINK_PATH . 'tpl/dispatch_jump.tpl'

我们可以改变默认的模板：

//默认错误跳转对应的模板文件
'dispatch_error_tmpl' => APP_PATH . 'tpl/dispatch_jump.tpl',
//默认成功跳转对应的模板文件
'dispatch_success_tmpl' => APP_PATH . 'tpl/dispatch_jump.tpl',

也可以使用项目内部的模板文件

//默认错误跳转对应的模板文件
'dispatch_error_tmpl' => 'public/error',
//默认成功跳转对应的模板文件
'dispatch_success_tmpl' => 'public/success',

模板文件可以使用模板标签，并且可以使用下面的模板变量：

变量 | 含义
$data | 要返回的数据
$msg | 页面提示信息
$code | 返回的code
$wait | 跳转等待时间 单位为秒
$url | 跳转页面地址

error方法会自动判断当前请求是否属于Ajax请求，如果属于Ajax请求则会自动转换为default_ajax_return配置的格式返回信息。 success在Ajax请求下不返回信息，需要开发者自行处理。

重定向

\think\Controller类的redirect方法可以实现页面的重定向功能。

redirect方法的参数用法和Url::build方法的用法一致（参考URL生成部分），例如：

//重定向到News模块的Category操作
$this->redirect('News/category', ['cate_id' => 2]);

上面的用法是跳转到News模块的category操作，重定向后会改变当前的URL地址。

或者直接重定向到一个指定的外部URL地址，例如：

//重定向到指定的URL地址 并且使用302
$this->redirect('http://thinkphp.cn/blog/2',302);


空操作

空操作是指系统在找不到指定的操作方法的时候，会定位到空操作（_empty）方法来执行，利用这个机制，我们可以实现错误页面和一些URL的优化。
例如，下面我们用空操作功能来实现一个城市切换的功能。

我们只需要给City控制器类定义一个_empty （空操作）方法：

<?php
namespace app\index\controller;

class City
{
    public function _empty($name)
    {
        //把所有城市的操作解析到city方法
        return $this->showCity($name);
    }

    //注意 showCity方法 本身是 protected 方法
    protected function showCity($name)
    {
        //和$name这个城市相关的处理
         return '当前城市' . $name;
    }
}

接下来，我们就可以在浏览器里面输入

http://serverName/index/city/beijing/
http://serverName/index/city/shanghai/
http://serverName/index/city/shenzhen/

由于City并没有定义beijing、shanghai或者shenzhen操作方法，因此系统会定位到空操作方法 _empty中去解析，_empty方法的参数就是当前URL里面的操作名，因此会看到依次输出的结果是：

当前城市:beijing
当前城市:shanghai
当前城市:shenzhen

V5.0.2+开始，空操作方法不需要任何参数，如果要获取当前的操作方法名，直接调用当前请求对象来获取，你也可以使用依赖注入（参考请求章节的依赖注入）


空控制器

空控制器的概念是指当系统找不到指定的控制器名称的时候，系统会尝试定位空控制器(Error)，利用这个机制我们可以用来定制错误页面和进行URL的优化。
现在我们把前面的需求进一步，把URL由原来的

http://serverName/index/city/shanghai/

变成

http://serverName/index/shanghai/

这样更加简单的方式，如果按照传统的模式，我们必须给每个城市定义一个控制器类，然后在每个控制器类的index方法里面进行处理。 可是如果使用空控制器功能，这个问题就可以迎刃而解了。

我们可以给项目定义一个Error控制器类

<?php
namespace app\index\controller;

use think\Request;

class Error
{
    public function index(Request $request)
    {
        //根据当前控制器名来判断要执行那个城市的操作
        $cityName = $request->controller();
        return $this->city($cityName);
    }

    //注意 city方法 本身是 protected 方法
    protected function city($name)
    {
        //和$name这个城市相关的处理
         return '当前城市' . $name;
    }
}

接下来，我们就可以在浏览器里面输入

http://serverName/index/beijing/
http://serverName/index/shanghai/
http://serverName/index/shenzhen/

由于系统并不存在beijing、shanghai或者shenzhen控制器，因此会定位到空控制器（Error）去执行，会看到依次输出的结果是：

当前城市:beijing
当前城市:shanghai
当前城市:shenzhen

空控制器和空操作还可以同时使用，用以完成更加复杂的操作。

空控制器Error是可以定义的

// 更改默认的空控制器名
'empty_controller'      => 'MyError',

当找不到控制器的时候，就会定位到MyError控制器类进行操作。


多级控制器

多级控制器

新版支持任意层次级别的控制器，并且支持路由，例如：

namespace app\index\controller\one;

use think\Controller;

class Blog extends Controller
{
    public function index()
    {
        return $this->fetch();
    }

    public function add()
    {
        return $this->fetch();
    }

    public function edit($id)
    {
        return $this->fetch();
    }
}

该控制器类的文件位置为：

application/index/controller/one/Blog.php

访问地址可以使用

http://serverName/index.php/index/one.blog/index

如果要在路由定义中使用多级控制器，可以使用：

\think\Route::get('blog/add','index/one.blog/add');


分层控制器

访问控制器

ThinkPHP引入了分层控制器的概念，通过URL访问的控制器为访问控制器层（Controller）或者主控制器，访问控制器是由\think\App类负责调用和实例化的，无需手动实例化。

URL解析和路由后，会把当前的URL地址解析到 [ 模块/控制器/操作 ]，其实也就是执行某个控制器类的某个操作方法，下面是一个示例：

namespace app\index\controller;

class Blog
{
    public function index()
    {
        return 'index';
    }

    public function add()
    {
        return 'add';
    }

    public function edit($id)
    {
        return 'edit:'.$id;
    }
}

当前定义的主控制器位于index模块下面，所以当访问不同的URL地址的页面输出如下：

http://serverName/index/blog/index // 输出 index
http://serverName/index/blog/add     // 输出 add
http://serverName/index/blog/edit/id/5 // 输出 edit:5

新版的控制器可以不需要继承任何基类，当然，你可以定义一个公共的控制器基础类来被继承，也可以通过控制器扩展来完成不同的功能（例如Restful实现）。

如果不经过路由访问的话，URL中的控制器名会首先强制转为小写，然后再解析为驼峰法实例化该控制器。

分层控制器

除了访问控制器外，我们还可以定义其他分层控制器类，这些分层控制器是不能够被URL访问直接调用到的，只能在访问控制器、模型类的内部，或者视图模板文件中进行调用。

例如，我们定义Blog事件控制器如下：

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\Loader::controller('Blog', 'event');
echo $event->update(5); // 输出 update:5
echo $event->delete(5); // 输出 delete:5

为了方便调用，系统提供了controller助手函数直接实例化多层控制器，例如：

$event = controller('Blog', 'event');
echo $event->update(5); // 输出 update:5
echo $event->delete(5); // 输出 delete:5

支持跨模块调用，例如：

$event = controller('Admin/Blog', 'event');
echo $event->update(5); // 输出 update:5

表示实例化Admin模块的Blog控制器类，并执行update方法。

除了实例化分层控制器外，还可以直接调用分层控制器类的某个方法，例如：

echo \think\Loader::action('Blog/update', ['id' => 5], 'event'); // 输出 update:5

也可以使用助手函数action实现相同的功能：

echo action('Blog/update', ['id' => 5], 'event'); // 输出 update:5

利用分层控制器的机制，我们可以用来实现Widget（其实就是在模板中调用分层控制器），例如：
定义index\widget\Blog控制器类如下：

namespace app\index\widget;

class Blog {
    public function header()
    {
        return 'header';
    }

    public function left()
    {
        return 'left';
    }

    public function menu($name)
    {
        return 'menu:'.$name;
    }
}

我们在模板文件中就可以直接调用app\index\widget\Blog分层控制器了，
使用助手函数action

{:action('Blog/header', '', 'widget')}
{:action('Blog/menu', ['name' => 'think'], 'widget')}

框架还提供了widget函数用于简化Widget控制器的调用，
可以直接使用助手函数widget

{:widget('Blog/header')}
{:widget('Blog/menu', ['name' => 'think'])}


Rest控制器

Rest控制器

新版不再推荐使用Rest控制器，建议用资源控制器替代。下一个大版本即将废弃。

如果需要让你的控制器支持RESTful的话，可以使用Rest控制器，在定义访问控制器的时候直接继承think\controller\Rest即可，例如：

namespace app\index\controller;

use think\controller\Rest;

class Blog extends Rest
{
}

配合示例需要，我们首先在应用配置文件中添加：

// URL伪静态后缀
'url_html_suffix'        => 'html|xml|json|jsonp',

RESTFul方法定义

RESTFul方法和标准模式的操作方法定义主要区别在于，需要对请求类型和资源类型进行判断，大多数情况下，通过路由定义可以把操作方法绑定到某个请求类型和资源类型。如果你没有定义路由的话，需要自己在操作方法里面添加判断代码，示例：

<?php

namespace app\index\controller;

use think\controller\Rest;

class Blog extends Rest
{
    public function rest()
    {
        switch ($this->method){
        case 'get': // get请求处理代码
            if ($this->type == 'html'){
            } elseif ($this->type == 'xml'){
            }
            break;
        case 'put': // put请求处理代码
            break;
        case 'post': // post请求处理代码
            break;
        }
    }
}

在Rest操作方法中，可以使用$this->type获取当前访问的资源类型，用$this->method获取当前的请求类型。

RESTFul 输出

使用Rest类提供的 response 方法

$this->response($data, 'json', 200);

使用think\Response类

Response::create($data, 'json')->code(200);

使用助手函数

json($data, 200);

$data为需要输出的数据，第二个参数为输出数据的http状态码
方法会自动对$data数据进行输出类型编码，目前支持的包括xml,json,jsonp,html等编码格式输出，例如：

// 输出 json 格式数据
json($data, 200);
// 输出 jsonp 格式数据
jsonp($data, 200);
// 输出 xml 格式数据
xml($data, 200);
除了普通方式定义Restful操作方法外，系统还支持另外一种自动调用方式，就是根据当前请求类型和资源类型自动调用相关操作方法。系统的自动调用规则是： | 定义规范 | 说明
操作名_提交类型_资源后缀 | 标准的Restful方法定义，例如 read_get_pdf
操作名_资源后缀 | 当前提交类型和restDefaultMethod相同的时候，例如read_pdf
操作名_提交类型 | 当前资源后缀和restDefaultType相同的时候，例如read_post

这种方式的rest方法定义采用了空操作机制，所以要使用这种方式的前提就是不能为当前操作定义方法，如果检测到相关的restful方法则不再检查后面的方法规范，例如我们定义了InfoController如下：

namespace app\index\controller;

use think\controller\Rest;

class Info extends Rest
{
    public function read_get_xml($id)
    {
        // 输出id为1的Info的XML数据
    }

    public function read_xml($id)
    {
        // 输出id为1的Info的XML数据
    }

    public function read_json($id)
    {
        // 输出id为1的Info的json数据
    }
}

如果我们访问的URL是：

http://serverName/index/info/read/id/1.xml

假设我们没有定义路由，这样访问的是Info模块的read操作，那么上面的请求会调用Info类的 read_get_xml方法，而不是read_xml方法，但是如果访问的URL是：

http://serverName/index/info/read/id/1.json

那么则会调用read_json方法。


自动定位控制器

如果你使用了多级控制器的话，可以设置controller_auto_search参数开启自动定位控制器，便于URL访问，例如首先在配置文件中添加：

'controller_auto_search' => true,

然后定义控制器如下：

namespace app\index\controller\one;

use think\Controller;

class Blog extends Controller
{
    public function index()
    {
        return $this->fetch();
    }

    public function add()
    {
        return $this->fetch();
    }

    public function edit($id)
    {
        return $this->fetch('edit:'.$id);
    }
}

我们就可以直接访问下面的URL地址了：

http://serverName/index.php/index/one/Blog


资源控制器

资源控制器可以让你轻松的创建RESTFul资源控制器，可以通过命令行生成需要的资源控制器，例如：

// 生成index模块的Blog资源控制器
php think make:controller index/Blog

或者使用完整的命名空间生成

php think make:controller app\index\controller\Blog

然后你只需要为资源控制器注册一个资源路由：

Route::resource('blog','index/Blog');

设置后会自动注册7个路由规则，如下：

请求类型 | 生成路由规则 | 对应操作方法
GET | blog | index
GET | blog/create | create
POST | blog | save
GET | blog/:id | read
GET | blog/:id/edit | edit
PUT | blog/:id | update
DELETE | blog/:id | delete


请求信息

如果要获取当前的请求信息，可以使用\think\Request类，
除了下文中的

$request = Request::instance();

也可以使用助手函数

$request = request();

当然，最方便的还是使用注入请求对象的方式来获取变量。

例如：

获取URL信息

$request = Request::instance();
// 获取当前域名
echo 'domain: ' . $request->domain() . '<br/>';
// 获取当前入口文件
echo 'file: ' . $request->baseFile() . '<br/>';
// 获取当前URL地址 不含域名
echo 'url: ' . $request->url() . '<br/>';
// 获取包含域名的完整URL地址
echo 'url with domain: ' . $request->url(true) . '<br/>';
// 获取当前URL地址 不含QUERY_STRING
echo 'url without query: ' . $request->baseUrl() . '<br/>';
// 获取URL访问的ROOT地址
echo 'root:' . $request->root() . '<br/>';
// 获取URL访问的ROOT地址
echo 'root with domain: ' . $request->root(true) . '<br/>';
// 获取URL地址中的PATH_INFO信息
echo 'pathinfo: ' . $request->pathinfo() . '<br/>';
// 获取URL地址中的PATH_INFO信息 不含后缀
echo 'pathinfo: ' . $request->path() . '<br/>';
// 获取URL地址中的后缀信息
echo 'ext: ' . $request->ext() . '<br/>';

输出结果为：

domain: http://tp5.com
file: /index.php
url: /index/index/hello.html?name=thinkphp
url with domain: http://tp5.com/index/index/hello.html?name=thinkphp
url without query: /index/index/hello.html
root:
root with domain: http://tp5.com
pathinfo: index/index/hello.html
pathinfo: index/index/hello
ext: html

设置/获取 模块/控制器/操作名称

$request = Request::instance();
echo "当前模块名称是" . $request->module();
echo "当前控制器名称是" . $request->controller();
echo "当前操作名称是" . $request->action();

如果当前访问的地址是 http://serverName/index.php/index/hello_world/index
输出结果为：

当前模块名称是index
当前控制器名称是HelloWorld
当前操作名称是index

设置模块名称值需要像module方法中传入名称即可，同样使用于设置控制器名称和操作名称

Request::instance()->module('module_name');

获取请求参数

$request = Request::instance();
echo '请求方法：' . $request->method() . '<br/>';
echo '资源类型：' . $request->type() . '<br/>';
echo '访问地址：' . $request->ip() . '<br/>';
echo '是否AJax请求：' . var_export($request->isAjax(), true) . '<br/>';
echo '请求参数：';
dump($request->param());
echo '请求参数：仅包含name';
dump($request->only(['name']));
echo '请求参数：排除name';
dump($request->except(['name']));

输出结果为：

请求方法：GET
资源类型：html
访问地址：127.0.0.1
是否AJax请求：false
请求参数：
array (size=2)
  'test' => string 'ddd' (length=3)
  'name' => string 'thinkphp' (length=8)

请求参数：仅包含name
array (size=1)
  'name' => string 'thinkphp' (length=8)

请求参数：排除name
array (size=1)
  'test' => string 'ddd' (length=3)

获取路由和调度信息

hello方法修改如下：

$request = Request::instance();
echo '路由信息：';
dump($request->route());
echo '调度信息：';
dump($request->dispatch());

路由定义为：

return [
    'hello/:name' =>['index/hello',[],['name'=>'\w+']],
];

访问下面的URL地址：

http://serverName/hello/thinkphp

输出信息为：

路由信息：
array (size=4)
  'rule' => string 'hello/:name' (length=11)
  'route' => string 'index/hello' (length=11)
  'pattern' =>
    array (size=1)
      'name' => string '\w+' (length=3)
  'option' =>
    array (size=0)
      empty

调度信息：
array (size=2)
  'type' => string 'module' (length=6)
  'module' =>
    array (size=3)
      0 => null
      1 => string 'index' (length=5)
      2 => string 'hello' (length=5)

设置请求信息

如果某些环境下面获取的请求信息有误，可以手动设置这些信息参数，使用下面的方式：

$request = Request::instance();
$request->root('index.php');
$request->pathinfo('index/index/hello');


输入变量

概述

可以通过Request对象完成全局输入变量的检测、获取和安全过滤，支持包括$_GET、$_POST、$_REQUEST、$_SERVER、$_SESSION、$_COOKIE、$_ENV等系统变量，以及文件上传信息。

检测变量是否设置

可以使用has方法来检测一个变量参数是否设置，如下：

Request::instance()->has('id','get');
Request::instance()->has('name','post');

或者使用助手函数

input('?get.id');
input('?post.name');

变量检测可以支持所有支持的系统变量。

变量获取

变量获取使用\think\Request类的如下方法及参数：

变量类型方法('变量名/变量修饰符','默认值','过滤方法')

变量类型方法包括：

方法 | 描述
param | 获取当前请求的变量
get | 获取 $_GET 变量
post | 获取 $_POST 变量
put | 获取 PUT 变量
delete | 获取 DELETE 变量
session | 获取 $_SESSION 变量
cookie | 获取 $_COOKIE 变量
request | 获取 $_REQUEST 变量
server | 获取 $_SERVER 变量
env | 获取 $_ENV 变量
route | 获取 路由（包括PATHINFO） 变量
file | 获取 $_FILES 变量

获取PARAM变量

PARAM变量是框架提供的用于自动识别GET、POST或者PUT请求的一种变量获取方式，是系统推荐的获取请求参数的方法，用法如下：

// 获取当前请求的name变量
Request::instance()->param('name');
// 获取当前请求的所有变量（经过过滤）
Request::instance()->param();
// 获取当前请求的所有变量（原始数据）
Request::instance()->param(false);
// 获取当前请求的所有变量（包含上传文件）
Request::instance()->param(true);

param方法会把当前请求类型的参数和PATH_INFO变量以及GET请求合并。

使用助手函数实现：

input('param.name');
input('param.');
或者
input('name');
input('');

因为input函数默认就采用PARAM变量读取方式。

获取GET变量

Request::instance()->get('id'); // 获取某个get变量
Request::instance()->get('name'); // 获取get变量
Request::instance()->get(); // 获取所有的get变量（经过过滤的数组）
Request::instance()->get(false); // 获取所有的get变量（原始数组）

或者使用内置的助手函数input方法实现相同的功能：

input('get.id');
input('get.name');
input('get.');

注：pathinfo地址参数不能通过get方法获取，查看“获取PARAM变量”

获取POST变量

Request::instance()->post('name'); // 获取某个post变量
Request::instance()->post(); // 获取经过过滤的全部post变量
Request::instance()->post(false); // 获取全部的post原始变量

使用助手函数实现：

input('post.name');
input('post.');

获取PUT变量

Request::instance()->put('name'); // 获取某个put变量
Request::instance()->put(); // 获取全部的put变量（经过过滤）
Request::instance()->put(false); // 获取全部的put原始变量

使用助手函数实现：

input('put.name');
input('put.');

获取REQUEST变量

Request::instance()->request('id'); // 获取某个request变量
Request::instance()->request(); // 获取全部的request变量（经过过滤）
Request::instance()->request(false); // 获取全部的request原始变量数据

使用助手函数实现：

input('request.id');
input('request.');

获取SERVER变量

Request::instance()->server('PHP_SELF'); // 获取某个server变量
Request::instance()->server(); // 获取全部的server变量

使用助手函数实现：

input('server.PHP_SELF');
input('server.');

获取SESSION变量

Request::instance()->session('user_id'); // 获取某个session变量
Request::instance()->session(); // 获取全部的session变量

使用助手函数实现：

input('session.user_id');
input('session.');

获取Cookie变量

Request::instance()->cookie('user_id'); // 获取某个cookie变量
Request::instance()->cookie(); // 获取全部的cookie变量

使用助手函数实现：

input('cookie.user_id');
input('cookie.');

变量过滤

框架默认没有设置任何过滤规则，你可以是配置文件中设置全局的过滤规则：

// 默认全局过滤方法 用逗号分隔多个
'default_filter'         => 'htmlspecialchars',

也支持使用Request对象进行全局变量的获取过滤，过滤方式包括函数、方法过滤，以及PHP内置的Types of filters，我们可以设置全局变量过滤方法，例如：

Request::instance()->filter('htmlspecialchars');

支持设置多个过滤方法，例如：

Request::instance()->filter(['strip_tags','htmlspecialchars']),

也可以在获取变量的时候添加过滤方法，例如：

Request::instance()->get('name','','htmlspecialchars'); // 获取get变量 并用htmlspecialchars函数过滤
Request::instance()->param('username','','strip_tags'); // 获取param变量 并用strip_tags函数过滤
Request::instance()->post('name','','org\Filter::safeHtml'); // 获取post变量 并用org\Filter类的safeHtml方法过滤

可以支持传入多个过滤规则，例如：

Request::instance()->param('username','','strip_tags,strtolower'); // 获取param变量 并依次调用strip_tags、strtolower函数过滤

Request对象还支持PHP内置提供的Filter ID过滤，例如：

Request::instance()->post('email','',FILTER_VALIDATE_EMAIL);

框架对FilterID做了转换支持，因此也可以使用字符串的方式，例如：

Request::instance()->post('email','','email');

采用字符串方式定义FilterID的时候，系统会自动进行一次filter_id调用转换成Filter常量。

具体的字符串根据filter_list函数的返回值来定义。

需要注意的是，采用Filter ID 进行过滤的话，如果不符合过滤要求的话 会返回false，因此你需要配合默认值来确保最终的值符合你的规范。

例如，

Request::instance()->post('email','',FILTER_VALIDATE_EMAIL);

就表示，如果不是规范的email地址的话 返回空字符串。

如果当前不需要进行任何过滤的话，可以使用（V5.0.3+版本）

// 获取get变量 并且不进行任何过滤 即使设置了全局过滤
Request::instance()->get('name','',false);

获取部分变量

如果你只需要获取当前请求的部分参数，可以使用：

// 只获取当前请求的id和name变量
Request::instance()->only('id,name');

或者使用数组方式

// 只获取当前请求的id和name变量
Request::instance()->only(['id','name']);

默认获取的是当前请求参数，如果需要获取其它类型的参数，可以使用第二个参数，例如：

// 只获取GET请求的id和name变量
Request::instance()->only(['id','name'],'get');
// 只获取POST请求的id和name变量
Request::instance()->only(['id','name'],'post');

排除部分变量

也支持排除某些变量获取，例如

// 排除id和name变量
Request::instance()->except('id,name');

或者使用数组方式

// 排除id和name变量
Request::instance()->except(['id','name']);

同样支持指定变量类型获取：

// 排除GET请求的id和name变量
Request::instance()->except(['id','name'],'get');
// 排除POST请求的id和name变量
Request::instance()->except(['id','name'],'post');

变量修饰符

input函数支持对变量使用修饰符功能，可以更好的过滤变量。

用法如下：

input('变量类型.变量名/修饰符');

或者

Request::instance()->变量类型('变量名/修饰符');

例如：

input('get.id/d');
input('post.name/s');
input('post.ids/a');
Request::instance()->get('id/d');

ThinkPHP5.0版本默认的变量修饰符是/s，如果需要传入字符串之外的变量可以使用下面的修饰符，包括：

修饰符 | 作用
s | 强制转换为字符串类型
d | 强制转换为整型类型
b | 强制转换为布尔类型
a | 强制转换为数组类型
f | 强制转换为浮点类型

如果你要获取的数据为数组，请一定注意要加上 /a 修饰符才能正确获取到。


更改变量

如果需要更改请求变量的值，可以通过下面的方式：

// 更改GET变量
Request::instance()->get(['id'=>10]);
// 更改POST变量
Request::instance()->post(['name'=>'thinkphp']);

尽量避免直接修改$_GET 或者 $_POST数据，同时也不能直接修改param变量，例如下面的操作是无效的：

// 更改请求变量
Request::instance()->param(['id'=>10]);


请求类型

获取请求类型

在很多情况下面，我们需要判断当前操作的请求类型是GET、POST、PUT、DELETE或者HEAD，一方面可以针对请求类型作出不同的逻辑处理，另外一方面有些情况下面需要验证安全性，过滤不安全的请求。

ThinkPHP5.0 取消了用于判断请求类型的系统常量(如IS_GET，IS_POST等)，统一采用 think\Request类 处理请求类型。

用法如下

// 是否为 GET 请求
if (Request::instance()->isGet()) echo "当前为 GET 请求";
// 是否为 POST 请求
if (Request::instance()->isPost()) echo "当前为 POST 请求";
// 是否为 PUT 请求
if (Request::instance()->isPut()) echo "当前为 PUT 请求";
// 是否为 DELETE 请求
if (Request::instance()->isDelete()) echo "当前为 DELETE 请求";
// 是否为 Ajax 请求
if (Request::instance()->isAjax()) echo "当前为 Ajax 请求";
// 是否为 Pjax 请求
if (Request::instance()->isPjax()) echo "当前为 Pjax 请求";
// 是否为手机访问
if (Request::instance()->isMobile()) echo "当前为手机访问";
// 是否为 HEAD 请求
if (Request::instance()->isHead()) echo "当前为 HEAD 请求";
// 是否为 Patch 请求
if (Request::instance()->isPatch()) echo "当前为 PATCH 请求";
// 是否为 OPTIONS 请求
if (Request::instance()->isOptions()) echo "当前为 OPTIONS 请求";
// 是否为 cli
if (Request::instance()->isCli()) echo "当前为 cli";
// 是否为 cgi
if (Request::instance()->isCgi()) echo "当前为 cgi";

助手函数

// 是否为 GET 请求
if (request()->isGet()) echo "当前为 GET 请求";
……

通过注入请求对象的功能，可以更简单的实现。请参考后面的注入请求对象一节的内容。


请求伪装

请求类型伪装

支持请求类型伪装，可以在POST表单里面提交_method变量，传入需要伪装的请求类型，例如：

<form method="post" action="">
    <input type="text" name="name" value="Hello">
    <input type="hidden" name="_method" value="PUT" >
    <input type="submit" value="提交">
</form>

提交后的请求类型会被系统识别为PUT请求。

你可以设置为任何合法的请求类型，包括GET、POST、PUT和DELETE等。

如果你需要改变伪装请求的变量名，可以修改应用配置文件：

// 表单请求类型伪装变量
'var_method'             => '_m',

AJAX/PJAX伪装

可以对请求进行AJAX请求伪装，如下：

http://localhost/index?_ajax=1

或者PJAX请求伪装

http://localhost/index?_pjax=1

如果你需要改变伪装请求的变量名，可以修改应用配置文件：

// 表单ajax伪装变量
'var_ajax'               => '_a',
// 表单pjax伪装变量
'var_pjax'               => '_p',

_ajax和_pjax可以通过GET/POST/PUT等请求变量伪装。


HTTP头信息

可以使用Request对象的header方法获取当前请求的HTTP 请求头信息，例如：

$info = Request::instance()->header();
echo $info['accept'];
echo $info['accept-encoding'];
echo $info['user-agent'];

也可以直接获取某个请求头信息，例如：

$agent = Request::instance()->header('user-agent');

HTTP请求头信息的名称不区分大小写，并且_会自动转换为-，所以下面的写法都是等效的：

$agent = Request::instance()->header('user-agent');
$agent = Request::instance()->header('User-Agent');
$agent = Request::instance()->header('USER_AGENT');


伪静态

URL伪静态通常是为了满足更好的SEO效果，ThinkPHP支持伪静态URL设置，可以通过设置url_html_suffix参数随意在URL的最后增加你想要的静态后缀，而不会影响当前操作的正常执行。例如，我们设置

'url_html_suffix' => 'shtml'

的话，我们可以把下面的URL http://serverName/Home/Blog/read/id/1 变成 http://serverName/Home/Blog/read/id/1.shtml

后者更具有静态页面的URL特征，但是具有和前面的URL相同的执行效果，并且不会影响原来参数的使用。

默认情况下，伪静态的设置为html，如果我们设置伪静态后缀为空字符串，

'url_html_suffix'=>''

则支持所有的静态后缀访问，如果要获取当前的伪静态后缀，可以使用Request对象的ext方法。

例如：

http://serverName/index/blog/3.html
http://serverName/index/blog/3.shtml
http://serverName/index/blog/3.xml
http://serverName/index/blog/3.pdf

都可以正常访问。

我们可以在控制器的操作方法中获取当前访问的伪静态后缀，例如：

$ext = Request::instance()->ext();

如果希望支持多个伪静态后缀，可以直接设置如下：

// 多个伪静态后缀设置 用|分割
'url_html_suffix' => 'html|shtml|xml'

那么，当访问 http://serverName/Home/blog/3.pdf 的时候会报系统错误。

如果要关闭伪静态访问，可以设置

// 关闭伪静态后缀访问
'url_html_suffix' => false,

关闭伪静态访问后，不再支持伪静态方式的URL访问，并且伪静态后缀将会被解析为最后一个参数的值，例如：

http://serverName/index/blog/read/id/3.html

最终的id参数的值将会变成 3.html。


方法注入

如果你需要在Request请求对象中添加自己的方法，可以使用Request对象的方法注入功能，例如：

// 通过hook方法注入动态方法
Request::hook('user','getUserInfo');

getUserInfo函数定义如下

function getUserInfo(Request $request, $userId)
{
    // 根据$userId获取用户信息
    return $info;
}

接下来，我们可以直接在控制器中使用：

public function index()
{
    $info = Request::instance()->user($userId);
}


属性注入

可以动态注入当前Request对象的属性，方法：

// 动态绑定属性
Request::instance()->bind('user',new User);
// 或者使用
Request::instance()->user = new User;

获取绑定的属性使用下面的方式：

Request::instance()->user;

如果控制器注入请求对象的话，也可以直接使用

$this->request->user;

或者使用助手函数：

request()->user;


参数绑定

方法参数绑定是把URL地址（或者路由地址）中的变量作为操作方法的参数直接传入。

操作方法参数绑定

按名称绑定

参数绑定方式默认是按照变量名进行绑定，例如，我们给Blog控制器定义了两个操作方法read和archive方法，由于read操作需要指定一个id参数，archive方法需要指定年份（year）和月份（month）两个参数，那么我们可以如下定义：

namespace app\index\Controller;

class Blog
{
    public function read($id)
    {
        return 'id='.$id;
    }

    public function archive($year='2016',$month='01')
    {
        return 'year='.$year.'&month='.$month;
    }
}

注意这里的操作方法并没有具体的业务逻辑，只是简单的示范。

URL的访问地址分别是：

http://serverName/index.php/index/blog/read/id/5
http://serverName/index.php/index/blog/archive/year/2016/month/06

两个URL地址中的id参数和year和month参数会自动和read操作方法以及archive操作方法的同名参数绑定。

变量名绑定不一定由访问URL决定，路由地址也能起到相同的作用

输出的结果依次是：

id=5
year=2016&month=06

按照变量名进行参数绑定的参数必须和URL中传入的变量名称一致，但是参数顺序不需要一致。也就是说

http://serverName/index.php/index/blog/archive/month/06/year/2016

和上面的访问结果是一致的，URL中的参数顺序和操作方法中的参数顺序都可以随意调整，关键是确保参数名称一致即可。

如果用户访问的URL地址是（至于为什么会这么访问暂且不提）：

http://serverName/index.php/index/blog/read/

那么会抛出下面的异常提示： 参数错误:id

报错的原因很简单，因为在执行read操作方法的时候，id参数是必须传入参数的，但是方法无法从URL地址中获取正确的id参数信息。由于我们不能相信用户的任何输入，因此建议你给read方法的id参数添加默认值，例如：

    public function read($id=0)
    {
        return 'id='.$id;
    }

这样，当我们访问 http://serverName/index.php/index/blog/read/ 的时候 就会输出

id=0

始终给操作方法的参数定义默认值是一个避免报错的好办法

按顺序绑定

还可以支持按照URL的参数顺序进行绑定的方式，合理规划URL参数的顺序绑定对简化URL地址可以起到一定的帮助。

还是上面的例子，控制器不变，还是使用：

namespace app\index\Controller;

class Blog
{
    public function read($id)
    {
        return 'id='.$id;
    }

    public function archive($year='2016',$month='01')
    {
        return 'year='.$year.'&month='.$month;
    }
}

我们在配置文件中添加配置参数如下：

// URL参数方式改成顺序解析
'url_param_type'         => 1,

接下来，访问下面的URL地址：

http://serverName/index.php/index/blog/read/5
http://serverName/index.php/index/blog/archive/2016/06

输出的结果依次是：

id=5
year=2016&month=06

按参数顺序绑定的话，参数的顺序不能随意调整，如果访问：

http://serverName/index.php/index/blog/archive/06/2016

最后的输出结果则变成：

id=5
year=06&month=2016

注意

按顺序绑定参数的话，操作方法的参数只能使用URL pathinfo变量，而不能使用get或者post变量。

参数绑定有一个特例，如果你的操作方法中定义有Request对象作为参数的话，无论参数位置在哪里，都会自动注入，而不需要进行参数绑定。

架构方法参数绑定（V5.0.1）

可以对架构函数进行参数绑定，当前请求的路由变量可以自动绑定到架构函数的参数，例如：

namespace app\index\Controller;

class Blog
{
    protected $name;
    public function __construct($name = null)
    {
        $this->name = $name;
    }
}

如果访问

http://localhost/index/index/index/name/thinkphp

当前请求的路由变量name的值thinkphp会自动传入架构方法的name变量。


依赖注入

ThinkPHP的依赖注入（也称之为控制反转）是一种较为轻量的实现，无需任何的配置，并且主要针对访问控制器进行依赖注入。可以在控制器的构造函数或者操作方法（指访问请求的方法）中类型声明任何（对象类型）依赖，这些依赖会被自动解析并注入到控制器实例或方法中。

自动注入请求对象

架构方法注入

在控制器的架构方法中会自动注入当前请求对象，例如：

namespace app\index\controller;

use think\Request;

class Index
{
    protected $request;

    public function __construct(Request $request)
    {
        $this->request = $request;
    }

    public function hello()
    {
        return 'Hello,' . $this->request->param('name') . '！';
    }

}

操作方法注入

控制器的操作方法中如果需要调用请求对象Request的话，可以在方法中定义Request类型的参数，并且参数顺序无关，例如：

namespace app\index\controller;

use think\Request;

class Index
{

    public function hello(Request $request)
    {
        return 'Hello,' . $request->param('name') . '！';
    }

}

访问URL地址的时候 无需传入request参数，系统会自动注入当前的Request对象实例到该参数。

如果继承了系统的Controller类的话，也可以直接调用request属性，例如：

<?php
namespace app\index\controller;

use think\Controller;

class Index extends Controller
{

    public function hello()
    {
        return 'Hello,'.$this->request->param('name');
    }

}

其它对象自动注入（V5.0.1）

从5.0.1版本开始，控制器的架构方法和操作方法支持任意对象的自动注入。

架构方法注入

namespace app\index\controller;

use app\index\model\User;
use think\Request;

class Index
{
    protected $request;
    protected $user;

    public function __construct(Request $request, User $user)
    {
        $this->request = $request;
        $this->user = $user;
    }

}

对于已经进行了绑定（属性注入）的对象，即可自动完成依赖注入，如果没有进行对象绑定的话，会自动实例化一个新的对象示例传入（如果类定义有instance方法，则会自动调用instance方法进行实例化）。

架构方法的依赖注入不影响其它类型的参数绑定。

操作方法注入

我们把User模型绑定到当前请求对象：

Request::instance()->bind('user', \app\index\model\User::get(1));

然后就可以在操作方法中进行对象参数的自动注入，代码：

<?php
namespace app\index\controller;

use app\index\model\User;
use think\Controller;

class Index extends Controller
{

    public function hello(User $user)
    {
        return 'Hello,'.$user->name;
    }

}

如果没有事先在Request对象中进行对象绑定的话，调用hello方法的时候user参数会自动实例化，相当于完成了下面的绑定操作：

Request::instance()->bind('user', new \app\index\model\User);

对象自动注入不影响原来的参数绑定。

invoke方法自动调用（v5.0.2）

5.0.2版本开始，如果依赖注入的类有定义一个可调用的静态invoke方法，则会自动调用invoke方法完成依赖注入的自动实例化。

invoke方法的参数是当前请求对象实例，例如：

namespace app\index\model;

use think\Model;
class User extends Model
{
    public static function invoke(Request $request)
    {
        $id = $request->param('id');
        return User::get($id);
    }
}


请求缓存

请求缓存（V5.0.1）

5.0.1以上版本支持请求缓存功能，支持对请求地址设置缓存访问，并设置有效期。

版本 | 新增功能
5.0.4 | 请求缓存支持模块单独开启
5.0.3 | 增加全局请求缓存支持

请求缓存仅对GET请求有效，有两种方式可以设置请求缓存：

路由参数

可以在路由规则里面定义cache参数开启当前路由规则的请求缓存，例如：

// 定义GET请求路由规则 并设置3600秒的缓存
Route::get('new/:id','News/read',['cache'=>3600]);

第二次访问相同的路由地址的时候，会自动获取请求缓存的数据响应输出，并发送304状态码。

默认请求缓存的标识为当前访问的pathinfo地址，可以定义请求缓存的标识，如下：

// 定义GET请求路由规则 并设置3600秒的缓存
Route::get('new/:id','News/read',[
    'cache' =>  [ 'new/:id/:page',3600]
]);

:id、:page表示使用当前请求的param参数进行动态标识替换，也就是根据id和page变量进行3600秒的请求缓存。

如果cache参数传入false，则表示关闭当前路由的请求缓存（即使开启全局请求缓存）。

动态设置

可以在app_begin行为里面动态设置请求缓存，例如：

Request::instance()->cache('blog/:id',3600);

表示对blog/:id定义的动态访问地址进行3600秒的请求缓存。

变量支持当前的请求变量（也就是param方法的所有变量）。

可以使用当前的URL地址作为缓存标识，如下：

Request::instance()->cache('__URL__',600);

支持对某个URL后缀的请求进行缓存，例如：

Request::instance()->cache('[html]',600);

表示对所有的html后缀访问（GET）请求进行10分钟的缓存。

V5.0.3+版本增加了请求缓存自动判断，只需要在配置文件中开启：

'request_cache' =>  true,
'request_cache_expire'  =>  3600,

就会自动根据当前请求URL地址（只针对GET请求类型）进行请求缓存，全局缓存有效期为3600秒。

V5.0.4+版本开始可以在模块配置中单独设置开启请求缓存。

路由中设置的请求缓存依然有效并且优先。


连接数据库

ThinkPHP内置了抽象数据库访问层，把不同的数据库操作封装起来，我们只需要使用公共的Db类进行操作，而无需针对不同的数据库写不同的代码和底层实现，Db类会自动调用相应的数据库驱动来处理。采用PDO方式，目前包含了Mysql、SqlServer、PgSQL、Sqlite等数据库的支持。

如果应用需要使用数据库，必须配置数据库连接信息，数据库的配置文件有多种定义方式。

  • 一、配置文件定义
  • 二、方法配置
  • 三、模型类定义
  • 配置参数参考

一、配置文件定义

常用的配置方式是在应用目录或者模块目录下面的database.php中添加下面的配置参数：

return [
    // 数据库类型
    'type'        => 'mysql',
    // 数据库连接DSN配置
    'dsn'         => '',
    // 服务器地址
    'hostname'    => '127.0.0.1',
    // 数据库名
    'database'    => 'thinkphp',
    // 数据库用户名
    'username'    => 'root',
    // 数据库密码
    'password'    => '',
    // 数据库连接端口
    'hostport'    => '',
    // 数据库连接参数
    'params'      => [],
    // 数据库编码默认采用utf8
    'charset'     => 'utf8',
    // 数据库表前缀
    'prefix'      => 'think_',
    // 数据库调试模式
    'debug'       => false,
    // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
    'deploy'      => 0,
    // 数据库读写是否分离 主从式有效
    'rw_separate' => false,
    // 读写分离后 主服务器数量
    'master_num'  => 1,
    // 指定从服务器序号
    'slave_no'    => '',
    // 是否严格检查字段是否存在
    'fields_strict'  => true,
];

type参数支持命名空间完整定义，不带命名空间定义的话，默认采用\think\db\connector作为命名空间，如果使用应用自己扩展的数据库驱动，可以配置为：

// 数据库类型
'type'        => '\org\db\Mysql',

表示数据库的连接器采用 \org\db\Mysql类作为数据库连接驱动，而不是默认的\think\db\connector\Mysql。

每个模块可以设置独立的数据库连接参数，并且相同的配置参数可以无需重复设置，例如我们可以在admin模块的database.php配置文件中定义：

return [
    // 服务器地址
    'hostname'    => '192.168.1.100',
    // 数据库名
    'database'    => 'admin',
];

表示admin模块的数据库地址改成 192.168.1.100，数据库名改成admin，其它的连接参数和应用的database.php中的配置一样。

连接参数

可以针对不同的连接需要添加数据库的连接参数（具体的连接参数可以参考PHP手册），内置采用的参数包括如下：

PDO::ATTR_CASE              => PDO::CASE_NATURAL,
PDO::ATTR_ERRMODE           => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_ORACLE_NULLS      => PDO::NULL_NATURAL,
PDO::ATTR_STRINGIFY_FETCHES => false,
PDO::ATTR_EMULATE_PREPARES  => false,

在database中设置的params参数中的连接配置将会和内置的设置参数合并，如果需要使用长连接，并且返回数据库的小写列名，可以采用下面的方式定义：

'params' => [
    \PDO::ATTR_PERSISTENT   => true,
    \PDO::ATTR_CASE         => \PDO::CASE_LOWER,
],

你可以在params里面配置任何PDO支持的连接参数。

二、方法配置

我们可以在调用Db类的时候动态定义连接信息，例如：

Db::connect([
    // 数据库类型
    'type'        => 'mysql',
    // 数据库连接DSN配置
    'dsn'         => '',
    // 服务器地址
    'hostname'    => '127.0.0.1',
    // 数据库名
    'database'    => 'thinkphp',
    // 数据库用户名
    'username'    => 'root',
    // 数据库密码
    'password'    => '',
    // 数据库连接端口
    'hostport'    => '',
    // 数据库连接参数
    'params'      => [],
    // 数据库编码默认采用utf8
    'charset'     => 'utf8',
    // 数据库表前缀
    'prefix'      => 'think_',
]);

或者使用字符串方式：

Db::connect('mysql://root:1234@127.0.0.1:3306/thinkphp#utf8');

字符串连接的定义格式为：

数据库类型://用户名:密码@数据库地址:数据库端口/数据库名#字符集

注意：字符串方式可能无法定义某些参数，例如前缀和连接参数。

如果我们已经在应用配置文件（注意这里不是数据库配置文件）中配置了额外的数据库连接信息，例如：

//数据库配置1
'db_config1' => [
    // 数据库类型
    'type'        => 'mysql',
    // 服务器地址
    'hostname'    => '127.0.0.1',
    // 数据库名
    'database'    => 'thinkphp',
    // 数据库用户名
    'username'    => 'root',
    // 数据库密码
    'password'    => '',
    // 数据库编码默认采用utf8
    'charset'     => 'utf8',
    // 数据库表前缀
    'prefix'      => 'think_',
],
//数据库配置2
'db_config2' => 'mysql://root:1234@localhost:3306/thinkphp#utf8';

我们可以改成

Db::connect('db_config1');
Db::connect('db_config2');

三、模型类定义

如果在某个模型类里面定义了connection属性的话，则该模型操作的时候会自动连接给定的数据库连接，而不是配置文件中设置的默认连接信息，通常用于某些数据表位于当前数据库连接之外的其它数据库，例如：

//在模型里单独设置数据库连接信息
namespace app\index\model;

use think\Model;

class User extends Model
{
    protected $connection = [
        // 数据库类型
        'type'        => 'mysql',
        // 数据库连接DSN配置
        'dsn'         => '',
        // 服务器地址
        'hostname'    => '127.0.0.1',
        // 数据库名
        'database'    => 'thinkphp',
        // 数据库用户名
        'username'    => 'root',
        // 数据库密码
        'password'    => '',
        // 数据库连接端口
        'hostport'    => '',
        // 数据库连接参数
        'params'      => [],
        // 数据库编码默认采用utf8
        'charset'     => 'utf8',
        // 数据库表前缀
        'prefix'      => 'think_',
    ];
}

也可以采用DSN字符串方式定义，例如：

//在模型里单独设置数据库连接信息
namespace app\index\model;

use think\Model;

class User extends Model
{
    //或者使用字符串定义
    protected $connection = 'mysql://root:1234@127.0.0.1:3306/thinkphp#utf8';
}

需要注意的是，ThinkPHP的数据库连接是惰性的，所以并不是在实例化的时候就连接数据库，而是在有实际的数据操作的时候才会去连接数据库。

配置参数参考

下面是默认支持的数据库连接信息：

参数名 | 描述 | 默认值
type | 数据库类型 | 无
hostname | 数据库地址 | 127.0.0.1
database | 数据库名称 | 无
username | 数据库用户名 | 无
password | 数据库密码 | 无
hostport | 数据库端口号 | 无
dsn | 数据库连接dsn信息 | 无
params | 数据库连接参数 | 空
charset | 数据库编码 | utf8
prefix | 数据库的表前缀 | 无
debug | 是否调试模式 | false
deploy | 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) | 0
rw_separate | 数据库读写是否分离 主从式有效 | false
master_num | 读写分离后 主服务器数量 | 1
slave_no | 指定从服务器序号 | 无
fields_strict | 是否严格检查字段是否存在 | true
resultset_type | 数据集返回类型 | array
auto_timestamp | 自动写入时间戳字段 | false
sql_explain | 是否需要进行SQL性能分析 开启调试有效 | false
query | 指定查询对象 | think\db\Query
builder | 指定数据库Builder对象 | 无

常用数据库连接参数（params）可以参考PHP在线手册中的以PDO::ATTR_开头的常量。

注意：

如果是使用pgsql数据库驱动的话，请先导入 thinkphp/library/think/db/connector/pgsql.sql文件到数据库执行。


基本使用

配置了数据库连接信息后，我们就可以直接使用数据库运行原生SQL操作了，支持query（查询操作）和execute（写入操作）方法，并且支持参数绑定。

Db::query('select * from think_user where id=?',[8]);
Db::execute('insert into think_user (id, name) values (?, ?)',[8,'thinkphp']);

也支持命名占位符绑定，例如：

Db::query('select * from think_user where id=:id',['id'=>8]);
Db::execute('insert into think_user (id, name) values (:id, :name)',['id'=>8,'name'=>'thinkphp']);

可以使用多个数据库连接，使用

Db::connect($config)->query('select * from think_user where id=:id',['id'=>8]);

config是一个单独的数据库配置，支持数组和字符串，也可以是一个数据库连接的配置参数名。


查询方法

条件查询方法

where方法

可以使用where方法进行AND条件查询：

Db::table('think_user')
    ->where('name','like','%thinkphp')
    ->where('status',1)
    ->find();

多字段相同条件的AND查询可以简化为如下方式：

Db::table('think_user')
    ->where('name&title','like','%thinkphp')
    ->find();

whereOr方法

使用whereOr方法进行OR查询：

Db::table('think_user')
    ->where('name','like','%thinkphp')
    ->whereOr('title','like','%thinkphp')
    ->find();

多字段相同条件的OR查询可以简化为如下方式：

Db::table('think_user')
    ->where('name|title','like','%thinkphp')
    ->find();

混合查询

where方法和whereOr方法在复杂的查询条件中经常需要配合一起混合使用，下面举个例子：

$result = Db::table('think_user')->where(function ($query) {
    $query->where('id', 1)->whereor('id', 2);
})->whereOr(function ($query) {
    $query->where('name', 'like', 'think')->whereOr('name', 'like', 'thinkphp');
})->select();

生成的sql语句类似于下面：

SELECT * FROM `think_user` WHERE  (  `id` = 1 OR `id` = 2 ) OR (  `name` LIKE 'think' OR `name` LIKE 'thinkphp' )

注意闭包查询里面的顺序，而且第一个查询方法用where或者whereOr是没有区别的。

getTableInfo方法

使用getTableInfo可以获取表信息，信息类型 包括 fields,type,bind,pk，以数组的形式展示，可以指定某个信息进行获取

// 获取`think_user`表所有信息
Db::getTableInfo('think_user');
// 获取`think_user`表所有字段
Db::getTableInfo('think_user', 'fields');
// 获取`think_user`表所有字段的类型
Db::getTableInfo('think_user', 'type');
// 获取`think_user`表的主键
Db::getTableInfo('think_user', 'pk');


查询数据

基本查询

查询一个数据使用：

// table方法必须指定完整的数据表名
Db::table('think_user')->where('id',1)->find();

find 方法查询结果不存在，返回 null

查询数据集使用：

Db::table('think_user')->where('status',1)->select();

select 方法查询结果不存在，返回空数组

如果设置了数据表前缀参数的话，可以使用

Db::name('user')->where('id',1)->find();
Db::name('user')->where('status',1)->select();

如果你的数据表没有使用表前缀功能，那么name和table方法的一样的效果。

在find和select方法之前可以使用所有的链式操作方法。

默认情况下，find和select方法返回的都是数组。

助手函数

系统提供了一个db助手函数，可以更方便的查询：

db('user')->where('id',1)->find();
db('user')->where('status',1)->select();

注意：使用db助手函数默认每次都会重新连接数据库，而使用Db::name或者Db::table方法的话都是单例的。db函数如果需要采用相同的链接，可以传入第三个参数，例如：

db('user',[],false)->where('id',1)->find();
db('user',[],false)->where('status',1)->select();

上面的方式会使用同一个数据库连接，第二个参数为数据库的连接参数，留空表示采用数据库配置文件的配置。

使用Query对象或闭包查询

或者使用查询对象进行查询，例如：

$query = new \think\db\Query();
$query->table('think_user')->where('status',1);
Db::find($query);
Db::select($query);

或者直接使用闭包函数查询，例如：

Db::select(function($query){
    $query->table('think_user')->where('status',1);
});

值和列查询

查询某个字段的值可以用

// 返回某个字段的值
Db::table('think_user')->where('id',1)->value('name');

value 方法查询结果不存在，返回 null

查询某一列的值可以用

// 返回数组
Db::table('think_user')->where('status',1)->column('name');
// 指定索引
Db::table('think_user')->where('status',1)->column('name','id');

column 方法查询结果不存在，返回空数组

数据集分批处理

如果你需要处理成千上百条数据库记录，可以考虑使用chunk方法，该方法一次获取结果集的一小块，然后填充每一小块数据到要处理的闭包，该方法在编写处理大量数据库记录的时候非常有用。

比如，我们可以全部用户表数据进行分批处理，每次处理 100 个用户记录：

Db::table('think_user')->chunk(100, function($users) {
    foreach ($users as $user) {
        //
    }
});
// 或者交给回调方法myUserIterator处理
Db::table('think_user')->chunk(100, 'myUserIterator');

你可以通过从闭包函数中返回false来中止对数据集的处理：

Db::table('think_user')->chunk(100, function($users) {
    // 处理结果集...
    return false;
});

也支持在chunk方法之前调用其它的查询方法，例如：

Db::table('think_user')->where('score','>',80)->chunk(100, function($users) {
    foreach ($users as $user) {
        //
    }
});

JSON类型数据查询（mysql V5.0.1）

// 查询JSON类型字段 （info字段为json类型）
Db::table('think_user')->where('info$.email','thinkphp@qq.com')->find();


查询语法

查询表达式

查询表达式支持大部分的SQL查询语法，也是ThinkPHP查询语言的精髓，查询表达式的使用格式：

where('字段名','表达式','查询条件');
whereOr('字段名','表达式','查询条件');
版本 | 新增功能
5.0.4 | 支持对同一个字段多次调用查询方法

表达式不分大小写，支持的查询表达式有下面几种，分别表示的含义是：

表达式 | 含义
EQ、= | 等于（=）
NEQ、<> | 不等于（<>）
GT、> | 大于（>）
EGT、>= | 大于等于（>=）
LT、< | 小于（<）
ELT、<= | 小于等于（<=）
LIKE | 模糊查询
[NOT] BETWEEN | （不在）区间查询
[NOT] IN | （不在）IN 查询
[NOT] NULL | 查询字段是否（不）是NULL
[NOT] EXISTS | EXISTS查询
EXP | 表达式查询，支持SQL语法
> time | 时间比较
< time | 时间比较
between time | 时间比较
notbetween time | 时间比较

表达式查询的用法示例如下：

EQ ：等于（=）

例如：

where('id','eq',100);
where('id','=',100);

和下面的查询等效

where('id',100);

表示的查询条件就是 id = 100

NEQ： 不等于（<>）

例如：

where('id','neq',100);
where('id','<>',100);

表示的查询条件就是 id <> 100

GT：大于（>）

例如：

where('id','gt',100);
where('id','>',100);

表示的查询条件就是 id > 100

EGT：大于等于（>=）

例如：

where('id','egt',100);
where('id','>=',100);

表示的查询条件就是 id >= 100

LT：小于（<）

例如：

where('id','lt',100);
where('id','<',100);

表示的查询条件就是 id < 100

ELT： 小于等于（<=）

例如：

where('id','elt',100);
where('id','<=',100);

表示的查询条件就是 id <= 100

[NOT] LIKE： 同sql的LIKE

例如：

where('name','like','thinkphp%');

查询条件就变成 name like 'thinkphp%'

[NOT] BETWEEN ：同sql的[not] between

查询条件支持字符串或者数组，例如：

where('id','between','1,8');

和下面的等效：

where('id','between',[1,8]);

查询条件就变成 id BETWEEN 1 AND 8

[NOT] IN： 同sql的[not] in

查询条件支持字符串或者数组，例如：

where('id','not in','1,5,8');

和下面的等效：

where('id','not in',[1,5,8]);

查询条件就变成 id NOT IN (1,5, 8)

[NOT] IN查询支持使用闭包方式

[NOT] NULL ：

查询字段是否（不）是Null，例如：

where('name', null);
where('title','null');
where('name','not null');

如果你需要查询一个字段的值为字符串null或者not null，应该使用：

where('title','=', 'null');
where('name','=', 'not null');

EXP：表达式

支持更复杂的查询情况 例如：

where('id','in','1,3,8');

可以改成：

where('id','exp',' IN (1,3,8) ');

exp查询的条件不会被当成字符串，所以后面的查询条件可以使用任何SQL支持的语法，包括使用函数和字段名称。


高级查询

快捷查询

快捷查询方式是一种多字段相同查询条件的简化写法，可以进一步简化查询条件的写法，在多个字段之间用|分割表示OR查询，用&分割表示AND查询，可以实现下面的查询，例如：

Db::table('think_user')
    ->where('name|title','like','thinkphp%')
    ->where('create_time&update_time','>',0)
    ->find();

生成的查询SQL是：

SELECT * FROM `think_user` WHERE ( `name` LIKE 'thinkphp%' OR `title` LIKE 'thinkphp%' ) AND ( `create_time` > 0 AND `update_time` > 0 ) LIMIT 1

快捷查询支持所有的查询表达式。

区间查询

区间查询是一种同一字段多个查询条件的简化写法，例如：

Db::table('think_user')
    ->where('name',['like','thinkphp%'],['like','%thinkphp'])
    ->where('id',['>',0],['<>',10],'or')
    ->find();

生成的SQL语句为：

SELECT * FROM `think_user` WHERE ( `name` LIKE 'thinkphp%' AND `name` LIKE '%thinkphp' ) AND ( `id` > 0 OR `id` <> 10 ) LIMIT 1

区间查询的查询条件必须使用数组定义方式，支持所有的查询表达式。

下面的查询方式是错误的：

Db::table('think_user')
    ->where('name',['like','thinkphp%'],['like','%thinkphp'])
    ->where('id',5,['<>',10],'or')
    ->find();

批量查询

可以进行多个条件的批量条件查询定义，例如：

Db::table('think_user')
    ->where([
        'name'  =>  ['like','thinkphp%'],
        'title' =>  ['like','%thinkphp'],
        'id'    =>  ['>',0],
        'status'=>  1
    ])
    ->select();

生成的SQL语句为：

SELECT * FROM `think_user` WHERE `name` LIKE 'thinkphp%' AND `title` LIKE '%thinkphp' AND `id` > 0 AND `status` = '1'

闭包查询

Db::table('think_user')->select(function($query){
    $query->where('name','thinkphp')
        ->whereOr('id','>',10);
});

生成的SQL语句为：

SELECT * FROM `think_user` WHERE `name` = 'thinkphp' OR `id` > 10

使用Query对象查询

也可以事先封装Query对象，并传入select方法，例如：

$query = new \think\db\Query;
$query->name('user')
    ->where('name','like','%think%')
    ->where('id','>',10)
    ->limit(10);
Db::select($query);

如果使用Query对象的话，select方法之前调用的任何的链式操作都是无效。

混合查询

可以结合前面提到的所有方式进行混合查询，例如：

Db::table('think_user')
    ->where('name',['like','thinkphp%'],['like','%thinkphp'])
    ->where(function($query){
        $query->where('id',['<',10],['>',100],'or');
    })
    ->select();

生成的SQL语句是：

SELECT * FROM `think_user` WHERE ( `name` LIKE 'thinkphp%' AND `name` LIKE '%thinkphp' ) AND ( `id` < 10 or `id` > 100 )

字符串条件查询

对于一些实在复杂的查询，也可以直接使用原生SQL语句进行查询，例如：

Db::table('think_user')
    ->where('id > 0 AND name LIKE "thinkphp%"')
    ->select();

为了安全起见，我们可以对字符串查询条件使用参数绑定，例如：

Db::table('think_user')
    ->where('id > :id AND name LIKE :name ',['id'=>0, 'name'=>'thinkphp%'])
    ->select();

V5.0.4+开始，ThinkPHP支持对同一个字段多次调用查询条件，例如：

Db::table('think_user')
    ->where('name','like','%think%')
    ->where('name','like','%php%')
    ->where('id','in',[1,5,80,50])
    ->where('id','>',10)
    ->find();


更新数据

更新数据表中的数据

Db::table('think_user')
    ->where('id', 1)
    ->update(['name' => 'thinkphp']);

如果数据中包含主键，可以直接使用：

Db::table('think_user')
    ->update(['name' => 'thinkphp','id'=>1]);

update 方法返回影响数据的条数，没修改任何数据返回 0

如果要更新的数据需要使用SQL函数或者其它字段，可以使用下面的方式：

Db::table('think_user')
    ->where('id', 1)
    ->update([
        'login_time'  => ['exp','now()'],
        'login_times' => ['exp','login_times+1'],
    ]);

更新某个字段的值：

Db::table('think_user')
    ->where('id',1)
    ->setField('name', 'thinkphp');

setField 方法返回影响数据的条数，没修改任何数据字段返回 0

自增或自减一个字段的值

setInc/setDec 如不加第二个参数，默认值为1

// score 字段加 1
Db::table('think_user')
    ->where('id', 1)
    ->setInc('score');
// score 字段加 5
Db::table('think_user')
    ->where('id', 1)
    ->setInc('score', 5);
// score 字段减 1
Db::table('think_user')
    ->where('id', 1)
    ->setDec('score');
// score 字段减 5
Db::table('think_user')
    ->where('id', 1)
    ->setDec('score', 5);

延迟更新

setInc/setDec支持延时更新，如果需要延时更新则传入第三个参数
下例中延时10秒，给score字段增加1

Db::table('think_user')->where('id', 1)->setInc('score', 1, 10);

setInc/setDec 方法返回影响数据的条数

助手函数

// 更新数据表中的数据
db('user')->where('id',1)->update(['name' => 'thinkphp']);
// 更新某个字段的值
db('user')->where('id',1)->setField('name','thinkphp');
// 自增 score 字段
db('user')->where('id', 1)->setInc('score');
// 自减 score 字段
db('user')->where('id', 1)->setDec('score');


聚合查询

在应用中我们经常会用到一些统计数据，例如当前所有（或者满足某些条件）的用户数、所有用户的最大积分、用户的平均成绩等等，ThinkPHP为这些统计操作提供了一系列的内置方法，包括：

方法 | 说明
count | 统计数量，参数是要统计的字段名（可选）
max | 获取最大值，参数是要统计的字段名（必须）
min | 获取最小值，参数是要统计的字段名（必须）
avg | 获取平均值，参数是要统计的字段名（必须）
sum | 获取总分，参数是要统计的字段名（必须）

用法示例：

获取用户数：

Db::table('think_user')->count();
// 助手函数
db('user')->count();

或者根据字段统计：

Db::table('think_user')->count('id');
// 助手函数
db('user')->count('id');

获取用户的最大积分：

Db::table('think_user')->max('score');
// 助手函数
db('user')->max('score');

获取积分大于0的用户的最小积分：

Db::table('think_user')->where('score>0')->min('score');
// 助手函数
db('user')->where('score>0')->min('score');

获取用户的平均积分：

Db::table('think_user')->avg('score');
// 助手函数
db('user')->avg('score');

统计用户的总成绩：

Db::table('think_user')->sum('score');
// 助手函数
db('user')->sum('score');


删除数据

删除数据表中的数据

// 根据主键删除
Db::table('think_user')->delete(1);
Db::table('think_user')->delete([1,2,3]);

// 条件删除
Db::table('think_user')->where('id',1)->delete();
Db::table('think_user')->where('id','<',10)->delete();

delete 方法返回影响数据的条数，没有删除返回 0

助手函数

// 根据主键删除
db('user')->delete(1);
// 条件删除
db('user')->where('id',1)->delete();


时间查询

时间比较

使用where方法

where方法支持时间比较，例如：

// 大于某个时间
where('create_time','> time','2016-1-1');
// 小于某个时间
where('create_time','<= time','2016-1-1');
// 时间区间查询
where('create_time','between time',['2015-1-1','2016-1-1']);

第三个参数可以传入任何有效的时间表达式，会自动识别你的时间字段类型，支持的时间类型包括timestamps、datetime、date和int。

使用whereTime方法

whereTime方法提供了日期和时间字段的快捷查询，示例如下：

// 大于某个时间
db('user')
    ->whereTime('birthday', '>=', '1970-10-1')
    ->select();
// 小于某个时间
db('user')
    ->whereTime('birthday', '<', '2000-10-1')
    ->select();
// 时间区间查询
db('user')
    ->whereTime('birthday', 'between', ['1970-10-1', '2000-10-1'])
    ->select();
// 不在某个时间区间
db('user')
    ->whereTime('birthday', 'not between', ['1970-10-1', '2000-10-1'])
    ->select();

时间表达式

还提供了更方便的时间表达式查询，例如：

// 获取今天的博客
db('blog')
    ->whereTime('create_time', 'today')
    ->select();
// 获取昨天的博客
db('blog')
    ->whereTime('create_time', 'yesterday')
    ->select();
// 获取本周的博客
db('blog')
    ->whereTime('create_time', 'week')
    ->select();
// 获取上周的博客
db('blog')
    ->whereTime('create_time', 'last week')
    ->select();
// 获取本月的博客
db('blog')
    ->whereTime('create_time', 'month')
    ->select();
// 获取上月的博客
db('blog')
    ->whereTime('create_time', 'last month')
    ->select();
// 获取今年的博客
db('blog')
    ->whereTime('create_time', 'year')
    ->select();
// 获取去年的博客
db('blog')
    ->whereTime('create_time', 'last year')
    ->select();

如果查询当天、本周、本月和今年的时间，还可以简化为：

// 获取今天的博客
db('blog')
    ->whereTime('create_time', 'd')
    ->select();
// 获取本周的博客
db('blog')
    ->whereTime('create_time', 'w')
    ->select();
// 获取本月的博客
db('blog')
    ->whereTime('create_time', 'm')
    ->select();
// 获取今年的博客
db('blog')
    ->whereTime('create_time', 'y')
    ->select();


视图查询

视图查询可以实现不依赖数据库视图的多表查询，并不需要数据库支持视图，例如：

Db::view('User','id,name')
    ->view('Profile','truename,phone,email','Profile.user_id=User.id')
    ->view('Score','score','Score.user_id=Profile.id')
    ->where('score','>',80)
    ->select();

生成的SQL语句类似于：

SELECT User.id,User.name,Profile.truename,Profile.phone,Profile.email,Score.score FROM think_user User INNER JOIN think_profile Profile ON Profile.user_id=User.id INNER JOIN think_socre Score ON Score.user_id=Profile.id WHERE Score.score > 80

注意，视图查询无需调用table和join方法，并且在调用where和order方法的时候只需要使用字段名而不需要加表名。

默认使用INNER join查询，如果需要更改，可以使用：

Db::view('User','id,name')
    ->view('Profile','truename,phone,email','Profile.user_id=User.id','LEFT')
    ->view('Score','score','Score.user_id=Profile.id','RIGHT')
    ->where('score','>',80)
    ->select();

生成的SQL语句类似于：

SELECT User.id,User.name,Profile.truename,Profile.phone,Profile.email,Score.score FROM think_user User LEFT JOIN think_profile Profile ON Profile.user_id=User.id RIGHT JOIN think_socre Score ON Score.user_id=Profile.id WHERE Score.score > 80

可以使用别名：

Db::view('User',['id'=>'uid','name'=>'account'])
    ->view('Profile','truename,phone,email','Profile.user_id=User.id')
    ->view('Score','score','Score.user_id=Profile.id')
    ->where('score','>',80)
    ->select();

生成的SQL语句变成：

SELECT User.id AS uid,User.name AS account,Profile.truename,Profile.phone,Profile.email,Score.score FROM think_user User INNER JOIN think_profile Profile ON Profile.user_id=User.id INNER JOIN think_socre Score ON Score.user_id=Profile.id WHERE Score.score > 80

可以使用数组的方式定义表名以及别名，例如：

Db::view(['think_user'=>'member'],['id'=>'uid','name'=>'account'])
    ->view('Profile','truename,phone,email','Profile.user_id=member.id')
    ->view('Score','score','Score.user_id=Profile.id')
    ->where('score','>',80)
    ->select();

生成的SQL语句变成：

SELECT member.id AS uid,member.name AS account,Profile.truename,Profile.phone,Profile.email,Score.score FROM think_user member INNER JOIN think_profile Profile ON Profile.user_id=member.id INNER JOIN think_socre Score ON Score.user_id=Profile.id WHERE Score.score > 80


添加数据

添加一条数据

使用 Db 类的 insert 方法向数据库提交数据

$data = ['foo' => 'bar', 'bar' => 'foo'];
Db::table('think_user')->insert($data);

如果你在database.php配置文件中配置了数据库前缀(prefix)，那么可以直接使用 Db 类的 name 方法提交数据

Db::name('user')->insert($data);

insert 方法添加数据成功返回添加成功的条数，insert 正常情况返回 1

添加数据后如果需要返回新增数据的自增主键，可以使用getLastInsID方法：

Db::name('user')->insert($data);
$userId = Db::name('user')->getLastInsID();

或者直接使用insertGetId方法新增数据并返回主键值：

Db::name('user')->insertGetId($data);

insertGetId 方法添加数据成功返回添加数据的自增主键

添加多条数据

添加多条数据直接向 Db 类的 insertAll 方法传入需要添加的数据即可

$data = [
    ['foo' => 'bar', 'bar' => 'foo'],
    ['foo' => 'bar1', 'bar' => 'foo1'],
    ['foo' => 'bar2', 'bar' => 'foo2']
];
Db::name('user')->insertAll($data);

insertAll 方法添加数据成功返回添加成功的条数

助手函数

// 添加单条数据
db('user')->insert($data);

// 添加多条数据
db('user')->insertAll($list);


原生查询

Db类支持原生SQL查询操作，主要包括下面两个方法：

query方法

query方法用于执行SQL查询操作，如果数据非法或者查询错误则返回false，否则返回查询结果数据集（同select方法）。

使用示例：

Db::query("select * from think_user where status=1");

如果你当前采用了分布式数据库，并且设置了读写分离的话，query方法始终是在读服务器执行，因此query方法对应的都是读操作，而不管你的SQL语句是什么。

execute方法

execute用于更新和写入数据的sql操作，如果数据非法或者查询错误则返回false ，否则返回影响的记录数。

使用示例：

Db::execute("update think_user set name='thinkphp' where status=1");

如果你当前采用了分布式数据库，并且设置了读写分离的话，execute方法始终是在写服务器执行，因此execute方法对应的都是写操作，而不管你的SQL语句是什么。

参数绑定

支持在原生查询的时候使用参数绑定，包括问号占位符或者命名占位符，例如：

Db::query("select * from think_user where id=? AND status=?",[8,1]);
// 命名绑定
Db::execute("update think_user set name=:name where status=:status",['name'=>'thinkphp','status'=>1]);


子查询

首先构造子查询SQL，可以使用下面三种的方式来构建子查询。

1、使用select方法

当select方法的参数为false的时候，表示不进行查询只是返回构建SQL，例如：

$subQuery = Db::table('think_user')
    ->field('id,name')
    ->where('id','>',10)
    ->select(false);

生成的subQuery结果为：

SELECT `id`,`name` FROM `think_user` WHERE `id` > 10

2、使用fetchSql方法

fetchSql方法表示不进行查询而只是返回构建的SQL语句，并且不仅仅支持select，而是支持所有的CURD查询。

$subQuery = Db::table('think_user')
    ->field('id,name')
    ->where('id','>',10)
    ->fetchSql(true)
    ->select();

生成的subQuery结果为：

SELECT `id`,`name` FROM `think_user` WHERE `id` > 10

3、使用buildSql构造子查询

$subQuery = Db::table('think_user')
    ->field('id,name')
    ->where('id','>',10)
    ->buildSql();

生成的subQuery结果为：

( SELECT `id`,`name` FROM `think_user` WHERE `id` > 10 )

调用buildSql方法后不会进行实际的查询操作，而只是生成该次查询的SQL语句（为了避免混淆，会在SQL两边加上括号），然后我们直接在后续的查询中直接调用。

需要注意的是，使用前两种方法需要自行添加‘括号’。

然后使用子查询构造新的查询：

Db::table($subQuery.' a')
    ->where('a.name','like','thinkphp')
    ->order('id','desc')
    ->select();

生成的SQL语句为：

SELECT * FROM ( SELECT `id`,`name` FROM `think_user` WHERE `id` > 10 ) a WHERE a.name LIKE 'thinkphp' ORDER BY `id` desc

4、使用闭包构造子查询

IN/NOT IN和EXISTS/NOT EXISTS之类的查询可以直接使用闭包作为子查询，例如：

Db::table('think_user')
->where('id','IN',function($query){
    $query->table('think_profile')->where('status',1)->field('id');
})
->select();

生成的SQL语句是

SELECT * FROM `think_user` WHERE `id` IN ( SELECT `id` FROM `think_profile` WHERE `status` = 1 )
Db::table('think_user')
->where(function($query){
    $query->table('think_profile')->where('status',1);
},'exists')
->find();

生成的SQL语句为

SELECT * FROM `think_user` WHERE EXISTS ( SELECT * FROM `think_profile` WHERE `status` = 1 )


alias

alias用于设置当前数据表的别名，便于使用其他的连贯操作例如join方法等。

示例：

Db::table('think_user')->alias('a')->join('__DEPT__ b ','b.user_id= a.id')->select();

最终生成的SQL语句类似于：

SELECT * FROM think_user a INNER JOIN think_dept b ON b.user_id= a.id

v5.0.2+版本开始，可以传入数组批量设置数据表以及别名，例如：

Db::table('think_user')->alias(['think_user'=>'user','think_dept'=>'dept'])->join('think_dept','dept.user_id= user.id')->select();

最终生成的SQL语句类似于：

SELECT * FROM think_user user INNER JOIN think_dept dept ON dept.user_id= user.id


bind

bind方法用于手动参数绑定，大多数情况，无需进行手动绑定，系统会在查询和写入数据的时候自动使用参数绑定。

bind方法用法如下：

// 用于查询
Db::table('think_user')
->where('id',':id')
->where('name',':name')
->bind(['id'=>[10,\PDO::PARAM_INT],'name'=>'thinkphp'])
->select();

// 用于写入
Db::table('think_user')
->bind(['id'=>[10,\PDO::PARAM_INT],'email'=>'thinkphp@qq.com','name'=>'thinkphp'])
->where('id',':id')
->update(['name'=>':name','email'=>':email');


cache

cache方法用于查询缓存操作，也是连贯操作方法之一。

cache可以用于select、find、value和column方法，以及其衍生方法，使用cache方法后，在缓存有效期之内不会再次进行数据库查询操作，而是直接获取缓存中的数据，关于数据缓存的类型和设置可以参考缓存部分。

下面举例说明，例如，我们对find方法使用cache方法如下：

Db::table('think_user')->where('id=5')->cache(true)->find();

第一次查询结果会被缓存，第二次查询相同的数据的时候就会直接返回缓存中的内容，而不需要再次进行数据库查询操作。

默认情况下， 缓存有效期是由默认的缓存配置参数决定的，但cache方法可以单独指定，例如：

Db::table('think_user')->cache(true,60)->find();
// 或者使用下面的方式 是等效的
Db::table('think_user')->cache(60)->find();

表示对查询结果的缓存有效期60秒。

cache方法可以指定缓存标识：

Db::table('think_user')->cache('key',60)->find();

指定查询缓存的标识可以使得查询缓存更有效率。

这样，在外部就可以通过\think\Cache类直接获取查询缓存的数据，例如：

$result = Db::table('think_user')->cache('key',60)->find();
$data = \think\Cache::get('key');

cache方法支持设置缓存标签，例如：

Db::table('think_user')->cache('key',60,'tagName')->find();

缓存自动更新

这里的缓存自动更新是指一旦数据更新或者删除后会自动清理缓存（下次获取的时候会自动重新缓存）。

当你删除或者更新数据的时候，可以使用cache方法手动更新（清除）缓存，例如：

Db::table('think_user')->cache('user_data')->select([1,3,5]);
Db::table('think_user')->cache('user_data')->update(['id'=>1,'name'=>'thinkphp']);
Db::table('think_user')->cache('user_data')->select([1,5]);

最后查询的数据不会受第一条查询缓存的影响，确保查询和更新或者删除使用相同的缓存标识才能自动清除缓存。

如果使用find方法并且使用主键查询的情况，不需要指定缓存标识，会自动清理缓存，例如：

Db::table('think_user')->cache(true)->find(1);
Db::table('think_user')->update(['id'=>1,'name'=>'thinkphp']);
Db::table('think_user')->cache(true)->find(1);

最后查询的数据会是更新后的数据。


comment

COMMENT方法 用于在生成的SQL语句中添加注释内容，例如：

Db::table('think_score')->comment('查询考试前十名分数')
    ->field('username,score')
    ->limit(10)
    ->order('score desc')
    ->select();

最终生成的SQL语句是：

SELECT username,score FROM think_score ORDER BY score desc LIMIT 10 /* 查询考试前十名分数 */


distinct

DISTINCT 方法用于返回唯一不同的值 。

例如数据库表中有以下数据

以下代码会返回user_login字段不同的数据

Db::table('think_user')->distinct(true)->field('user_login')->select();

生成的SQL语句是： SELECT DISTINCT user_login FROM think_user

返回以下数组

array(2) {
  [0] => array(1) {
    ["user_login"] => string(7) "chunice"
  }
  [1] => array(1) {
    ["user_login"] => string(5) "admin"
  }
}

distinct方法的参数是一个布尔值。


failException

failException设置查询数据为空时是否需要抛出异常，如果不传入任何参数，默认为开启，用于select和find方法，例如：

// 数据不存在的话直接抛出异常
Db:name('blog')->where(['status' => 1])->failException()->select();
// 数据不存在返回空数组 不抛异常
Db:name('blog')->where(['status' => 1])->failException(false)->select();

或者可以使用更方便的查空报错

// 查询多条
Db:name('blog')->where(['status' => 1])->selectOrFail();

// 查询单条
Db:name('blog')->where(['status' => 1])->findOrFail();


fetchSql

fetchSql用于直接返回SQL而不是执行查询，适用于任何的CURD操作方法。 例如：

$result = Db::table('think_user')->fetchSql(true)->find(1);

输出result结果为： SELECT * FROM think_user where id = 1


field

field方法属于模型的连贯操作方法之一，主要目的是标识要返回或者操作的字段，可以用于查询和写入操作。

用于查询

指定字段

在查询操作中field方法是使用最频繁的。

Db::table('think_user')->field('id,title,content')->select();

这里使用field方法指定了查询的结果集中包含id,title,content三个字段的值。执行的SQL相当于：

SELECT id,title,content FROM table

可以给某个字段设置别名，例如：

Db::table('think_user')->field('id,nickname as name')->select();

执行的SQL语句相当于：

SELECT id,nickname as name FROM table
使用SQL函数

可以在field方法中直接使用函数，例如：

Db::table('think_user')->field('id,SUM(score)')->select();

执行的SQL相当于：

SELECT id,SUM(score) FROM table

除了select方法之外，所有的查询方法，包括find等都可以使用field方法。

使用数组参数

field方法的参数可以支持数组，例如：

Db::table('think_user')->field(['id','title','content'])->select();

最终执行的SQL和前面用字符串方式是等效的。

数组方式的定义可以为某些字段定义别名，例如：

Db::table('think_user')->field(['id','nickname'=>'name'])->select();

执行的SQL相当于：

SELECT id,nickname as name FROM table

对于一些更复杂的字段要求，数组的优势则更加明显，例如：

Db::table('think_user')->field(['id','concat(name,"-",id)'=>'truename','LEFT(title,7)'=>'sub_title'])->select();

执行的SQL相当于：

SELECT id,concat(name,'-',id) as truename,LEFT(title,7) as sub_title FROM table
获取所有字段

如果有一个表有非常多的字段，需要获取所有的字段（这个也许很简单，因为不调用field方法或者直接使用空的field方法都能做到）：

Db::table('think_user')->select();
Db::table('think_user')->field('*')->select();

上面的用法是等效的，都相当于执行SQL：

SELECT * FROM table

但是这并不是我说的获取所有字段，而是显式的调用所有字段（对于对性能要求比较高的系统，这个要求并不过分，起码是一个比较好的习惯），下面的用法可以完成预期的作用：

Db::table('think_user')->field(true)->select();

field(true)的用法会显式的获取数据表的所有字段列表，哪怕你的数据表有100个字段。

字段排除

如果我希望获取排除数据表中的content字段（文本字段的值非常耗内存）之外的所有字段值，我们就可以使用field方法的排除功能，例如下面的方式就可以实现所说的功能：

Db::table('think_user')->field('content',true)->select();

则表示获取除了content之外的所有字段，要排除更多的字段也可以：

Db::table('think_user')->field('user_id,content',true)->select();
//或者用
Db::table('think_user')->field(['user_id','content'],true)->select();

注意的是 字段排除功能不支持跨表和join操作。

用于写入

除了查询操作之外，field方法还有一个非常重要的安全功能--字段合法性检测。field方法结合数据库的写入方法使用就可以完成表单提交的字段合法性检测，如果我们在表单提交的处理方法中使用了：

Db::table('think_user')->field('title,email,content')->insert($data);

即表示表单中的合法字段只有title,email和content字段，无论用户通过什么手段更改或者添加了浏览器的提交字段，都会直接屏蔽。因为，其他是所有字段我们都不希望由用户提交来决定，你可以通过自动完成功能定义额外的字段写入。


force

force 方法用于数据集的强制索引操作，例如：

Db::table('think_user')->force('user')->select();

对查询强制使用user索引，user必须是数据表实际创建的索引名称。


group

GROUP方法也是连贯操作方法之一，通常用于结合合计函数，根据一个或多个列对结果集进行分组 。

group方法只有一个参数，并且只能使用字符串。

例如，我们都查询结果按照用户id进行分组统计：

Db::table('think_user')
    ->field('user_id,username,max(score)')
    ->group('user_id')
    ->select();

生成的SQL语句是：

SELECT user_id,username,max(score) FROM think_score GROUP BY user_id

也支持对多个字段进行分组，例如：

Db::table('think_user')
    ->field('user_id,test_time,username,max(score)')
    ->group('user_id,test_time')
    ->select();

生成的SQL语句是：

SELECT user_id,test_time,username,max(score) FROM think_score GROUP BY user_id,test_time


having

HAVING方法也是连贯操作之一，用于配合group方法完成从分组的结果中筛选（通常是聚合条件）数据。

having方法只有一个参数，并且只能使用字符串，例如：

Db::table('think_user')
    ->field('username,max(score)')
    ->group('user_id')
    ->having('count(test_time)>3')
    ->select();

生成的SQL语句是：

SELECT username,max(score) FROM think_score GROUP BY user_id HAVING count(test_time)>3


join

join通常有下面几种类型，不同类型的join操作会影响返回的数据结果。

  • INNER JOIN: 等同于 JOIN（默认的JOIN类型）,如果表中有至少一个匹配，则返回行
  • LEFT JOIN: 即使右表中没有匹配，也从左表返回所有的行
  • RIGHT JOIN: 即使左表中没有匹配，也从右表返回所有的行
  • FULL JOIN: 只要其中一个表中存在匹配，就返回行

说明

object join ( mixed join [, mixed $condition = null [, string $type = 'INNER']] )

JOIN方法也是连贯操作方法之一，用于根据两个或多个表中的列之间的关系，从这些表中查询数据。

参数

join

要关联的（完整）表名以及别名

支持三种写法：

写法1：[ '完整表名或者子查询'=>'别名' ]
写法2：'完整表名 别名'
写法3：'不带数据表前缀的表名'

condition

关联条件。可以为字符串或数组， 为数组时每一个元素都是一个关联条件。

type

关联类型。可以为:INNER、LEFT、RIGHT、FULL，不区分大小写，默认为INNER。

返回值
模型对象

举例

Db::table('think_artist')
->alias('a')
->join('think_work w','a.id = w.artist_id')
->join('think_card c','a.card_id = c.id')
->select();
Db::table('think_artist')
->alias('a')
->join('__WORK__ w','a.id = w.artist_id')
->join('__CARD__ c','a.card_id = c.id')
->select();
$join = [
    ['think_work w','a.id=w.artist_id'],
    ['think_card c','a.card_id=c.id'],
];
Db::table('think_user')->alias('a')->join($join)->select();

以上三种写法的效果一样，__WORK__和 __CARD__在最终解析的时候会转换为 think_work和 think_card。注意:'_表名_'这种方式中间的表名需要用大写

如果不想使用别名，后面的条件就要使用表全名，可以使用下面这种方式

Db::table('think_user')->join('__WORK__','__ARTIST__.id = __WORK__.artist_id')->select();

默认采用INNER JOIN 方式，如果需要用其他的JOIN方式，可以改成

Db::table('think_user')->alias('a')->join('word w','a.id = w.artist_id','RIGHT')->select();

表名也可以是一个子查询

$subsql = Db::table('think_work')->where(['status'=>1])->field('artist_id,count(id) count')->group('artist_id')->buildSql();
Db::table('think_user')->alias('a')->join([$subsql=> 'w'], 'a.artist_id = w.artist_id')->select();

因buildSql返回的语句带有()，所以这里不需要在两端再加上()。


limit

limit方法也是模型类的连贯操作方法之一，主要用于指定查询和操作的数量，特别在分页查询的时候使用较多。ThinkPHP的limit方法可以兼容所有的数据库驱动类的。

限制结果数量

例如获取满足要求的10个用户，如下调用即可：

Db::table('think_user')
    ->where('status=1')
    ->field('id,name')
    ->limit(10)
    ->select();

limit方法也可以用于写操作，例如更新满足要求的3条数据：

Db::table('think_user')
->where('score=100')
->limit(3)
->update(['level'=>'A']);

分页查询

用于文章分页查询是limit方法比较常用的场合，例如：

Db::table('think_article')->limit('10,25')->select();

表示查询文章数据，从第10行开始的25条数据（可能还取决于where条件和order排序的影响 这个暂且不提）。

你也可以这样使用，作用是一样的：

Db::table('think_article')->limit(10,25)->select();

对于大数据表，尽量使用limit限制查询结果，否则会导致很大的内存开销和性能问题。


lock

Lock方法是用于数据库的锁机制，如果在查询或者执行操作的时候使用：

lock(true);

就会自动在生成的SQL语句最后加上 FOR UPDATE或者FOR UPDATE NOWAIT（Oracle数据库）。


order

order方法属于模型的连贯操作方法之一，用于对操作的结果排序。

用法如下：

Db::table('think_user')->where('status=1')->order('id desc')->limit(5)->select();

注意：连贯操作方法没有顺序，可以在select方法调用之前随便改变调用顺序。

支持对多个字段的排序，例如：

Db::table('think_user')->where('status=1')->order('id desc,status')->limit(5)->select();

如果没有指定desc或者asc排序规则的话，默认为asc。

如果你的字段和mysql关键字有冲突，那么建议采用数组方式调用，例如：

Db::table('think_user')->where('status=1')->order(['order','id'=>'desc'])->limit(5)->select();


page

page方法也是模型的连贯操作方法之一，是完全为分页查询而诞生的一个人性化操作方法。

我们在前面已经了解了关于limit方法用于分页查询的情况，而page方法则是更人性化的进行分页查询的方法，例如还是以文章列表分页为例来说，如果使用limit方法，我们要查询第一页和第二页（假设我们每页输出10条数据）写法如下：

// 查询第一页数据
Db::table('think_article')->limit('0,10')->select();
// 查询第二页数据
Db::table('think_article')->limit('10,10')->select();

虽然利用扩展类库中的分页类Page可以自动计算出每个分页的limit参数，但是如果要自己写就比较费力了，如果用page方法来写则简单多了，例如：

// 查询第一页数据
Db::table('think_article')->page('1,10')->select();
// 查询第二页数据
Db::table('think_article')->page('2,10')->select();

显而易见的是，使用page方法你不需要计算每个分页数据的起始位置，page方法内部会自动计算。

和limit方法一样，page方法也支持2个参数的写法，例如：

Db::table('think_article')->page(1,10)->select();
// 和下面的用法等效
Db::table('think_article')->page('1,10')->select();

page方法还可以和limit方法配合使用，例如：

Db::table('think_article')->limit(25)->page(3)->select();

当page方法只有一个值传入的时候，表示第几页，而limit方法则用于设置每页显示的数量，也就是说上面的写法等同于：

Db::table('think_article')->page('3,25')->select();


partition

partition 方法用于是数据库水平分表

partition($data, $field, $rule);
// $data 分表字段的数据
// $field 分表字段的名称
// $rule 分表规则

注意：不要使用任何 SQL 语句中会出现的关键字当表名、字段名，例如 order 等。会导致数据模型拼装 SQL 语句语法错误。

partition 方法用法如下：

// 用于写入
$data = [
    'user_id'   => 110,
    'user_name' => 'think'
];

$rule = [
    'type' => 'mod', // 分表方式
    'num'  => 10     // 分表数量
];

Db::name('log')
    ->partition(['user_id' => 110], "user_id", $rule)
    ->insert($data);

// 用于查询
Db::name('log')
    ->partition(['user_id' => 110], "user_id", $rule)
    ->where(['user_id' => 110])
    ->select();


sequence

sequence方法用于pgsql数据库指定自增序列名，其它数据库不必使用，用法为：

Db::name('user')->sequence('id')->insert(['name'=>'thinkphp']);


strict

strict方法用于设置是否严格检查字段名，用法如下：

// 关闭字段严格检查
Db::name('user')
    ->strict(false)
    ->insert($data);

注意，系统默认值是由数据库配置参数fields_strict决定，因此修改数据库配置参数可以进行全局的严格检查配置，如下：

// 关闭严格检查字段是否存在
'fields_strict'  => false,

如果开启字段严格检查的话，在更新和写入数据库的时候，一旦存在非数据表字段的值，则会抛出异常。


table

table方法主要用于指定操作的数据表。

用法

一般情况下，操作模型的时候系统能够自动识别当前对应的数据表，所以，使用table方法的情况通常是为了：

  1. 切换操作的数据表；
  2. 对多表进行操作；

例如：

Db::table('think_user')->where('status>1')->select();

也可以在table方法中指定数据库，例如：

Db::table('db_name.think_user')->where('status>1')->select();

table方法指定的数据表需要完整的表名，但可以采用下面的方式简化数据表前缀的传入，例如：

Db::table('__USER__')->where('status>1')->select();

会自动获取当前模型对应的数据表前缀来生成 think_user 数据表名称。

需要注意的是table方法不会改变数据库的连接，所以你要确保当前连接的用户有权限操作相应的数据库和数据表。 切换数据表后，系统会自动重新获取切换后的数据表的字段缓存信息。

如果需要对多表进行操作，可以这样使用：

Db::field('user.name,role.title')
->table('think_user user,think_role role')
->limit(10)->select();

为了尽量避免和mysql的关键字冲突，可以建议使用数组方式定义，例如：

Db::field('user.name,role.title')
->table(['think_user'=>'user','think_role'=>'role'])
->limit(10)->select();

使用数组方式定义的优势是可以避免因为表名和关键字冲突而出错的情况。


union

UNION操作用于合并两个或多个 SELECT 语句的结果集。

使用示例：

Db::field('name')
      ->table('think_user_0')
      ->union('SELECT name FROM think_user_1')
      ->union('SELECT name FROM think_user_2')
      ->select();

闭包用法：

Db::field('name')
      ->table('think_user_0')
      ->union(function($query){
        $query->field('name')->table('think_user_1');
        })
      ->union(function($query){
        $query->field('name')->table('think_user_2');
        })
      ->select();

或者

Db::field('name')
      ->table('think_user_0')
      ->union(['SELECT name FROM think_user_1','SELECT name FROM think_user_2'])
      ->select();

支持UNION ALL 操作，例如：

Db::field('name')
      ->table('think_user_0')
      ->union('SELECT name FROM think_user_1',true)
      ->union('SELECT name FROM think_user_2',true)
      ->select();

或者

Db::field('name')
      ->table('think_user_0')
      ->union(['SELECT name FROM think_user_1','SELECT name FROM think_user_2'],true)
      ->select();

每个union方法相当于一个独立的SELECT语句。

注意：UNION 内部的 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时，每条 SELECT 语句中的列的顺序必须相同。


using

整理中


where

where方法的用法是ThinkPHP查询语言的精髓，也是ThinkPHP ORM的重要组成部分和亮点所在，可以完成包括普通查询、表达式查询、快捷查询、区间查询、组合查询在内的查询操作。where方法的参数支持字符串和数组，虽然也可以使用对象但并不建议。

表达式查询

新版的表达式查询采用全新的方式，查询表达式的使用格式：

Db::table('think_user')
    ->where('id','>',1)
    ->where('name','thinkphp')
    ->select();

更多的表达式查询语法，可以参考查询语法部分。

数组条件

可以通过数组方式批量设置查询条件。

普通查询

最简单的数组查询方式如下：

$map['name'] = 'thinkphp';
$map['status'] = 1;
// 把查询条件传入查询方法
Db::table('think_user')->where($map)->select();

// 助手函数
db('user')->where($map)->select();

最后生成的SQL语句是

SELECT * FROM think_user WHERE `name`='thinkphp' AND status=1

表达式查询

可以在数组条件中使用查询表达式，例如：

$map['id']  = ['>',1];
$map['mail']  = ['like','%thinkphp@qq.com%'];
Db::table('think_user')->where($map)->select();

字符串条件

使用字符串条件直接查询和操作，例如：

Db::table('think_user')->where('type=1 AND status=1')->select();

最后生成的SQL语句是

SELECT * FROM think_user WHERE type=1 AND status=1

使用字符串条件的时候，建议配合预处理机制，确保更加安全，例如：

Db::table('think_user')->where("id=:id and username=:name")->bind(['id'=>[1,\PDO::PARAM_INT],'name'=>'thinkphp'])->select();


查询事件

查询事件（V5.0.4+）

从5.0.4+版本开始，增加了数据库的CURD操作事件支持，包括：

事件 | 描述
before_select | select查询前回调
before_find | find查询前回调
after_insert | insert操作成功后回调
after_update | update操作成功后回调
after_delete | delete操作成功后回调

查询事件仅支持find、select、insert、update和delete方法。

注册事件

使用下面的方法注册数据库查询事件

Query::event('after_insert','callback');
Query::event('before_select',function($options,$query){
    // 事件处理
    return $result;
});


事务操作

使用事务处理的话，需要数据库引擎支持事务处理。比如 MySQL 的 MyISAM 不支持事务处理，需要使用 InnoDB 引擎。

使用 transaction 方法操作数据库事务，当发生异常会自动回滚，例如：

自动控制事务处理

Db::transaction(function(){
    Db::table('think_user')->find(1);
    Db::table('think_user')->delete(1);
});

也可以手动控制事务，例如：

// 启动事务
Db::startTrans();
try{
    Db::table('think_user')->find(1);
    Db::table('think_user')->delete(1);
    // 提交事务
    Db::commit();
} catch (\Exception $e) {
    // 回滚事务
    Db::rollback();
}

注意在事务操作的时候，确保你的数据库连接是相同的。


监听SQL

如果开启数据库的调试模式的话，你可以对数据库执行的任何SQL操作进行监听，使用如下方法：

Db::listen(function($sql, $time, $explain){
    // 记录SQL
    echo $sql. ' ['.$time.'s]';
    // 查看性能分析结果
    dump($explain);
});

默认如果没有注册任何监听操作的话，这些SQL执行会被根据不同的日志类型记录到日志中。


存储过程

5.0支持存储过程，如果我们定义了一个数据库存储过程sp_query，可以使用下面的方式调用：

$result = Db::query('call sp_query(8)');

返回的是一个二维数组，也可以使用参数绑定，例如：

$result = Db::query('call sp_query(?)',[8]);
// 或者命名绑定
$result = Db::query('call sp_query(:id)',['id'=>8]);


数据集

数据库的查询结果也就是数据集，默认的配置下，数据集的类型是一个二维数组，我们可以配置成数据集类，就可以支持对数据集更多的对象化操作，需要使用数据集类功能，可以配置数据库的resultset_type参数如下：

return [
    // 数据库类型
    'type'        => 'mysql',
    // 数据库连接DSN配置
    'dsn'         => '',
    // 服务器地址
    'hostname'    => '127.0.0.1',
    // 数据库名
    'database'    => 'thinkphp',
    // 数据库用户名
    'username'    => 'root',
    // 数据库密码
    'password'    => '',
    // 数据库连接端口
    'hostport'    => '',
    // 数据库连接参数
    'params'      => [],
    // 数据库编码默认采用utf8
    'charset'     => 'utf8',
    // 数据库表前缀
    'prefix'      => 'think_',
    // 数据集返回类型
    'resultset_type' => 'collection',
];

返回的数据集对象是think\Collection，提供了和数组无差别用法，并且另外封装了一些额外的方法。

可以直接使用数组的方式操作数据集对象，例如：

// 获取数据集
$users = Db::name('user')->select();
// 直接操作第一个元素
$item  = $users[0];
// 获取数据集记录数
$count = count($users);
// 遍历数据集
foreach($users as $user){
    echo $user['name'];
    echo $user['id'];
}

需要注意的是，如果要判断数据集是否为空，不能直接使用empty判断，而必须使用数据集对象的isEmpty方法判断，例如：

$users = Db::name('user')->select();
if($users->isEmpty()){
    echo '数据集为空';
}

Collection类包含了下列主要方法：

方法 | 描述
isEmpty | 是否为空
toArray | 转换为数组
all | 所有数据
merge | 合并其它数据
diff | 比较数组，返回差集
flip | 交换数据中的键和值
intersect | 比较数组，返回交集
keys | 返回数据中的所有键名
pop | 删除数据中的最后一个元素
shift | 删除数据中的第一个元素
unshift | 在数据开头插入一个元素
reduce | 通过使用用户自定义函数，以字符串返回数组
reverse | 数据倒序重排
chunk | 数据分隔为多个数据块
each | 给数据的每个元素执行回调
filter | 用回调函数过滤数据中的元素
column | 返回数据中的指定列
sort | 对数据排序
shuffle | 将数据打乱
slice | 截取数据中的一部分

如果只是个别数据的查询需要返回数据集对象，则可以使用

Db::name('user')
    ->fetchClass('\think\Collection')
    ->select();


分布式数据库

ThinkPHP内置了分布式数据库的支持，包括主从式数据库的读写分离，但是分布式数据库必须是相同的数据库类型。

配置database.deploy 为1 可以采用分布式数据库支持。如果采用分布式数据库，定义数据库配置信息的方式如下：

//分布式数据库配置定义
return [
    // 启用分布式数据库
    'deploy'    =>  1,
    // 数据库类型
    'type'        => 'mysql',
    // 服务器地址
    'hostname'    => '192.168.1.1,192.168.1.2',
    // 数据库名
    'database'    => 'demo',
    // 数据库用户名
    'username'    => 'root',
    // 数据库密码
    'password'    => '',
    // 数据库连接端口
    'hostport'    => '',
]

连接的数据库个数取决于hostname定义的数量，所以即使是两个相同的IP也需要重复定义，但是其他的参数如果存在相同的可以不用重复定义，例如：

'hostport'=>'3306,3306'

和

'hostport'=>'3306'

等效。

'username'=>'user1',
'password'=>'pwd1',

和

'username'=>'user1,user1',
'password'=>'pwd1,pwd1',

等效。

还可以设置分布式数据库的读写是否分离，默认的情况下读写不分离，也就是每台服务器都可以进行读写操作，对于主从式数据库而言，需要设置读写分离，通过下面的设置就可以：

    'rw_separate' => true,

在读写分离的情况下，默认第一个数据库配置是主服务器的配置信息，负责写入数据，如果设置了master_num参数，则可以支持多个主服务器写入。其它的都是从数据库的配置信息，负责读取数据，数量不限制。每次连接从服务器并且进行读取操作的时候，系统会随机进行在从服务器中选择。

还可以设置slave_no 指定某个服务器进行读操作。

如果从数据库连接错误，会自动切换到主数据库连接。

调用模型的CURD操作的话，系统会自动判断当前执行的方法的读操作还是写操作，如果你用的是原生SQL，那么需要注意系统的默认规则： 写操作必须用模型的execute方法，读操作必须用模型的query方法，否则会发生主从读写错乱的情况。

注意：主从数据库的数据同步工作不在框架实现，需要数据库考虑自身的同步或者复制机制。


定义

模型定义

定义一个User模型类：

namespace app\index\model;

use think\Model;

class User extends Model
{
}

默认主键为自动识别，如果需要指定，可以设置属性：

namespace app\index\model;

use think\Model;

class User extends Model
{
    protected $pk = 'uid';
}

模型会自动对应数据表，模型类的命名规则是除去表前缀的数据表名称，采用驼峰法命名，并且首字母大写，例如：

模型名 | 约定对应数据表（假设数据库的前缀定义是 think_）
User | think_user
UserType | think_user_type

如果你的规则和上面的系统约定不符合，那么需要设置Model类的数据表名称属性，以确保能够找到对应的数据表。

设置数据表

如果你想指定数据表甚至数据库连接的话，可以使用：

namespace app\index\model;

class User extends \think\Model
{
    // 设置当前模型对应的完整数据表名称
    protected $table = 'think_user';

    // 设置当前模型的数据库连接
    protected $connection = [
        // 数据库类型
        'type'        => 'mysql',
        // 服务器地址
        'hostname'    => '127.0.0.1',
        // 数据库名
        'database'    => 'thinkphp',
        // 数据库用户名
        'username'    => 'root',
        // 数据库密码
        'password'    => '',
        // 数据库编码默认采用utf8
        'charset'     => 'utf8',
        // 数据库表前缀
        'prefix'      => 'think_',
        // 数据库调试模式
        'debug'       => false,
    ];
}

和连接数据库的参数一样，connection属性的值也可以设置为数据库的配置参数。

5.0不支持单独设置当前模型的数据表前缀。

模型调用

模型类可以使用静态调用或者实例化调用两种方式，例如：

// 静态调用
$user = User::get(1);
$user->name = 'thinkphp';
$user->save();

// 实例化模型
$user = new User;
$user->name= 'thinkphp';
$user->save();

// 使用 Loader 类实例化（单例）
$user = Loader::model('User');

// 或者使用助手函数`model`
$user = model('User');
$user->name= 'thinkphp';
$user->save();

实例化模型类主要用于调用模型的自定义方法，更多用法参考后面的章节内容。


模型初始化

模型初始化

模型同样支持初始化，与控制器的初始化不同的是，模型的初始化是重写Model的initialize，具体如下

namespace app\index\model;

use think\Model;

class Index extends Model
{

    //自定义初始化
    protected function initialize()
    {
        //需要调用`Model`的`initialize`方法
        parent::initialize();
        //TODO:自定义的初始化
    }
}

同样也可以使用静态init方法，需要注意的是init只在第一次实例化的时候执行，并且方法内需要注意静态调用的规范，具体如下：

namespace app\index\model;

use think\Model;

class Index extends Model
{

    //自定义初始化
    protected static function init()
    {
        //TODO:自定义的初始化
    }
}


新增

新增数据有多种方式。

添加一条数据

第一种是实例化模型对象后赋值并保存：

$user           = new User;
$user->name     = 'thinkphp';
$user->email    = 'thinkphp@qq.com';
$user->save();

也可以使用data方法批量赋值：

$user = new User;
$user->data([
    'name'  =>  'thinkphp',
    'email' =>  'thinkphp@qq.com'
]);
$user->save();

或者直接在实例化的时候传入数据

$user = new User([
    'name'  =>  'thinkphp',
    'email' =>  'thinkphp@qq.com'
]);
$user->save();

如果需要过滤非数据表字段的数据，可以使用：

$user = new User($_POST);
// 过滤post数组中的非数据表字段数据
$user->allowField(true)->save();

如果你通过外部提交赋值给模型，并且希望指定某些字段写入，可以使用：

$user = new User($_POST);
// post数组中只有name和email字段会写入
$user->allowField(['name','email'])->save();

save方法新增数据返回的是写入的记录数。

获取自增ID

如果要获取新增数据的自增ID，可以使用下面的方式：

$user           = new User;
$user->name     = 'thinkphp';
$user->email    = 'thinkphp@qq.com';
$user->save();
// 获取自增ID
echo $user->id;

注意这里其实是获取模型的主键，如果你的主键不是id，而是user_id的话，其实获取自增ID就变成这样：

$user           = new User;
$user->name     = 'thinkphp';
$user->email    = 'thinkphp@qq.com';
$user->save();
// 获取自增ID
echo $user->user_id;

注意不要在同一个实例里面多次新增数据，如果确实需要多次新增，那么可以用下面的方式：

$user           = new User;
$user->name     = 'thinkphp';
$user->email    = 'thinkphp@qq.com';
$user->save();
$user->name     = 'onethink';
$user->email    = 'onethink@qq.com';
// 第二次开始必须使用下面的方式新增
$user->isUpdate(false)->save();

添加多条数据

支持批量新增，可以使用：

$user = new User;
$list = [
    ['name'=>'thinkphp','email'=>'thinkphp@qq.com'],
    ['name'=>'onethink','email'=>'onethink@qq.com']
];
$user->saveAll($list);

saveAll方法新增数据返回的是包含新增模型（带自增ID）的数据集（数组）。

saveAll方法新增数据默认会自动识别数据是需要新增还是更新操作，当数据中存在主键的时候会认为是更新操作，如果你需要带主键数据批量新增，可以使用下面的方式：

$user = new User;
$list = [
    ['id'=>1, 'name'=>'thinkphp', 'email'=>'thinkphp@qq.com'],
    ['id'=>2, 'name'=>'onethink', 'email'=>'onethink@qq.com'],
];
$user->saveAll($list, false);

如果你自己通过遍历批量新增数据，可以参考下面的方法：

$user = new User;
$list = [
    ['name'=>'thinkphp','email'=>'thinkphp@qq.com'],
    ['name'=>'onethink','email'=>'onethink@qq.com']
];
foreach($list as $data){
    $user->data($data,true)->isUpdate(false)->save();
}

静态方法

还可以直接静态调用create方法创建并写入：

$user = User::create([
    'name'  =>  'thinkphp',
    'email' =>  'thinkphp@qq.com'
]);
echo $user->name;
echo $user->email;
echo $user->id; // 获取自增ID

和save方法不同的是，create方法返回的是当前模型的对象实例。

助手函数

系统提供了model助手函数用于快速实例化模型，并且使用单例实现，例如：

// 使用model助手函数实例化User模型
$user = model('User');
// 模型对象赋值
$user->data([
    'name'  =>  'thinkphp',
    'email' =>  'thinkphp@qq.com'
]);
$user->save();

或者进行批量新增：

$user = model('User');
// 批量新增
$list = [
    ['name'=>'thinkphp','email'=>'thinkphp@qq.com'],
    ['name'=>'onethink','email'=>'onethink@qq.com']
];
$user->saveAll($list);


更新

查找并更新

在取出数据后，更改字段内容后更新数据。

$user = User::get(1);
$user->name     = 'thinkphp';
$user->email    = 'thinkphp@qq.com';
$user->save();

直接更新数据

也可以直接带更新条件来更新数据

$user = new User;
// save方法第二个参数为更新条件
$user->save([
    'name'  => 'thinkphp',
    'email' => 'thinkphp@qq.com'
],['id' => 1]);

上面两种方式更新数据，如果需要过滤非数据表字段的数据，可以使用：

$user = new User();
// 过滤post数组中的非数据表字段数据
$user->allowField(true)->save($_POST,['id' => 1]);

如果你通过外部提交赋值给模型，并且希望指定某些字段写入，可以使用：

$user = new User();
// post数组中只有name和email字段会写入
$user->allowField(['name','email'])->save($_POST, ['id' => 1]);

批量更新数据

可以使用saveAll方法批量更新数据，例如：

$user = new User;
$list = [
    ['id'=>1, 'name'=>'thinkphp', 'email'=>'thinkphp@qq.com'],
    ['id'=>2, 'name'=>'onethink', 'email'=>'onethink@qq.com']
];
$user->saveAll($list);

批量更新仅能根据主键值进行更新，其它情况请使用foreach遍历更新。

如果你自己通过遍历批量更新数据，可以参考下面的方法：

$user = new User;
$list = [
    ['id'=>1, 'name'=>'thinkphp', 'email'=>'thinkphp@qq.com'],
    ['id'=>2, 'name'=>'onethink', 'email'=>'onethink@qq.com']
];
foreach($list as $data){
    $user->data($data,true)->isUpdate(true)->save();
}

通过数据库类更新数据

必要的时候，你也可以使用数据库对象来直接更新数据，但这样就无法使用模型的事件功能。

$user = new User;
$user->where('id', 1)
    ->update(['name' => 'thinkphp']);

或者使用：

$user = new User;
$user->update(['id' => 1, 'name' => 'thinkphp']);

如果传入update的数据包含主键的话，可以无需使用where方法。

静态方法

模型支持静态方法直接更新数据，例如：

User::where('id', 1)
    ->update(['name' => 'thinkphp']);

或者使用：

User::update(['id' => 1, 'name' => 'thinkphp']);

闭包更新

可以通过闭包函数使用更复杂的更新条件，例如：

$user = new User;
$user->save(['name' => 'thinkphp'],function($query){
    // 更新status值为1 并且id大于10的数据
    $query->where('status', 1)->where('id', '>', 10);
});

自动识别

我们已经看到，模型的新增和更新方法都是save方法，系统有一套默认的规则来识别当前的数据需要更新还是新增。

  • 实例化模型后调用save方法表示新增；
  • 查询数据后调用save方法表示更新；
  • 调用模型的save方法后表示更新；

如果你的数据操作比较复杂，可以显式的指定当前调用save方法是新增操作还是更新操作。

显式更新数据：

// 实例化模型
$user = new User;
// 显式指定更新数据操作
$user->isUpdate(true)
    ->save(['id' => 1, 'name' => 'thinkphp']);

显示新增数据：

$user = User::get(1);
$user->name = 'thinkphp';
// 显式指定当前操作为新增操作
$user->isUpdate(false)->save();

注意不要在一个模型实例里面做多次更新，会导致部分重复数据不再更新，正确的方式应该是先查询后更新或者使用模型类的update方法更新。

如果你调用save方法进行多次数据写入的时候，需要注意，第二次save方法的时候必须使用isUpdate(false)，否则会视为更新数据。


删除

删除当前模型

删除模型数据，可以在实例化后调用delete方法。

$user = User::get(1);
$user->delete();

根据主键删除

或者直接调用静态方法

User::destroy(1);
// 支持批量删除多个数据
User::destroy('1,2,3');
// 或者
User::destroy([1,2,3]);

条件删除

使用数组进行条件删除，例如：

// 删除状态为0的数据
User::destroy(['status' => 0]);

还支持使用闭包删除，例如：

User::destroy(function($query){
    $query->where('id','>',10);
});

或者通过数据库类的查询条件删除

User::where('id','>',10)->delete();


查询

获取单个数据

获取单个数据的方法包括：

取出主键为1的数据
$user = User::get(1);
echo $user->name;

// 使用数组查询
$user = User::get(['name' => 'thinkphp']);

// 使用闭包查询
$user = User::get(function($query){
    $query->where('name', 'thinkphp');
});
echo $user->name;

如果你是在模型内部，请不要使用$this->name的方式来获取数据，请使用$this->getAttr('name') 替代。

或者在实例化模型后调用查询方法

$user = new User();
// 查询单个数据
$user->where('name', 'thinkphp')
    ->find();

get或者find方法返回的是当前模型的对象实例，可以使用模型的方法。

获取多个数据

版本 | 新增功能
5.0.4 | 支持在模型中设置resultSetType返回数据集对象的名称

取出多个数据：

// 根据主键获取多个数据
$list = User::all('1,2,3');
// 或者使用数组
$list = User::all([1,2,3]);
foreach($list as $key=>$user){
    echo $user->name;
}
// 使用数组查询
$list = User::all(['status'=>1]);
// 使用闭包查询
$list = User::all(function($query){
    $query->where('status', 1)->limit(3)->order('id', 'asc');
});
foreach($list as $key=>$user){
    echo $user->name;
}

数组方式和闭包方式的数据查询的区别在于，数组方式只能定义查询条件，闭包方式可以支持更多的连贯操作，包括排序、数量限制等。

或者在实例化模型后调用查询方法

$user = new User();
// 查询数据集
$user->where('name', 'thinkphp')
    ->limit(10)
    ->order('id', 'desc')
    ->select();

模型的all方法或者select方法返回的是一个包含模型对象的二维数组或者数据集对象。

自定义数据集对象

V5.0.4+版本开始，支持在模型中单独设置查询数据集的返回对象的名称（默认是数组），例如：

<?php
namespace app\index\model;

use think\Model;

class User extends Model
{
    // 设置返回数据集的对象名
    protected $resultSetType = 'collection';
}

resultSetType字段如果为空则使用数组作为数据集返回类型，如果设置为collection则表示使用think\Collection作为返回对象名，也可以设置自定义的数据集对象名称（使用完整的命名空间定义）。

获取某个字段或者某个列的值

// 获取某个用户的积分
User::where('id',10)->value('score');
// 获取某个列的所有值
User::where('status',1)->column('name');
// 以id为索引
User::where('status',1)->column('name','id');

注意value和column方法返回的不再是一个模型对象实例，而是单纯的值或者某个列的数组。

动态查询

支持动态查询方法，例如：

// 根据name字段查询用户
$user = User::getByName('thinkphp');

// 根据email字段查询用户
$user = User::getByEmail('thinkphp@qq.com');

通过Query类查询

或者使用数据库的查询方法进行更复杂的查询：

User::where('id','>',10)->select();
User::where('name','thinkphp')->find();

可以在模型中直接使用所有数据库的链式操作方法。

返回的查询结果是当前模型对应的对象或者包含模型对象的数据集。

数据分批处理

模型也可以支持对返回的数据分批处理，这在处理大量数据的时候非常有用，例如：

User::chunk(100,function($users){
    foreach($users as $user){
        // 处理user模型对象
    }
});


聚合

在模型中也可以调用数据库的聚合方法进行查询，例如： | 方法 | 说明
Count | 统计数量，参数是要统计的字段名（可选）
Max | 获取最大值，参数是要统计的字段名（必须）
Min | 获取最小值，参数是要统计的字段名（必须）
Avg | 获取平均值，参数是要统计的字段名（必须）
Sum | 获取总分，参数是要统计的字段名（必须）

静态调用：

User::count();
User::where('status','>',0)->count();
User::where('status',1)->avg('score');
User::max('score');

动态调用：

$user = new User;
$user->count();
$user->where('status','>',0)->count();
$user->where('status',1)->avg('score');
$user->max('score');


获取器

获取器

获取器的作用是在获取数据的字段值后自动进行处理，例如，我们需要对状态值进行转换，可以使用：

class User extends Model
{
    public function getStatusAttr($value)
    {
        $status = [-1=>'删除',0=>'禁用',1=>'正常',2=>'待审核'];
        return $status[$value];
    }
}

数据表的字段会自动转换为驼峰法，一般status字段的值采用数值类型，我们可以通过获取器定义，自动转换为字符串描述。

$user = User::get(1);
echo $user->status; // 例如输出“正常”

获取器还可以定义数据表中不存在的字段，例如：

class User extends Model
{
    public function getStatusTextAttr($value,$data)
    {
        $status = [-1=>'删除',0=>'禁用',1=>'正常',2=>'待审核'];
        return $status[$data['status']];
    }
}

获取器方法的第二个参数传入的是当前的所有数据数组。

我们就可以直接使用status_text字段的值了，例如：

$user = User::get(1);
echo $user->status_text; // 例如输出“正常”

获取器只有当获取某个数据属性的时候自动触发，如果你要获取包含修改器处理的全部数据属性的话，可以使用下面的方法：

$user = User::get(1);
// 获取全部获取器数据
dump($user->toArray());

获取原始数据

如果你定义了获取器的情况下，希望获取数据表中的原始数据，可以使用：

$user = User::get(1);
// 通过获取器获取字段
echo $user->status;
// 获取原始字段数据
echo $user->getData('status');
// 获取全部原始数据
dump($user->getData());


修改器

修改器

修改器的作用是可以在数据赋值的时候自动进行转换处理，例如：

class User extends Model
{
    public function setNameAttr($value)
    {
        return strtolower($value);
    }
}

如下代码实际保存到数据库中的时候会转为小写

$user = new User();
$user->name = 'THINKPHP';
$user->save();
echo $user->name; // thinkphp

也可以进行序列化字段的组装：

class User extends Model
{
    public function setNameAttr($value,$data)
    {
        return serialize($data);
    }
}

修改器方法的第二个参数会自动传入当前的所有数据数组。

批量修改

除了赋值的方式可以触发修改器外，还可以用下面的方法批量触发修改器：

$user = new User();
$data['name'] = 'THINKPHP';
$data['email'] = 'thinkphp@qq.com';
$user->data($data, true);
$user->save();
echo $user->name; // thinkphp

或者直接使用save方法触发，例如：

$user = new User();
$data['name'] = 'THINKPHP';
$data['email'] = 'thinkphp@qq.com';
$user->save($data);
echo $user->name; // thinkphp


时间戳

系统支持自动写入创建和更新的时间戳字段，有两种方式配置支持。

第一种方式，是在数据库配置文件中添加全局设置：

// 开启自动写入时间戳字段
'auto_timestamp' => true,

第二种是直接在单独的模型类里面设置：

protected $autoWriteTimestamp = true;

如果这两个地方设置为true，默认识别为整型int类型，如果你的时间字段不是int类型的话，例如使用datetime类型的话，可以这样设置：

// 开启自动写入时间戳字段
'auto_timestamp' => 'datetime',

或者

protected $autoWriteTimestamp = 'datetime';

字段名默认创建时间字段为create_time，更新时间字段为update_time，支持的字段类型包括timestamp/datetime/int。

写入数据的时候，系统会自动写入create_time和update_time字段，而不需要定义修改器，例如：

$user = new User();
$user->name = 'THINKPHP';
$user->save();
echo $user->create_time; // 输出类似 1462545582
echo $user->update_time; // 输出类似 1462545582

时间戳字段输出的时候一样支持获取器。

如果你的数据表字段不是默认值的话，可以按照下面的方式定义：

class User extends Model
{
    // 定义时间戳字段名
    protected $createTime = 'create_at';
    protected $updateTime = 'update_at';
}

下面是修改字段后的输出代码：

$user = new User();
$user->name = 'THINKPHP';
$user->save();
echo $user->create_at; // 输出类似 1462545612
echo $user->update_at; // 输出类似 1462545612

如果你只需要使用create_time字段而不需要自动写入update_time，则可以单独设置关闭某个字段，例如：

class User extends Model
{
    // 关闭自动写入update_time字段
    protected $updateTime = false;
}

如果不需要任何自动写入的时间戳字段的话，可以关闭时间戳自动写入功能，设置如下：

class User extends Model
{
    // 关闭自动写入时间戳
    protected $autoWriteTimestamp = false;
}

如果是关闭全局的自动时间写入，则可以使用：

// 关闭全局自动写入时间字段
'auto_timestamp' => false,


只读字段

只读字段用来保护某些特殊的字段值不被更改，这个字段的值一旦写入，就无法更改。 要使用只读字段的功能，我们只需要在模型中定义readonly属性：

namespace app\index\model;

use think\Model;

class User extends Model
{
    protected $readonly = ['name','email'];
}

例如，上面定义了当前模型的name和email字段为只读字段，不允许被更改。也就是说当执行更新方法之前会自动过滤到只读字段的值，避免更新到数据库。

下面举个例子说明下：

$user = User::get(5);
 // 更改某些字段的值
$user->name = 'TOPThink';
$user->email = 'Topthink@gmail.com';
$user->address = '上海静安区';
 // 保存更改后的用户数据
$user->save();

事实上，由于我们对name和email字段设置了只读，因此只有address字段的值被更新了，而name和email的值仍然还是更新之前的值。


软删除

软删除

版本 | 调整功能
5.0.2 | deleteTime 属性改为非静态定义

在实际项目中，对数据频繁使用删除操作会导致性能问题，软删除的作用就是把数据加上删除标记，而不是真正的删除，同时也便于需要的时候进行数据的恢复。

要使用软删除功能，需要引入SoftDelete trait，例如User模型按照下面的定义就可以使用软删除功能：

namespace app\index\model;

use think\Model;
use traits\model\SoftDelete;

class User extends Model
{
    use SoftDelete;
    protected $deleteTime = 'delete_time';
}

5.0.2版本之前deleteTime属性必须使用static定义。

deleteTime属性用于定义你的软删除标记字段，ThinkPHP5的软删除功能使用时间戳类型（数据表默认值为Null），用于记录数据的删除时间。

可以用类型转换指定软删除字段的类型，建议数据表的所有时间字段统一一种类型。

定义好模型后，我们就可以使用：

// 软删除
User::destroy(1);
// 真实删除
User::destroy(1,true);
$user = User::get(1);
// 软删除
$user->delete();
// 真实删除
$user->delete(true);

默认情况下查询的数据不包含软删除数据，如果需要包含软删除的数据，可以使用下面的方式查询：

User::withTrashed()->find();
User::withTrashed()->select();

如果仅仅需要查询软删除的数据，可以使用：

User::onlyTrashed()->find();
User::onlyTrashed()->select();

如果你的模型定义了base基础查询，请确保添加软删除的基础查询条件。


类型转换

支持给字段设置类型自动转换，会在写入和读取的时候自动进行类型转换处理，例如：

class User extends Model
{
    protected $type = [
        'status'    =>  'integer',
        'score'     =>  'float',
        'birthday'  =>  'datetime',
        'info'      =>  'array',
    ];
}

下面是一个类型自动转换的示例：

$user = new User;
$user->status = '1';
$user->score = '90.50';
$user->birthday = '2015/5/1';
$user->info = ['a'=>1,'b'=>2];
$user->save();
var_dump($user->status); // int 1
var_dump($user->score); // float 90.5;
var_dump($user->birthday); // string '2015-05-01 00:00:00'
var_dump($user->info);// array (size=2) 'a' => int 1  'b' => int 2

数据库查询默认取出来的数据都是字符串类型，如果需要转换为其他的类型，需要设置，支持的类型包括如下类型：

integer

设置为integer（整型）后，该字段写入和输出的时候都会自动转换为整型。

float

该字段的值写入和输出的时候自动转换为浮点型。

boolean

该字段的值写入和输出的时候自动转换为布尔型。

array

如果设置为强制转换为array类型，系统会自动把数组编码为json格式字符串写入数据库，取出来的时候会自动解码。

object

该字段的值在写入的时候会自动编码为json字符串，输出的时候会自动转换为stdclass对象。

serialize

指定为序列化类型的话，数据会自动序列化写入，并且在读取的时候自动反序列化。

json

指定为json类型的话，数据会自动json_encode写入，并且在读取的时候自动json_decode处理。

timestamp

指定为时间戳字段类型的话，该字段的值在写入时候会自动使用strtotime生成对应的时间戳，输出的时候会自动转换为dateFormat属性定义的时间字符串格式，默认的格式为Y-m-d H:i:s，如果希望改变其他格式，可以定义如下：

class User extends Model
{
    protected $dateFormat = 'Y/m/d';
    protected $type = [
        'status'    =>  'integer',
        'score'     =>  'float',
        'birthday'  =>  'timestamp',
    ];
}

或者在类型转换定义的时候使用：

class User extends Model
{
    protected $type = [
        'status'    =>  'integer',
        'score'     =>  'float',
        'birthday'  =>  'timestamp:Y/m/d',
    ];
}

然后就可以

$user = User::find(1);
echo $user->birthday; // 2015/5/1

datetime

和timestamp类似，区别在于写入和读取数据的时候都会自动处理成时间字符串Y-m-d H:i:s的格式。


数据完成

数据自动完成指在不需要手动赋值的情况下对字段的值进行处理后写入数据库。

系统支持auto、insert和update三个属性，可以分别在写入、新增和更新的时候进行字段的自动完成机制，auto属性自动完成包含新增和更新操作，例如我们定义User模型类如下：

namespace app\index\model;

use think\Model;

class User extends Model
{
    protected $auto = ['name', 'ip'];
    protected $insert = ['status' => 1];
    protected $update = [];

    protected function setNameAttr($value)
    {
        return strtolower($value);
    }

    protected function setIpAttr()
    {
        return request()->ip();
    }
}

在新增数据的时候，会对name、ip和 status 字段自动完成或者处理。

$user = new User;
$user->name = 'ThinkPHP';
$user->save();
echo $user->name; // thinkphp
echo $user->status; // 1

在保存操作的时候，会自动处理name字段的值及完成ip字段的赋值。

$user = User::find(1);
$user->name = 'THINKPHP';
$user->save();
echo $user->name; // thinkphp
echo $user->ip; // 127.0.0.1

开发者需要理清“修改器”与“自动完成”的关系。


查询范围

可以对模型的查询和写入操作进行封装，例如：

namespace app\index\model;

use think\Model;

class User extends Model
{

    protected function scopeThinkphp($query)
    {
        $query->where('name','thinkphp')->field('id,name');
    }

    protected function scopeAge($query)
    {
        $query->where('age','>',20)->limit(10);
    }

}

就可以进行下面的条件查询：

// 查找name为thinkphp的用户
User::scope('thinkphp')->get();
// 查找年龄大于20的10个用户
User::scope('age')->all();
// 查找name为thinkphp的用户并且年龄大于20的10个用户
User::scope('thinkphp,age')->all();

可以直接使用闭包函数进行查询，例如：

User::scope(function($query){
    $query->where('age','>',20)->limit(10);
})->all();

支持动态调用的方式，例如：

$user = new User;
// 查找name为thinkphp的用户
$user->thinkphp()->get();
// 查找年龄大于20的10个用户
$user->age()->all();
// 查找name为thinkphp的用户并且年龄大于20的10个用户
$user->thinkphp()->age()->all();

命名范围方法之前不能调用查询的连贯操作方法，必须首先被调用。

全局查询范围

如果你的所有查询都需要一个基础的查询范围，那么可以在模型类里面定义一个静态的base方法，例如：

namespace app\index\model;

use think\Model;

class User extends Model
{
    // 定义全局的查询范围
    protected function base($query)
    {
        $query->where('status',1);
    }
}

全局查询范围方法在5.0.2版本之前必须定义为static静态方法。

然后，执行下面的代码：

$user = User::get(1);

最终的查询条件会是

status = 1 AND id = 1

如果需要动态关闭/开启全局查询访问，可以使用：

// 关闭全局查询范围
User::useGlobalScope(false)->get(1);
// 开启全局查询范围
User::useGlobalScope(true)->get(2);


模型分层

ThinkPHP支持模型的分层 ，除了Model层之外，我们可以项目的需要设计和创建其他的模型层。

通常情况下，不同的分层模型仍然是继承系统的\think\Model类或其子类，所以，其基本操作和Model类的操作是一致的。

例如在index模块的设计中需要区分数据层、逻辑层、服务层等不同的模型层，我们可以在模块目录下面创建model、logic和service目录，把对用户表的所有模型操作分成三层：

  • 数据层：app\index\model\User 用于定义数据相关的自动验证和自动完成和数据存取接口
  • 逻辑层：app\index\logic\User 用于定义用户相关的业务逻辑
  • 服务层：app\index\service\User 用于定义用户相关的服务接口等

三个模型层的定义如下：

app\index\model\User.php

namespace app\index\model;

use think\Model;

class User extends Model
{
}

实例化方法：\think\Loader::model('User')

Logic类：app\index\logic\User.php

namespace app\index\logic;

use think\Model;

class User extends Model
{
}

实例化方法：\think\Loader::model('User','logic');

Service类：app\index\service\User.php

namespace app\index\service;

use think\Model;

class User extends Model
{
}

实例化方法：\think\Loader::model('User','service');


数组访问和转换

数组访问

版本 | 新增功能
5.0.4 | 增加appendRelationAttr方法追加关联模型的属性

模型对象支持数组方式访问，例如：

$user = User::find(1);
echo $user->name ; // 有效
echo $user['name'] // 同样有效
$user->name = 'thinkphp'; // 有效
$user['name'] = 'thinkphp'; // 同样有效
$user->save();

转换为数组

可以使用toArray方法将当前的模型实例输出为数组，例如：

$user = User::find(1);
dump($user->toArray());

支持设置不输出的字段属性：

$user = User::find(1);
dump($user->hidden(['create_time','update_time'])->toArray());

数组输出的字段值会经过获取器的处理，也可以支持追加其它获取器定义（不在数据表字段列表中）的字段，例如：

$user = User::find(1);
dump($user->append(['status_text'])->toArray());

支持设置允许输出的属性，例如：

$user = User::find(1);
dump($user->visible(['id','name','email'])->toArray());

追加关联模型的属性（V5.0.4+）

V5.0.4+版本开始，支持追加一对一关联模型的属性到当前模型，例如：

$user = User::find(1);
dump($user->appendRelationAttr('profile',['email','nickname'])->toArray());

profile是关联定义方法名，email和nickname是Profile模型的属性。


JSON序列化

版本 | 新增功能
5.0.4 | 增加appendRelationAttr方法追加关联模型的属性

可以调用模型的toJson方法进行JSON序列化

$user = User::get(1);
echo $user->toJson();

可以设置无需输出的字段，例如：

$user = User::get(1);
echo $user->hidden(['create_time','update_time'])->toJson();

或者追加其它的字段：

$user = User::get(1);
echo $user->append(['status_text'])->toJson();

设置允许输出的属性：

$user = User::get(1);
echo $user->visible(['id','name','email'])->toJson();

模型对象可以直接被JSON序列化，例如：

echo json_encode(User::get(1));

输出结果类似于：

{"id":"1","name":"","title":"","status":"1","update_time":"1430409600","score":"90.5"}

或者也可以直接echo 一个模型对象，例如：

echo User::get(1);

输出的结果和上面是一样的。

追加关联模型的属性（V5.0.4+）

V5.0.4+版本开始，支持追加一对一关联模型的属性到当前模型，例如：

$user = User::find(1);
echo $user->appendRelationAttr('profile',['email','nickname'])->toJson();

profile是关联定义方法名，email和nickname是Profile模型的属性。


事件

模型事件

版本 | 新增功能
5.0.4 | 增加模型事件注册快捷方法

模型类支持before_delete、after_delete、before_write、after_write、before_update、after_update、before_insert、after_insert事件行为

标签位 | 描述
before_insert | 新增前
after_insert | 新增后
before_update | 更新前
after_update | 更新后
before_write | 写入前
after_write | 写入后
before_delete | 删除前
after_delete | 删除后

使用方法如下：

User::event('before_insert', function ($user) {
            if ($user->status != 1) {
                return false;
            }
        });

注册的回调方法支持传入一个参数（当前的模型对象实例），并且before_write、before_insert、 before_update 、before_delete事件方法如果返回false，则不会继续执行。

支持给一个位置注册多个回调方法，例如：

        User::event('before_insert', function ($user) {
            if ($user->status != 1) {
                return false;
            }
        });
        // 注册回调到beforeInsert函数
        User::event('before_insert', 'beforeInsert');

可以在模型类的init方法里面统一注册模型事件，例如：

namespace app\index\model;

use think\Model;

class User extends Model
{
    protected static function init()
    {
        User::event('before_insert', function ($user) {
            if ($user->status != 1) {
                return false;
            }
        });
    }
}

快捷注册（V5.0.4+）

V5.0.4+版本开始，系统提供了内置的事件注册的快捷方法，你可以用下面的方式替代

namespace app\index\model;

use think\Model;

class User extends Model
{
    protected static function init()
    {
        User::beforeInsert(function ($user) {
            if ($user->status != 1) {
                return false;
            }
        });
    }
}

这些模型类的快捷方法如下：

标签位 | 描述
beforeInsert | 新增前
afterInsert | 新增后
beforeUpdate | 更新前
afterUpdate | 更新后
beforeWrite | 写入前
afterWrite | 写入后
beforeDelete | 删除前
afterDelete | 删除后


一对一关联

一对一关联

版本 | 新增功能
5.0.4 | 增加关联属性绑定到父模型功能

定义

定义一对一关联，例如，一个用户都有一个个人资料，我们定义User模型如下：

namespace app\index\model;

use think\Model;

class User extends Model
{
    public function profile()
    {
        return $this->hasOne('Profile');
    }
}

hasOne方法的参数包括：

hasOne('关联模型名','外键名','主键名',['模型别名定义'],'join类型');

默认的join类型为INNER。

V5.0.3+版本开始，可以支持为关联模型定义需要查询的字段，例如：

namespace app\index\model;

use think\Model;

class User extends Model
{
    public function profile()
    {
        return $this->hasOne('Profile')->field('id,name,email');
    }
}

关联查找

定义好关联之后，就可以使用下面的方法获取关联数据：

$user = User::find(1);
// 输出Profile关联模型的email属性
echo $user->profile->email;

默认情况下， 我们使用的是user_id 作为外键关联，如果不是的话则需要在关联定义的时候指定，例如：

namespace app\index\model;

use think\Model;

class User extends Model
{
    public function profile()
    {
        return $this->hasOne('Profile','uid');
    }
}

有一点需要注意的是，关联方法的命名规范是驼峰法，而关联属性则一般是小写+下划线的方式，系统在获取的时候会自动转换对应，读取user_profile关联属性则对应的关联方法应该是userProfile。

设置别名

如果你的模型名和数据库关键字冲突的话，可以设置别名，例如：

namespace app\index\model;

use think\Model;

class User extends Model
{
    public function profile()
    {
        return $this->hasOne('Profile','uid')->setAlias(['user'=>'member']);
    }
}

关联新增

$user = User::find(1);
// 如果还没有关联数据 则进行新增
$user->profile()->save(['email' => 'thinkphp']);

系统会自动把当前模型的主键传入profile模型。

关联更新

和新增一样使用save方法进行更新关联数据。

$user = User::find(1);
$user->profile->email = 'thinkphp';
$user->profile->save();
// 或者
$user->profile->save(['email' => 'thinkphp']);

定义相对的关联

我们可以在Profile模型中定义一个相对的关联关系，例如：

namespace app\index\model;

use think\Model;

class Profile extends Model
{
    public function user()
    {
        return $this->belongsTo('User');
    }
}

belongsTo的参数包括：

belongsTo('关联模型名','外键名','关联表主键名',['模型别名定义'],'join类型');

默认的关联外键是user_id，如果不是，需要在第二个参数定义

namespace app\index\model;

use think\Model;

class Profile extends Model
{
    public function user()
    {
        return $this->belongsTo('User','uid');
    }
}

我们就可以根据档案资料来获取用户模型的信息

$profile = Profile::find(1);
// 输出User关联模型的属性
echo $profile->user->account;

绑定属性到父模型（V5.0.4+）

可以在定义关联的时候使用bind方法绑定属性到父模型，例如：

namespace app\index\model;

use think\Model;

class User extends Model
{
    public function profile()
    {
        return $this->hasOne('Profile','uid')->bind('nickname,email');
    }
}

或者使用数组的方式指定绑定属性别名

namespace app\index\model;

use think\Model;

class User extends Model
{
    public function profile()
    {
        return $this->hasOne('Profile','uid')->bind([
                'email',
                'truename'  => 'nickname',
                'profile_id'  => 'id',
            ]);
    }
}

然后使用关联预载入查询的时候，可以使用

$user = User::find(1);
// 输出Profile关联模型的email属性
echo $user->email;
echo $user->profile_id;

绑定关联属性不影响原有关联属性的读取，绑定关联模型的属性支持读取器。

如果不是预载入查询，请使用模型的appendRelationAttr方法追加属性。


一对多关联

一对多关联

关联定义

一对多关联的情况也比较常见，使用hasMany方法定义，
参数包括：

hasMany('关联模型名','外键名','主键名',['模型别名定义']);

例如一篇文章可以有多个评论

namespace app\index\model;

use think\Model;

class Article extends Model
{
    public function comments()
    {
        return $this->hasMany('Comment');
    }
}

同样，也可以定义外键的名称

namespace app\index\model;

use think\Model;

class Article extends Model
{
    public function comments()
    {
        return $this->hasMany('Comment','art_id');
    }
}

如果需要指定查询字段，可以使用下面的方式：

namespace app\index\model;

use think\Model;

class Article extends Model
{
    public function comments()
    {
        return $this->hasMany('Comment')->field('id,author,content');
    }
}

关联查询

我们可以通过下面的方式获取关联数据

$article = Article::get(1);
// 获取文章的所有评论
dump($article->comments);
// 也可以进行条件搜索
dump($article->comments()->where('status',1)->select());

根据关联条件查询

可以根据关联条件来查询当前模型对象数据，例如：

// 查询评论超过3个的文章
$list = Article::has('comments','>',3)->select();
// 查询评论状态正常的文章
$list = Article::hasWhere('comments',['status'=>1])->select();

关联新增

$article = Article::find(1);
// 增加一个关联数据
$article->comments()->save(['content'=>'test']);
// 批量增加关联数据
$article->comments()->saveAll([
    ['content'=>'thinkphp'],
    ['content'=>'onethink'],
]);

定义相对的关联

要在 Comment 模型定义相对应的关联，可使用 belongsTo 方法：

name app\index\model;

use think\Model;

class Comment extends Model
{
    public function article()
    {
        return $this->belongsTo('article');
    }
}


远程一对多

远程一对多关联用于定义有跨表的一对多关系，例如：

  • 每个城市有多个用户
  • 每个用户有多个话题
  • 城市和话题之间并无关联

关联定义

就可以直接通过远程一对多关联获取每个城市的多个话题，City模型定义如下：

namespace app\index\model;

use think\Model;

class City extends Model
{
    public function topics()
    {
        return $this->hasManyThrough('Topic','User');
    }
}

远程一对多关联，需要同时存在Topic和User模型。

hasManyThrough方法的参数如下：

hasManyThrough('关联模型名','中间模型名','外键名','中间模型关联键名','当前模型主键名',['模型别名定义']);

关联查询

我们可以通过下面的方式获取关联数据

$city = City::get(1);
// 获取同城的所有话题
dump($city->topics);
// 也可以进行条件搜索
dump($city->topics()->where('topic.status',1)->select());

条件搜索的时候，需要带上模型名作为前缀


多对多关联

多对多关联

关联定义

例如，我们的用户和角色就是一种多对多的关系，我们在User模型定义如下：

namespace app\index\model;

use think\Model;

class User extends Model
{
    public function roles()
    {
        return $this->belongsToMany('Role');
    }
}

belongsToMany方法的参数如下：

belongsToMany('关联模型名','中间表名','外键名','当前模型关联键名',['模型别名定义']);

关联查询

我们可以通过下面的方式获取关联数据

$user = User::get(1);
// 获取用户的所有角色
dump($user->roles);

关联新增

$user = User::get(1);
// 增加关联数据 会自动写入中间表数据
$user->roles()->save(['name'=>'管理员']);
// 批量增加关联数据
$user->roles()->saveAll([
    ['name'=>'管理员'],
    ['name'=>'操作员'],
]);

只新增中间表数据，可以使用

$user = User::get(1);
// 仅增加关联的中间表数据
$user->roles()->save(1);
// 或者
$role = Role::get(1);
$user->roles()->save($role);
// 批量增加关联数据
$user->roles()->saveAll([1,2,3]);

单独更新中间表数据，可以使用：

$user = User::get(1);
// 增加关联的中间表数据
$user->roles()->attach(1);
// 传入中间表的额外属性
$user->roles()->attach(1,['remark'=>'test']);
// 删除中间表数据
$user->roles()->detach([1,2,3]);

定义相对的关联

我们可以在Role模型中定义一个相对的关联关系，例如：

namespace app\index\model;

use think\Model;

class Role extends Model
{
    public function users()
    {
        return $this->belongsToMany('User');
    }
}


多态一对多

多态一对多关联（V5.0.4+）

多态关联允许一个模型在单个关联定义方法中从属一个以上其它模型，例如用户可以评论书和文章，但评论表通常都是同一个数据表的设计。多态一对多关联关系，就是为了满足类似的使用场景而设计。

下面是关联表的数据表结构：

article
    id - integer
    title - string
    content - text

book
    id - integer
    title - string

comment
    id - integer
    content - text
    commentable_id - integer
    commentable_type - string

有两个需要注意的字段是 comment 表中的 commentable_id 和 commentable_type我们称之为多态字段。其中， commentable_id 用于存放书或者文章的 id（主键） ，而 commentable_type 用于存放所属模型的类型。通常的设计是多态字段有一个公共的前缀（例如这里用的commentable），当然，也支持设置完全不同的字段名（例如使用data_id和type）。

多态关联定义

接着，让我们来查看创建这种关联所需的模型定义：

文章模型：

<?php
namespace app\index\model;

use think\Model;

class Article extends Model
{
    /**
     * 获取所有针对文章的评论。
     */
    public function comments()
    {
        return $this->morphMany('Comment', 'commentable');
    }
}

morphMany方法的参数如下：

morphMany('关联模型名','多态字段信息','多态类型');

关联模型名（必须）：关联的模型名称，可以使用模型名（如Comment）或者完整的命名空间模型名（如app\index\model\Comment）。

多态字段信息（可选）：支持两种方式定义 如果是字符串表示多态字段的前缀，多态字段使用 多态前缀_type和多态前缀_id，如果是数组，表示使用['多态类型字段名','多态ID字段名']，默认为当前的关联方法名作为字段前缀。

多态类型（可选）：当前模型对应的多态类型，默认为当前模型名，可以使用模型名（如Article）或者完整的命名空间模型名（如app\index\model\Article）。

书籍模型：

<?php
namespace app\index\model;

use think\Model;

class Book extends Model
{
    /**
     * 获取所有针对书籍的评论。
     */
    public function comments()
    {
        return $this->morphMany('Comment', 'commentable');
    }
}

书籍模型的设置方法同文章模型一致，区别在于多态类型不同，但由于多态类型默认会取当前模型名，因此不需要单独设置。

下面是评论模型的关联定义：

<?php
namespace app\index\model;

use think\Model;

class Comment extends Model
{
    /**
     * 获取评论对应的多态模型。
     */
    public function commentable()
    {
        return $this->morphTo();
    }
}

morphTo方法的参数如下：

morphTo('多态字段信息',['多态类型别名']);

多态字段信息（可选）：支持两种方式定义 如果是字符串表示多态字段的前缀，多态字段使用 多态前缀_type和多态前缀_id，如果是数组，表示使用['多态类型字段名','多态ID字段名']，默认为当前的关联方法名作为字段前缀
多态类型别名（可选）：数组方式定义

获取多态关联

一旦你的数据表及模型被定义，则可以通过模型来访问关联。例如，若要访问某篇文章的所有评论，则可以简单的使用 comments 动态属性：

$article = Article::get(1);

foreach ($article->comments as $comment) {
    dump($comment);
}

你也可以从多态模型的多态关联中，通过访问调用 morphTo 的方法名称来获取拥有者，也就是此例子中 Comment 模型的 commentable 方法。所以，我们可以使用动态属性来访问这个方法：

$comment = Comment::get(1);
$commentable = $comment->commentable;

Comment 模型的 commentable 关联会返回 Article 或 Book 模型的对象实例，这取决于评论所属模型的类型。

自定义多态关联的类型字段

默认情况下，ThinkPHP 会使用模型名作为多态表的类型区分，例如，Comment属于 Article 或者 Book , commentable_type 的默认值可以分别是 Article 或者 Book 。我们可以通过定义多态的时候传入参数来对数据库进行解耦。

    public function commentable()
    {
        return $this->morphTo('commentable',[
            'book'  =>  'app\index\model\Book',
            'post'  =>  'app\admin\model\Article',
        ]);
    }


动态属性

模型对象的关联属性可以直接作为当前模型对象的动态属性进行赋值或者取值操作（延迟查询），虽然该属性并非数据表字段，例如：

namespace app\index\model;

use think\Model;

class User extends Model
{
    public function profile()
    {
        return $this->hasOne('Profile');
    }
}

我们在使用

// 查询模型数据
$user = User::find(1);
// 获取动态属性
dump($user->profile);
// 给关联模型属性赋值
$user->profile->phone = '1234567890';
// 保存关联模型数据
$user->profile->save();

在获取动态属性profile的同时，模型会通过定义的关联方法去查询关联对象的数据并赋值给该动态属性，这是一种关联数据的“惰性加载”，只有真正访问关联属性的时候才会进行关联查询。

当有大量的关联数据需要查询的时候，一般都会考虑选择关联预载入的方式（参考下一节）。


关联预载入

版本 | 新增功能
5.0.4 | 一对一关联支持JOIN和IN两种方式预载入查询

关联查询的预查询载入功能，主要解决了N+1次查询的问题，例如下面的查询如果有3个记录，会执行4次查询：

$list = User::all([1,2,3]);
foreach($list as $user){
    // 获取用户关联的profile模型数据
    dump($user->profile);
}

如果使用关联预查询功能，对于一对一关联来说，只有一次查询，对于一对多关联的话，就可以变成2次查询，有效提高性能。

$list = User::with('profile')->select([1,2,3]);
foreach($list as $user){
    // 获取用户关联的profile模型数据
    dump($user->profile);
}

支持预载入多个关联，例如：

$list = User::with('profile,book')->select([1,2,3]);

也可以支持嵌套预载入，例如：

$list = User::with('profile.phone')->select([1,2,3]);
foreach($list as $user){
    // 获取用户关联的phone模型
    dump($user->profile->phone);
}

可以在模型的get和all方法中使用预载入，和使用select方法是等效的：

$list = User::all([1,2,3],'profile,book');

如果要指定属性查询，可以使用：

$list = User::field('id,name')->with(['profile'=>function($query){$query->withField('email,phone');}])->select([1,2,3]);
foreach($list as $user){
    // 获取用户关联的profile模型数据
    dump($user->profile);
}

关联预载入名称是关联方法名，从V5.0.4+版本开始，支持传入方法名的小写和下划线定义方式，例如如果关联方法名是userProfile和userBook的话：

$list = User::with('userProfile,userBook')->select([1,2,3]);

等效于：

$list = User::with('user_profile,user_book')->select([1,2,3]);

V5.0.4+版本开始一对一关联预载入支持两种方式：JOIN方式（默认）和IN方式，如果要使用IN方式关联预载入，在关联定义方法中添加

namespace app\index\model;

use think\Model;

class User extends Model
{
    public function profile()
    {
        // 设置预载入查询方式为IN方式
        return $this->hasOne('Profile')->setEagerlyType(1);
    }
}


聚合模型

聚合模型

版本 | 调整功能
5.0.3 | 支持使用field属性定义需要的字段
5.0.2 | relationModel 属性改为非静态定义

通过聚合模型可以把一对一关联的操作更加简化，只需要把你的模型类继承think\model\Merge，就可以自动完成关联查询、关联保存和关联删除。

例如下面的用户表关联了档案表，两个表信息如下：

think_user

字段名 | 描述
id | 主键
name | 用户名
password | 密码
nickname | 昵称

think_profile

字段名 | 描述
id | 主键
truename | 真实姓名
phone | 电话
email | 邮箱
user_id | 用户ID

我们只需要定义好主表的模型，例如下面是User模型的定义：

namespace app\index\model;

use think\model\Merge;

class User extends Merge
{
    // 定义关联模型列表
    protected static $relationModel = ['Profile'];
    // 定义关联外键
    protected $fk = 'user_id';
    protected $mapFields = [
        // 为混淆字段定义映射
        'id'        =>  'User.id',
        'profile_id' =>  'Profile.id',
    ];
}

V5.0.2+版本relationModel 属性不再使用static定义了。

如果需要单独设置关联数据表，可以使用：

namespace app\index\model;

use think\model\Merge;

class User extends Merge
{
    // 设置主表名
    protected $table = 'think_user';
    // 定义关联模型列表
    protected static $relationModel = [
        // 给关联模型设置数据表
        'Profile'   =>  'think_user_profile',
    ];
    // 定义关联外键
    protected $fk = 'user_id';
    protected $mapFields = [
        // 为混淆字段定义映射
        'id'        =>  'User.id',
        'profile_id' =>  'Profile.id',
    ];
}

注意：对于关联表中存在混淆的字段名一定要通过mapFields属性定义。

接下来，我们可以和使用普通模型一样的方法来操作用户模型及其关联数据。

// 关联查询
$user = User::get(1);
echo $user->id;
echo $user->name;
echo $user->phone;
echo $user->email;
echo $user->profile_id;
$user->email = 'thinkphp@qq.com';
// 关联保存
$user->save();
// 关联删除
$user->delete();
// 根据主键关联删除
User::destroy([1,2,3]);

操作两个数据表就和操作一个表一样的感觉，关联表的写入、更新和删除自动采用事务（只要数据库支持事务），一旦主表写入失败或者发生异常就会发生回滚。

如果主表除了Profile关联之外，还有其他的一对多关联，一样可以定义额外的关联，例如：

namespace app\index\model;
use think\model\Merge;

class User extends Merge
{
    // 定义关联模型列表
    protected static $relationModel = ['Profile'];
    // 定义关联外键
    protected $fk = 'user_id';
    protected $mapFields = [
        // 为混淆字段定义映射
        'id'        =>  'User.id',
        'profile_id' =>  'Profile.id',
    ];

    public function articles(){
        return $this->hasMany('Article');
    }
}

对一对多关联进行操作，例如：

$user = User::get(1);
// 读取关联信息
dump($user->articles);
// 或者进行关联预载入
$user = User::get(1,'articles');

注意：不能再次对 已经在relationModel属性中定义过的关联表进行关联定义和预载入查询。


视图实例化

视图功能由\think\View类配合视图驱动（模板引擎）类一起完成，目前的内置模板引擎包含PHP原生模板和Think模板引擎。

因为新版的控制器可以无需继承任何的基础类，因此在控制器中如何使用视图取决于你怎么定义控制器。

继承\think\Controller类

如果你的控制器继承了\think\Controller类的话，则无需自己实例化视图类，可以直接调用控制器基础类封装的相关视图类的方法。

 // 渲染模板输出
return $this->fetch('hello',['name'=>'thinkphp']);
下面的方法可以直接被调用： | 方法 | 说明
fetch | 渲染模板输出
display | 渲染内容输出
assign | 模板变量赋值
engine | 初始化模板引擎

如果需要调用View类的其它方法，可以直接使用$this->view 对象：

助手函数

如果你只是需要渲染模板输出的话，可以使用系统提供的助手函数view，可以完成相同的功能：

return view('hello',['name'=>'thinkphp']);

助手函数调用格式：

view('[模板文件]'[,'模板变量（数组）'][,模板替换（数组）])

无论你是否继承think\Controller类，助手函数都可以使用，也是最方便的一种。

直接实例化视图类（不建议）

任何情况下，你都可以直接实例化视图类进行渲染模板。

// 实例化视图类
$view = new View();
 // 渲染模板输出 并赋值模板变量
return $view->fetch('hello',['name'=>'thinkphp']);

实例化视图类的时候，可以传入模板引擎相关配置参数，例如：

// 实例化视图类
$view = new View([
    'type'          => 'think',
    'view_path'     => '',
    'view_suffix'   => 'html',
    'view_depr'     => '/',
]);
 // 渲染模板输出 并赋值模板变量
return $view->fetch('hello',['name'=>'thinkphp']);

如果需要使用应用自己扩展的模板引擎驱动，可以使用：

// 实例化视图类
$view = new View([
    'type'          => '\org\template\Think',
    'view_path'     => '',
    'view_suffix'   => 'html',
    'view_depr'     => '/',
]);

如果实例化不当，很容易导致配置参数无效的情况。因此如果不是必要，不建议直接实例化View类进行操作。


模板引擎

内置模板引擎

视图的模板文件可以支持不同的解析规则，默认情况下无需手动初始化模板引擎。

可以通过下面的几种方式对模板引擎进行初始化。

配置文件

在应用配置文件中配置template参数即可，例如：

'template'               => [
    // 模板引擎类型 支持 php think 支持扩展
    'type'         => 'Think',
    // 模板路径
    'view_path'    => './template/',
    // 模板后缀
    'view_suffix'  => 'html',
    // 模板文件名分隔符
    'view_depr'    => DS,
    // 模板引擎普通标签开始标记
    'tpl_begin'    => '{',
    // 模板引擎普通标签结束标记
    'tpl_end'      => '}',
    // 标签库标签开始标记
    'taglib_begin' => '{',
    // 标签库标签结束标记
    'taglib_end'   => '}',
],

调用视图类进行操作或者使用view助手函数的时候会自动实例化相关的模板引擎并传入参数。

视图根目录

视图文件的根目录默认情况下位于模块的view目录，每个模块的视图目录可以通过模板参数view_path自定义。

V5.0.3+版本开始，新增了view_base模板引擎参数，可以用于定义全局的视图根目录，然后模块作为子目录。

实例化视图

可以在实例化视图的时候直接传入模板引擎配置参数，会在渲染输出的时候自动初始化模板引擎，例如：

$view = new View([
    'type'               => 'think',
    'view_path'          => './template/',
    'view_suffix'        => 'php',
    'view_depr'          => DS,
    'tpl_begin'          => '{', // 模板引擎普通标签开始标记
    'tpl_end'            => '}', // 模板引擎普通标签结束标记
    'strip_space'        => true, // 去除模板文件里面的html空格与换行
    'tpl_cache'          => true, // 开启模板编译缓存
    'taglib_pre_load'    => '', // 需要额外加载的标签库(须指定标签库名称)，多个以逗号分隔
    'tpl_replace_string' => [],// 模板过滤输出（与输出替换章节不同，前者对模版进行过滤）
]);

think模板引擎是ThinkPHP内置的一个基于XML的高效的编译型模板引擎，系统默认使用的模板引擎是内置模板引擎，关于这个模板引擎的标签详细使用可以参考模板部分。

调用engine方法初始化

视图类也提供了engine方法对模板解析引擎进行初始化或者切换不同的模板引擎，例如：

$view = new View();
return $view->engine('php')->fetch();

表示当前视图的模板文件使用原生php进行解析。

设置模板引擎参数

除了在实例化的时候传入外，可以动态设置模板引擎的相关参数，例如：

$view = new View();
return $view->config('view_path','./template/')->fetch();

使用第三方模板引擎

官方扩展库中提供了一个类似于angularjs语法的模板引擎think-angular，具体可以参考参考手册。


模板赋值

模板赋值

除了系统变量和配置参数输出无需赋值外，其他变量如果需要在模板中输出必须首先进行模板赋值操作，绑定数据到模板输出有下面几种方式：

版本 | 新增功能
5.0.4 | 增加全局静态模板赋值方法share

assign方法

namespace index\app\controller;

class Index extends \think\Controller
{
    public function index()
    {
        // 模板变量赋值
        $this->assign('name','ThinkPHP');
        $this->assign('email','thinkphp@qq.com');
        // 或者批量赋值
        $this->assign([
            'name'  => 'ThinkPHP',
            'email' => 'thinkphp@qq.com'
        ]);
        // 模板输出
        return $this->fetch('index');
    }
}

传入参数方法

方法fetch 及 display 均可传入模版变量，例如

namespace app\index\controller;

class Index extends \think\Controller
{
    public function index()
    {
        return $this->fetch('index', [
            'name'  => 'ThinkPHP',
            'email' => 'thinkphp@qq.com'
        ]);
    }
}
class Index extends \think\Controller
{
    public function index()
    {
        $content = '{$name}-{$email}';
        return $this->display($content, [
            'name'  => 'ThinkPHP',
            'email' => 'thinkphp@qq.com'
        ]);
    }
}

对象赋值

class Index extends \think\Controller
{
    public function index()
    {
        $view = $this->view;
        $view->name     = 'ThinkPHP';
        $view->email    = 'thinkphp@qq.com';
        // 模板输出
        return $view->fetch('index');
    }
}

助手函数

如果使用view助手函数渲染输出的话，可以使用下面的方法进行模板变量赋值：

return view('index', [
    'name'  => 'ThinkPHP',
    'email' => 'thinkphp@qq.com'
]);

share方法

V5.0.4+开始，支持在任何地方使用静态方法进行模板变量赋值，例如：

think\View::share('name','value');
// 或者批量赋值
think\View::share(['name1'=>'value','name2'=>'value2']);

全局静态模板变量最终会和前面使用方法赋值的模板变量合并。


模板渲染

模板渲染

版本 | 新增功能
5.0.4 | 支持渲染视图根目录的模板文件

渲染模板最常用的是使用\think\View类的fetch方法，调用格式：

fetch('[模板文件]'[,'模板变量（数组）'])

模板文件的写法支持下面几种：

用法 | 描述
不带任何参数 | 自动定位当前操作的模板文件
[模块@][控制器/][操作] | 常用写法，支持跨模块
完整的模板文件名 | 直接使用完整的模板文件名（包括模板后缀）

下面是一个最典型的用法，不带任何参数：

// 不带任何参数 自动定位当前操作的模板文件
$view = new View();
return $view->fetch();

表示系统会按照默认规则自动定位模板文件，其规则是：

当前模块/默认视图目录/当前控制器（小写）/当前操作（小写）.html

如果有更改模板引擎的view_depr设置（假设 'view_depr'=>'_'）的话，则上面的自动定位规则变成：

当前模块/默认视图目录/当前控制器（小写）_当前操作（小写）.html

如果没有按照模板定义规则来定义模板文件（或者需要调用其他控制器下面的某个模板），可以使用：

// 指定模板输出
return $view->fetch('edit');

表示调用当前控制器下面的edit模板

return $view->fetch('member/read');

表示调用Member控制器下面的read模板。

跨模块渲染模板

return $view->fetch('admin@member/edit');

渲染输出不需要写模板文件的路径和后缀。这里面的控制器和操作并不一定需要有实际对应的控制器和操作，只是一个目录名称和文件名称而已，例如，你的项目里面可能根本没有Public控制器，更没有Public控制器的menu操作，但是一样可以使用

return $view->fetch('public/menu');

输出这个模板文件。理解了这个，模板输出就清晰了。

fetch方法支持在渲染输出的时候传入模板变量，例如：

return $view->fetch('read', ['a'=>'a','b'=>'b']);

V5.0.4+版本开始，支持从视图根目录开始读取模板，例如：

$view->fetch('/menu');

表示读取的模板是

当前模块/默认视图目录/menu.html

自定义模板路径

如果你的模板文件位置比较特殊或者需要自定义模板文件的位置，可以采用下面的几种方式处理。

渲染完整模板

return $view->fetch('./template/public/menu.html');

这种方式需要带模板路径和后缀指定一个完整的模板文件位置，这里的template/public目录是位于当前项目入口文件位置下面。如果是其他的后缀文件，也支持直接输出，例如：

return $view->fetch('./template/public/menu.tpl');

只要./template/public/menu.tpl是一个实际存在的模板文件。

要注意模板文件位置是相对于应用的入口文件，而不是模板目录。

渲染内容

如果希望直接解析内容而不通过模板文件的话，可以使用display方法：

$view = new View();
return $view->display($content,$vars);

渲染的内容中一样可以使用模板引擎的相关标签。


输出替换

模板输出替换

版本 | 新增功能
5.0.4 | 增加__ROOT__ __STATIC__ __JS__和__CSS__内置替换规则

支持对视图输出的内容进行字符替换，例如：

namespace index\app\controller;

class Index extends \think\Controller
{
    public function index()
    {
        $this->assign('name','thinkphp');
        return $this->fetch('index',[],['__PUBLIC__'=>'/public/']);
    }
}

如果需要全局替换的话，可以直接在配置文件中添加：

'view_replace_str'  =>  [
    '__PUBLIC__'=>'/public/',
    '__ROOT__' => '/',
]

然后就可以直接使用

namespace index\app\controller;

class Index extends \think\Controller
{
    public function index()
    {
        $this->assign('name','thinkphp');
        return $this->fetch('index');
    }
}

如果你手动实例化视图类，请确保在实例化的时候传入配置参数：

$view = new View([],Config::get('view_replace_str'));
return $view->fetch();

助手函数view也支持全局配置参数view_replace_str的设置，如果需要设置不同的替换参数，可以使用：

return view('index',['name'=>'thinkphp'],['__PUBLIC__'=>'/public/']);

在渲染模板或者内容输出的时候就会自动根据设置的替换规则自动替换。

要使得你的全局替换生效，确保你的控制器类继承think\Controller或者使用view助手函数渲染输出。


模板定位

模板文件定义

每个模块的模板文件是独立的，为了对模板文件更加有效的管理，ThinkPHP对模板文件进行目录划分，默认的模板文件定义规则是：

视图目录/控制器名（小写）/操作名（小写）+模板后缀

默认的视图目录是模块的view目录，框架的默认视图文件后缀是.html。

模板渲染规则

模板渲染使用\think\View类的fetch方法，渲染规则为：

模块@控制器/操作

模板文件目录默认位于模块的view目录下面，视图类的fetch方法中的模板文件的定位规则如下：

如果调用没有任何参数的fetch方法：

return $view->fetch();

则按照系统的默认规则定位模板文件到：

[模板文件目录]/当前控制器名（小写+下划线）/当前操作名（小写）.html

如果（指定操作）调用：

return $view->fetch('add');

则定位模板文件为：

[模板文件目录]/当前控制器名（小写+下划线）/add.html

如果调用控制器的某个模板文件使用：

return $view->fetch('user/add');

则定位模板文件为：

[模板文件目录]/user/add.html

跨模块调用模板

return $view->fetch('admin@user/add');

全路径模板调用：

return $view->fetch(APP_PATH.request()->module().'/view/public/header.html');


模板标签

模板文件可以包含普通标签和标签库标签，标签的定界符都可以重新配置。

普通标签

普通标签用于变量输出和模板注释，普通模板标签默认以{ 和 } 作为开始和结束标识，并且在开始标记紧跟标签的定义，如果之间有空格或者换行则被视为非模板标签直接输出。 例如：{$name} 、{$vo.name} 、{$vo['name']|strtoupper} 都属于正确的标签，而{ $name} 、{ $vo.name}则不属于。

要更改普通标签的起始标签和结束标签，可以更改下面的配置参数：

'template'  => [
    // 模板引擎
    'type'   => 'think',
    // 普通标签开始标记
    'tpl_begin' =>    '<{',
    // 普通标签结束标记
    'tpl_end'   =>    '}>'
],

普通标签的定界符就被修改了，原来的 {$name} 和 {$vo.name} 必须使用 <{$name}> 和 <{$vo.name}> 才能生效了。

标签库标签

标签库标签可以用于模板变量输出、文件包含、条件控制、循环输出等功能，而且完全可以自己扩展功能。

5.0版本的标签库默认定界符和普通标签一样使用{和}，是为了便于在编辑器里面编辑不至于报错，当然，你仍然可以更改标签库标签的起始和结束标签，修改下面的配置参数：

'template'      => [
    // 模板引擎
    'type'   => 'think',
    //标签库标签开始标签
    'taglib_begin'  =>  '<',
    //标签库标签结束标记
    'taglib_end'    =>  '>',
],

原来的

{eq name="name" value="value"}
相等
{else/}
不相等
{/eq}

就需要改成

<eq name="name" value="value">
相等
<else/>
不相等
</eq>


变量输出

在模板中输出变量的方法很简单，例如，在控制器中我们给模板变量赋值：

$view = new View();
$view->name = 'thinkphp';
return $view->fetch();

然后就可以在模板中使用：

Hello,{$name}！

模板编译后的结果就是：

Hello,<?php echo($name);?>！

这样，运行的时候就会在模板中显示： Hello,ThinkPHP！

注意模板标签的{和$之间不能有任何的空格，否则标签无效。所以，下面的标签

Hello,{ $name}！

将不会正常输出name变量，而是直接保持不变输出： Hello,{ $name}！

模板标签的变量输出根据变量类型有所区别，刚才我们输出的是字符串变量，如果是数组变量，

$data['name'] = 'ThinkPHP';
$data['email'] = 'thinkphp@qq.com';
$view->assign('data',$data);

那么，在模板中我们可以用下面的方式输出：

Name：{$data.name}
Email：{$data.email}

或者用下面的方式也是有效：

Name：{$data['name']}
Email：{$data['email']}

当我们要输出多维数组的时候，往往要采用后面一种方式。

如果data变量是一个对象（并且包含有name和email两个属性），那么可以用下面的方式输出：

Name：{$data:name}
Email：{$data:email}

或者

Name：{$data->name}
Email：{$data->email}


系统变量

系统变量输出

普通的模板变量需要首先赋值后才能在模板中输出，但是系统变量则不需要，可以直接在模板中输出，系统变量的输出通常以{$Think 打头，例如：

{$Think.server.script_name} // 输出$_SERVER['SCRIPT_NAME']变量
{$Think.session.user_id} // 输出$_SESSION['user_id']变量
{$Think.get.pageNumber} // 输出$_GET['pageNumber']变量
{$Think.cookie.name}  // 输出$_COOKIE['name']变量

支持输出 $_SERVER、$_ENV、 $_POST、 $_GET、 $_REQUEST、$_SESSION和 $_COOKIE变量。

常量输出

还可以输出常量

{$Think.const.APP_PATH}

或者直接使用

{$Think.APP_PATH}

配置输出

输出配置参数使用：

{$Think.config.default_module}
{$Think.config.default_controller}

语言变量

输出语言变量可以使用：

{$Think.lang.page_error}
{$Think.lang.var_error}


请求参数

模板支持直接输出Request请求对象的方法参数，用法如下：

$Request.方法名.参数

例如：

{$Request.get.id}
{$Request.param.name}

以$Request.开头的变量输出会认为是系统Request请求对象的参数输出。

支持Request类的大部分方法，但只支持方法的第一个参数。

下面都是有效的输出：

// 调用Request对象的get方法 传入参数为id
{$Request.get.id}
// 调用Request对象的param方法 传入参数为name
{$Request.param.name}
// 调用Request对象的param方法 传入参数为user.nickname
{$Request.param.user.nickname}
// 调用Request对象的root方法
{$Request.root}
// 调用Request对象的root方法，并且传入参数true
{$Request.root.true}
// 调用Request对象的path方法
{$Request.path}
// 调用Request对象的module方法
{$Request.module}
// 调用Request对象的controller方法
{$Request.controller}
// 调用Request对象的action方法
{$Request.action}
// 调用Request对象的ext方法
{$Request.ext}
// 调用Request对象的host方法
{$Request.host}
// 调用Request对象的ip方法
{$Request.ip}
// 调用Request对象的header方法
{$Request.header.accept-encoding}


使用函数

我们往往需要对模板输出变量使用函数，可以使用：

{$data.name|md5}

编译后的结果是：

<?php echo (md5($data['name'])); ?>

如果函数有多个参数需要调用，则使用：

{$create_time|date="y-m-d",###}

表示date函数传入两个参数，每个参数用逗号分割，这里第一个参数是y-m-d，第二个参数是前面要输出的create_time变量，因为该变量是第二个参数，因此需要用###标识变量位置，编译后的结果是：

<?php echo (date("y-m-d",$create_time)); ?>

如果前面输出的变量在后面定义的函数的第一个参数，则可以直接使用：

{$data.name|substr=0,3}

表示输出

<?php echo (substr($data['name'],0,3)); ?>

虽然也可以使用：

{$data.name|substr=###,0,3}

但完全没用这个必要。

还可以支持多个函数过滤，多个函数之间用“|”分割即可，例如：

{$name|md5|strtoupper|substr=0,3}

编译后的结果是：

<?php echo (substr(strtoupper(md5($name)),0,3)); ?>

函数会按照从左到右的顺序依次调用。

如果你觉得这样写起来比较麻烦，也可以直接这样写：

{:substr(strtoupper(md5($name)),0,3)}

变量输出使用的函数可以支持内置的PHP函数或者用户自定义函数，甚至是静态方法。


使用默认值

我们可以给变量输出提供默认值，例如：

{$user.nickname|default="这家伙很懒，什么也没留下"}

对系统变量依然可以支持默认值输出，例如：

{$Think.get.name|default="名称为空"}

默认值和函数可以同时使用，例如：

{$Think.get.name|getName|default="名称为空"}


使用运算符

我们可以对模板输出使用运算符，包括对“+”“ ” “*” “/”和“%”的支持。

例如：

运算符 | 使用示例
+ | {$a+$b}
- | {$a-$b}
* | {$a*$b}
/ | {$a/$b}
% | {$a%$b}
++ | {$a++} 或 {++$a}
-- | {$a--} 或 {--$a}
综合运算 | {$a+$b*10+$c}

在使用运算符的时候，不再支持常规函数用法，例如：

{$user.score+10} //正确的
{$user['score']+10} //正确的
{$user['score']*$user['level']} //正确的
{$user['score']|myFun*10} //错误的
{$user['score']+myFun($user['level'])} //正确的


三元运算

模板可以支持三元运算符，例如：

{$status? '正常' : '错误'}
{$info['status']? $info['msg'] : $info['error']}
{$info.status? $info.msg : $info.error }

5.0版本还支持如下的写法：

{$varname.aa ?? 'xxx'}

表示如果有设置$varname则输出$varname,否则输出'xxx'。 解析后的代码为：

<?php echo isset($varname['aa']) ? $varname['aa'] : '默认值'; ?>
{$varname?='xxx'}

表示$varname为真时才输出xxx。 解析后的代码为：

<?php if(!empty($name)) echo 'xxx'; ?>

{$varname ?: 'no'}

表示如果$varname为真则输出$varname，否则输出no。解析后的代码为：

<?php echo $varname ? $varname : 'no'; ?>

{$a==$b ? 'yes' : 'no'}

前面的表达式为真输出yes,否则输出no， 条件可以是==、===、!=、!==、>=、<=


原样输出

可以使用literal标签来防止模板标签被解析，例如：

{literal}
    Hello,{$name}！
{/literal}

上面的{$name}标签被literal标签包含，因此并不会被模板引擎解析，而是保持原样输出。

literal标签还可以用于页面的JS代码外层，确保JS代码中的某些用法和模板引擎不产生混淆。

总之，所有可能和内置模板引擎的解析规则冲突的地方都可以使用literal标签处理。

需要注意的是配置‘view_replace_str’替换参数，会替换掉literal标签内的内容，可以配置‘template.tpl_replace_string’避免替换掉literal标签内的内容。


模板注释

模板支持注释功能，该注释文字在最终页面不会显示，仅供模板制作人员参考和识别。

单行注释

格式：

{/* 注释内容 */ } 或 {// 注释内容 }

例如：

{// 这是模板注释内容 }

注意{和注释标记之间不能有空格。

多行注释

支持多行注释，例如：

{/* 这是模板
注释内容*/ }

模板注释支持多行，模板注释在生成编译缓存文件后会自动删除，这一点和Html的注释不同。


模板布局

ThinkPHP的模板引擎内置了布局模板功能支持，可以方便的实现模板布局以及布局嵌套功能。

有三种布局模板的支持方式：

第一种方式：全局配置方式

这种方式仅需在项目配置文件中添加相关的布局模板配置，就可以简单实现模板布局功能，比较适用于全站使用相同布局的情况，需要配置开启layout_on 参数（默认不开启），并且设置布局入口文件名layout_name（默认为layout）。

'template'  =>  [
    'layout_on'     =>  true,
    'layout_name'   =>  'layout',
]

开启layout_on后，我们的模板渲染流程就有所变化，例如：

namespace app\index\controller;

use think\Controller;

Class User extends Controller
{
     public function add()
     {
         return $this->fetch('add');
     }
}

在不开启layout_on布局模板之前，会直接渲染 application/index/view/user/add.html 模板文件,开启之后，首先会渲染application/index/view/layout.html 模板，布局模板的写法和其他模板的写法类似，本身也可以支持所有的模板标签以及包含文件，区别在于有一个特定的输出替换变量{__CONTENT__}，例如，下面是一个典型的layout.html模板的写法：

{include file="public/header" /}
 {__CONTENT__}
{include file="public/footer" /}

读取layout模板之后，会再解析user/add.html 模板文件，并把解析后的内容替换到layout布局模板文件的{CONTENT} 特定字符串。

当然可以通过设置来改变这个特定的替换字符串，例如：

'template'  =>  [
    'layout_on'     =>  true,
    'layout_name'   =>  'layout',
    'layout_item'   =>  '{__REPLACE__}'
]

一个布局模板同时只能有一个特定替换字符串。

采用这种布局方式的情况下，一旦user/add.html 模板文件或者layout.html布局模板文件发生修改，都会导致模板重新编译。

如果需要指定其他位置的布局模板，可以使用：

'template'  =>  [
    'layout_on'     =>  true,
    'layout_name'   =>  'layout/layoutname',
    'layout_item'   =>  '{__REPLACE__}'
]

就表示采用application/index/view/layout/layoutname.html作为布局模板。

如果某些页面不需要使用布局模板功能，可以在模板文件开头加上 {__NOLAYOUT__} 字符串。

如果上面的user/add.html 模板文件里面包含有{__NOLAYOUT__}，则即使当前开启布局模板，也不会进行布局模板解析。

第二种方式：模板标签方式

这种布局模板不需要在配置文件中设置任何参数，也不需要开启layout_on，直接在模板文件中指定布局模板即可，相关的布局模板调整也在模板中进行。

以前面的输出模板为例，这种方式的入口还是在user/add.html 模板，但是我们可以修改下add模板文件的内容，在头部增加下面的布局标签（记得首先关闭前面的layout_on设置，否则可能出现布局循环）：

{layout name="layout" /}

表示当前模板文件需要使用layout.html 布局模板文件，而布局模板文件的写法和上面第一种方式是一样的。当渲染user/add.html 模板文件的时候，如果读取到layout标签，则会把当前模板的解析内容替换到layout布局模板的{CONTENT} 特定字符串。

一个模板文件中只能使用一个布局模板，如果模板文件中没有使用任何layout标签则表示当前模板不使用任何布局。

如果需要使用其他的布局模板，可以改变layout的name属性，例如：

{layout name="newlayout" /}

还可以在layout标签里面指定要替换的特定字符串：

{layout name="Layout/newlayout" replace="[__REPLACE__]" /}

第三种方式：使用layout控制模板布局

使用内置的layout方法可以更灵活的在程序中控制模板输出的布局功能，尤其适用于局部需要布局或者关闭布局的情况，这种方式也不需要在配置文件中开启layout_on。例如：

namespace app\index\controller;

use think\Controller;

class User extends Controller
{
     public function add()
     {
         $this->view->engine->layout(true);
         return $this->fetch('add');
     }
}

表示当前的模板输出启用了布局模板，并且采用默认的layout布局模板。

如果当前输出需要使用不同的布局模板，可以动态的指定布局模板名称，例如：

namespace app\index\controller;

use think\Controller;

class User extends Controller
{
     public function add()
     {
         $this->view->engine->layout('Layout/newlayout');
         return $this->display('add');
     }
}

或者使用layout方法动态关闭当前模板的布局功能（这种用法可以配合第一种布局方式，例如全局配置已经开启了布局，可以在某个页面单独关闭）：

namespace app\index\controller;

use think\Controller;

class User extends Controller
{
     public function add()
     {
        // 临时关闭当前模板的布局功能
         $this->view->engine->layout(false);
         return $this->display('add');
     }
}

三种模板布局方式中，第一种和第三种是在程序中配置实现模板布局，第二种方式则是单纯通过模板标签在模板中使用布局。具体选择什么方式，需要根据项目的实际情况来了。


模板继承

模板继承是一项更加灵活的模板布局方式，模板继承不同于模板布局，甚至来说，应该在模板布局的上层。模板继承其实并不难理解，就好比类的继承一样，模板也可以定义一个基础模板（或者是布局），并且其中定义相关的区块（block），然后继承（extend）该基础模板的子模板中就可以对基础模板中定义的区块进行重载。

因此，模板继承的优势其实是设计基础模板中的区块（block）和子模板中替换这些区块。

每个区块由{block} {/block}标签组成。 下面就是基础模板中的一个典型的区块设计（用于设计网站标题）：

{block name="title"}<title>网站标题</title>{/block}

block标签必须指定name属性来标识当前区块的名称，这个标识在当前模板中应该是唯一的，block标签中可以包含任何模板内容，包括其他标签和变量，例如：

{block name="title"}<title>{$web_title}</title>{/block}

你甚至还可以在区块中加载外部文件：

{block name="include"}{include file="Public:header" /}{/block}

一个模板中可以定义任意多个名称标识不重复的区块，例如下面定义了一个base.html基础模板：

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>{block name="title"}标题{/block}</title>
</head>
<body>
{block name="menu"}菜单{/block}
{block name="left"}左边分栏{/block}
{block name="main"}主内容{/block}
{block name="right"}右边分栏{/block}
{block name="footer"}底部{/block}
</body>
</html>

然后我们在子模板（其实是当前操作的入口模板）中使用继承：

{extend name="base" /}
{block name="title"}{$title}{/block}
{block name="menu"}
<a href="/" >首页</a>
<a href="/info/" >资讯</a>
<a href="/bbs/" >论坛</a>
{/block}
{block name="left"}{/block}
{block name="main"}
{volist name="list" id="vo"}
<a href="/new/{$vo.id}">{$vo.title}</a><br/>
 {$vo.content}
{/volist}
{/block}
{block name="right"}
 最新资讯：
{volist name="news" id="new"}
<a href="/new/{$new.id}">{$new.title}</a><br/>
{/volist}
{/block}
{block name="footer"}
{__block__}
 @ThinkPHP 版权所有
{/block}

上例中，我们可以看到在子模板中使用了extend标签来继承了base模板。
在子模板中，可以对基础模板中的区块进行重载定义，如果没有重新定义的话，则表示沿用基础模板中的区块定义，如果定义了一个空的区块，则表示删除基础模板中的该区块内容。 上面的例子，我们就把left区块的内容删除了，其他的区块都进行了重载。而

{block name="footer"}
{__block__}@ThinkPHP 版权所有
{/block}

这一区块中有{_block_}这个标签，当区块中有这个标记时，就不只是直接重载这个区块，它表示引用所继承模板对应区块的内容到这个位置，最终这个区块是合并后的内容。所以这里footer区块最后的内容是： 底部@ThinkPHP 版权所有

extend标签的用法和include标签一样，你也可以加载其他模板：

{extend name="Public:base" /}

或者使用绝对文件路径加载

{extend name="./Template/Public/base.html" /}

在当前子模板中，只能定义区块而不能定义其他的模板内容，否则将会直接忽略，并且只能定义基础模板中已经定义的区块。

例如，如果采用下面的定义：

{block name="title"}<title>{$title}</title>{/block}
<a href="/" >首页</a>
<a href="/info/" >资讯</a>
<a href="/bbs/" >论坛</a>

导航部分将是无效的，不会显示在模板中。

模板可以多级继承，比如B继承了A，而C又继承了B，最终C中的区块会覆盖B和A中的同名区块，但C和B中的区块必须是A中已定义过的。

子模板中的区块定义顺序是随意的，模板继承的用法关键在于基础模板如何布局和设计规划了，如果结合原来的布局功能，则会更加灵活。


包含文件

在当前模版文件中包含其他的模版文件使用include标签，标签用法：

{include file='模版文件1,模版文件2,...' /}

包含的模板文件中不能再使用模板布局或者模板继承。

使用模版表达式

模版表达式的定义规则为：模块@控制器/操作

例如：

{include file="public/header" /} // 包含头部模版header
{include file="public/menu" /} // 包含菜单模版menu
{include file="blue/public/menu" /} // 包含blue主题下面的menu模版

可以一次包含多个模版，例如：

{include file="public/header,public/menu" /}

注意，包含模版文件并不会自动调用控制器的方法，也就是说包含的其他模版文件中的变量赋值需要在当前操作中完成。

使用模版文件

可以直接包含一个模版文件名（包含完整路径），例如：

{include file="../application/view/default/public/header.html" /}

路径以 项目目录/public/ 路径下为起点

传入参数

无论你使用什么方式包含外部模板，Include标签支持在包含文件的同时传入参数，例如，下面的例子我们在包含header模板的时候传入了title和keywords参数：

{include file="Public/header" title="$title" keywords="开源WEB开发框架" /}

就可以在包含的header.html文件里面使用title和keywords变量，如下：

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>[title]</title>
<meta name="keywords" content="[keywords]" />
</head>

上面title参数传入的是个变量$title，模板内的[title]最终会替换成$title的值，当然$title这个变量必须要存在。

包含文件中可以再使用include标签包含别的文件，但注意不要形成A包含A，或者A包含B而B又包含A这样的死循环。

注意：由于模板解析的特点，从入口模板开始解析，如果外部模板有所更改，模板引擎并不会重新编译模板，除非在调试模式下或者缓存已经过期。如果部署模式下修改了包含的外部模板文件后，需要把模块的缓存目录清空，否则无法生效。


标签库

内置的模板引擎除了支持普通变量的输出之外，更强大的地方在于标签库功能。

标签库类似于Java的Struts中的JSP标签库，每一个标签库是一个独立的标签库文件，标签库中的每一个标签完成某个功能，采用XML标签方式（包括开放标签和闭合标签）。

标签库分为内置和扩展标签库，内置标签库是Cx标签库。

导入标签库

使用taglib标签导入当前模板中需要使用的标签库，例如：

{taglib name="html" /}

如果没有定义html标签库的话，则导入无效。

也可以导入多个标签库，使用：

{taglib name="html,article" /}

导入标签库后，就可以使用标签库中定义的标签了，假设article标签库中定义了read标签：

{article:read name="hello" id="data" }
{$data.id}:{$data.title}
{/article:read}

在上面的标签中，{article:read}... {/article:read} 就是闭合标签，起始和结束标签必须成对出现。

如果是 {article:read name="hello" /} 就是开放标签。

闭合和开放标签取决于标签库中的定义，一旦定义后就不能混淆使用，否则就会出现错误。

内置标签

内置标签库无需导入即可使用，并且不需要加XML中的标签库前缀，ThinkPHP内置的标签库是Cx标签库，所以，Cx标签库中的所有标签，我们可以在模板文件中直接使用，我们可以这样使用：

{eq name="status" value="1" }
正常
{/eq}

如果Cx不是内置标签的话，可能就需要这么使用了：

{cx:eq name="status" value="1" }
正常
{/cx:eq}

更多的Cx标签库中的标签用法，参考内置标签。

内置标签库可以简化模板中标签的使用，所以，我们还可以把其他的标签库定义为内置标签库（前提是多个标签库没有标签冲突的情况），例如：

'taglib_build_in'    =>    'cx,article'

配置后，上面的标签用法就可以改为：

{read name="hello" id="data" }
{$data.id}:{$data.title}
{/read}

标签库预加载

标签库预加载是指无需手动在模板文件中导入标签库即可使用标签库中的标签，通常用于某个标签库需要被大多数模板使用的情况。

在应用或者模块的配置文件中添加：

'taglib_pre_load'    =>    'article,html'

设置后，模板文件就不再需要使用

{taglib name="html,article" /}

但是仍然可以在模板中调用：

{article:read name="hello" id="data" }
{$data.id}:{$data.title}
{/article:read}


循环输出标签

VOLIST标签

volist标签通常用于查询数据集（select方法）的结果输出，通常模型的select方法返回的结果是一个二维数组，可以直接使用volist标签进行输出。 在控制器中首先对模版赋值：

$list = User::all();
$this->assign('list',$list);

在模版定义如下，循环输出用户的编号和姓名：

{volist name="list" id="vo"}
{$vo.id}:{$vo.name}<br/>
{/volist}

Volist标签的name属性表示模板赋值的变量名称，因此不可随意在模板文件中改变。id表示当前的循环变量，可以随意指定，但确保不要和name属性冲突，例如：

{volist name="list" id="data"}
{$data.id}:{$data.name}<br/>
{/volist}

支持输出查询结果中的部分数据，例如输出其中的第5～15条记录

{volist name="list" id="vo" offset="5" length='10'}
{$vo.name}
{/volist}

输出偶数记录

{volist name="list" id="vo" mod="2" }
{eq name="mod" value="1"}{$vo.name}{/eq}
{/volist}

Mod属性还用于控制一定记录的换行，例如：

{volist name="list" id="vo" mod="5" }
{$vo.name}
{eq name="mod" value="4"}<br/>{/eq}
{/volist}

为空的时候输出提示：

{volist name="list" id="vo" empty="暂时没有数据" }
{$vo.id}|{$vo.name}
{/volist}

empty属性不支持直接传入html语法，但可以支持变量输出，例如：

$this->assign('empty','<span class="empty">没有数据</span>');
$this->assign('list',$list);

然后在模板中使用：

{volist name="list" id="vo" empty="$empty" }
{$vo.id}|{$vo.name}
{/volist}

输出循环变量：

{volist name="list" id="vo" key="k" }
{$k}.{$vo.name}
{/volist}

如果没有指定key属性的话，默认使用循环变量i，例如：

{volist name="list" id="vo"  }
{$i}.{$vo.name}
{/volist}

如果要输出数组的索引，可以直接使用key变量，和循环变量不同的是，这个key是由数据本身决定，而不是循环控制的，例如：

{volist name="list" id="vo"  }
{$key}.{$vo.name}
{/volist}

模板中可以直接使用函数设定数据集，而不需要在控制器中给模板变量赋值传入数据集变量，如：

{volist name=":fun('arg')" id="vo"}
{$vo.name}
{/volist}

FOREACH标签

foreach标签类似与volist标签，只是更加简单，没有太多额外的属性，最简单的用法是：

{foreach $list as $vo}
    {$vo.id}:{$vo.name}
{/foreach}

该用法解析后是最简洁的。

也可以使用下面的用法：

{foreach name="list" item="vo"}
    {$vo.id}:{$vo.name}
{/foreach}

name表示数据源 item表示循环变量。

可以输出索引，如下：

{foreach name="list" item="vo" }
    {$key}|{$vo}
{/foreach}

也可以定义索引的变量名

{foreach name="list" item="vo" key="k" }
   {$k}|{$vo}
{/foreach}

FOR标签

用法：

{for start="开始值" end="结束值" comparison="" step="步进值" name="循环变量名" }
{/for}

开始值、结束值、步进值和循环变量都可以支持变量，开始值和结束值是必须，其他是可选。comparison 的默认值是lt，name的默认值是i，步进值的默认值是1，举例如下：

{for start="1" end="100"}
{$i}
{/for}

解析后的代码是

for ($i=1;$i<100;$i+=1){
    echo $i;
}


比较标签

比较标签用于简单的变量比较，复杂的判断条件可以用if标签替换，比较标签是一组标签的集合，基本上用法都一致，如下：

{比较标签 name="变量" value="值"}
内容
{/比较标签}

系统支持的比较标签以及所表示的含义分别是：

标签 | 含义
eq或者 equal | 等于
neq 或者notequal | 不等于
gt | 大于
egt | 大于等于
lt | 小于
elt | 小于等于
heq | 恒等于
nheq | 不恒等于

他们的用法基本是一致的，区别在于判断的条件不同，并且所有的比较标签都可以和else标签一起使用。

例如，要求name变量的值等于value就输出，可以使用：

{eq name="name" value="value"}value{/eq}

或者

{equal name="name" value="value"}value{/equal}

也可以支持和else标签混合使用：

{eq name="name" value="value"}
相等
{else/}
不相等
{/eq}

当 name变量的值大于5就输出

{gt name="name" value="5"}value{/gt}

当name变量的值不小于5就输出

{egt name="name" value="5"}value{/egt}

比较标签中的变量可以支持对象的属性或者数组，甚至可以是系统变量，例如： 当vo对象的属性（或者数组，或者自动判断）等于5就输出

{eq name="vo.name" value="5"}
{$vo.name}
{/eq}

当vo对象的属性等于5就输出

{eq name="vo:name" value="5"}
{$vo.name}
{/eq}

当$vo['name']等于5就输出

{eq name="vo['name']" value="5"}
{$vo.name}
{/eq}

而且还可以支持对变量使用函数 当vo对象的属性值的字符串长度等于5就输出

{eq name="vo:name|strlen" value="5"}{$vo.name}{/eq}

变量名可以支持系统变量的方式，例如：

{eq name="Think.get.name" value="value"}相等{else/}不相等{/eq}

通常比较标签的值是一个字符串或者数字，如果需要使用变量，只需要在前面添加“$”标志： 当vo对象的属性等于$a就输出

{eq name="vo:name" value="$a"}{$vo.name}{/eq}

所有的比较标签可以统一使用compare标签（其实所有的比较标签都是compare标签的别名），例如： 当name变量的值等于5就输出

{compare name="name" value="5" type="eq"}value{/compare}

等效于

{eq name="name" value="5" }value{/eq}

其中type属性的值就是上面列出的比较标签名称


条件判断

SWITCH标签

用法：

{switch name="变量" }
    {case value="值1" break="0或1"}输出内容1{/case}
    {case value="值2"}输出内容2{/case}
    {default /}默认情况
{/switch}

使用方法如下：

{switch name="User.level"}
    {case value="1"}value1{/case}
    {case value="2"}value2{/case}
    {default /}default
{/switch}

其中name属性可以使用函数以及系统变量，例如：

{switch name="Think.get.userId|abs"}
    {case value="1"}admin{/case}
    {default /}default
{/switch}

对于case的value属性可以支持多个条件的判断，使用”|”进行分割，例如：

{switch name="Think.get.type"}
    {case value="gif|png|jpg"}图像格式{/case}
    {default /}其他格式
{/switch}

表示如果$_GET["type"] 是gif、png或者jpg的话，就判断为图像格式。

Case标签还有一个break属性，表示是否需要break，默认是会自动添加break，如果不要break，可以使用：

{switch name="Think.get.userId|abs"}
    {case value="1" break="0"}admin{/case}
    {case value="2"}admin{/case}
    {default /}default
{/switch}

也可以对case的value属性使用变量，例如：

{switch name="User.userId"}
    {case value="$adminId"}admin{/case}
    {case value="$memberId"}member{/case}
    {default /}default
{/switch}

使用变量方式的情况下，不再支持多个条件的同时判断。

简洁的用法

{switch $User.userId}
    {case $adminId}admin{/case}
    {case $memberId}member{/case}
{/switch}

IF标签

用法示例：

{if condition="($name == 1) OR ($name > 100) "} value1
{elseif condition="$name eq 2"/}value2
{else /} value3
{/if}

除此之外，我们可以在condition属性里面使用php代码，例如：

{if condition="strtoupper($user['name']) neq 'THINKPHP'"}ThinkPHP
{else /} other Framework
{/if}

condition属性可以支持点语法和对象语法，例如： 自动判断user变量是数组还是对象

{if condition="$user.name neq 'ThinkPHP'"}ThinkPHP
{else /} other Framework
{/if}

或者知道user变量是对象

{if condition="$user:name neq 'ThinkPHP'"}ThinkPHP
{else /} other Framework
{/if}

由于if标签的condition属性里面基本上使用的是php语法，尽可能使用判断标签和Switch标签会更加简洁，原则上来说，能够用switch和比较标签解决的尽量不用if标签完成。因为switch和比较标签可以使用变量调节器和系统变量。如果某些特殊的要求下面，IF标签仍然无法满足要求的话，可以使用原生php代码或者PHP标签来直接书写代码。

简洁的用法

{if condition="表达式"}
{if (表达式)}
{if 表达式}

这三种写法结果是一样的

范围判断

范围判断标签包括in notin between notbetween四个标签，都用于判断变量是否中某个范围。

IN和NOTIN

用法： 假设我们中控制器中给id赋值为1：

$id =    1;
$this->assign('id',$id);

我们可以使用in标签来判断模板变量是否在某个范围内，例如：

{in name="id" value="1,2,3"}
id在范围内
{/in}

最后会输出：id在范围内。

如果判断不在某个范围内，可以使用notin标签：

{notin name="id" value="1,2,3"}
id不在范围内
{/notin}

最后会输出：id不在范围内。

可以把上面两个标签合并成为：

{in name="id" value="1,2,3"}
id在范围内
{else/}
id不在范围内
{/in}

name属性还可以支持直接判断系统变量，例如：

{in name="Think.get.id" value="1,2,3"}
$_GET['id'] 在范围内
{/in}

更多的系统变量用法可以参考系统变量部分。

value属性也可以使用变量，例如：

{in name="id" value="$range"}
id在范围内
{/in}

$range变量可以是数组，也可以是以逗号分隔的字符串。

value属性还可以使用系统变量，例如：

{in name="id" value="$Think.post.ids"}
id在范围内
{/in}

BETWEEN 和 NOTBETWEEN

可以使用between标签来判断变量是否在某个区间范围内，可以使用：

{between name="id" value="1,10"}
输出内容1
{/between}

同样，也可以使用notbetween标签来判断变量不在某个范围内：

{notbetween name="id" value="1,10"}
输出内容2
{/notbetween}

也可以使用else标签把两个用法合并，例如：

{between name="id" value="1,10"}
输出内容1
{else/}
输出内容2
{/between}

当使用between标签的时候，value只需要一个区间范围，也就是只支持两个值，后面的值无效，例如

{between name="id" value="1,3,10"}
输出内容1
{/between}

实际判断的范围区间是1~3，而不是1~10，也可以支持字符串判断，例如：

{between name="id" value="A,Z"}
输出内容1
{/between}

name属性可以直接使用系统变量，例如：

{between name="Think.post.id" value="1,5"}
输出内容1
{/between}

value属性也可以使用变量，例如：

{between name="id" value="$range"}
输出内容1
{/between}

变量的值可以是字符串或者数组，还可以支持系统变量。

{between name="id" value="$Think.get.range"}
输出内容1
{/between}

RANGE

也可以直接使用range标签，替换前面的判断用法：

{range name="id" value="1,2,3" type="in"}
输出内容1
{/range}

其中type属性的值可以用in/notin/between/notbetween，其它属性的用法和IN或者BETWEEN一致。

PRESENT NOTPRESENT标签

present标签用于判断某个变量是否已经定义，用法：

{present name="name"}
name已经赋值
{/present}

如果判断没有赋值，可以使用：

{notpresent name="name"}
name还没有赋值
{/notpresent}

可以把上面两个标签合并成为：

{present name="name"}
name已经赋值
{else /}
name还没有赋值
{/present}

present标签的name属性可以直接使用系统变量，例如：

{present name="Think.get.name"}
$_GET['name']已经赋值
{/present}

EMPTY NOTEMPTY 标签

empty标签用于判断某个变量是否为空，用法：

{empty name="name"}
name为空值
{/empty}

如果判断没有赋值，可以使用：

{notempty name="name"}
name不为空
{/notempty}

可以把上面两个标签合并成为：

{empty name="name"}
name为空
{else /}
name不为空
{/empty}

name属性可以直接使用系统变量，例如：

{empty name="Think.get.name"}
$_GET['name']为空值
{/empty}

DEFINED 标签

DEFINED标签用于判断某个常量是否有定义，用法如下：

{defined name="NAME"}
NAME常量已经定义
{/defined}

name属性的值要注意严格大小写

如果判断没有被定义，可以使用：

{notdefined name="NAME"}
NAME常量未定义
{/notdefined}

可以把上面两个标签合并成为：

{defined name="NAME"}
NAME常量已经定义
{else /}
NAME常量未定义
{/defined}


资源文件加载

传统方式的导入外部JS和CSS文件的方法是直接在模板文件使用：

<script type='text/javascript' src='/static/js/common.js'>
<link rel="stylesheet" type="text/css" href="/static/css/style.css" />

系统提供了专门的标签来简化上面的导入：

{load href="/static/js/common.js" /}
{load href="/static/css/style.css" /}

并且支持同时加载多个资源文件，例如：

{load href="/static/js/common.js,/static/css/style.css" /}

系统还提供了两个标签别名js和css 用法和load一致，例如：

{js href="/static/js/common.js" /}
{css href="/static/css/style.css" /}


标签嵌套

模板引擎支持标签的多层嵌套功能，可以对标签库的标签指定可以嵌套。

系统内置的标签中，volist、switch、if、elseif、else、foreach、compare（包括所有的比较标签）、（not）present、（not）empty、（not）defined等标签都可以嵌套使用。例如：

{volist name="list" id="vo"}
    {volist name="vo['sub']" id="sub"}
        {$sub.name}
    {/volist}
{/volist}

上面的标签可以用于输出双重循环。


原生PHP

Php代码可以和标签在模板文件中混合使用，可以在模板文件里面书写任意的PHP语句代码 ，包括下面两种方式：

使用php标签

例如：

{php}echo 'Hello,world!';{/php}

我们建议需要使用PHP代码的时候尽量采用php标签，因为原生的PHP语法可能会被配置禁用而导致解析错误。

使用原生php代码

<?php echo 'Hello,world!'; ?>

注意：php标签或者php代码里面就不能再使用标签（包括普通标签和XML标签）了，因此下面的几种方式都是无效的：

{php}{eq name='name'value='value'}value{/eq}{/php}

Php标签里面使用了eq标签，因此无效

{php}if( {$user} != 'ThinkPHP' ) echo  'ThinkPHP' ;{/php}

Php标签里面使用了{$user}普通标签输出变量 ，因此无效。

{php}if( $user.name != 'ThinkPHP' ) echo  'ThinkPHP' ;{/php}

Php标签里面使用了$user.name 点语法变量输出 ，因此无效。

简而言之，在PHP标签里面不能再使用PHP本身不支持的代码。

如果设置了tpl_deny_php参数为true，就不能在模板中使用原生的PHP代码，但是仍然支持PHP标签输出。


定义标签

ASSIGN标签

ASSIGN标签用于在模板文件中定义变量，用法如下：

{assign name="var" value="123" /}

在运行模板的时候，赋值了一个var的变量，值是123。

name属性支持系统变量，例如：

{assign name="Think.get.id" value="123" /}

表示在模板中给$_GET['id'] 赋值了 123

value属性也支持变量，例如：

{assign name="var" value="$val" /}

或者直接把系统变量赋值给var变量，例如：

{assign name="var" value="$Think.get.name" /}

相当于，执行了：$var = $_GET['name'];

DEFINE标签

DEFINE标签用于中模板中定义常量，用法如下：

{define name="MY_DEFINE_NAME" value="3" /}

在运行模板的时候，就会定义一个MY_DEFINE_NAME的常量。

value属性可以支持变量（包括系统变量），例如：

{define name="MY_DEFINE_NAME" value="$name" /}

或者

{define name="MY_DEFINE_NAME" value="$Think.get.name" /}


介绍

日志记录由\think\Log类完成，主要完成日志记录和跟踪调试。由于日志记录了所有的运行错误，因此养成经常查看日志文件的习惯，可以避免和及早发现很多的错误隐患。

日志初始化

在使用日志记录之前，首先需要初始化日志类，指定当前使用的日志记录方式。

Log::init([
    'type'  =>  'File',
    'path'  =>  APP_PATH.'logs/'
]);

上面在日志初始化的时候，指定了文件方式记录日志，并且日志保存目录为APP_PATH.'logs/'。

如果你没有执行日志初始化操作的话，默认会自动调用配置参数log来进行初始化。

不同的日志类型可能会使用不同的初始化参数。

如果应用需要扩展自己的日志驱动，可以使用：

Log::init([
    'type'  =>  '\org\Log\File',
    'path'  =>  APP_PATH.'logs/'
]);


日志驱动

日志驱动

日志可以通过驱动支持不同的方式写入，默认日志会记录到文件中，系统已经内置的写入驱动包括 File、Socket，如果要临时关闭日志写入，可以设置日志类型为Test即可，例如：

'log'   => [
    // 可以临时关闭日志写入
    'type'  => 'test',
],

File驱动

日志的记录方式默认是File方式，可以通过驱动的方式来扩展支持更多的记录方式。

记录方式由log.type参数配置，例如：

'log'   => [
    // 日志记录方式，支持 file socket
    'type' => 'File',
    //日志保存目录
    'path' => LOG_PATH,
    //单个日志文件的大小限制，超过后会自动记录到第二个文件
    'file_size'     =>2097152,
    //日志的时间格式，默认是` c `
    'time_format'   =>'c'
],

为了避免同一个目录下面的日志文件过多的性能问题，file方式记录的日志文件会自动生成日期子目录。

Scoket驱动

Socket驱动配置，具体参考后面的远程调试部分。

其他驱动

thinkphp5.0支持SAE驱动的扩展，具体参考“SAE”章节

每个日志记录方式需要对应一个日志驱动文件，例如File方式记录，对应的驱动文件是library/think/log/driver/File.php。


日志写入

日志写入

版本 | 新增功能
5.0.4 | 命令行模式下日志自动定时写入
5.0.4 | debug日志类型仅用于调试模式记录

手动记录

一般情况下，系统的日志记录是自动的，无需手动记录，但是某些时候也需要手动记录日志信息，Log类提供了3个方法用于记录日志。

方法 | 描述
Log::record() | 记录日志信息到内存
Log::save() | 把保存在内存中的日志信息（用指定的记录方式）写入
Log::write() | 实时写入一条日志信息

由于系统在请求结束后会自动调用Log::save方法，所以通常，你只需要调用Log::record记录日志信息即可。

record方法用法如下：

Log::record('测试日志信息');

默认的话记录的日志级别是INFO，也可以指定日志级别：

Log::record('测试日志信息，这是警告级别','notice');

采用record方法记录的日志信息不是实时保存的，如果需要实时记录的话，可以采用write方法，例如：

Log::write('测试日志信息，这是警告级别，并且实时写入','notice');

V5.0.4+版本开始，为避免内容溢出，在命令行下面执行的话 日志信息会定时自动写入。

日志级别

ThinkPHP对系统的日志按照级别来分类，并且这个日志级别完全可以自己定义，系统内部使用的级别包括：

  • log 常规日志，用于记录日志
  • error 错误，一般会导致程序的终止
  • notice 警告，程序可以运行但是还不够完美的错误
  • info 信息，程序输出信息
  • debug 调试，用于调试信息
  • sql SQL语句，用于SQL记录，只在数据库的调试模式开启时有效

系统提供了不同日志级别的快速记录方法，例如：

Log::error('错误信息');
Log::info('日志信息');
// 和下面的用法等效
Log::record('错误信息','error');
Log::record('日志信息','info');

还封装了一个助手函数用于日志记录，例如：

trace('错误信息','error');
trace('日志信息','info');

也支持指定级别日志的输入，需要配置信息：

'log'   => [
    'type'  => 'File',
    // 日志记录级别，使用数组表示
    'level' => ['error'],
],


独立日志

为了便于分析，File类型的日志驱动还支持设置某些级别的日志信息单独文件记录，例如：

'log'   => [
    'type'          => 'file',
    // error和sql日志单独记录
    'apart_level'   =>  ['error','sql'],
],

设置后，就会单独生成error 和 sql两个类型的日志文件，主日志文件中将不再包含这两个级别的日志信息。

独立日志文件按天保存，不限制大小。


日志清空

日志类提供了日志清空的方法，可以在需要的时候手动清空日志，日志清空仅仅是清空内存中的日志。

使用方法如下：

Log::clear();


写入授权

5.0的日志功能支持写入授权，我们可以设置某个请求的日志授权Key，然后设置允许授权写入的配置Key，实现个别用户日志记录的功能，从而提高高负载下面的日志记录性能。

首先需要在应用配置文件或者应用公共文件中添加当前访问的授权Key定义，例如：

// 设置IP为授权Key
Log::key(Request::instance()->ip());

然后在日志配置参数中增加allow_key参数，如下：

'log'   =>  [
    // 日志类型为File
    'type'      =>  'File',
    // 授权只有202.12.36.89 才能记录日志
    'allow_key' =>  ['202.12.36.89'],
]


调试模式

ThinkPHP有专门为开发过程而设置的调试模式，开启调试模式后，会牺牲一定的执行效率，但带来的方便和除错功能非常值得。

我们强烈建议ThinkPHP开发人员在开发阶段始终开启调试模式（直到正式部署后关闭调试模式），方便及时发现隐患问题和分析、解决问题。

应用默认开启调试模式，在完成开发阶段部署到生产环境后，可以修改应用配置文件的app_debug参数关闭调试模式切换到部署模式。

// 关闭调试模式
'app_debug' => false,

除此之外，还可以在应用的ROOT_PATH目录下面定义.env文件，并且定义APP_DEBUG配置参数用于替代入口文件的常量定义，这样便于在部署环境中设置环境变量来开启和关闭调试模式。

.env文件的定义格式如下：

// 设置开启调试模式
app_debug =  true
// 其它的环境变量设置
// ...

定义了.env文件后，配置文件中定义app_debug参数无效。

调试模式的优势在于：

  • 开启日志记录，任何错误信息和调试信息都会详细记录，便于调试；
  • 会详细记录整个执行过程；
  • 模板修改可以即时生效；
  • 记录SQL日志，方便分析SQL；
  • 通过Trace功能更好的调试和发现错误；
  • 发生异常的时候会显示详细的异常信息；

由于调试模式没有任何缓存，因此涉及到较多的文件IO操作和模板实时编译，所以在开启调试模式的情况下，性能会有一定的下降，但不会影响部署模式的性能。另外需要注意的是，一旦关闭调试模式，项目的调试配置文件即刻失效。

一旦关闭调试模式，发生错误后不会提示具体的错误信息，如果你仍然希望看到具体的错误信息，那么可以如下设置：

// 显示错误信息
'show_error_msg'        =>  true,


异常处理

和PHP默认的异常处理不同，ThinkPHP抛出的不是单纯的错误信息，而是一个人性化的错误页面。

默认异常处理

在调试模式下，系统默认展示的错误页面：

只有在调试模式下面才能显示具体的错误信息，如果在部署模式下面，你可能看到的是一个简单的提示文字，例如：

本着严谨的原则，5.0版本默认情况下会对任何错误（包括警告错误）抛出异常，如果不希望如此严谨的抛出异常，可以在应用公共函数文件中或者配置文件中使用error_reporting方法设置错误报错级别（请注意，在入口文件中设置是无效的），例如：

// 异常错误报错级别,
error_reporting(E_ERROR | E_PARSE );

异常处理接管

框架支持异常页面由开发者自定义类进行处理，需要配置参数exception_handle

    // 异常处理handle类 留空使用 \think\exception\Handle
    'exception_handle'       => '\\app\\common\\exception\\Http',

自定义类需要继承Handle并且实现render方法，可以参考如下代码：

<?php
namespace app\common\exception;
use think\exception\Handle;
use think\exception\HttpException;
class Http extends Handle
{

    public function render(\Exception $e)
    {
        if ($e instanceof HttpException) {
            $statusCode = $e->getStatusCode();
        }
        //TODO::开发者对异常的操作
        //可以在此交由系统处理
        return parent::render($e);
    }

}

需要注意的是，如果配置了'exception_handle'，且没有再次调用系统render的情况下，配置http_exception_template就不再生效，具体可以参考Handle类内实现的功能。

部署模式异常

一旦关闭调试模式，发生错误后不会提示具体的错误信息，如果你仍然希望看到具体的错误信息，那么可以如下设置：

// 显示错误信息
'show_error_msg'        =>  true,

异常捕获

可以使用PHP的异常捕获进行必要的处理，但需要注意一点，在异常捕获中不要使用think\Controller类的error、success和redirect方法，因为上述三个方法会抛出HttpResponseException异常，从而影响正常的异常捕获，例如：

try{
    Db::name('user')->find();
    $this->success('执行成功!');
}catch(\Exception $e){
    $this->error('执行错误');
}

应该改成

try{
    Db::name('user')->find();
}catch(\Exception $e){
    $this->error('执行错误');
}
$this->success('执行成功!');


抛出异常

手动抛出异常

可以使用\think\Exception类来抛出异常

// 使用think自带异常类抛出异常
throw new \think\Exception('异常消息', 100006);

如果不使用think异常类，也可以定义自己的异常类来抛出异常

throw new \foobar\Exception('异常消息');

也可以使用系统提供的助手函数来简化处理：

exception('异常消息', 100006);

// 使用自定义异常类
exception('异常消息', 100006, \foobar\Exceeption);

抛出 HTTP 异常

可以使用\think\exception\HttpException类来抛出异常

// 抛出 HTTP 异常
throw new \think\exception\HttpException(404, '异常消息', null, [参数]);

系统提供了助手函数abort简化HTTP异常的处理，例如：

abort(404, '异常消息', [参数])

HTTP异常可以单独定义异常模板，请参考后面的404页面。


Trace调试

调试模式并不能完全满足我们调试的需要，有时候我们需要手动的输出一些调试信息。除了本身可以借助一些开发工具进行调试外，ThinkPHP还提供了一些内置的调试工具和函数。例如，Trace调试功能就是ThinkPHP提供给开发人员的一个用于开发调试的辅助工具。可以实时显示当前页面的操作的请求信息、运行情况、SQL执行、错误提示等，并支持自定义显示，5.0版本的Trace调试支持没有页面输出的操作调试。

Trace调试功能对调试模式和部署模式都有效，可以单独开启和关闭。
只是在部署模式下面，显示的调试信息没有调试模式完整，通常我们建议Trace配合调试模式一起使用。

开启Trace调试

默认关闭Trace调试功能，要开启Trace调试功能，只需要配置下面参数：

// 开启应用Trace调试
'app_trace' =>  true,

如果定义了环境变量app_trace，那么以环境变量配置为准。

页面Trace显示

要开启页面Trace功能，需要配置trace参数为：

// Trace信息
'trace'     =>  [
    //支持Html,Console
    'type'  =>  'html',
]

设置后并且你的页面有输出的话，页面右下角会显示ThinkPHP的LOGO：

我们看到的LOGO后面的数字就是当前页面的执行时间（单位是秒） 点击该图标后，会展开详细的Trace信息，如图：

Trace框架有6个选项卡，分别是基本、文件、流程、错误、SQL和调试，点击不同的选项卡会切换到不同的Trace信息窗口。

选项卡 | 描述
基本 | 当前页面的基本摘要信息，例如执行时间、内存开销、文件加载数、查询次数等等
文件 | 详细列出当前页面执行过程中加载的文件及其大小
流程 | 会列出当前页面执行到的行为和相关流程
错误 | 当前页面执行过程中的一些错误信息，包括警告错误
SQL | 当前页面执行到的SQL语句信息
调试 | 开发人员在程序中进行的调试输出

Trace的选项卡是可以定制和扩展的，默认的配置为：

// 显示Trace信息
'trace' =>[
    'type'       =>  'Html',
    'trace_tabs' =>  [
         'base'=>'基本',
         'file'=>'文件',
         'info'=>'流程',
         'error|notice'=>'错误',
         'sql'=>'SQL',
         'debug|log'=>'调试'
     ]
]

也就是我们看到的默认情况下显示的选项卡，如果你希望增加新的选项卡：用户，则可以修改配置如下：

// 显示Trace信息
'trace' =>[
    'type'       =>  'Html',
    'trace_tabs' =>  [
         'base'=>'基本',
         'file'=>'文件',
         'info'=>'流程',
         'error'=>'错误',
         'sql'=>'SQL',
         'debug'=>'调试',
         'user'=>'用户'
     ]
]

也可以把某几个选项卡合并，例如：

// 显示Trace信息
'trace' =>[
    'type'       =>  'Html',
    'trace_tabs' =>  [
         'base'=>'基本',
         'file'=>'文件',
         'error|notice'=>'错误',
         'sql'=>'SQL',
         'debug|log|info'=>'调试',
     ]
]

更改后的Trace显示效果如图：

浏览器Trace显示

trace功能支持在浏览器的console直接输出，这样可以方便没有页面输出的操作功能调试，只需要设置：

// Trace信息
'trace' =>[
    // 使用浏览器console输出trace信息
    'type'  =>  'console',
]

运行后打开浏览器的console控制台可以看到如图所示的信息：

浏览器Trace输出仍然支持trace_tabs设置。


变量调试

除了Trace调试之外，系统还提供了\think\Debug类用于各种调试。

输出某个变量是开发过程中经常会用到的调试方法，除了使用php内置的var_dump和print_r之外，ThinkPHP框架内置了一个对浏览器友好的dump方法，用于输出变量的信息到浏览器查看。

用法：

Debug::dump($var, $echo=true, $label=null)
或者
dump($var, $echo=true, $label=null)

相关参数的使用如下：

参数 | 描述
var（必须） | 要输出的变量，支持所有变量类型
echo（可选） | 是否直接输出，默认为true，如果为false则返回但不输出
label（可选） | 变量输出的label标识，默认为空

如果echo参数为false 则返回要输出的字符串

使用示例：

 $blog = Db::name('blog')->where('id', 3)->find();

 Debug::dump($blog);
 // 下面的用法是等效的
 dump($blog);

在浏览器输出的结果是：

array(12) {
   ["id"]            => string(1) "3"
   ["name"]          => string(0) ""
   ["user_id"]       => string(1) "0"
   ["cate_id"]       => string(1) "0"
   ["title"]         => string(4) "test"
   ["content"]       => string(4) "test"
   ["create_time"]   => string(1) "0"
   ["update_time"]   => string(1) "0"
   ["status"]        => string(1) "0"
   ["read_count"]    => string(1) "0"
   ["comment_count"] => string(1) "0"
   ["tags"]          => string(0) ""
}

如果需要在调试变量输出后中止程序的执行，可以使用halt函数，例如：

 $blog = Db::name('blog')->where('id', 3)->find();

 halt($blog);
 echo '这里的信息是看不到的';

执行后会输出

array(12) {
   ["id"]            => string(1) "3"
   ["name"]          => string(0) ""
   ["user_id"]       => string(1) "0"
   ["cate_id"]       => string(1) "0"
   ["title"]         => string(4) "test"
   ["content"]       => string(4) "test"
   ["create_time"]   => string(1) "0"
   ["update_time"]   => string(1) "0"
   ["status"]        => string(1) "0"
   ["read_count"]    => string(1) "0"
   ["comment_count"] => string(1) "0"
   ["tags"]          => string(0) ""
}

并中止执行后续的程序。


性能调试

开发过程中，有些时候为了测试性能，经常需要调试某段代码的运行时间或者内存占用开销，系统提供了think\Debug类可以很方便的获取某个区间的运行时间和内存占用情况。 例如：

Debug::remark('begin');
// ...其他代码段
Debug::remark('end');
// ...也许这里还有其他代码
// 进行统计区间
echo Debug::getRangeTime('begin','end').'s';

表示统计begin位置到end位置的执行时间（单位是秒），begin必须是一个已经标记过的位置，如果这个时候end位置还没被标记过，则会自动把当前位置标记为end标签，输出的结果类似于：0.0056s

默认的统计精度是小数点后4位，如果觉得这个统计精度不够，还可以设置例如：

echo Debug::getRangeTime('begin','end',6).'s';

可能的输出会变成：0.005587s

如果你的环境支持内存占用统计的话，还可以使用getRangeMem方法进行区间内存开销统计（单位为kb），例如：

echo Debug::getRangeMem('begin','end').'kb';

第三个参数使用m表示进行内存开销统计，输出的结果可能是：625kb

同样，如果end标签没有被标记的话，会自动把当前位置先标记位end标签。

助手函数

系统还提供了助手函数debug用于完成相同的作用，上面的代码可以改成：

debug('begin');
// ...其他代码段
debug('end');
// ...也许这里还有其他代码
// 进行统计区间
echo debug('begin','end').'s';
echo debug('begin','end',6).'s';
echo debug('begin','end','m').'kb';


SQL调试

查看SQL记录

如果开启了数据库的调试模式的话，可以在日志文件（或者设置的日志输出类型）中看到详细的SQL执行记录以及性能分析。

下面是一个典型的SQL日志：

[ SQL ] SHOW COLUMNS FROM `think_action` [ RunTime:0.001339s ]
[ EXPLAIN : array ( 'id' => '1', 'select_type' => 'SIMPLE', 'table' => 'think_action', 'partitions' => NULL, 'type' => 'ALL', 'possible_keys' => NULL, 'key' => NULL, 'key_len' => NULL, 'ref' => NULL, 'rows' => '82', 'filtered' => '100.00', 'extra' => NULL, ) ]
[ SQL ] SELECT * FROM `think_action` LIMIT 1 [ RunTime:0.000539s ]

监听SQL

如果开启数据库的调试模式的话，你可以对数据库执行的任何SQL操作进行监听，使用如下方法：

Db::listen(function($sql,$time,$explain){
    // 记录SQL
    echo $sql. ' ['.$time.'s]';
    // 查看性能分析结果
    dump($explain);
});

默认如果没有注册任何监听操作的话，这些SQL执行会被根据不同的日志类型记录到日志中。

调试执行的SQL语句

在模型操作中 ，为了更好的查明错误，经常需要查看下最近使用的SQL语句，我们可以用getLastsql方法来输出上次执行的sql语句。例如：

User::get(1);
echo User::getLastSql();

输出结果是 SELECT * FROM 'think_user' WHERE 'id' = '1'

也可以使用fetchSql方法直接返回当前的查询SQL而不执行，例如：

echo User::fetchSql()->find(1);

输出的结果是一样的。

getLastSql方法只能获取最后执行的SQL记录，如果需要了解更多的SQL日志，可以通过查看当前的Trace信息或者日志文件。


远程调试

ThinkPHP5.0版本开始，提供了Socket日志驱动用于本地和远程调试。

Socket调试

只需要在配置文件中设置如下：

'log' =>  [
    'type'                => 'socket',
    'host'                => 'slog.thinkphp.cn',
    //日志强制记录到配置的client_id
    'force_client_ids'    => [],
    //限制允许读取日志的client_id
    'allow_client_ids'    => [],
]

上面的host配置地址是官方提供的公用服务端，首先需要去申请client_id 。

使用Chrome浏览器运行后，打开审查元素->Console，可以看到如下所示：

SocketLog通过websocket将调试日志打印到浏览器的console中。你还可以用它来分析开源程序，分析SQL性能，结合taint分析程序漏洞。

安装Chrome插件

SocketLog首先需要安装chrome插件，Chrome插件安装页面 （需翻墙）

使用方法

  • 首先，请在chrome浏览器上安装好插件。
  • 安装服务端npm install -g socketlog-server , 运行命令 socketlog-server 即可启动服务。 将会在本地起一个websocket服务 ，监听端口是1229 。
  • 如果想服务后台运行： socketlog-server > /dev/null &

参数

  • client_id: 在chrome浏览器中，可以设置插件的Client_ID ，Client_ID是你任意指定的字符串。

  • 设置client_id后能实现以下功能：

  • 1，配置allow_client_ids 配置项，让指定的浏览器才能获得日志，这样就可以把调试代码带上线。 普通用户访问不会触发调试，不会发送日志。 开发人员访问就能看的调试日志， 这样利于找线上bug。 Client_ID 建议设置为姓名拼音加上随机字符串，这样如果有员工离职可以将其对应的client_id从配置项allow_client_ids中移除。 client_id除了姓名拼音，加上随机字符串的目的，以防别人根据你公司员工姓名猜测出client_id,获取线上的调试日志。

  • 设置allow_client_ids示例代码：

    'allow_client_ids'=>['thinkphp_zfH5NbLn','luofei_DJq0z80H'],
  • 2, 设置force_client_ids配置项，让后台脚本也能输出日志到chrome。 网站有可能用了队列，一些业务逻辑通过后台脚本处理， 如果后台脚本需要调试，你也可以将日志打印到浏览器的console中， 当然后台脚本不和浏览器接触，不知道当前触发程序的是哪个浏览器，所以我们需要强制将日志打印到指定client_id的浏览器上面。 我们在后台脚本中使用SocketLog时设置force_client_ids 配置项指定要强制输出浏览器的client_id 即可。


404页面

一旦抛出了HttpException异常，可以支持定义单独的异常页面的模板地址，只需要在应用配置文件中增加：

'http_exception_template'    =>  [
    // 定义404错误的重定向页面地址
    404 =>  APP_PATH.'404.html',
    // 还可以定义其它的HTTP status
    401 =>  APP_PATH.'401.html',
]

模板文件支持模板引擎中的标签。

http_exception_template配置仅在部署模式下面生效。

一般来说HTTP异常是由系统自动抛出的，但我们也可以手动抛出

throw new \think\exception\HttpException(404, '页面不存在');

或者通过助手函数abort手动抛出HTTP异常，例如：

abort(404,'页面不存在');


验证器

概述

ThinkPHP5.0验证使用独立的\think\Validate类或者验证器进行验证。

独立验证

任何时候，都可以使用Validate类进行独立的验证操作，例如：

$validate = new Validate([
    'name'  => 'require|max:25',
    'email' => 'email'
]);
$data = [
    'name'  => 'thinkphp',
    'email' => 'thinkphp@qq.com'
];
if (!$validate->check($data)) {
    dump($validate->getError());
}

验证器

这是5.0推荐的验证方式，为具体的验证场景或者数据表定义好验证器类，直接调用验证类的check方法即可完成验证，下面是一个例子：

我们定义一个\app\index\validate\User验证器类用于User的验证。

namespace app\index\validate;

use think\Validate;

class User extends Validate
{
    protected $rule = [
        'name'  =>  'require|max:25',
        'email' =>  'email',
    ];

}

在需要进行User验证的地方，添加如下代码即可：

$data = [
    'name'=>'thinkphp',
    'email'=>'thinkphp@qq.com'
];

$validate = Loader::validate('User');

if(!$validate->check($data)){
    dump($validate->getError());
}

使用助手函数实例化验证器

$validate = validate('User');


验证规则

版本 | 新增功能
5.0.4 | 支持传入field参数批量设置验证字段的描述信息

设置规则

可以在实例化Validate类的时候传入验证规则，例如：

$rules = [
    'name'  => 'require|max:25',
    'age'   => 'number|between:1,120',
];
$validate = new Validate($rules);

也可以使用rule方法动态添加规则，例如：

$rules = [
    'name'  => 'require|max:25',
    'age'   => 'number|between:1,120',
];
$validate = new Validate($rules);
$validate->rule('zip', '/^\d{6}$/');
$validate->rule([
    'email'   => 'email',
]);

规则定义

规则定义支持下面两种方式：

$rules = [
    'name'  => 'require|max:25',
    'age'   => 'number|between:1,120',
];
$validate = new Validate($rules);

对于一个字段可以设置多个验证规则，使用|分割。

或者采用数组方式定义多个规则（适用于你的验证规则中有|的情况）

$rules = [
    'name'  => ['require','max'=>25],
    'age'   => ['number','between'=>'1,120'],
];
$validate = new Validate($rules);

属性定义

通常情况下，我们实际在定义验证类的时候，可以通过属性的方式直接定义验证规则等信息，例如：

namespace app\index\validate;

use think\Validate;

class User extends Validate
{
    protected $rule =   [
        'name'  => 'require|max:25',
        'age'   => 'number|between:1,120',
        'email' => 'email',
    ];

    protected $message  =   [
        'name.require' => '名称必须',
        'name.max'     => '名称最多不能超过25个字符',
        'age.number'   => '年龄必须是数字',
        'age.between'  => '年龄只能在1-120之间',
        'email'        => '邮箱格式错误',
    ];

}

验证数据

下面是一个典型的验证数据的例子：

$rule = [
    'name'  => 'require|max:25',
    'age'   => 'number|between:1,120',
    'email' => 'email',
];

$msg = [
    'name.require' => '名称必须',
    'name.max'     => '名称最多不能超过25个字符',
    'age.number'   => '年龄必须是数字',
    'age.between'  => '年龄只能在1-120之间',
    'email'        => '邮箱格式错误',
];

$data = [
    'name'  => 'thinkphp',
    'age'   => 10,
    'email' => 'thinkphp@qq.com',
];

$validate = new Validate($rule, $msg);
$result   = $validate->check($data);

如果需要批量验证，可以使用：

$validate = new Validate($rule, $msg);
$result   = $validate->batch()->check($data);

批量验证如果验证不通过，返回的是一个错误信息的数组。

如果你定义了User验证器类的话，可以使用下面的验证代码：

$data = [
    'name'  => 'thinkphp',
    'age'   => 10,
    'email' => 'thinkphp@qq.com',
];
$validate = Loader::validate('User');
if(!$validate->check($data)){
    dump($validate->getError());
}

闭包函数验证

可以对某个字段使用闭包验证，例如：

$validate = new \think\Validate([
    'name'  => function($value,$rule) {
        return $rule==$value ? true : false;
    },
]);

自定义验证规则

系统内置了一些常用的规则，如果还不够用，可以自己扩展验证规则。

如果使用了验证器的话，可以直接在验证器类添加自己的验证方法，例如：

namespace app\index\validate;

use think\Validate;

class User extends Validate
{
    protected $rule = [
        'name'  =>  'checkName:thinkphp',
        'email' =>  'email',
    ];

    protected $message = [
        'name'  =>  '用户名必须',
        'email' =>  '邮箱格式错误',
    ];

    // 自定义验证规则
    protected function checkName($value,$rule,$data)
    {
        return $rule == $value ? true : '名称错误';
    }
}

验证方法支持传入的参数包括 $value $rule 和 $data三个，并且第三个参数支持引用传参。

并且需要注意的是，自定义的验证规则方法名不能和已有的规则冲突。

接下来，就可以这样进行验证：

$validate = Loader::validate('User');
if(!$validate->check($data)){
    dump($validate->getError());
}

如果没有使用验证器类，则支持使用extend方法扩展验证规则，例如：

$validate = new Validate(['name' => 'checkName:1']);
$validate->extend('checkName', function ($value, $rule) {
    return $rule == $value ? true : '名称错误';
});
$data   = ['name' => 1];
$result = $validate->check($data);

支持批量注册验证规则，例如：

$validate = new Validate(['name' => 'checkName:1']);
$validate->extend([
    'checkName'=> function ($value, $rule) {
    return $rule == $value ? true : '名称错误';
},
    'checkStatus'=> [$this,'checkStatus']
]);
$data   = ['name' => 1];
$result = $validate->check($data);

设置字段信息

V5.0.4+版本开始，验证类的架构方法支持传入field参数批量设置字段的描述信息，例如：

$rule = [
    'name'  => 'require|max:25',
    'age'   => 'number|between:1,120',
    'email' => 'email',
];

$field = [
    'name'  => '名称',
    'age'   => '年龄',
    'email' => '邮箱',
];

$data = [
    'name'  => 'thinkphp',
    'age'   => 10,
    'email' => 'thinkphp@qq.com',
];

$validate = new Validate($rule, [] , $field);
$result   = $validate->check($data);


错误信息

验证规则的错误提示信息有三种方式可以定义，如下：

使用默认的错误提示信息

如果没有定义任何的验证提示信息，系统会显示默认的错误信息，例如：

$rule = [
    'name'  => 'require|max:25',
    'age'   => 'number|between:1,120',
    'email' => 'email',
];

$data = [
    'name'  => 'thinkphp',
    'age'   => 121,
    'email' => 'thinkphp@qq.com',
];
$validate = new Validate($rule);
$result   = $validate->check($data);
if(!$result){
    echo $validate->getError();
}

会输出 age只能在 1 - 120 之间。

可以给age字段设置中文名，例如：

$rule = [
    'name'  => 'require|max:25',
    'age|年龄'   => 'number|between:1,120',
    'email' => 'email',
];

$data = [
    'name'  => 'thinkphp',
    'age'   => 121,
    'email' => 'thinkphp@qq.com',
];
$validate = new Validate($rule);
$result   = $validate->check($data);
if(!$result){
    echo $validate->getError();
}

会输出 年龄只能在 1 - 120 之间。

验证规则和提示信息分开定义

如果要输出自定义的错误信息，有两种方式可以设置。下面的一种方式是验证规则和提示信息分开定义：

$rule = [
    'name'  => 'require|max:25',
    'age'   => 'number|between:1,120',
    'email' => 'email',
];
$msg = [
    'name.require' => '名称必须',
    'name.max'     => '名称最多不能超过25个字符',
    'age.number'   => '年龄必须是数字',
    'age.between'  => '年龄必须在1~120之间',
    'email'        => '邮箱格式错误',
];
$data = [
    'name'  => 'thinkphp',
    'age'   => 121,
    'email' => 'thinkphp@qq.com',
];
$validate = new Validate($rule,$msg);
$result   = $validate->check($data);
if(!$result){
    echo $validate->getError();
}

会输出 年龄必须在1~120之间。

验证规则和提示信息一起定义

可以支持验证规则和错误信息一起定义的方式，如下：

$rule = [
    ['name','require|max:25','名称必须|名称最多不能超过25个字符'],
    ['age','number|between:1,120','年龄必须是数字|年龄必须在1~120之间'],
    ['email','email','邮箱格式错误']
];

$data = [
    'name'  => 'thinkphp',
    'age'   => 121,
    'email' => 'thinkphp@qq.com',
];
$validate = new Validate($rule);
$result   = $validate->check($data);
if(!$result){
    echo $validate->getError();
}


验证场景

验证场景

版本 | 新增功能
5.0.4 | 增加hasScene方法用于检查是否存在验证场景

可以在定义验证规则的时候定义场景，并且验证不同场景的数据，例如：

$rule = [
    'name'  => 'require|max:25',
    'age'   => 'number|between:1,120',
    'email' => 'email',
];
$msg = [
    'name.require' => '名称必须',
    'name.max'     => '名称最多不能超过25个字符',
    'age.number'   => '年龄必须是数字',
    'age.between'  => '年龄只能在1-120之间',
    'email'        => '邮箱格式错误',
];
$data = [
    'name'  => 'thinkphp',
    'age'   => 10,
    'email' => 'thinkphp@qq.com',
];
$validate = new Validate($rule);
$validate->scene('edit', ['name', 'age']);
$result = $validate->scene('edit')->check($data);

表示验证edit场景（该场景定义只需要验证name和age字段）。

如果使用了验证器，可以直接在类里面定义场景，例如：

namespace app\index\validate;

use think\Validate;

class User extends Validate
{
    protected $rule =   [
        'name'  => 'require|max:25',
        'age'   => 'number|between:1,120',
        'email' => 'email',
    ];

    protected $message  =   [
        'name.require' => '名称必须',
        'name.max'     => '名称最多不能超过25个字符',
        'age.number'   => '年龄必须是数字',
        'age.between'  => '年龄只能在1-120之间',
        'email'        => '邮箱格式错误',
    ];

    protected $scene = [
        'edit'  =>  ['name','age'],
    ];

}

然后再需要验证的地方直接使用 scene 方法验证

$data = [
    'name'  => 'thinkphp',
    'age'   => 10,
    'email' => 'thinkphp@qq.com',
];

$validate = new Validate($rule);
$result = $validate->scene('edit')->check($data);

可以在定义场景的时候对某些字段的规则重新设置，例如：

namespace app\index\validate;

use think\Validate;

class User extends Validate
{
    protected $rule =   [
        'name'  => 'require|max:25',
        'age'   => 'number|between:1,120',
        'email' => 'email',
    ];

    protected $message  =   [
        'name.require' => '名称必须',
        'name.max'     => '名称最多不能超过25个字符',
        'age.number'   => '年龄必须是数字',
        'age.between'  => '年龄只能在1-120之间',
        'email'        => '邮箱格式错误',
    ];

    protected $scene = [
        'edit'  =>  ['name','age'=>'require|number|between:1,120'],
    ];

}

可以对场景设置一个回调方法，用于动态设置要验证的字段，例如：

$rule = [
    'name'  => 'require|max:25',
    'age'   => 'number|between:1,120',
    'email' => 'email',
];
$msg = [
    'name.require' => '名称必须',
    'name.max'     => '名称最多不能超过25个字符',
    'age.number'   => '年龄必须是数字',
    'age.between'  => '年龄只能在1-120之间',
    'email'        => '邮箱格式错误',
];
$data = [
    'name'  => 'thinkphp',
    'age'   => 10,
    'email' => 'thinkphp@qq.com',
];
$validate = new Validate($rule);
$validate->scene('edit', function($key,$data){
    return 'email'==$key && isset($data['id'])? true : false;
});
$result = $validate->scene('edit')->check($data);


控制器验证

如果你需要在控制器中进行验证，并且继承了\think\Controller的话，可以调用控制器类提供的validate方法进行验证，如下：

$result = $this->validate(
    [
        'name'  => 'thinkphp',
        'email' => 'thinkphp@qq.com',
    ],
    [
        'name'  => 'require|max:25',
        'email'   => 'email',
    ]);
if(true !== $result){
    // 验证失败 输出错误信息
    dump($result);
}

如果定义了验证器类的话，例如：

namespace app\index\validate;

use think\Validate;

class User extends Validate
{
    protected $rule = [
        'name'  =>  'require|max:25',
        'email' =>  'email',
    ];

    protected $message = [
        'name.require'  =>  '用户名必须',
        'email' =>  '邮箱格式错误',
    ];

    protected $scene = [
        'add'   =>  ['name','email'],
        'edit'  =>  ['email'],
    ];
}

控制器中的验证代码可以简化为：

$result = $this->validate($data,'User');
if(true !== $result){
    // 验证失败 输出错误信息
    dump($result);
}

如果要使用场景，可以使用：

$result = $this->validate($data,'User.edit');
if(true !== $result){
    // 验证失败 输出错误信息
    dump($result);
}

在validate方法中还支持做一些前置的操作回调，使用方式如下：

$result = $this->validate($data,'User.edit',[],[$this,'some']);
if(true !== $result){
    // 验证失败 输出错误信息
    dump($result);
}


模型验证

在模型中的验证方式如下：

$User = new User;
$result = $User->validate(
    [
        'name'  => 'require|max:25',
        'email'   => 'email',
    ],
    [
        'name.require' => '名称必须',
        'name.max'     => '名称最多不能超过25个字符',
        'email'        => '邮箱格式错误',
    ]
)->save($data);
if(false === $result){
    // 验证失败 输出错误信息
    dump($User->getError());
}

第二个参数如果不传的话，则采用默认的错误提示信息。

如果使用下面的验证器类的话：

namespace app\index\validate;
use think\Validate;
class User extends Validate
{
    protected $rule = [
        'name'  =>  'require|max:25',
        'email' =>  'email',
    ];

    protected $message = [
        'name.require'  =>  '用户名必须',
        'email' =>  '邮箱格式错误',
    ];

    protected $scene = [
        'add'   =>  ['name','email'],
        'edit'  =>  ['email'],
    ];
}

模型验证代码可以简化为：

$User = new User;
// 调用当前模型对应的User验证器类进行数据验证
$result = $User->validate(true)->save($data);
if(false === $result){
    // 验证失败 输出错误信息
    dump($User->getError());
}

如果需要调用的验证器类和当前的模型名称不一致，则可以使用：

$User = new User;
// 调用Member验证器类进行数据验证
$result = $User->validate('Member')->save($data);
if(false === $result){
    // 验证失败 输出错误信息
    dump($User->getError());
}

同样也可以支持场景验证：

$User = new User;
// 调用Member验证器类进行数据验证
$result = $User->validate('User.edit')->save($data);
if(false === $result){
    // 验证失败 输出错误信息
    dump($User->getError());
}


内置规则

系统内置的验证规则如下：

格式验证类

require

验证某个字段必须，例如：

'name'=>'require'

number 或者 integer

验证某个字段的值是否为数字（采用filter_var验证），例如：

'num'=>'number'

float

验证某个字段的值是否为浮点数字（采用filter_var验证），例如：

'num'=>'float'

boolean

验证某个字段的值是否为布尔值（采用filter_var验证），例如：

'num'=>'boolean'

email

验证某个字段的值是否为email地址（采用filter_var验证），例如：

'email'=>'email'

array

验证某个字段的值是否为数组，例如：

'info'=>'array'

accepted

验证某个字段是否为为 yes, on, 或是 1。这在确认"服务条款"是否同意时很有用，例如：

'accept'=>'accepted'

date

验证值是否为有效的日期，例如：

'date'=>'date'

会对日期值进行strtotime后进行判断。

alpha

验证某个字段的值是否为字母，例如：

'name'=>'alpha'

alphaNum

验证某个字段的值是否为字母和数字，例如：

'name'=>'alphaNum'

alphaDash

验证某个字段的值是否为字母和数字，下划线_及破折号-，例如：

'name'=>'alphaDash'

activeUrl

验证某个字段的值是否为有效的域名或者IP，例如：

'host'=>'activeUrl'

url

验证某个字段的值是否为有效的URL地址（采用filter_var验证），例如：

'url'=>'url'

ip

验证某个字段的值是否为有效的IP地址（采用filter_var验证），例如：

'ip'=>'ip'

支持验证ipv4和ipv6格式的IP地址。

dateFormat:format

验证某个字段的值是否为指定格式的日期，例如：

'create_time'=>'dateFormat:y-m-d'

长度和区间验证类

in

验证某个字段的值是否在某个范围，例如：

'num'=>'in:1,2,3'

notIn

验证某个字段的值不在某个范围，例如：

'num'=>'notIn:1,2,3'

between

验证某个字段的值是否在某个区间，例如：

'num'=>'between:1,10'

notBetween

验证某个字段的值不在某个范围，例如：

'num'=>'notBetween:1,10'

length:num1,num2

验证某个字段的值的长度是否在某个范围，例如：

'name'=>'length:4,25'

或者指定长度

'name'=>'length:4'

如果验证的数据是数组，则判断数组的长度。
如果验证的数据是File对象，则判断文件的大小。

max:number

验证某个字段的值的最大长度，例如：

'name'=>'max:25'

如果验证的数据是数组，则判断数组的长度。
如果验证的数据是File对象，则判断文件的大小。

min:number

验证某个字段的值的最小长度，例如：

'name'=>'min:5'

如果验证的数据是数组，则判断数组的长度。
如果验证的数据是File对象，则判断文件的大小。

after:日期

验证某个字段的值是否在某个日期之后，例如：

'begin_time' => 'after:2016-3-18',

before:日期

验证某个字段的值是否在某个日期之前，例如：

'end_time'   => 'before:2016-10-01',

expire:开始时间,结束时间

验证当前操作（注意不是某个值）是否在某个有效日期之内，例如：

'expire_time'   => 'expire:2016-2-1,2016-10-01',

allowIp:allow1,allow2,...

验证当前请求的IP是否在某个范围，例如：

'name'   => 'allowIp:114.45.4.55',

该规则可以用于某个后台的访问权限

denyIp:allow1,allow2,...

验证当前请求的IP是否禁止访问，例如：

'name'   => 'denyIp:114.45.4.55',

字段比较类

confirm

验证某个字段是否和另外一个字段的值一致，例如：

'repassword'=>'require|confirm:password'

5.0.4+版本开始，增加了字段自动匹配验证规则，如password和password_confirm是自动相互验证的，只需要使用

'password'=>'require|confirm'

会自动验证和password_confirm进行字段比较是否一致，反之亦然。

different

验证某个字段是否和另外一个字段的值不一致，例如：

'name'=>'require|different:account'

egt 或者 >=

验证是否大于等于某个值，例如：

'score'=>'egt:60'
'num'=>'>=:100'

gt 或者 >

验证是否大于某个值，例如：

'score'=>'gt:60'
'num'=>'>:100'

elt 或者 <=

验证是否小于等于某个值，例如：

'score'=>'elt:100'
'num'=>'<=:100'

lt 或者 <

验证是否小于某个值，例如：

'score'=>'lt:100'
'num'=>'<:100'

eq 或者 = 或者 same

验证是否等于某个值，例如：

'score'=>'eq:100'
'num'=>'=:100'
'num'=>'same:100'

filter验证

支持使用filter_var进行验证，例如：

'ip'=>'filter:validate_ip'

正则验证

支持直接使用正则验证，例如：

'zip'=>'\d{6}',
// 或者
'zip'=>'regex:\d{6}',

如果你的正则表达式中包含有|符号的话，必须使用数组方式定义。

'accepted'=>['regex'=>'/^(yes|on|1)$/i'],

也可以实现预定义正则表达式后直接调用，例如：

上传验证

file

验证是否是一个上传文件

image:width,height,type

验证是否是一个图像文件，width height和type都是可选，width和height必须同时定义。

fileExt:允许的文件后缀

验证上传文件后缀

fileMime:允许的文件类型

验证上传文件类型

fileSize:允许的文件字节大小

验证上传文件大小

行为验证

使用行为验证数据，例如：

'data'=>'behavior:\app\index\behavior\Check'

其它验证

unique:table,field,except,pk

验证当前请求的字段值是否为唯一的，例如：

// 表示验证name字段的值是否在user表（不包含前缀）中唯一
'name'   => 'unique:user',
// 验证其他字段
'name'   => 'unique:user,account',
// 排除某个主键值
'name'   => 'unique:user,account,10',
// 指定某个主键值排除
'name'   => 'unique:user,account,10,user_id',

如果需要对复杂的条件验证唯一，可以使用下面的方式：

// 多个字段验证唯一验证条件
'name'   => 'unique:user,status^account',
// 复杂验证条件
'name'   => 'unique:user,status=1&account='.$data['account'],

requireIf:field,value

验证某个字段的值等于某个值的时候必须，例如：

// 当account的值等于1的时候 password必须
'password'=>'requireIf:account,1'

requireWith:field

验证某个字段有值的时候必须，例如：

// 当account有值的时候password字段必须
'password'=>'requireWith:account'


静态调用

如果需要使用内置的规则验证单个数据，可以使用静态调用的方式。

// 日期格式验证
Validate::dateFormat('2016-03-09','Y-m-d'); // true
// 验证是否有效的日期
Validate::is('2016-06-03','date'); // true
// 验证是否有效邮箱地址
Validate::is('thinkphp@qq.com','email'); // true
// 验证是否在某个范围
Validate::in('a',['a','b','c']); // true
// 验证是否大于某个值
Validate::gt(10,8); // true
// 正则验证
Validate::regex(100,'\d+'); // true

静态验证的返回值为布尔值，错误信息需要自己处理。

更多验证规则可以参考前面的内置规则。


表单令牌

验证规则支持对表单的令牌验证，首先需要在你的表单里面增加下面隐藏域：

<input type="hidden" name="__token__" value="{$Request.token}" />

或者

{:token()}

然后在你的验证规则中，添加token验证规则即可，例如，如果使用的是验证器的话，可以改为：

    protected $rule = [
        'name'  =>  'require|max:25|token',
        'email' =>  'email',
    ];

如果你的令牌名称不是__token__，则表单需要改为：

<input type="hidden" name="__hash__" value="{$Request.token.__hash__}" />

或者：

{:token('__hash__')}

验证器中需要改为：

    protected $rule = [
        'name'  =>  'require|max:25|token:__hash__',
        'email' =>  'email',
    ];

如果需要自定义令牌生成规则，可以调用Request类的token方法，例如：

namespace app\index\controller;

use think\Controller;

class Index extends Controller
{
    public function index()
    {
        $token = $this->request->token('__token__', 'sha1');
        $this->assign('token', $token);
        return $this->fetch();
    }
}

然后在模板表单中使用：

<input type="hidden" name="__token__" value="{$token}" />

或者不需要在控制器写任何代码，直接在模板中使用：

{:token('__token__', 'sha1')}


缓存

概述

ThinkPHP采用think\Cache类提供缓存功能支持。

版本 | 新增功能
5.0.2 | 增加remember方法

设置

缓存支持采用驱动方式，所以缓存在使用之前，需要进行连接操作，也就是缓存初始化操作。

$options = [
    // 缓存类型为File
    'type'  =>  'File',
    // 缓存有效期为永久有效
    'expire'=>  0,
    //缓存前缀
    'prefix'=>  'think'
     // 指定缓存目录
    'path'  =>  APP_PATH.'runtime/cache/',
];
Cache::connect($options);

或者通过定义配置参数的方式，在应用配置文件中添加：

'cache'  => [
    'type'   => 'File',
    'path'   => CACHE_PATH,
    'prefix' => '',
    'expire' => 0,
],

支持的缓存类型包括file、memcache、wincache、sqlite、redis和xcache。

缓存参数根据不同的缓存方式会有所区别，通用的缓存参数如下：

参数 | 描述
type | 缓存类型
expire | 缓存有效期 （默认为0 表示永久缓存）
prefix | 缓存前缀（默认为空）

使用

缓存初始化之后，就可以进行相关缓存操作了。

如果通过配置文件方式定义缓存参数的话，可以无需手动进行缓存初始化操作，可以直接进行缓存读取和设置等操作。

设置缓存

设置缓存有效期

Cache::set('name',$value,3600);

如果设置成功返回true，否则返回false。

缓存自增

针对数值类型的缓存数据，可以使用自增操作，例如：

// name自增（步进值为1）
Cache::inc('name');
// name自增（步进值为3）
Cache::inc('name',3);

缓存自减

针对数值类型的缓存数据，可以使用自减操作，例如：

// name自减（步进值为1）
Cache::dec('name');
// name自减（步进值为3）
Cache::dec('name',3);

获取缓存

获取缓存数据可以使用：

dump(Cache::get('name'));

如果name值不存在，则默认返回 false。

支持指定默认值，例如：

dump(Cache::get('name',''));

表示如果name值不存在，则返回空字符串。

删除缓存

Cache::rm('name');

获取并删除缓存

Cache::pull('name');

如果name值不存在，则返回null。

清空缓存

Cache::clear();

不存在则写入缓存数据后返回（v5.0.2+）

Cache::remember('name',function(){
    return time();
});

助手函数

系统对缓存操作提供了助手函数cache，用法如下：

$options = [
     // 缓存类型为File
    'type'   => 'File',
     // 缓存有效期为永久有效
    'expire' => 0,
     // 指定缓存目录
    'path'   => APP_PATH . 'runtime/cache/',
];

// 缓存初始化
// 不进行缓存初始化的话，默认使用配置文件中的缓存配置
cache($options);

// 设置缓存数据
cache('name', $value, 3600);
// 获取缓存数据
var_dump(cache('name'));
// 删除缓存数据
cache('name', NULL);

// 设置缓存的同时并且进行参数设置
cache('test', $value, $options);

缓存标签

支持给缓存数据打标签，例如：

Cache::tag('tag')->set('name1','value1');
Cache::tag('tag')->set('name2','value2');
// 或者批量设置缓存标签
Cache::set('name1','value1');
Cache::set('name2','value2');
Cache::tag('tag',['name1','name2']);
// 清除tag标签的缓存数据
Cache::clear('tag');

同时使用多个缓存类型

如果要同时使用多个缓存类型进行操作的话，可以做如下配置：

'cache' =>  [
    // 使用复合缓存类型
    'type'  =>  'complex',
    // 默认使用的缓存
    'default'   =>  [
        // 驱动方式
        'type'   => 'File',
        // 缓存保存目录
        'path'   => CACHE_PATH,
    ],
    // 文件缓存
    'file'   =>  [
        // 驱动方式
        'type'   => 'file',
        // 设置不同的缓存保存目录
        'path'   => RUNTIME_PATH . 'file/',
    ],
    // redis缓存
    'redis'   =>  [
        // 驱动方式
        'type'   => 'redis',
        // 服务器地址
        'host'       => '127.0.0.1',
    ],
],

cache.type配置为complex之后，就可以缓存多个缓存类型和缓存配置，每个缓存配置的方法和之前一样，并且你可以给相同类型的缓存类型（使用不同的缓存标识）配置不同的缓存配置参数。

当使用

Cache::set('name', 'value');
Cache::get('name');

的时候，其实使用的是default缓存标识的缓存配置，如果需要切换到其它的缓存标识操作，可以使用：

// 切换到file操作
Cache::store('file')->set('name','value');
Cache::get('name');
// 切换到redis操作
Cache::store('redis')->set('name','value');
Cache::get('name');


Session

概述

ThinkPHP采用think\Session类提供Session功能支持。

版本 | 新增功能
5.0.2 | 增加flash方法和flush方法

Session初始化

在ThinkPHP5.0中使用\think\Session类进行Session相关操作，Session会在第一次调用Session类的时候按照配置的参数自动初始化，例如，我们在应用配置中添加如下配置：

'session'                => [
    'prefix'         => 'think',
    'type'           => '',
    'auto_start'     => true,
],

如果我们使用上述的session配置参数的话，无需任何操作就可以直接调用Session类的相关方法，例如：

Session::set('name','thinkphp');
Session::get('name');

如果你应用下面的不同模块需要不同的session参数，那么可以在模块配置文件中重新设置：

'session'                => [
    'prefix'         => 'module',
    'type'           => '',
    'auto_start'     => true,
],

或者调用init方法进行初始化：

Session::init([
    'prefix'         => 'module',
    'type'           => '',
    'auto_start'     => true,
]);

如果你没有使用Session类进行Session操作的话，例如直接操作$_SESSION,必须使用上面的方式手动初始化或者直接调用session_start()方法进行session初始化。

设置参数

默认支持的session设置参数包括： | 参数 | 描述
type | session类型
expire | session过期时间
prefix | session前缀
auto_start | 是否自动开启
use_trans_sid | 是否使用use_trans_sid
var_session_id | 请求session_id变量名
id | session_id
name | session_name
path | session保存路径
domain | session cookie_domain
use_cookies | 是否使用cookie
cache_limiter | session_cache_limiter
cache_expire | session_cache_expire

如果做了session驱动扩展，可能有些参数不一定有效。

基础用法

赋值

// 赋值（当前作用域）
Session::set('name','thinkphp');
// 赋值think作用域
Session::set('name','thinkphp','think');

判断是否存在

// 判断（当前作用域）是否赋值
Session::has('name');
// 判断think作用域下面是否赋值
Session::has('name','think');

取值

// 取值（当前作用域）
Session::get('name');
// 取值think作用域
Session::get('name','think');

如果name的值不存在，返回null。

删除

// 删除（当前作用域）
Session::delete('name');
// 删除think作用域下面的值
Session::delete('name','think');

指定作用域

// 指定当前作用域
Session::prefix('think');

取值并删除

// 取值并删除
Session::pull('name');

如果name的值不存在，返回Null。

清空

// 清除session（当前作用域）
Session::clear();
// 清除think作用域
Session::clear('think');

下次请求之前有效（v5.0.2+）

// 设置session 并且在下一次请求之前有效
Session::flash('name','value');

提前清除当前请求有效的数据（v5.0.2+）

// 清除当前请求有效的session
Session::flush();

二级数组

支持session的二维数组操作，例如：

// 赋值（当前作用域）
Session::set('name.item','thinkphp');
// 判断（当前作用域）是否赋值
Session::has('name.item');
// 取值（当前作用域）
Session::get('name.item');
// 删除（当前作用域）
Session::delete('name.item');

助手函数

系统也提供了助手函数session完成相同的功能，例如：

// 初始化session
session([
    'prefix'     => 'module',
    'type'       => '',
    'auto_start' => true,
]);

// 赋值（当前作用域）
session('name', 'thinkphp');

// 赋值think作用域
session('name', 'thinkphp', 'think');

// 判断（当前作用域）是否赋值
session('?name');

// 取值（当前作用域）
session('name');

// 取值think作用域
session('name', '', 'think');

// 删除（当前作用域）
session('name', null);

// 清除session（当前作用域）
session(null);

// 清除think作用域
session(null, 'think');

Session驱动

支持指定 Session 驱动，配置文件如下：

'session' => [
    'prefix'     => 'module',
    'type'       => 'redis',
    'auto_start' => true,
     // redis主机
    'host'       => '127.0.0.1',
     // redis端口
    'port'       => 6379,
     // 密码
    'password'   => '',
]

表示使用redis作为session类型。


Cookie

概述

ThinkPHP采用think\Cookie类提供Cookie支持。

版本 | 新增功能
5.0.4 | 增加forever方法用于永久保存

基本操作

初始化

// cookie初始化
Cookie::init(['prefix'=>'think_','expire'=>3600,'path'=>'/']);
// 指定当前前缀
Cookie::prefix('think_');

支持的参数及默认值如下：

// cookie 名称前缀
'prefix'    => '',
// cookie 保存时间
'expire'    => 0,
// cookie 保存路径
'path'      => '/',
// cookie 有效域名
'domain'    => '',
//  cookie 启用安全传输
'secure'    => false,
// httponly设置
'httponly'  => '',
// 是否使用 setcookie
'setcookie' => true,

设置

// 设置Cookie 有效期为 3600秒
Cookie::set('name','value',3600);
// 设置cookie 前缀为think_
Cookie::set('name','value',['prefix'=>'think_','expire'=>3600]);
// 支持数组
Cookie::set('name',[1,2,3]);

判断

Cookie::has('name');
// 判断指定前缀的cookie值是否存在
Cookie::has('name','think_');

获取

Cookie::get('name');
// 获取指定前缀的cookie值
Cookie::get('name','think_');

删除

//删除cookie

Cookie::delete('name');
// 删除指定前缀的cookie
Cookie::delete('name','think_');

清空

// 清空指定前缀的cookie
Cookie::clear('think_');

助手函数

系统提供了cookie助手函数用于基本的cookie操作，例如：

// 初始化
cookie(['prefix' => 'think_', 'expire' => 3600]);

// 设置
cookie('name', 'value', 3600);

// 获取
echo cookie('name');

// 删除
cookie('name', null);

// 清除
cookie(null, 'think_');


多语言

ThinkPHP内置通过\think\Lang类提供多语言支持，如果你的应用涉及到国际化的支持，那么可以定义相关的语言包文件。任何字符串形式的输出，都可以定义语言常量。

开启和加载语言包

默认情况下，系统载入的是配置的默认语言包，并且不会自动侦测当前系统的语言。
默认语言由default_lang配置参数设置，系统默认设置为：

// 默认语言
'default_lang'           => 'zh-cn',

要启用语言自动侦测和多语言自动切换，需要开启多语言切换，在应用的公共配置文件添加：

// 开启语言切换
'lang_switch_on' => true,

开启后，系统会自动检测当前语言（主要是指浏览器访问的情况下）会对两种情况进行检测：

  • 是否有$_GET['lang']
  • 识别$_SERVER['HTTP_ACCEPT_LANGUAGE']中的第一个语言
  • 检测到任何一种情况下采用Cookie缓存
  • 如果检测到的语言在允许的语言列表内认为有效，否则使用默认设置的语言

如果不希望浏览器自动侦测语言，请关闭lang_switch_on后设置默认语言。

如果在自动侦测语言的时候，希望设置允许的语言列表，不在列表范围的语言则仍然使用默认语言，可以使用：

// 设置允许的语言
Lang::setAllowLangList(['zh-cn','en-us']);

语言变量定义

语言变量的定义，只需要在需要使用多语言的地方，写成：

Lang::get('add user error');
// 使用系统封装的助手函数
lang('add user error');

也就是说，字符串信息要改成Lang::get方法来表示。

语言定义一般采用英语来描述。

语言文件定义

系统会默认加载下面三个语言包：

框架语言包: thinkphp\lang\当前语言.php
应用语言包: application\lang\当前语言.php
模块语言包: application\模块\lang\当前语言.php

如果你还需要加载其他的语言包，可以在设置或者自动检测语言之后，用load方法进行加载：

Lang::load(APP_PATH . 'common\lang\zh-cn.php');

ThinkPHP语言文件定义采用返回数组方式：

return [
     'hello thinkphp'  => '欢迎使用ThinkPHP',
     'data type error' => '数据类型错误',
];

也可以在程序里面动态设置语言定义的值，使用下面的方式：

Lang::set('define2','语言定义');
$value = Lang::get('define2');

通常多语言的使用是在控制器里面，但是模型类的自动验证功能里面会用到提示信息，这个部分也可以使用多语言的特性。例如原来的方式是把提示信息直接写在模型里面定义：

['title','require','标题必须！',1],

如果使用了多语言功能的话（假设，我们在当前语言包里面定义了' lang_var'=>'标题必须！'），就可以这样定义模型的自动验证

['title','require','{%lang_var}',1],

如果要在模板中输出语言变量不需要在控制器中赋值，可以直接使用模板引擎特殊标签来直接输出语言定义的值：

{$Think.lang.lang_var}

可以输出当前语言包里面定义的 lang_var语言定义。

变量传入支持

语言包定义的时候支持传入变量，有两种方式

使用命名绑定方式，例如：

'file_format'    =>    '文件格式: {:format},文件大小：{:size}',

在模板中输出语言字符串的时候传入变量值即可：

{:lang('file_format',['format' => 'jpeg,png,gif,jpg','size' => '2MB'])}

第二种方式是使用格式字串，如果你需要使用第三方的翻译工具，建议使用该方式定义变量。

'file_format'    =>    '文件格式: %s,文件大小：%d',

在模板中输出多语言的方式更改为：

{:lang('file_format',['jpeg,png,gif,jpg','2MB'])}


分页

分页实现

版本 | 新增功能
5.0.1 | 支持第二个参数直接传入总记录数

ThinkPHP5.0内置了分页实现，要给数据添加分页输出功能在5.0变得非常简单，可以直接在Db类查询的时候调用paginate方法：

// 查询状态为1的用户数据 并且每页显示10条数据
$list = Db::name('user')->where('status',1)->paginate(10);
// 把分页数据赋值给模板变量list
$this->assign('list', $list);
// 渲染模板输出
return $this->fetch();

也可以改成模型的分页查询代码：

// 查询状态为1的用户数据 并且每页显示10条数据
$list = User::where('status',1)->paginate(10);
// 把分页数据赋值给模板变量list
$this->assign('list', $list);
// 渲染模板输出
return $this->fetch();

模板文件中分页输出代码如下：

<div>
<ul>
{volist name='list' id='user'}
    <li> {$user.nickname}</li>
{/volist}
</ul>
</div>
{$list->render()}

也可以单独赋值分页输出的模板变量

// 查询状态为1的用户数据 并且每页显示10条数据
$list = User::where('status',1)->paginate(10);
// 获取分页显示
$page = $list->render();
// 模板变量赋值
$this->assign('list', $list);
$this->assign('page', $page);
// 渲染模板输出
return $this->fetch();

模板文件中分页输出代码如下：

<div>
<ul>
{volist name='list' id='user'}
    <li> {$user.nickname}</li>
{/volist}
</ul>
</div>
{$page}

默认情况下，生成的分页输出是完整分页功能，带总分页数据和上下页码，分页样式只需要通过样式修改即可，完整分页默认生成的分页输出代码为：

<ul class="pagination">
<li><a href="?page=1">&laquo;</a></li>
<li><a href="?page=1">1</a></li>
<li class="active"><span>2</span></li>
<li class="disabled"><span>&raquo;</span></li>
</ul>

传入总记录数（ V5.0.1）

V5.0.1版本开始，支持传入总记录数而不会自动进行总数计算，例如：

// 查询状态为1的用户数据 并且每页显示10条数据 总记录数为1000
$list = User::where('status',1)->paginate(10,1000);
// 获取分页显示
$page = $list->render();
// 模板变量赋值
$this->assign('list', $list);
$this->assign('page', $page);
// 渲染模板输出
return $this->fetch();

简洁分页

如果你仅仅需要输出一个 仅仅只有上下页的分页输出，可以使用下面的简洁分页代码：

// 查询状态为1的用户数据 并且每页显示10条数据
$list = User::where('status',1)->paginate(10,true);
// 把分页数据赋值给模板变量list
$this->assign('list', $list);
// 渲染模板输出
return $this->fetch();

简洁分页模式的输出代码为：

<ul class="pager">
<li><a href="?page=1">&laquo;</a></li>
<li class="disabled"><span>&raquo;</span></li>
</ul>

由于简洁分页模式不需要查询总数据数，因此可以提高查询性能。

分页参数

主要的分页参数如下： | 参数 | 描述
list_rows | 每页数量
page | 当前页
path | url路径
query | url额外参数
fragment | url锚点
var_page | 分页变量
type | 分页类名

分页参数的设置方式有两种，第一种是在配置文件中定义，例如：

//分页配置
'paginate'               => [
    'type'     => 'bootstrap',
    'var_page' => 'page',
],

type属性支持命名空间，例如：

//分页配置
'paginate'               => [
    'type'     => '\org\page\bootstrap',
    'var_page' => 'page',
],

也可以在调用分页方法的时候传入，例如：

$list = Db::name('user')->where('status',1)->paginate(10,true,[
    'type'     => 'bootstrap',
    'var_page' => 'page',
]);


上传

上传文件

ThinkPHP5.0对文件上传的支持更加简单。

内置的上传只是上传到本地服务器，上传到远程或者第三方平台的话需要自己扩展。

假设表单代码如下：

<form action="/index/index/upload" enctype="multipart/form-data" method="post">
<input type="file" name="image" /> <br>
<input type="submit" value="上传" />
</form>

然后在控制器中添加如下的代码：

public function upload(){
    // 获取表单上传文件 例如上传了001.jpg
    $file = request()->file('image');
    // 移动到框架应用根目录/public/uploads/ 目录下
    $info = $file->move(ROOT_PATH . 'public' . DS . 'uploads');
    if($info){
        // 成功上传后 获取上传信息
        // 输出 jpg
        echo $info->getExtension();
        // 输出 20160820/42a79759f284b767dfcb2a0197904287.jpg
        echo $info->getSaveName();
        // 输出 42a79759f284b767dfcb2a0197904287.jpg
        echo $info->getFilename();
    }else{
        // 上传失败获取错误信息
        echo $file->getError();
    }
}

move方法成功的话返回的是一个SplFileInfo对象，你可以对上传后的文件进行后续操作。

多文件上传

如果你使用的是多文件上传表单，例如：

<form action="/index/index/upload" enctype="multipart/form-data" method="post">
<input type="file" name="image[]" /> <br>
<input type="file" name="image[]" /> <br>
<input type="file" name="image[]" /> <br>
<input type="submit" value="上传" />
</form>

控制器代码可以改成：

public function upload(){
    // 获取表单上传文件
    $files = request()->file('image');
    foreach($files as $file){
        // 移动到框架应用根目录/public/uploads/ 目录下
        $info = $file->move(ROOT_PATH . 'public' . DS . 'uploads');
        if($info){
            // 成功上传后 获取上传信息
            // 输出 jpg
            echo $info->getExtension();
            // 输出 42a79759f284b767dfcb2a0197904287.jpg
            echo $info->getFilename();
        }else{
            // 上传失败获取错误信息
            echo $file->getError();
        }
    }
}

上传验证

支持对上传文件的验证，包括文件大小、文件类型和后缀：

public function upload(){
    // 获取表单上传文件 例如上传了001.jpg
    $file = request()->file('image');
    // 移动到框架应用根目录/public/uploads/ 目录下
    $info = $file->validate(['size'=>15678,'ext'=>'jpg,png,gif'])->move(ROOT_PATH . 'public' . DS . 'uploads');
    if($info){
        // 成功上传后 获取上传信息
        // 输出 jpg
        echo $info->getExtension();
        // 输出 20160820/42a79759f284b767dfcb2a0197904287.jpg
        echo $info->getSaveName();
        // 输出 42a79759f284b767dfcb2a0197904287.jpg
        echo $info->getFilename();
    }else{
        // 上传失败获取错误信息
        echo $file->getError();
    }
}

如果上传文件验证不通过，则move方法返回false。

验证参数 | 说明
size | 上传文件的最大字节
ext | 文件后缀，多个用逗号分割或者数组
type | 文件MIME类型，多个用逗号分割或者数组

还有一个额外的自动验证规则是，如果上传的文件后缀是图像文件后缀，则会检查该文件是否是一个合法的图像文件。

上传规则

默认情况下，会在上传目录下面生成以当前日期为子目录，以微秒时间的md5编码为文件名的文件，例如上面生成的文件名可能是：

/home/www/upload/20160510/42a79759f284b767dfcb2a0197904287.jpg

我们可以指定上传文件的命名规则，使用rule方法即可，例如：

// 获取表单上传文件 例如上传了001.jpg
$file = request()->file('image');
// 移动到服务器的上传目录 并且使用md5规则
$file->rule('md5')->move('/home/www/upload/');

最终生成的文件名类似于：

/home/www/upload/72/ef580909368d824e899f77c7c98388.jpg
系统默认提供了几种上传命名规则，包括： | 规则 | 描述
date | 根据日期和微秒数生成
md5 | 对文件使用md5_file散列生成
sha1 | 对文件使用sha1_file散列生成

其中md5和sha1规则会自动以散列值的前两个字符作为子目录，后面的散列值作为文件名。

如果需要使用自定义命名规则，可以在rule方法中传入函数或者方法，例如：

// 获取表单上传文件 例如上传了001.jpg
$file = request()->file('image');
// 移动到服务器的上传目录 并且使用uniqid规则
$file->rule('uniqid')->move('/home/www/upload/');

生成的文件名类似于：

/home/www/upload/573d3b6d7abe2.jpg

如果你希望保留原文件名称，可以使用：

// 获取表单上传文件 例如上传了001.jpg
$file = request()->file('image');
// 移动到服务器的上传目录 并且使用原文件名
$file->move('/home/www/upload/','');

默认情况下，会覆盖服务器上传目录下的同名文件，如果不希望覆盖，可以使用：

// 获取表单上传文件 例如上传了001.jpg
$file = request()->file('image');
// 移动到服务器的上传目录 并且设置不覆盖
$file->move('/home/www/upload/',true,false);

获取文件hash散列值

可以获取上传文件的哈希散列值，例如：

// 获取表单上传文件
$file = request()->file('image');
// 移动到服务器的上传目录 并且使用原文件名
$upload = $file->move('/home/www/upload/');
// 获取上传文件的hash散列值
echo $upload->md5();
echo $upload->sha1();

v5.0.1以上版本可以统一使用hash方法获取文件散列值

// 获取表单上传文件
$file = request()->file('image');
// 移动到服务器的上传目录 并且使用原文件名
$upload = $file->move('/home/www/upload/');
// 获取上传文件的hash散列值
echo $upload->hash('sha1');
echo $upload->hash('md5');

返回对象

上传成功后返回的仍然是一个File对象，除了File对象自身的方法外，并且可以使用SplFileObject的属性和方法，便于进行后续的文件处理。


图像处理

安装扩展

使用Composer安装ThinkPHP5的图像处理类库：

composer require topthink/think-image

图像操作

下面来看下图像操作类的基础方法。

打开图像文件

假设当前入口文件目录下面有一个image.png文件，如图所示：

使用open方法打开图像文件进行相关操作：

$image = \think\Image::open('./image.png');

也可以从直接获取当前请求中的文件上传对象，例如：

$image = \think\Image::open(request()->file('image'));

获取图像信息

可以获取打开图片的信息，包括图像大小、类型等，例如：

$image = \think\Image::open('./image.png');
// 返回图片的宽度
$width = $image->width();
// 返回图片的高度
$height = $image->height();
// 返回图片的类型
$type = $image->type();
// 返回图片的mime类型
$mime = $image->mime();
// 返回图片的尺寸数组 0 图片宽度 1 图片高度
$size = $image->size();

裁剪图片

使用crop和save方法完成裁剪图片功能。

$image = \think\Image::open('./image.png');
//将图片裁剪为300x300并保存为crop.png
$image->crop(300, 300)->save('./crop.png');

生成的图片如图：

支持从某个坐标开始裁剪，例如下面从（100，30）开始裁剪，例如：

$image = \think\Image::open('./image.png');
//将图片裁剪为300x300并保存为crop.png
$image->crop(300, 300,100,30)->save('./crop.png');

生成的图片如图：

生成缩略图

使用thumb方法生成缩略图，例如：

$image = \think\Image::open('./image.png');
// 按照原图的比例生成一个最大为150*150的缩略图并保存为thumb.png
$image->thumb(150, 150)->save('./thumb.png');

生成的缩略图如图所示：

我们看到实际生成的缩略图并不是150*150，因为默认采用原图等比例缩放的方式生成缩略图，最大宽度是150。
可以支持其他类型的缩略图生成，设置包括\think\Image的下列常量或者对应的数字：

//常量，标识缩略图等比例缩放类型
const THUMB_SCALING   = 1;
//常量，标识缩略图缩放后填充类型
const THUMB_FILLED    = 2;
//常量，标识缩略图居中裁剪类型
const THUMB_CENTER    = 3;
//常量，标识缩略图左上角裁剪类型
const THUMB_NORTHWEST = 4;
//常量，标识缩略图右下角裁剪类型
const THUMB_SOUTHEAST = 5;
//常量，标识缩略图固定尺寸缩放类型
const THUMB_FIXED     = 6;

比如我们居中裁剪：

$image = \think\Image::open('./image.png');
// 按照原图的比例生成一个最大为150*150的缩略图并保存为thumb.png
$image->thumb(150,150,\think\Image::THUMB_CENTER)->save('./thumb.png');

后生成的缩略图效果如图：

再比如我们右下角剪裁

$image = \think\Image::open('./image.png');
// 按照原图的比例生成一个最大为150*150的缩略图并保存为thumb.png
$image->thumb(150,150,\think\Image::THUMB_SOUTHEAST)->save('./thumb.png');

生成的缩略图效果如图：

这里就不再对其他用法一一举例了。

图像翻转

使用flip可以对图像进行翻转操作，默认是以x轴进行翻转，例如：

$image = \think\Image::open('./image.png');
// 对图像进行以x轴进行翻转操作
$image->flip()->save('./filp_image.png');

生成的效果如图：

我们也可以改变参数，以y轴进行翻转，例如：

$image = \think\Image::open('./image.png');
// 对图像进行以y轴进行翻转操作
$image->flip(\think\image::FLIP_Y)->save('./filp_image.png');

生成的效果如图：

图像的翻转可以理解为图像的镜面效果与图像旋转有所不同。

图像旋转

使用rotate可以对图像进行旋转操作（默认是顺时针旋转90度），我们用默认90度进行旋转举例：

$image = \think\Image::open('./image.png');
// 对图像使用默认的顺时针旋转90度操作
$image->rotate()->save('./rotate_image.png');

生成的效果如图：

图像保存参数

save方法可以配置的参数 | 参数 | 默认 | 描述
pathname | 必填项 | 图像保存路径名称
type | 默认与原图相同 | 图像类型
quality | 80 | 图像质量
interlace | true | 是否对JPEG类型图像设置隔行扫描

设置隔行扫描的情况下在网页进行浏览时。是从上到下一行一行的显示，否则图片整个显示出来 然后由模糊到清晰显示。

添加水印

系统支持添加图片及文字水印，下面依次举例说明
添加图片水印，我们下载官网logo文件到根目录进行举例：

$image = \think\Image::open('./image.png');
// 给原图左上角添加水印并保存water_image.png
$image->water('./logo.png')->save('water_image.png');

water方法的第二个参数表示水印的位置，默认值是WATER_SOUTH，可以传入下列\think\Image类的常量或者对应的数字：

//常量，标识左上角水印
const WATER_NORTHWEST = 1;
//常量，标识上居中水印
const WATER_NORTH     = 2;
//常量，标识右上角水印
const WATER_NORTHEAST = 3;
//常量，标识左居中水印
const WATER_WEST      = 4;
//常量，标识居中水印
const WATER_CENTER    = 5;
//常量，标识右居中水印
const WATER_EAST      = 6;
//常量，标识左下角水印
const WATER_SOUTHWEST = 7;
//常量，标识下居中水印
const WATER_SOUTH     = 8;
//常量，标识右下角水印
const WATER_SOUTHEAST = 9;

我们用左上角来进行测试：

$image = \think\Image::open('./image.png');
// 给原图左上角添加水印并保存water_image.png
$image->water('./logo.png',\think\Image::WATER_NORTHWEST)->save('water_image.png');

生成的图片效果如下：

还可以支持水印图片的透明度（0~100，默认值是100），例如：

$image = \think\Image::open('./image.png');
// 给原图左上角添加透明度为50的水印并保存alpha_image.png
$image->water('./logo.png',\think\Image::WATER_NORTHWEST,50)->save('alpha_image.png');

生成的图片效果如下：

也可以支持给图片添加文字水印（我们复制一个字体文件HYQingKongTiJ.ttf到入口目录），我们现在生成一个像素20px，颜色为#ffffff的水印效果：

$image = \think\Image::open('./image.png');
// 给原图左上角添加水印并保存water_image.png
$image->text('十年磨一剑 - 为API开发设计的高性能框架','HYQingKongTiJ.ttf',20,'#ffffff')->save('text_image.png');

生成的图片效果：

文字水印参数

文字水印比较多，在此只做说明不做演示了

参数 | 默认 | 描述
text | 不能为空 | 添加的文字
font | 不能为空 | 字体文件路径
size | 不能为空 | 字号，单位是像素
color | #00000000 | 文字颜色
locate | WATER_SOUTHEAST | 文字写入位置
offset | 0 | 文字相对当前位置的偏移量
angle | 0 | 文字倾斜角度


文件处理

ThinkPHP5.0内置了一个文件处理类 \think\File，内置的文件上传操作也是调用了该类进行处理的。

File类继承了PHP的SplFileObject类，因此可以调用SplFileObject类所有的属性和方法。


验证码

首先使用Composer安装think-captcha扩展包：

composer require topthink/think-captcha

验证码配置

然后在应用配置文件中添加验证码的配置参数

'captcha'  => [
        // 验证码字符集合
        'codeSet'  => '2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY',
        // 验证码字体大小(px)
        'fontSize' => 25,
        // 是否画混淆曲线
        'useCurve' => true,
         // 验证码图片高度
        'imageH'   => 30,
        // 验证码图片宽度
        'imageW'   => 100,
        // 验证码位数
        'length'   => 5,
        // 验证成功后是否重置
        'reset'    => true
],

并且确保开启了URL路由。

验证码显示

<div>{:captcha_img()}</div>

或者

<div><img src="{:captcha_src()}" alt="captcha" /></div>

上面两种的最终效果是一样的，根据需要调用即可。

控制器验证

使用TP5的内置验证功能，添加captcha验证规则即可

$this->validate($data,[
    'captcha|验证码'=>'require|captcha'
]);

或者手动验证

if(!captcha_check($captcha)){
 //验证失败
};


单元测试

单元测试

首先安装ThinkPHP5的单元测试扩展，进入命令行，切换到tp5的应用根目录下面，执行：

composer require topthink/think-testing

由于单元测试扩展的依赖较多，因此安装过程会比较久，请耐心等待。

安装完成后，会在应用根目录下面增加tests目录和phpunit.xml文件。
默认带了一个tests/ExampleTest.php单元测试文件，我们可以直接在命令行下面运行单元测试：

php think unit

请始终使用以上命令进行单元测试，而不是直接用phpunit来运行单元测试。

添加单元测试文件

我们来添加一个新的单元测试文件，单元测试文件为tests/IndexTest.php，内容如下：

<?php
use tests\TestCase;

class IndexTest extends TestCase
{

    public function testSomethingIsTrue()
    {
        $this->assertTrue(true);
    }

}

注意，单元测试文件中定义的测试类如果不存在冲突，可以不需要使用命名空间。


函数

你可以方便的在ThinkPHP5中添加自定义函数，包括替换已有的助手函数。

扩展系统函数

这里指的系统函数是对系统自带的函数进行替换或者增加，具体方式如下：

在应用目录下面增加一个助手函数文件（文件名随意，例如application/helper.php），添加：

// 增加一个新的table助手函数
function table($table, $config = [])
{
    return \think\Db::connect($config)->setTable($table);
}

// 替换已有的db助手函数
function db($name, $config= [])
{
    return \think\Db::connect($config)->name($name);
}

然后，在应用配置文件中设置：

// 扩展函数文件
'extra_file_list'        => [ APP_PATH . 'helper.php', THINK_PATH . 'helper.php'],

extra_file_list 定义的顺序不能反，否则就不能替换已有的助手函数。

注意，尽量避免直接修改核心的helper.php文件。

添加应用函数

如果需要给当前应用添加函数，只需要在应用的公共文件（application/common.php）中定义需要的函数即可，系统会自动加载，如果你需要增加新的函数文件，例如需要增加一个sys.php，那么就需要和上面一样设置extra_file_list配置：

// 扩展函数文件
'extra_file_list'        => [ APP_PATH . 'helper.php', THINK_PATH . 'helper.php', APP_PATH . 'sys.php'],


类库

如果你需要在核心之外扩展和使用第三方类库，并且该类库不是通过Composer安装使用，那么可以直接放入应用根目录下面的extend目录下面，该目录是官方建议的第三方扩展类库目录。

类的命名规范遵循PSR-2及PSR-4规范，例如，如果有一个扩展类库的命名空间是 first.second.Foo，类定义如下：

namespace first\second;

class Foo
{
}

使用第三方类库的时候注意不要和系统的命名空间产生冲突，例如核心的think、app以及Composer类库自身定义的命名空间。

那么实际的类文件位置应该是：

extend/first/second/Foo.php

使用first.second.Foo类的时候，直接实例化即可使用，例如：

$foo = new \first\second\Foo();

或者先

use first\second\Foo;

然后

$foo = new Foo();

你可以在入口文件中随意修改extend目录的名称，例如：

define('EXTEND_PATH', '../extension/');

ThinkPHP5建议所有的扩展类库都使用命名空间定义，如果你的类库没有使用命名空间，则不支持自动加载，必须使用Loader::import方法先导入文件后才能使用。

Loader::import('first.second.Foo');
$foo = new \Foo();

强烈建议使用Composer安装和更新扩展类库，ThinkPHP5.0的扩展类库都采用Composer方式进行安装。

手册网


行为

概述

版本 | 新增功能
5.0.4 | 行为类的方法采用驼峰法命名
行为绑定支持绑定到类的方法

行为（Behavior）是ThinkPHP扩展机制中比较关键的一项扩展，行为既可以独立调用，也可以绑定到某个标签中进行侦听，在官方提出的CBD模式中行为也占了主要的地位，可见行为在ThinkPHP框架中意义非凡。

这里指的行为是一个比较抽象的概念，你可以把行为想象成在应用执行过程中的一个动作或者处理。在框架的执行流程中，例如路由检测是一个行为，静态缓存是一个行为，用户权限检测也是行为，大到业务逻辑，小到浏览器检测、多语言检测等等都可以当做是一个行为，甚至说你希望给你的网站用户的第一次访问弹出Hello，world！这些都可以看成是一种行为，行为的存在让你无需改动框架和应用，而在外围通过扩展或者配置来改变或者增加一些功能。

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

行为标签位

在定义行为之前，我们先来了解下系统有哪些标签位，系统核心提供的标签位置包括下面几个（按照执行顺序排列）： | 标签位 | 描述
app_init | 应用初始化标签位
app_begin | 应用开始标签位
module_init | 模块初始化标签位
action_begin | 控制器开始标签位
view_filter | 视图输出过滤标签位
app_end | 应用结束标签位
log_write | 日志write方法标签位
response_end | 输出结束标签位（V5.0.1+）

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

除了这些系统内置标签之外，开发人员还可以在应用中添加自己的应用标签。

添加行为标签位

可以使用\think\Hook类的listen方法添加自己的行为侦听位置，例如：

Hook::listen('action_init');

可以给侦听方法传入参数（仅能传入一个参数），该参数使用引用传值，因此必须使用变量，例如：

Hook::listen('action_init',$params);

侦听的标签位置可以随意放置。

行为定义

行为类的定义很简单，定义行为的执行入口方法run即可，例如：

namespace app\index\behavior;

class Test
{
    public function run(&$params)
    {
        // 行为逻辑
    }
}

行为类并不需要继承任何类，相对比较灵活。

如果行为类需要绑定到多个标签，可以采用如下定义：

namespace app\index\behavior;

class Test
{
    public function app_init(&$params)
    {

    }

    public function app_end(&$params)
    {

    }
}

绑定到app_init和app_end后 就会调用相关的方法。

V5.0.4+版本以上，行为类的方法需要采用驼峰法命名，上面的行为类需要改成：

namespace app\index\behavior;

class Test
{
    public function appInit(&$params)
    {

    }

    public function appEnd(&$params)
    {

    }
}

行为绑定

行为定义完成后，就需要绑定到某个标签位置才能生效，否则是不会执行的。
使用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则当前标签位的后续行为将不会执行，但应用将继续运行。

我们也可以直接在APP_PATH目录下面或者模块的目录下面定义tags.php文件来统一定义行为，定义格式如下：

return [
    'app_init'=> [
        'app\\index\\behavior\\CheckAuth',
        'app\\index\\behavior\\CheckLang'
    ],
    'app_end'=> [
        'app\\admin\\behavior\\CronRun'
    ]
]

如果APP_PATH目录下面和模块目录下面的tags.php都定义了app_init的行为绑定的话，会采用合并模式，如果希望覆盖，那么可以在模块目录下面的tags.php中定义如下：

return [
    'app_init'=> [
        'app\\index\\behavior\\CheckAuth',
        '_overlay'=>true
    ],
    'app_end'=> [
        'app\\admin\\behavior\\CronRun'
    ]
]

如果某个行为标签定义了'_overlay' =>true 就表示覆盖之前的相同标签下面的行为定义。

闭包支持

可以不用定义行为直接把闭包函数绑定到某个标签位，例如：

Hook::add('app_init',function(){
    echo 'Hello,world!';
});

如果标签位有传入参数的话，闭包也可以支持传入参数，例如：

Hook::listen('action_init',$params);
Hook::add('action_init',function($params){
    var_dump($params);
});

直接执行行为

如果需要，你也可以不绑定行为标签，直接调用某个行为，使用：

// 执行 app\index\behavior\CheckAuth行为类的run方法 并引用传入params参数
$result = Hook::exec('app\\index\\behavior\\CheckAuth','run',$params);


驱动

系统的驱动类都支持单独扩展，并且驱动文件的位置和命名空间可以随意设置，包括缓存、日志、调试和数据库驱动。

以缓存驱动为例，如果我们扩展了一个自己的redis驱动，类名为
app\driver\cache\Redis，那么我们只需要设置缓存类型为：

'cache'                  => [
    // 驱动方式
    'type'   => '\app\driver\cache\Redis',
    // 缓存前缀
    'prefix' => '',
    // 缓存有效期 0表示永久缓存
    'expire' => 0,
],


Time

时间戳操作

首先通过 composer 安装

composer require topthink/think-helper

在文件头部引入

use think\helper\Time;

比如需要获得今天的零点时间戳和23点59分59秒的时间戳

list($start, $end) = Time::today();

echo $start; // 零点时间戳
echo $end; // 23点59分59秒的时间戳

完整示例如下:

// 今日开始和结束的时间戳
Time::today();

// 昨日开始和结束的时间戳
Time::yesterday();

// 本周开始和结束的时间戳
Time::week();

// 上周开始和结束的时间戳
Time::lastWeek();

// 本月开始和结束的时间戳
Time::month();

// 上月开始和结束的时间戳
Time::lastMonth();

// 今年开始和结束的时间戳
Time::year();

// 去年开始和结束的时间戳
Time::lastYear();

// 获取7天前零点到现在的时间戳
Time::dayToNow(7)

// 获取7天前零点到昨日结束的时间戳
Time::dayToNow(7, true)

// 获取7天前的时间戳
Time::daysAgo(7)

//  获取7天后的时间戳
Time::daysAfter(7)

// 天数转换成秒数
Time::daysToSecond(5)

// 周数转换成秒数
Time::weekToSecond(5)


数据库迁移工具

数据库迁移工具

首先通过 composer 安装

composer require topthink/think-migration

注意事项，不支持修改文件配置目录

在命令行下运行查看帮助，可以看到新增的命令

php think
 migrate
  migrate:create     Create a new migration
  migrate:rollback   Rollback the last or to a specific migration
  migrate:run        Migrate the database
  migrate:status     Show migration status
 optimize
  optimize:autoload  Optimizes PSR0 and PSR4 packages to be loaded wit
h classmaps too, good for production.
  optimize:config    Build config and common file cache.
  optimize:route     Build route cache.
  optimize:schema    Build database schema cache.
 seed
  seed:create        Create a new database seeder
  seed:run           Run database seeders

创建迁移类，首字母必须为大写

php think migrate:create Users

可以看到目录下有新文件 .\database\migrations\20161117144043_users.php

使用实例

<?php

use Phinx\Migration\AbstractMigration;

class Users extends AbstractMigration
{
    /**
     * Change Method.
     */
    public function change()
    {
        // create the table
        $table = $this->table('users',array('engine'=>'MyISAM'));
        $table->addColumn('username', 'string',array('limit' => 15,'default'=>'','comment'=>'用户名，登陆使用'))
            ->addColumn('password', 'string',array('limit' => 32,'default'=>md5('123456'),'comment'=>'用户密码'))
            ->addColumn('login_status', 'boolean',array('limit' => 1,'default'=>0,'comment'=>'登陆状态'))
            ->addColumn('login_code', 'string',array('limit' => 32,'default'=>0,'comment'=>'排他性登陆标识'))
            ->addColumn('last_login_ip', 'integer',array('limit' => 11,'default'=>0,'comment'=>'最后登录IP'))
            ->addColumn('last_login_time', 'datetime',array('default'=>0,'comment'=>'最后登录时间'))
            ->addColumn('is_delete', 'boolean',array('limit' => 1,'default'=>0,'comment'=>'删除状态，1已删除'))
            ->addIndex(array('username'), array('unique' => true))
            ->create();
    }

    /**
     * Migrate Up.
     */
    public function up()
    {

    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}

更具体的使用可查看

http://docs.phinx.org/en/latest/


Workerman

Workerman

Workerman是一款纯PHP开发的开源高性能的PHP socket 服务器框架。被广泛的用于手机app、手游服务端、网络游戏服务器、聊天室服务器、硬件通讯服务器、智能家居、车联网、物联网等领域的开发。 支持TCP长连接，支持Websocket、HTTP等协议，支持自定义协议。基于workerman开发者可以更专注于业务逻辑开发，不必再为PHP Socket底层开发而烦恼。

首先通过 composer 安装

composer require topthink/think-worker

如果需要在window下做服务端，还需要

composer require workerman/workerman-for-win

服务端使用示例如下:

新增启动服务文件server.php，在项目根目录

#!/usr/bin/env php
<?php
define('APP_PATH', __DIR__ . '/application/');
define('BIND_MODULE','push/Worker');
// 加载框架引导文件
require __DIR__ . '/thinkphp/start.php';

新增服务处理类，push.app是我本地测试域名

<?php

namespace app\push\controller;

use think\worker\Server;

class Worker extends Server
{
    protected $socket = 'websocket://push.app:2346';

    /**
     * 收到信息
     * @param $connection
     * @param $data
     */
    public function onMessage($connection, $data)
    {
        $connection->send('我收到你的信息了');
    }

    /**
     * 当连接建立时触发的回调函数
     * @param $connection
     */
    public function onConnect($connection)
    {

    }

    /**
     * 当连接断开时触发的回调函数
     * @param $connection
     */
    public function onClose($connection)
    {

    }

    /**
     * 当客户端的连接上发生错误时触发
     * @param $connection
     * @param $code
     * @param $msg
     */
    public function onError($connection, $code, $msg)
    {
        echo "error $code $msg\n";
    }

    /**
     * 每个进程启动
     * @param $worker
     */
    public function onWorkerStart($worker)
    {

    }
}

在命令行下运行，启动监听服务

php server.php

打开chrome浏览器，先打开push.app域名下的网页（js跨域不能通讯），按F12打开调试控制台，在Console一栏输入(或者把下面代码放入到html页面用js运行)

ws = new WebSocket("ws://push.app:2346");
ws.onopen = function() {
    alert("连接成功");
    ws.send('tom');
    alert("给服务端发送一个字符串：tom");
};
ws.onmessage = function(e) {
    alert("收到服务端的消息：" + e.data);
};

继续测试

ws.send('保持连接，发第二次信息，查看服务器回应');


MongoDb

首先，确保你已经安装了MongoDB driver for PHP（重要），参考：

http://pecl.php.net/package/mongodb

然后使用Composer安装ThinkPHP5.0的MongoDb驱动：

composer require topthink/think-mongo

修改你的数据库配置文件database.php中的type参数为：

'type'  =>  '\think\mongo\Connection',

接下来可以使用Db类直接操作MongoDb了，例如：

Db::name('demo')
    ->find();
Db::name('demo')
    ->field('id,name')
    ->limit(10)
    ->order('id','desc')
    ->select();

或者使用模型操作：

User::get(1);
User::all('1,2,3');


SAE

SAE介绍

Sina App Engine（简称SAE）是新浪研发中心开发的国内首个公有云计算平台，是新浪云计算战略的核心组成部分，作为一个简单高效的分布式Web服务开发、运行平台越来越受开发者青睐。

SAE环境和普通环境有所不同，它是一个分布式服务器集群，能让你的程序同时运行在多台服务器中。并提供了很多高效的分布式服务。SAE为了提升性能和安全，禁止了本地IO写操作，使用MemcacheX、Storage等存储型服务代替传统IO操作，效率比传统IO读写操作高，有效解决因IO瓶颈导致程序性能低下的问题。

正是因为SAE和普通环境的不同，使得普通程序不能直接放在SAE上，需要经过移植才能放在SAE上运行。也使得很多能在SAE上运行的程序不能在普通环境下运行。

thinkphp5.0支持了SAE的扩展，让开发人员感受不到SAE和普通环境的差别。甚至可以不学习任何SAE知识，只要会ThinkPHP开发，就能将你的程序运行在SAE上。

安装SAE扩展

composer require topthink/think-sae

相关配置

数据库配置

数据库配置文件database.php中修改为：

// 数据库类型
'type'        => 'mysql',
// 服务器地址
'hostname'    => SAE_MYSQL_HOST_M . ',' . SAE_MYSQL_HOST_S,
// 数据库名
'database'    => SAE_MYSQL_DB,
// 用户名
'username'    => SAE_MYSQL_USER,
// 密码
'password'    => SAE_MYSQL_PASS,
// 端口
'hostport'    => SAE_MYSQL_PORT,

驱动配置

'log'       =>  [
    'type'  => '\think\sae\Log',
]

'template'  =>  [
    'type'          =>  'Think',
    'compile_type'  =>  '\think\sae\Template',

]
'cache'     =>  [
    'type'  =>  '\think\sae\Cache',
]


标签扩展

标签库加载

模板中加载标签库，预加载自定义标签库，扩展内置标签库的加载 请参考:模板/标签库

建议开发者将自定义标签库请放置应用目录中，请勿放在框架系统目录内，以免使用Composer更新框架时导致自定义标签库的丢失

下面以标签库放在common作为一个示例：

<?php
namespace app\common\taglib;
use think\template\TagLib;
class Demo extends TagLib{
    /**
     * 定义标签列表
     */
    protected $tags   =  [
        // 标签定义： attr 属性列表 close 是否闭合（0 或者1 默认1） alias 标签别名 level 嵌套层次
        'close'     => ['attr' => 'time,format', 'close' => 0], //闭合标签，默认为不闭合
        'open'      => ['attr' => 'name,type', 'close' => 1],

    ];

    /**
     * 这是一个闭合标签的简单演示
     */
    public function tagClose($tag)
    {
        $format = empty($tag['format']) ? 'Y-m-d H:i:s' : $tag['format'];
        $time = empty($tag['time']) ? time() : $tag['time'];
        $parse = '<?php ';
        $parse .= 'echo date("' . $format . '",' . $time . ');';
        $parse .= ' ?>';
        return $parse;
    }

    /**
     * 这是一个非闭合标签的简单演示
     */
    public function tagOpen($tag, $content)
    {
        $type = empty($tag['type']) ? 0 : 1; // 这个type目的是为了区分类型，一般来源是数据库
        $name = $tag['name']; // name是必填项，这里不做判断了
        $parse = '<?php ';
        $parse .= '$test_arr=[[1,3,5,7,9],[2,4,6,8,10]];'; // 这里是模拟数据
        $parse .= '$__LIST__ = $test_arr[' . $type . '];';
        $parse .= ' ?>';
        $parse .= '{volist name="__LIST__" id="' . $name . '"}';
        $parse .= $content;
        $parse .= '{/volist}';
        return $parse;
    }

}

这时候我们的控制器继承Controller,在配置参数中配置：

'template'               => [
            // 模板引擎类型 支持 php think 支持扩展
            'type'         => 'Think',
            // 模板路径
            'view_path'    => '',
            // 模板后缀
            'view_suffix'  => '.html',
            // 预先加载的标签库
            'taglib_pre_load'     =>    'app\common\taglib\Demo',

        ],

我们就可以在控制器中对模版赋值：

//给模版给以一个当前时间戳的值
$this->assign('demo_time',$this->request->time());

在模版中调用我们已经预先加载的标签：

<h1>闭合标签</h1>
{demo:close time='$demo_time'/}
<hr>
<h1>非闭合标签</h1>
{demo:open name='demo_name'}
    {$key}=>{$demo_name}<br>
{/demo:open}
<br>
{demo:open name='demo_name' type='1'}
    {$key}=>{$demo_name}<br>
{/demo:open}

关于标签库开发

暂时可以参考3.2的官方手册中关于标签库扩展的部分，建议自己分析内置标签库Cx。


自动生成目录结构

ThinkPHP5.0 具备自动创建功能，可以用来自动生成需要的模块及目录结构和文件等，自动生成主要调用\think\Build类库。

生成规则定义

首先需要定义一个用于自动生成的规则定义文件，通常命名为build.php。

默认的框架的根目录下面自带了一个build.php示例参考文件，内容如下：

return [
    // 生成运行时目录
    '__file__' => ['common.php'],

    // 定义index模块的自动生成
    'index'    => [
        '__file__'   => ['common.php'],
        '__dir__'    => ['behavior', 'controller', 'model', 'view'],
        'controller' => ['Index', 'Test', 'UserType'],
        'model'      => [],
        'view'       => ['index/index'],
    ],
    // 。。。 其他更多的模块定义
];

可以给每个模块定义需要自动生成的文件和目录，以及MVC类。

  • __dir__ 表示生成目录（支持多级目录）
  • __file__ 表示生成文件（不定义默认会生成 config.php 文件）
  • controller 表示生成controller类
  • model表示生成model类
  • view表示生成html文件（支持子目录）

自动生成以APP_PATH为起始目录，__dir__ 和 __file__ 表示需要自动创建目录和文件，其他的则表示为模块自动生成。

模块的自动生成则以 APP_PATH.'模块名/' 为起始目录。

并且会自动生成模块的默认的Index访问控制器文件用于显示框架的欢迎页面。

我们还可以在APP_PATH目录下面自动生成其它的文件和目录，或者增加多个模块的自动生成，例如：

return [
    '__file__'  => ['hello.php','test.php'],
    // 定义index模块的自动生成
    'index'   => [
        '__file__'   => ['tags.php', 'user.php', 'hello.php'],
        '__dir__'    => ['behavior', 'controller', 'model', 'view'],
        'controller' => ['Index', 'Test', 'UserType'],
        'model'      => [],
        'view'       => ['index/index'],
    ],
    // 定义test模块的自动生成
    'test'=>[
        '__dir__'   =>  ['behavior','controller','model','widget'],
        'controller'=>  ['Index','Test','UserType'],
        'model'     =>   ['User','UserType'],
        'view'      =>  ['index/index','index/test'],
    ],
 ];

命令行自动生成

我们通过控制台来完成自动生成，切换到命令行，在应用的根目录输入下面命令：

>php think build

如果看到输出

Successed

则表示自动生成成功。

默认会读取应用目录application下面的build.php 作为自动生成的定义文件，如果你的定义文件位置不同，则需要使用--config参数指定如下：

>php think build --config build.php

表示读取根目录下的build.php文件。

生成模块指令

>php think build --module test

表示自动生成test模块。

添加自动生成代码

如果你不习惯命令行操作，也可以直接调用\think\Build类的方法进行自动生成，例如：

// 定义应用目录
define('APP_PATH', __DIR__ . '/../application/');
// 加载框架引导文件
require __DIR__ . '/../thinkphp/start.php';
// 读取自动生成定义文件
$build = include 'build.php';
// 运行自动生成
\think\Build::run($build);

run方法第二个参数用于指定要生成的应用类库的命名空间，默认是app，第三个参数是设置是否需要使用类后缀。
例如：

// 定义应用目录
define('APP_PATH', __DIR__ . '/../application/');
// 加载框架引导文件
require __DIR__ . '/../thinkphp/start.php';
// 读取自动生成定义文件
$build = include 'build.php';
// 运行自动生成
\think\Build::run($build,'application',true);

可以不依赖自动生成文件，直接使用默认目录生成模块，例如：

// 定义应用目录
define('APP_PATH', __DIR__ . '/../application/');
// 加载框架引导文件
require __DIR__ . '/../thinkphp/start.php';
// 自动生成admin模块
\think\Build::module('admin');

module方法第二个参数和第三个参数的用法和run方法一样。


创建类库文件

快速生成控制器类

执行下面的指令可以生成index模块的Blog控制器类库文件

>php think make:controller index/Blog

生成的控制器类文件如下：

<?php

namespace app\index\controller;

use think\Controller;
use think\Request;

class Blog extends Controller
{
    /**
     * 显示资源列表
     *
     * @return \think\Response
     */
    public function index()
    {
        //
    }

    /**
     * 显示创建资源表单页.
     *
     * @return \think\Response
     */
    public function create()
    {
        //
    }

    /**
     * 保存新建的资源
     *
     * @param  \think\Request  $request
     * @return \think\Response
     */
    public function save(Request $request)
    {
        //
    }

    /**
     * 显示指定的资源
     *
     * @param  int  $id
     * @return \think\Response
     */
    public function read($id)
    {
        //
    }

    /**
     * 显示编辑资源表单页.
     *
     * @param  int  $id
     * @return \think\Response
     */
    public function edit($id)
    {
        //
    }

    /**
     * 保存更新的资源
     *
     * @param  \think\Request  $request
     * @param  int  $id
     * @return \think\Response
     */
    public function update(Request $request, $id)
    {
        //
    }

    /**
     * 删除指定资源
     *
     * @param  int  $id
     * @return \think\Response
     */
    public function delete($id)
    {
        //
    }
}

默认生成的控制器类继承\think\Controller ，并且生成了资源操作方法，如果仅仅生成空的控制器则可以使用：

>php think make:controller index\Blog --plain

快速生成模型类

执行下面的指令可以生成index模块的Blog模型类库文件

>php think make:model index/Blog

生成的模型类文件如下：

namespace app\index\model;

use think\Model;

class Blog extends Model
{

}


生成类库映射文件

生成类库映射文件optimize:autoload

可以使用下面的指令生成类库映射文件，提高系统自动加载的性能。

>php think optimize:autoload

指令执行成功后，会在rumtime目录下面生成classmap.php文件，生成的类库映射文件会扫描系统目录和应用目录的类库。


生成路由缓存

生成路由缓存optimize:route

如果你的应用定义了比较多的路由规则，可以使用下面的指令生成路由缓存文件，提高系统的路由检测的性能。

>php think optimize:route

指令执行成功后，会在rumtime目录下面生成route.php文件，生成的路由缓存文件仅仅支持在应用的路由配置文件中定义的路由（包括方法定义和配置定义）。


清除缓存文件

清除缓存文件clear

如果需要清除应用的缓存文件，可以使用下面的命令：

php think clear

不带任何参数调用clear命令的话，会清除runtime目录（包括模板缓存、日志文件及其子目录）下面的所有的文件，但会保留目录。

如果需要清除某个指定目录下面的文件，可以使用：

php think clear --path d:\www\tp5\runtime\log\


生成配置缓存文件

生成配置缓存optimize:config

可以为应用或者模块生成配置缓存文件

php think optimize:config

默认生成应用的配置缓存文件，调用后会在runtime目录下面生成init.php文件，生成配置缓存文件后，应用目录下面的config.php common.php以及tags.php不会被加载，被runtime/init.php取代。

如果需要生成某个模块的配置缓存，可以使用：

php think optimize:config --module index

调用后会在runtime/index目录下面生成init.php文件，生成后，index模块目录下面的config.php common.php以及tags.php不会被加载，被runtime/index/init.php取代。


生成数据表字段缓存

生成数据表字段缓存optimize:schema

版本要求V5.0.1

可以通过生成数据表字段信息缓存，提升数据库查询的性能，避免多余的查询。命令如下：

php think optimize:schema

会自动生成当前数据库配置文件中定义的数据表字段缓存，也可以指定数据库生成字段缓存（必须有用户权限），例如，下面指定生成demo数据库下面的所有数据表的字段缓存信息。

php think optimize:schema --db demo

执行后会自动在runtime/schema目录下面按照数据表生成字段缓存文件。

如果你的应用使用了不同的数据库连接，可以根据模块来生成，如下：

php think optimize:schema --module index

会读取index模块的模型来生成数据表字段缓存。

没有继承think\Model类的模型和抽象类不会生成。

更新数据表字段缓存也是同样的方式，每次执行都会重新生成缓存。如果需要单独更新某个数据表的缓存，可以使用：

php think optimize:schema --table think_user

支持指定数据库名称

php think optimize:schema --table demo.think_user


自定义命令行

创建自定义命令行

第一步，配置command.php文件，目录在application/command.php

<?php
return [
    'app\home\command\Test',
];

第二步，建立命令类文件，新建application/home/command/Test.php

<?php
namespace app\home\command;

use think\console\Command;
use think\console\Input;
use think\console\Output;

class Test extends Command
{
    protected function configure()
    {
        $this->setName('test')->setDescription('Here is the remark ');
    }

    protected function execute(Input $input, Output $output)
    {
        $output->writeln("TestCommand:");
    }
}

这个文件定义了一个叫test的命令，备注为Here is the remark，
执行命令会输出TestCommand。

第三步，测试-命令帮助-命令行下运行

php think

输出

Think Console version 0.1

Usage:
  command [options] [arguments]

Options:
  -h, --help            Display this help message
  -V, --version         Display this console version
  -q, --quiet           Do not output any message
      --ansi            Force ANSI output
      --no-ansi         Disable ANSI output
  -n, --no-interaction  Do not ask any interactive question
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Available commands:
  build              Build Application Dirs
  clear              Clear runtime file
  help               Displays help for a command
  list               Lists commands
  test               Here is the remark
 make
  make:controller    Create a new resource controller class
  make:model         Create a new model class
 optimize
  optimize:autoload  Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.
  optimize:config    Build config and common file cache.
  optimize:route     Build route cache.
  optimize:schema    Build database schema cache.

第四步，运行test命令

php think test

输出

TestCommand:


虚拟主机环境

ThinkPHP 支持各种各样的线上生产环境，如果你的生产环境与开发环境不符，需要稍作调整 ThinkPHP 的配置，以适应线上生产环境

修改入口文件

5.0默认的应用入口文件位于public/index.php，内容如下：

// 定义应用目录
define('APP_PATH', __DIR__ . '/../application/');
// 加载框架引导文件
require __DIR__ . '/../thinkphp/start.php';

入口文件位置的设计是为了让应用部署更安全，public目录为web可访问目录，其他的文件都可以放到非WEB访问目录下面。

我们也可以改变入口文件的位置及内容，例如把入口文件改到根目录下面改成：

// 应用目录
define('APP_PATH', __DIR__.'/apps/');
// 加载框架引导文件
require './thinkphp/start.php';

注意：APP_PATH的定义支持相对路径和绝对路径，但必须以“/”结束

如果你调整了框架核心目录的位置或者目录名，只需要这样修改：

// 改变应用目录的名称
define('APP_PATH', __DIR__.'/apps/');
// 加载框架引导文件
require './think/start.php';

这样最终的应用目录结构如下：

www  WEB部署目录（或者子目录）
├─index.php       应用入口文件
├─apps            应用目录
└─think           框架目录


Linux 主机环境

部分 Linux 主机设置了 open_basedir（可将用户访问文件的活动范围限制在指定的区域，通常是入口文件根目录的路径） 选项，导致 ThinkPHP5 访问白屏或者报错

如果把ThinkPHP5部署在了LAMP/LNMP环境上很有可能出现白屏的情况，这个时候需要开启 php 错误提示来判断是否是因为设置了open_basedir选项出错。

打开 php.ini 搜索 display_errors，把 Off 修改为 On就开启了 php 错误提示，这时再访问之前白屏的页面就会出现错误信息。如果错误信息如下那么很有可能就是因为open_basedir的问题。

php.ini 修改方法

把权限作用域由入口文件目录修改为框架根目录

打开 php.ini 搜索 open_basedir,把

open_basedir = "/home/wwwroot/tp5/public/:/tmp/:/var/tmp/:/proc/"

修改为

open_basedir = "/home/wwwroot/tp5/:/tmp/:/var/tmp/:/proc/"

如果你的 php.ini 文件的 open_basedir 设置选项是被注释的或者为 none，那么你需要通过 Apache 或者 Nginx 来修改

php.ini 文件通常是在 /usr/local/php/etc 目录中，当然了这取决于你 LAMP 环境配置

Apache 修改方法

Apache 需要修改 httpd.conf 或者同目录下的 vhost 目录下 你的域名.conf 文件，如果你的生成环境是 LAMP 一键安装包配置那么多半就是直接修改 你的域名.conf 文件

apache
├─vhost
    ├─www.thinkphp.cn.conf
    ├─......
├─httpd.conf

打开 你的域名.conf 文件 搜索 open_basedir,把

php_admin_value open_basedir "/home/wwwroot/www.thinkphp.cn/public/:/tmp/:/var/tmp/:/proc/"

修改为

php_admin_value open_basedir "/home/wwwroot/www.thinkphp.cn/:/tmp/:/var/tmp/:/proc/"

然后重新启动 apache 即可生效

域名.conf 文件通常是在 /usr/local/apache/conf 目录中，当然了这取决于你 LAMP 环境配置

Nginx/Tengine 修改方法

Nginx 需要修改 nginx.conf 或者 conf/vhost 目录下 你的域名.conf 文件，如果你的生成环境是 LNMP/LTMP 一键安装包配置那么多半就是直接修改 你的域名.conf 文件

nginx
├─conf
    ├─vhost
        ├─www.thinkphp.cn.conf
    ├─nginx.conf
    ├─......
├─nginx.conf

打开 你的域名.conf 文件 搜索 open_basedir,把

fastcgi_param  PHP_VALUE  "open_basedir=/home/wwwroot/www.thinkphp.cn/public/:/tmp/:/proc/";

修改为

fastcgi_param  PHP_VALUE  "open_basedir=/home/wwwroot/www.thinkphp.cn/:/tmp/:/proc/";

然后重新启动 Nginx 即可生效

域名.conf 文件通常是在 /usr/local/nginx/conf/vhost 目录中，当然了这取决于你 LNMP/LTMP 环境配置

fpm/fastcgi user.ini 修改方法

打开 项目根目录下找到 user.ini 文件，搜索 open_basedir,把

open_basedir=/home/wwwroot/www.thinkphp.cn/public/:/tmp/:/proc/

修改为

open_basedir=/home/wwwroot/www.thinkphp.cn/:/tmp/:/proc/

然后重新启动 web 服务器 即可生效

修改 ThinkPHP5 入口文件

直接修改 ThinkPHP5 的入口文件会把你的框架文件及程序目录暴露在外网，敬请注意安全防护。
修改入口文件方法请参考(部署-虚拟主机环境)


URL重写

可以通过URL重写隐藏应用的入口文件index.php,下面是相关服务器的配置参考：

[ Apache ]

  1. httpd.conf配置文件中加载了mod_rewrite.so模块
  2. AllowOverride None 将None改为 All
  3. 把下面的内容保存为.htaccess文件放到应用入口文件的同级目录下
<IfModule mod_rewrite.c>
Options +FollowSymlinks -Multiviews
RewriteEngine on

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?/$1 [QSA,PT,L]
</IfModule>

[ IIS ]

如果你的服务器环境支持ISAPI_Rewrite的话，可以配置httpd.ini文件，添加下面的内容：

RewriteRule (.*)$ /index\.php\?s=$1 [I]

在IIS的高版本下面可以配置web.Config，在中间添加rewrite节点：

<rewrite>
 <rules>
 <rule name="OrgPage" stopProcessing="true">
 <match url="^(.*)$" />
 <conditions logicalGrouping="MatchAll">
 <add input="{HTTP_HOST}" pattern="^(.*)$" />
 <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
 <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
 </conditions>
 <action type="Rewrite" url="index.php/{R:1}" />
 </rule>
 </rules>
 </rewrite>

[ Nginx ]

在Nginx低版本中，是不支持PATHINFO的，但是可以通过在Nginx.conf中配置转发规则实现：

  location / { // …..省略部分代码
   if (!-e $request_filename) {
   rewrite  ^(.*)$  /index.php?s=/$1  last;
   break;
    }
 }

其实内部是转发到了ThinkPHP提供的兼容URL，利用这种方式，可以解决其他不支持PATHINFO的WEB服务器环境。

如果你的应用安装在二级目录，Nginx的伪静态方法设置如下，其中youdomain是所在的目录名称。

location /youdomain/ {
    if (!-e $request_filename){
        rewrite  ^/youdomain/(.*)$  /youdomain/index.php?s=/$1  last;
    }
}

原来的访问URL：

http://serverName/index.php/模块/控制器/操作/[参数名/参数值...]

设置后，我们可以采用下面的方式访问：

http://serverName/模块/控制器/操作/[参数名/参数值...]

如果你没有修改服务器的权限，可以在index.php入口文件做修改，这不是正确的做法，并且不一定成功，视服务器而定，只是在框架执行前补全$_SERVER['PATH_INFO']参数

$_SERVER['PATH_INFO'] = $_SERVER['REQUEST_URI' ];


配置参考

惯例配置

应用设置

// 应用命名空间
'app_namespace'          => 'app',
// 应用调试模式
'app_debug'              => true,
// 应用模式状态
'app_status'             => '',
// 应用Trace
'app_trace'              => false,
// 是否支持多模块
'app_multi_module'       => true,
// 注册的根命名空间
'root_namespace'         => [],
// 扩展配置文件 （V5.0.1 已经废弃）
'extra_config_list'      => ['database', 'route', 'validate'],
// 扩展函数文件
'extra_file_list'        => [THINK_PATH . 'helper' . EXT],
// 默认输出类型
'default_return_type'    => 'html',
// 默认AJAX 数据返回格式,可选json xml ...
'default_ajax_return'    => 'json',
// 默认JSONP格式返回的处理方法
'default_jsonp_handler'  => 'jsonpReturn',
// 默认JSONP处理方法
'var_jsonp_handler'      => 'callback',
// 默认时区
'default_timezone'       => 'PRC',
// 是否开启多语言
'lang_switch_on'         => false,
// 默认全局过滤方法 用逗号分隔多个
'default_filter'         => '',
// 默认语言
'default_lang'           => 'zh-cn',
// 应用类库后缀
'class_suffix'           => false,
// 控制器类后缀
'controller_suffix'      => false,

模块设置

// 默认模块名
'default_module'         => 'index',
// 禁止访问模块
'deny_module_list'       => ['common'],
// 默认控制器名
'default_controller'     => 'Index',
// 默认操作名
'default_action'         => 'index',
// 默认验证器
'default_validate'       => '',
// 默认的空控制器名
'empty_controller'       => 'Error',
// 操作方法后缀
'action_suffix'          => '',
// 自动搜索控制器
'controller_auto_search' => false,

URL设置

// PATHINFO变量名 用于兼容模式
'var_pathinfo'           => 's',
// 兼容PATH_INFO获取
'pathinfo_fetch'         => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
// pathinfo分隔符
'pathinfo_depr'          => '/',
// URL伪静态后缀
'url_html_suffix'        => 'html',
// URL普通方式参数 用于自动生成
'url_common_param'       => false,
// URL参数方式 0 按名称成对解析 1 按顺序解析
'url_param_type'         => 0,
// 是否开启路由
'url_route_on'           => true,
// 是否强制使用路由
'url_route_must'         => false,
// 域名部署
'url_domain_deploy'      => false,
// 域名根，如thinkphp.cn
'url_domain_root'        => '',
// 是否自动转换URL中的控制器和操作名
'url_convert'            => true,
// 默认的访问控制器层
'url_controller_layer'   => 'controller',
// 表单请求类型伪装变量
'var_method'             => '_method',
// 表单ajax伪装变量 V5.0.2+
'var_ajax'               => '_ajax',
// 表单pjax伪装变量 V5.0.2+
'var_pjax'               => '_pjax',
// 是否开启请求缓存 true自动缓存 支持设置请求缓存规则 V5.0.3+
'request_cache'          => false,
// 请求缓存有效期 V5.0.3+
'request_cache_expire'   => null,

模板引擎设置

'template'               => [
    // 模板引擎类型 支持 php think 支持扩展
    'type'         => 'Think',
    // 模板路径
    'view_path'    => '',
    // 模板后缀
    'view_suffix'  => 'html',
    // 模板文件名分隔符
    'view_depr'    => DS,
    // 模板引擎普通标签开始标记
    'tpl_begin'    => '{',
    // 模板引擎普通标签结束标记
    'tpl_end'      => '}',
    // 标签库标签开始标记
    'taglib_begin' => '{',
    // 标签库标签结束标记
    'taglib_end'   => '}',
],

// 视图输出字符串内容替换
'view_replace_str'       => [],
// 默认跳转页面对应的模板文件
'dispatch_success_tmpl'  => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl',
'dispatch_error_tmpl'    => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl',

异常及错误设置

// 异常页面的模板文件
'exception_tmpl'         => THINK_PATH . 'tpl' . DS . 'think_exception.tpl',

// 错误显示信息,非调试模式有效
'error_message'          => '页面错误！请稍后再试～',
// 显示错误信息
'show_error_msg'         => false,

日志设置

'log'                    => [
    // 日志记录方式，支持 file socket
    'type' => 'File',
    // 日志保存目录
    'path' => LOG_PATH,
],

Trace设置

'trace'                  => [
    // 内置Html Console 支持扩展
    'type' => 'Html',
],

缓存设置

'cache'                  => [
    // 驱动方式
    'type'   => 'File',
    // 缓存保存目录
    'path'   => CACHE_PATH,
    // 缓存前缀
    'prefix' => '',
    // 缓存有效期 0表示永久缓存
    'expire' => 0,
],

会话设置

'session'                => [
    'id'             => '',
    // SESSION_ID的提交变量,解决flash上传跨域
    'var_session_id' => '',
    // SESSION 前缀
    'prefix'         => 'think',
    // 驱动方式 支持redis memcache memcached
    'type'           => '',
    // 是否自动开启 SESSION
    'auto_start'     => true,
],

Cookie设置

'cookie'                 => [
    // cookie 名称前缀
    'prefix'    => '',
    // cookie 保存时间
    'expire'    => 0,
    // cookie 保存路径
    'path'      => '/',
    // cookie 有效域名
    'domain'    => '',
    //  cookie 启用安全传输
    'secure'    => false,
    // httponly设置
    'httponly'  => '',
    // 是否使用 setcookie
    'setcookie' => true,
],

数据库设置

'database'               => [
    // 数据库类型
    'type'           => 'mysql',
    // 数据库连接DSN配置
    'dsn'            => '',
    // 服务器地址
    'hostname'       => 'localhost',
    // 数据库名
    'database'       => '',
    // 数据库用户名
    'username'       => 'root',
    // 数据库密码
    'password'       => '',
    // 数据库连接端口
    'hostport'       => '',
    // 数据库连接参数
    'params'         => [],
    // 数据库编码默认采用utf8
    'charset'        => 'utf8',
    // 数据库表前缀
    'prefix'         => '',
    // 数据库调试模式
    'debug'          => false,
    // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
    'deploy'         => 0,
    // 数据库读写是否分离 主从式有效
    'rw_separate'    => false,
    // 读写分离后 主服务器数量
    'master_num'     => 1,
    // 指定从服务器序号
    'slave_no'       => '',
    // 是否严格检查字段是否存在
    'fields_strict'  => true,
    // 数据集返回类型
    'resultset_type' => 'array',
    // 自动写入时间戳字段
    'auto_timestamp' => false,
    // 是否需要进行SQL性能分析
    'sql_explain'    => false,
],

分页配置

'paginate'               => [
    'type'      => 'bootstrap',
    'var_page'  => 'page',
    'list_rows' => 15,
],


常量参考

预定义常量

预定义常量是指系统内置定义好的常量，不会随着环境的变化而变化，包括：

EXT           类库文件后缀（.php）
THINK_VERSION 框架版本号

路径常量

系统和应用的路径常量用于系统默认的目录规范，可以通过重新定义改变，如果不希望定制目录，这些常量一般不需要更改。

DS 当前系统的目录分隔符
THINK_PATH 框架系统目录
ROOT_PATH 框架应用根目录
APP_PATH 应用目录（默认为application）
CONF_PATH 配置目录（默认为APP_PATH）
LIB_PATH 系统类库目录（默认为 THINK_PATH.'library/'）
CORE_PATH 系统核心类库目录 （默认为 LIB_PATH.'think/'）
TRAIT_PATH 系统trait目录（默认为 LIB_PATH.'traits/'）
EXTEND_PATH 扩展类库目录（默认为 ROOT_PATH . 'extend/')
VENDOR_PATH 第三方类库目录（默认为 ROOT_PATH . 'vendor/'）
RUNTIME_PATH 应用运行时目录（默认为 ROOT_PATH.'runtime/'）
LOG_PATH 应用日志目录 （默认为 RUNTIME_PATH.'log/'）
CACHE_PATH 项目模板缓存目录（默认为 RUNTIME_PATH.'cache/'）
TEMP_PATH 应用缓存目录（默认为 RUNTIME_PATH.'temp/'）

系统常量

系统常量会随着开发环境的改变或者设置的改变而产生变化。

IS_WIN 是否属于Windows 环境
IS_CLI 是否属于命令行模式
THINK_START_TIME 开始运行时间（时间戳）
THINK_START_MEM 开始运行时候的内存占用
ENV_PREFIX 环境变量配置前缀


助手函数

系统为一些常用的操作方法封装了助手函数，便于使用，包含如下：

助手函数 | 描述
abort | 中断执行并发送HTTP状态码
action | 调用控制器类的操作
cache | 缓存管理
config | 获取和设置配置参数
controller | 实例化控制器
cookie | Cookie管理
db | 实例化数据库类
debug | 调试时间和内存占用
dump | 浏览器友好的变量输出
exception | 抛出异常处理
halt | 变量调试输出并中断执行
import | 导入所需的类库
input | 获取输入数据 支持默认值和过滤
json | JSON数据输出
jsonp | JSONP数据输出
lang | 获取语言变量值
load_trait | 快速导入Traits PHP5.5以上无需调用
model | 实例化Model
redirect | 重定向输出
request | 实例化Request对象
response | 实例化Response对象
session | Session管理
trace | 记录日志信息
token | 生成表单令牌输出
url | Url生成
validate | 实例化验证器
vendor | 快速导入第三方框架类库
view | 渲染模板输出
widget | 渲染输出Widget
xml | XML数据输出

核心框架不依赖任何助手函数，系统只是默认加载了助手函数，配置如下：

// 扩展函数文件定义
'extra_file_list'   => [THINK_PATH . 'helper' . EXT],

因此，你可以随意修改助手函数的名称或者添加自己的助手函数，然后修改配置为：

// 扩展函数文件定义
'extra_file_list'   =>  [APP_PATH . 'helper' . EXT],
// 使用扩展函数文件
'extra_file_list'   => [
    THINK_PATH . 'helper' . EXT,
    APP_PATH . 'helper' . EXT
],


升级指导

从V5.0.3升级到V5.0.4

从V5.0.3升级到V5.0.4需要注意如下事项：

模型的关联定义方法必须采用驼峰法（小写字母打头）命名规范，但关联调用可以支持驼峰和小写方式。

行为类的方法必须使用驼峰法命名，如果你使用了钩子位作为行为执行方法入口，请修改为驼峰法，例如 app_init钩子位对应的行为方法名应该是 appInit。

如果你使用了Query类的fetchClass方法自定义数据集返回对象的话，请改为在模型中设置resultSetType属性，数据库类不再支持自定义查询数据集对象（只支持数组和系统的think\Collection数据集对象）

从V5.0.2升级到V5.0.3

从V5.0.2升级到V5.0.3需要注意如下事项：

对于join方法和view方法使用子查询的情况，请尽量使用数组方式：

['子查询'=>'别名']

从V5.0.1升级到V5.0.2

从V5.0.1升级到V5.0.2需要注意如下事项：

下列模型属性和方法由原来的静态（static）定义改为动态定义：

  • 聚合模型的relationModel属性
  • Model类的useGlobalScope 属性
  • 软删除属性 deleteTime属性
  • 全局查询范围方法base改为动态方法

原来的join方法和view方法的第一个参数规范化，支持下面三种用法：

用法一：[ '带前缀表名'=>'别名' ]

用法二：'带前缀表名 别名'

用法三：'不带前缀的表名'

如果有其它用法注意调整，下面的用法不再支持：

'不带前缀表名 别名'

如果使用了空操作方法，无需给_empty方法添加任何参数，当前操作名的获取直接使用请求对象的action函数获取。

从V5.0升级到V5.0.1

从V5.0升级到V5.0.1需要注意如下事项：

  • 扩展配置参数extra_config_list废弃，除了数据库配置之外的扩展配置放入application/extra目录自动识别加载。
  • 模型的field属性无需配置字段类型
  • 查询构建器使用手动参数绑定的时候不要使用？号占位绑定，使用命名参数绑定
  • 如果使用了file_get_contents('php://input')请改为Request对象的getInput() 方法获取
  • 文件File类取消md5()和sha1()方法，请使用hash('md5')和hash('sha1')方法替代

从V5.0RC4升级到V5.0

可以轻松的从RC4版本升级到正式版，不过请注意如下事项：

  • 如果定义了路由映射（静态路由）的则改为普通路由规则定义
  • 定义了路由规则之后，原来的URL地址被禁止访问，请注意检查是否还有这种情况
  • 如果配置了url_deny_suffix参数，改为路由的deny_ext参数设置
  • 模型save方法返回值改为影响的记录数，并且方法参数中取消了getId参数
  • Request对象controller方法返回驼峰控制器名，如果使用该方法渲染模板的话，请使用Loader::parseName(Request::instance()->controller())转换
  • 如果使用了Sqlsrv驱动则，原来自动转换小写数据表字段默认不对数据表字段进行小写转换，请更改PDO::ATTR_CASE参数
  • 如果部署在sae 需要使用sae扩展包
  • 如果使用了Sqlsrv/Orace/Firebird驱动，则自行添加原来的驱动文件
  • 配置参数读取的时候取消环境变量判断，需要读取环境变量的时候改用Env类
  • 环境变量定义文件更改为 .env 由原来的PHP数组改为ini格式定义（支持数组方式）
  • 状态配置和扩展配置的加载顺序调整 便于状态配置文件中可以更改扩展配置的参数
  • 取消域名绑定到路由分组功能
  • 控制器类的success和error方法url参数支持传入空字符串，则不做任何处理

关键几点：

默认模板目录全部是小写+下滑线规范；
控制器类的success、error和redirect方法无需使用return；
模型的save方法返回值更改为影响的记录数，而非主键，使用model->id方式获取主键；
路由定义后不能再使用原来URL地址访问；

从V3.2版本升级到V5.0

3.2版本无法直接升级到5.0版本，这里只是给出了升级指导思想和为了使用3.X版本的开发者更快的熟悉并上手这个全新的版本。同时也强烈建议开发者抛弃之前旧的思维模式，因为5.0是一个全新的颠覆重构版本。

需要摒弃的3.X旧思想

URL的变动

首先对3.X的不严谨给开发者们带来的不正确的引导表示歉意，在5.0版本正式废除类似/id/1方式 可以通过‘get’获取到‘id’的方法，严格来讲这样的url是不属于$_GET的，现在可以通过‘param’获取，具体使用可以通过请求部分查询。

模型的变动

新版的模型查询返回默认‘对象’，系统默认增加了'toArray'方法，许多开发者在'all'或'select'尝试使用'toArray'来转换为数组，在此希望开发者能理解‘对象’的概念，尝试使用‘对象’进行数据的使用，或者使用'db'方法进行数据库的操作，也提醒一下部分‘滥用’'toArray'的开发者，'all'或'select'结果是对象的数组集合，是无法使用'toArray'进行转换的。

新版变化

命名规范

  • 目录和文件名采用‘小写+下划线’，并且以小写字母开头；
  • 类库、函数文件统一以.php为后缀；
  • 类的文件名均以命名空间定义，并且命名空间的路径和类库文件所在路径一致（包括大小写）；
  • 类名和类文件名保持一致，并统一采用驼峰法命名（首字母大写）

函数

  • 系统已经不依赖任何函数，只是对常用的操作封装提供了助手函数；
  • 单字母函数废弃，默认系统加载助手函数，具体参考上一个章节‘助手函数’；

路由

5.0的URL访问不再支持普通URL模式，路由也不支持正则路由定义，而是全部改为规则路由配合变量规则（正则定义）的方式，具体这里不再赘述。

控制器

控制器的命名空间有所调整，并且可以无需继承任何的控制器类。

  • 应用类库的命名空间统一为app（可修改）而不是模块名；
  • 控制器的类名默认不带Controller后缀，可以配置开启controller_suffix参数启用控制器类后缀；
  • 控制器操作方法采用return方式返回数据，而非直接输出；
  • 废除原来的操作前后置方法；

版本对比

3.2版本控制器写法

<?php
namespace Home\Controller;

use Think\Controller;

class IndexController extends Controller
{
    public function hello()
    {
        echo 'hello,thinkphp!';
    }
}

5.0版本控制器写法

namespace app\index\controller;

class Index
{
    public function index()
    {
        return 'hello,thinkphp!';
    }
}

3.2版本控制器命名

IndexController.class.php

5.0版本控制器命名

Index.php

怎么才能在控制器中正确的输出模板
5.0在控制器中输出模板，使用方法如下：
如果你继承think\Controller的话，可以使用：

return $this->fetch('index/hello');

如果你的控制器没有继承 think\Controller的话，使用：

return view('index/hello');

模型

如果非要对比与旧版本的改进，模型被分为数据库、模型、验证器三部分，分别对应M方法、模型、自动验证，同时均有所加强，下面做简单介绍。

数据库

5.0的数据库查询功能增强，原先需要通过模型才能使用的链式查询可以直接通过Db类调用，原来的M函数调用可以改用db函数，例如：
3.2版本

M('User')->where(['name'=>'thinkphp'])->find();

5.0版本

db('User')->where('name','thinkphp')->find();

模型

新版的模型查询增加了静态方法，例如：

User::get(1);
User::all();
User::where('id','>',10)->find();

模型部分增强了很多功能，具体请查阅“模型章节”。

自动验证

对比旧的版本，可以理解为之前的自动验证且不同于之前的验证；
ThinkPHP5.0验证使用独立的\think\Validate类或者验证器进行验证，不仅适用于模型，在控制器也可直接调用，具体使用规则请参考“验证”章节，这里不再赘述。

配置文件

新版对配置很多的配置参数或者配置层次都和之前不同了，建议大家要么看看代码，要么仔细通读下官方的开发手册，不要因为配置的问题浪费自己一整天的时间。

异常

5.0对错误零容忍，默认情况下会对任何级别的错误抛出异常，并且重新设计了异常页面，展示了详尽的错误信息，便于调试。

系统常量的废弃

5.0版本相对于之前版本对系统变化进行了大量的废弃，用户如果有相关需求可以自行定义
下面是废除常量

REQUEST_METHOD IS_GET IS_POST IS_PUT IS_DELETE IS_AJAX __EXT__ COMMON_MODULE MODULE_NAME CONTROLLER_NAME ACTION_NAME APP_NAMESPACE APP_DEBUG MODULE_PATH等

部分常量可以在Request里面进行获取，具体参考“请求章节”。

再次说明本章节仅仅为之前使用3.X版本开发者快速理解5.0所写，具体5.0的功能还需要开发者通读手册。

助手函数

5.0助手函数和3.2版本的单字母函数对比如下：

3.2版本 | 5.0版本
C | config
E | exception
G | debug
L | lang
T | 废除
I | input
N | 废除
D | model
M | db
A | controller
R | action
B | 废除
U | url
W | widget
S | cache
F | 废除


更新日志

版本更新日志

  • 2016-12-20 V5.0.4
  • 2016-11-11 V5.0.3
  • 2016-10-24 V5.0.2
  • 2016-9-28 V5.0.1
  • 2016-9-15 V5.0
  • 2016-7-1 RC4版本
  • 2016-4-23 RC3版本
  • 2016-3-11 RC2版本
  • 2016-1-30 RC1版本

2016-12-20 V5.0.4

主要调整：

  • 关联模型重构并增加多态一对多关联；
  • 数据库支持一个字段多次调用不同查询条件；
  • 增加数据库CURD事件支持；
  • 路由到类和控制器的方法支持传入额外参数；
  • 支持全局模板变量赋值；
  • 模型支持独立设置查询数据集对象；
  • 日志针对命令行及调试做出改进；
  • 改进Hook类的行为方法调用

[请求和路由]

  • 请求缓存支持模块单独开启
  • Request类post方法支持获取json方式的请求数据
  • 路由到类的方法和控制器方法 支持传入额外参数，用于方法的参数
  • 改进控制器自动搜索的目录规范
  • 改进请求缓存
  • 改进自动参数绑定
  • 修正路由的请求缓存设置
  • 改进Route类name方法

[数据库和模型]

  • 增加数据库查询（CURD）事件
  • 改进多表更新的字段不存在问题
  • 改进Model类的useGlobalScope方法
  • 修正子查询作为表名查询的问题
  • Model类增加resultSetType属性 用于指定模型查询的数据集对象（默认为空返回数组）
  • Model类增加toCollection方法（自动调用）
  • 关联模型架构调整
  • 改进预载入with方法的参数支持小写和下划线定义
  • 修正关联多对多一处错误
  • 改进关联多对多的查询
  • 关联模型支持多态一对多关联
  • 预载入关联查询支持关联对象属性绑定到当前模型
  • 支持追加关联对象的属性到当前模型数据
  • 一对一关联预载入支持JOIN和IN两种方式（默认为JOIN）
  • 改进多对多查询
  • 改进模型更新的数据变化比较规则
  • 查询支持一个字段多次查询条件
  • 改进sql日志的sql语句
  • 修正join自身表的别名覆盖问题
  • 模型类的connection属性和数据库默认配置合并
  • 改进in和between查询条件的自动参数绑定
  • 改进Query类对数据集对象以及关联字段排序的支持
  • 增加模型的快捷事件方法
  • 改进Query类的getTableInfo方法缓存读取
  • model类的saveAll方法支持调用allowField方法进行字段过滤
  • 修正关联查询的时候 whereTime方法的bug
  • 改进Query类的聚合查询
  • table方法支持字符串方式的子查询
  • 修正count avg方法使用fetchsql无法正确返回sql的问题

[其它]

  • 改进命令行下的日志记录
  • 部署模式下简化日志记录
  • 增加debug日志类型 仅限调试模式记录
  • 改进Template类parseTemplateFile方法
  • 改进Validate类的getRuleMsg方法
  • 控制器的error方法在AJAX请求默认返回url为空
  • Validate类架构方法增加field参数 用于设置验证字段的描述
  • 改进App类invokeMethod方法对架构函数依赖注入的支持
  • 增加RedirectResponse的restore方法返回值
  • View类增加share静态方法 用于静态赋值模板变量
  • 验证类增加hasScene方法判断是否存在某个场景的验证配置
  • 修正redis和session驱动的destroy方法返回值
  • 空操作方法的参数传入去掉操作方法后缀
  • 在控制器中调用request和view增加类型提示
  • 改进input助手函数支持多维数据获取
  • Cache类增加pull和remember方法
  • 改进验证类的confirm验证规则 支持自动规则识别
  • 改进验证类的错误信息定义
  • 增加Validate类自定义验证错误信息的替换规则
  • Cookie类增加forever方法用于永久保存
  • 模板渲染支持从视图根目录读取模板
  • 改进Hook类的exec方法

[调整]

  • Db类查询不再支持设置自定义数据集对象
  • 废除Query类的fetchClass方法
  • 控制器的error方法在AJAX请求默认返回的url为空
  • 关联方法定义不支持使用小写下划线，必须使用驼峰法
  • 行为类的方法必须使用驼峰法命名

2016-11-11 V5.0.3

主要调整：

  • 请求缓存增强；
  • 路由增强；
  • 数据库和模型完善；
  • 支持反射的异常捕获；
  • File类改进；
  • 修正社区反馈的一些BUG；

[ 请求和路由 ]

  • 资源路由自动注册的路由规则的时候会记录当前使用的资源标识；
  • 增强请求缓存功能和规则定义，支持全局自动缓存
  • 修正控制器自动搜索的大小写问题
  • 修正路由绑定到命名空间后 类的自动定位
  • 改进Route类的parseRule方法 路由地址中的变量替换不自动去除路由变量
  • 改进控制器自动搜索
  • Route类增加setOption和getOption方法 用于记录当前路由执行过程中的参数信息
  • 优化路由分组方法
  • 改进分组路由的url生成

[ 数据库和模型 ]

  • 一对一关联查询方法支持定义field方法
  • 聚合模型支持设置field属性
  • 改进Query类的alias方法
  • 改进Query类join和view方法的table参数
  • 改进Query类where方法
  • 改进Query类的paginate方法，支持order方法
  • 改进Query类的min和max方法支持日期类型
  • 修正软删除withTrashed方法
  • 优化Connection类的getRealSql方法生成的sql

[ 其它 ]

  • 增加request_cache和request_cache_expire配置参数用于配置全局请求缓存；
  • 修正input助手函数的数组过滤
  • cache助手函数支持清空操作
  • 改进Config类load方法 一级配置名称强制转为小写
  • 修正Url多次生成的问题
  • File类修正某些环境下面无法识别上传文件的问题
  • 改进App类的空操作方法调用
  • 域名部署URL生成不依赖 url_domain_deploy 配置参数
  • 修正Url类域名部署的问题
  • 视图文件目录支持集中式存放 不放入模块目录
  • cache助手函数支持 remember方法
  • Request类的input方法或者input助手函数的filter参数支持传入null 表示不过滤

2016-10-24 V5.0.2

主要调整：

  • 数据库和模型完善；
  • 路由功能完善；
  • 增加yaml配置格式支持；
  • 依赖注入完善；
  • Session类完善；
  • Cookie类完善；
  • Validate类完善；
  • 支持反射类的异常捕获；
  • 修正社区反馈BUG；

[ 请求和路由 ]

  • 依赖注入的类如果定义了invoke方法则自动调用
  • Request类的header方法增加自定义header支持
  • Request类禁止直接实例化调用
  • 改进Request类ip方法
  • 路由变量规则支持闭包定义
  • 路由参数增加ajax和pjax判断
  • 别名路由增加允许和排除操作
  • 改进路由域名绑定后的url生成
  • 路由生成改进对路由到类的支持
  • 路由生成支持url_param_type配置参数
  • 路由生成支持别名路由
  • Route重定向规则支持更多schema
  • 别名路由支持定义单独方法的请求类型
  • 改进路由分组的url生成
  • 路由规则的组合变量支持可选分隔符定义
  • 改进路由合并参数的获取
  • 路由规则支持单独设置url分隔符，路由参数为 param_depr
  • 自动搜索控制器支持自定义访问控制器层的情况
  • 改进路由标识不区分大小写
  • 改进路由地址是否定义过路由规则的检测

[ 数据库和模型 ]

  • 改进Query类的join方法
  • 改进Query类分页方法的参数绑定
  • 修正软删除方法
  • 修正Query类parseOrder方法一处错误
  • 修正sqlsrv驱动parseOrder方法
  • 修正Query类setInc和setDec方法
  • 改进Model类的save方法支持非自增主键的处理
  • 整型字段的参数绑定如果为空写入默认值0
  • 改进Model类has和hasWhere方法
  • 改进Query类的value方法缓存判断
  • 改进Query类join方法对子查询支持
  • 改进Query类的table方法和alias方法用法
  • 关联预载入支持hasOne自关联
  • 改进Builder类的parseKey方法
  • 改进Builder类的join/alias/table方法的解析
  • 改进全局查询范围
  • 改进Query类的聚合查询方法的返回值
  • 改进关联属性的读取
  • 改进聚合模型主键和关联键相同的情况
  • 改进模型在开启class_suffix参数情况下的name属性的识别

[ 其它 ]

  • Cache类增加remember方法 用于当获取的缓存不存在的时候自动写入
  • Session类增加flash方法用于设置下一次请求有效的值
  • Session类增加flush方法用于清空当前请求有效的值
  • Session类增加push方法用于更新数组数据
  • 增加yaml配置格式支持
  • 改进App类的反射异常无法捕获问题
  • 修正session助手函数的清空操作
  • 改进验证类的image方法
  • 改进验证类的activeUrl方法
  • 改进自定义验证规则的使用
  • 改进控制器自动搜索后的控制器名获取
  • 修正import方法加载extend目录类库
  • 修正json_encode时 "Failed calling XXX::jsonSerialize()" 的异常
  • 改进Loader类model和validate方法的单例问题
  • 改进方法执行的日志记录
  • 改进模板引擎的Think变量解析
  • 改进Lang类load方法
  • 验证错误信息支持多语言读取
  • 改进ROOT_PATH常量
  • 改进语言包加载
  • 改进模板session和cookie变量获取，自动判断前缀
  • 缓存驱动统一增加handler方法用于获取操作对象的句柄（某些缓存类型可能为null）
  • File类增加__call方法用于兼容5.0版本的md5和 sha1方法
  • 改进文件缓存驱动的clear方法
  • Lang类增加setLangCookieExpire方法设置多语言cookie过期时间
  • 增加route_complete_match配置参数

[ 调整 ]

下列模型属性和方法由原来的静态（static）定义改为动态定义：

  • 聚合模型的relationModel属性
  • Model类的useGlobalScope属性
  • 全局查询范围方法base改为动态方法
  • 软删除属性 deleteTime属性

2016-9-28 V5.0.1

主要调整：

  • 依赖注入完善；
  • 扩展配置文件位置调整；
  • 新增数据表字段缓存命令；
  • 支持设置当前的查询对象；
  • 支持请求和路由缓存；

[ 请求和路由 ]

  • 改进Controller类的success和error方法的跳转地址识别 支持更多Scheme
  • 操作方法和架构方法支持任何对象自动注入
  • Requesst类增加getInput方法 用于获取php://input值
  • 路由到方法的时候 支持架构方法注入请求对象
  • 改进Route类路由到类的判断
  • Request增加cache方法，支持请求缓存
  • 绑定到模块后 路由依然优先检查
  • 路由增加请求缓存参数
  • 修正路由组合变量的可选变量的BUG

[ 数据库 ]

  • 修正pgsql数据库驱动的数据表字段信息读取
  • 改进Query类的view方法 第二个参数默认值更改为true 获取全部的字段
  • 数据库配置信息增加query参数用于配置查询对象名称
  • 型类增加query属性用于配置模型需要的查询对象名称
  • 改进数据表字段缓存读取
  • 改进数据表字段缓存生成 模型为抽象类或者 没有继承Model类 不生成字段缓存
  • 改进模型的字段缓存 虚拟模型不生成字段缓存
  • 改进数据表字段缓存生成 支持读取模块的模型生成
  • 改进聚合模型的save方法 主键写入
  • 模型类的field属性定义简化 取消Query类的allowField和setFieldType方法及相关属性
  • 改进数据表字段缓存生成 支持生成多个数据库的
  • 更新数据库驱动类 改进getTables方法
  • 增加optimize:schema 命令 用于生成数据表字段信息缓存
  • 修正一个查询条件多个条件的时候的参数绑定BUG
  • 分页查询方法paginate第二个参数传入数字表示总记录数
  • 修正mysql的JSON字段查询
  • 改进Query类的getOptions方法 当name参数不存在的时候返回null

[ 模型和关联 ]

  • 模型类的field属性不需要添加字段类型定义
  • 改进Model类 添加getDb静态方法获取db查询对象
  • 改进聚合模型save方法返回值
  • 改进Relation类save方法
  • 修正关联模型 多对多save方法一处问题
  • 改进Model类的save方法 修正不按主键查询的更新问题
  • 时间字段获取器获取的时候为NULL则不做转换

[ 其它 ]

  • 改进配置缓存生成 支持扩展配置
  • 取消extra_config_list配置参数 扩展配置文件直接放到 extra目录下面即可自动加载（数据库配置文件位置不变）
  • cache助手函数支持判断缓存是否有效
  • 修正 模板引擎驱动类的config方法
  • 修复在配置Model属性field=true情况下,通过__call调用db()引发的BUG
  • 改进模板引擎驱动的config方法 支持获取配置参数值
  • 改进redirct的url地址解析
  • 删除File类的md5和sha1方法 改为hash方法 支持更多的散列值类型生成
  • 增加response_end行为标签
  • 改进默认语言的加载

2016-9-15 V5.0

[ 请求和路由 ]

  • Request对象支持动态绑定属性
  • 定义了路由规则的URL原地址禁止访问
  • 改进路由规则存储结构
  • 路由分组功能增强，支持嵌套和虚拟分组
  • 路由URL高效反解
  • 改进Request对象param方法获取优先级
  • 路由增加name方法设置和获取路由标识
  • 增加MISS和AUTO路由规则
  • Route类增加auto方法 支持注册一个自动解析URL的路由
  • 路由规则支持模型绑定
  • 路由变量统一使用param方法获取
  • 路由规则标识功能和自动标识
  • 增加生成路由缓存指令 optimize:route
  • Request对象增加route方法单独获取路由变量
  • Request对象的param get post put request delete server cookie env方法的第一个参数传入false 则表示获取原始数据 不进行过滤
  • 改进自动路由标识生成 支持不同的路由规则 指向同一个路由标识，改进Url自动生成对路由标识的支持
  • 改进Request类 filter属性的初始化
  • 改进Request类的isAjax和isPjax方法
  • Request类增加token方法
  • 路由配置文件支持多个 使用 route_config_file 配置参数配置
  • 域名绑定支持https检测
  • 改进域名绑定 支持同时绑定模块和其他 支持绑定到数组定义的路由规则，取消域名绑定到分组
  • 路由规则增加PATCH请求类型支持
  • 增加route_complete_match配置参数设置全局路由规则定义是否采用完整匹配 可以由路由规则的参数complete_match 进行覆盖
  • 改进路由的 后缀参数识别 优先于系统的伪静态后缀参数
  • Url类增加root方法用于指定当前root地址（不含域名）
  • 改进Url生成对可选参数的支持

[ 数据库 ]

  • 查询条件自动参数绑定
  • 改进分页方法支持参数绑定
  • Query类的cache方法增加缓存标签参数
  • Query类的update和delete方法支持调用cache方法 会自动清除指定key的缓存 配合查询方法的cache方法一起使用
  • 改进Query类的延迟写入方法
  • Query类的column和value方法支持fetchsql
  • 改进日期查询方法
  • 改进存储过程方法exec的支持
  • 改进Connection类的getLastInsID方法获取
  • 记录数据库的连接日志（连接时间和DSN）
  • 改进Query类的select方法的返回结果集判断
  • Connection类增加getNumRows方法
  • 数据库事务方法取消返回值
  • 改进Query类的chunk方法对主键的获取
  • 改进当数据库驱动类型使用完整命名空间的时候 Query类的builder方法的问题

[ 模型 ]

  • 增加软删除功能
  • 关联模型和预载入改进
  • 关联预载入查询闭包支持更多的连贯操作
  • 完善savell方法支持更新和验证
  • 关联定义统一返回Relation类
  • Model类的has和hasWhere方法对join类型的支持
  • Model类的data方法 批量赋值数据的时候 清空原始数据
  • Model类的get方法第三个参数传入true的时候会自动更新缓存
  • Model类增加只读字段支持
  • Model类增加useGlobalScope方法设置是否启用全局查询范围
  • Model类的base方法改为静态定义 全局多次调用有效
  • Model类支持设定主键、字段信息和字段类型，不依赖自动获取，提高性能
  • Model类的data方法 支持修改器
  • 改进Relation类对非数字类型主键的支持
  • 改进Relation类的一对多删除
  • 修正Relation类的一对多关联预载入查询

[ 日志和缓存 ]

  • 支持日志类型分离存储
  • 日志允许设置记录级别
  • 增加缓存标签功能
  • 缓存类增加pull方法用于获取并删除
  • cache助手函数增加tag参数
  • 简化日志信息，隐藏数据库密码
  • 增加cache/session redis驱动的库选择逻辑;
  • memcached驱动的配置参数支持option参数
  • 调试模式下面 日志记录增加页面的header和param参数记录
  • memcached缓存驱动增加连接账号密码参数
  • 缓存支持设置complex类型 支持配置多种缓存并用store切换
  • 缓存类增加tag方法 用于缓存标签设置 clear方法支持清除某个缓存标签的数据
  • File类型日志驱动支持设置单独文件记录不同的日志级别
  • 改进文件缓存和日志的存储文件名命名规范
  • 缓存类增加inc和dec方法 针对数值型数据提供自增和自减操作
  • Cache类增加has方法 get方法支持默认值

[ 其它 ]

  • 视图类支持设置模板引擎参数
  • 增加表单令牌生成和验证
  • 增加中文验证规则
  • 增加image和文件相关验证规则
  • 重定向Response对象支持with方法隐含传参
  • 改进Session类自动初始化
  • session类增加pull方法用于获取并删除
  • 增加Env类用于获取环境变量
  • Request类get/post/put等更改赋值后param方法依然有效
  • 改进Jump跳转地址支持Url::build 解析
  • 优化Hook类
  • 应用调试模式和页面trace支持环境变量设置
  • config助手函数支持 config('?name') 用法
  • 支持使用BIND_MODULE常量的方式绑定模块
  • 入口文件自动绑定模块功能
  • 改进验证异常类的错误信息和模板输出，支持批量验证的错误信息抛出
  • 完善console 增加output一些常用的方法
  • 增加token助手函数 用于在页面快速显示令牌
  • 增加halt方法用于变量调试并中断输出
  • 改进Validate类的number验证规则 和 integer区分开
  • optimize:autoload增加对extend扩展目录的扫描
  • 改进Validate类的boolean验证规则 支持表单数据
  • 改进cookie助手函数支持 判断是否存在某个cookie值
  • 改进abort助手函数 支持抛出HttpResponseException异常
  • 改进File类增加对上传错误的处理
  • 改进File类move方法的返回对象增加上传表单信息，增加获取文件散列值的方法
  • 改进File类的move方法的返回对象改为返回File对象实例
  • 增加clear和optimize:config 指令
  • 改进File类和Validate类的图像文件类型验证
  • 控制器的操作方法支持注入Request之外的对象实例
  • Request类 param(true) 支持获取带文件的数据
  • input助手函数第一个参数增加默认值
  • Validate类增加image验证规则 并改进max min length支持多种数据类型
  • json输出时数据编码失败后抛出异常

[ 调整 ]

  • 废除路由映射（静态路由）定义
  • 取消url_deny_suffix配置 改由路由的deny_ext参数设置
  • 模型save方法返回值改为影响的记录数，取消getId参数
  • Request对象controller方法返回驼峰控制器名
  • 控制器前置操作方法不存在则抛出异常
  • Loader类db方法增加name标识参数
  • db助手函数增加第三个参数用于指定连接标识
  • Sqlsrv驱动默认不对数据表字段进行小写转换
  • 移除sae驱动 改为扩展包
  • Oracle驱动移出核心包
  • Firebird驱动移出核心包
  • 取消别名定义文件alias.php
  • 配置参数读取的时候取消环境变量判断 需要读取环境变量的时候使用Env类
  • 环境变量定义文件更改为 .env 由原来的PHP数组改为ini格式定义（支持数组方式）
  • 状态配置和扩展配置的加载顺序调整 便于状态配置文件中可以更改扩展配置的参数
  • 取消域名绑定到路由分组功能
  • 控制器类的success和error方法url参数支持传入空字符串，则不做任何处理
  • 控制器的error success result redirect方法均不需要使用return
  • 创建目录的权限修改为0644

2016-7-1 RC4版本

[ 底层架构 ]

  • 增加Request类 并支持自动注入
  • 统一Composer的自动加载机制
  • 增加Response类的子类扩展
  • 增加File类用于上传和文件操作
  • 取消模式扩展 SAE支持降权
  • 优化框架入口文件
  • 改进异常机制
  • App类输入/输出调整
  • 单元测试的完美支持
  • 增加新的控制台指令
  • 取消系统路径之外的大部分常量定义
  • 类库映射文件由命令行动态生成 包含应用类库

[ 数据库 ]

  • 增加分表规则方法
  • 增加日期和时间表达式查询方法
  • 增加分页查询方法
  • 增加视图查询方法
  • 默认保持数据表字段大小写
  • 数据缓存自动更新机制
  • 完善事务嵌套支持
  • 改进存储过程数据读取
  • 支持设置数据库查询数据集返回类型

[ 模型 ]

  • 增加Merge扩展模型
  • 模型支持动态查询
  • 增加更多的类型自动转换支持
  • 增加全局查询范围
  • toJson/toArray支持隐藏和增加属性输出
  • 增加远程一对多关联

[ 其它 ]

  • 日志存储结构调整
  • Trace调试功能从日志类独立并增强
  • 原Input类功能并入Request类
  • 类库映射文件采用命令行生成 包含应用类库
  • 验证类的check方法data数据取消引用传参
  • 路由增加MISS路由规则
  • 路由增加路由别名功能

2016-4-23 RC3版本

[ 底层架构 ]

  • 框架核心仓库和应用仓库分离 便于composer独立更新
  • 数据库类重构，拆分为Connection（连接器）/Query（查询器）/Builder（SQL生成器）
  • 模型类重构，更加对象化

[ 数据库 ]

  • 新的查询语法
  • 闭包查询和闭包事务
  • Query对象查询
  • 数据分批处理
  • 数据库SQL执行监听

[ 模型 ]

  • 对象化操作
  • 支持静态调用（查询）
  • 支持读取器/修改器
  • 时间戳字段
  • 对象/数组访问
  • JSON序列化
  • 事件触发
  • 命名范围
  • 类型自动转换
  • 数据验证和完成
  • 关联查询/写入
  • 关联预载入

[ 其它更新 ]

  • 路由类增加快速路由支持
  • 验证Validate类重构
  • Build类增加快速创建模块的方法
  • Url生成类改进
  • Validate类改进
  • View类及模板引擎驱动设计改进
  • 取消模板引擎的模板主题设计
  • 修正社区反馈的一些问题
  • 助手函数重新命名
  • router.php文件位置移动

2016-3-11 RC2版本

  • 重新设计的自动验证和自动完成机制（原有自动验证和完成支持采用traits\model\Auto兼容）；
  • 验证类Validate独立设计；
  • 自动生成功能交给Console完成；
  • 对数据表字段大小写的处理；
  • 改进Controller类（取消traits\contorller\View）；
  • 改进Input类；
  • 改进Url类；
  • 改进Cookie类；
  • 优化Loader类；
  • 优化Route类；
  • 优化Template类；
  • Session类自动初始化；
  • 增加traits\model\Bulk模型扩展用于大批量数据写入和更新；
  • 缓存类和日志类增加Test驱动；
  • 对异常机制和错误处理的改进；
  • 增加URL控制器和操作是否自动转换开关；
  • 支持类名后缀设置；
  • 取消操作绑定到类的功能；
  • 取消use_db_switch参数设计；

2016-1-30 RC1版本

[ 底层架构 ]

  • 真正的惰性加载
  • 核心类库组件化
  • 框架引导文件
  • 完善的类库自动加载（支持Composer）
  • 采用Traits扩展
  • API友好（输出、异常和调试）
  • 文件命名规范调整

[ 调试和异常 ]

  • 专为API开发而设计的输出、调试和异常处理
  • 日志类支持本地文件/SAE/页面Trace/SocketLog输出，可以实现远程浏览器插件调试
  • 内置trace方法直接远程调试
  • 异常预警通知驱动设计
  • 数据库SQL性能分析支持

[ 路由 ]

  • 动态注册路由
  • 自定义路由检测方法
  • 路由分组功能
  • 规则路由中的变量支持采用正则规则定义（包括全局和局部）
  • 闭包路由
  • 支持路由到多层控制器

[ 控制器 ]

  • 控制器类无需继承controller类
  • 灵活的多层控制器支持
  • 可以Traits引入高级控制器功能
  • rest/yar/rpc/hprose/jsonrpc控制器扩展
  • 前置操作方法支持排除和指定操作

[ 模型 ]

  • 简化的核心模型
  • Traits引入高级模型/视图模型/关联模型
  • 主从分布时候主数据库读操作支持
  • 改进的join方法和order方法

[ 视图 ]

  • 视图解析驱动设计（模板引擎）
  • 所有方法不再直接输出而是返回交由系统统一输出处理
  • 动态切换模板主题设计
  • 动态切换模板引擎设计

[ 数据库 ]

  • 完全基于PDO实现
  • 简化的数据库驱动设计
  • SQL性能监控（需要开启数据库调试模式）
  • PDO参数绑定改进

[ 其他方面 ]

  • 目录和MVC文件自动生成支持
  • I函数默认添加变量修饰符为/s
  • 一个行为类里面支持为多个标签位定义不同的方法
  • 更多的社交扩展类库