Thinkphp开发文档二次整理版

基础部分

安装

环境要求

​ *php>=7.1.0

命令下载

通过Composer进行下载,操作步骤下载软件 phpstudy --->点击软件管理 --->安装Composer --->再点击网站 --->点击管理 --->点击Composer --->复制如下命令代码:

​ 稳定版:composer create-project topthink/think tp,tp可以任意修改为目录名

​ 更新命令:composer update topthink/framework

​ 开发版:composer create-project topthink/think=6.0.x-dev tp

**  更新操作会删除thinkphp目录重新下载安装新版本,但不会影响app目录,因此不要在核心框架目录,添加任何应用代码和类库,更新必须在你的应用根目录下面执行。

调试运行

开启调试模式

​ 将.example.env文件,直接更名为.env文件,并修改APP_DEBUG开启或关闭调试模式,以及数据库连接操作。

测试运行

​ 通过Composer命令行php think run进行运行,后在浏览器输入地址http://localhost:8000/,看到欢迎页面即代表安装运行成功!

​ 或通过命令行php think run -p 80修改运行端口号,80端口可以直接通过http://localhost进入欢迎页面。

部署访问

​ 实际部署中,应该是绑定域名访问到public目录,确保其它目录不在项目根目录下面。

​ mac或者linux环境下面,注意需要设置runtime目录权限为777。

​ 多应用模式部署后,记得删除app目录下的controller目录(系统根据该目录作为判断是否单应用的依据)。

开发规范

命名规范

目录和文件

  • 目录使用小写+下划线;
  • 类库、函数文件统一以.php为后缀;
  • 类的文件名均以命名空间定义,并且命名空间的路径和类库文件所在路径一致;
  • 类(包含接口和Trait)文件采用驼峰法命名(首字母大写),其它文件采用小写+下划线命名;
  • 类名(包括接口和Trait)和文件名保持一致,统一采用驼峰法命名(首字母大写);

函数和类、属性命名

  • 类的命名采用驼峰法(首字母大写),例如 UserUserType
  • 函数的命名使用小写字母和下划线(小写字母开头)的方式,例如 get_client_ip
  • 方法的命名使用驼峰法(首字母小写),例如 getUserName
  • 属性的命名使用驼峰法(首字母小写),例如 tableNameinstance
  • 特例:以双下划线__打头的函数或方法作为魔术方法,例如 __call 和 __autoload

常量和配置

  • 常量以大写字母和下划线命名,例如 APP_PATH
  • 配置参数以小写字母和下划线命名,例如 url_route_on 和url_convert
  • 环境变量定义使用大写字母和下划线命名,例如APP_DEBUG

数据表和字段

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

避免使用PHP保留字

PHP 关键词
__halt_compiler()abstractandarray()as
breakcallablecasecatchclass
cloneconstcontinuedeclaredefault
die()doechoelseelseif
empty()enddeclareendforendforeachendif
endswitchendwhileeval()exit()extends
finalfinallyfn (从 PHP 7.4 开始)forforeach
functionglobalgotoifimplements
includeinclude_onceinstanceofinsteadofinterface
isset()list()match (从 PHP 8.0 开始)namespacenew
orprintprivateprotectedpublic
requirerequire_oncereturnstaticswitch
throwtraittryunset()use
varwhilexoryieldyield from

​ 编译时常量

CLASSDIRFILEFUNCTIONLINEMETHOD
NAMESPACETRAIT

应用模式

单应用

基础认知

  • 框架默认安装的模式为单应用

  • 根目录下的config目录下全是配置文件,也可以增加自定义的配置文件。

手动加载配置文件

​ 单应用模式的config目录下的所有配置文件系统都会自动读取,不需要手动加载。如果存在子目录,可以通过Config类的load方法手动加载。

// \think\facade\Config::load('配置文件夹/文件名', '读取方法名');// 加载config/extra/config.php 配置文件 读取到extra
\think\facade\Config::load('extra/config', 'extra');

URL访问

http://serverName/index.php(或者其它入口文件)/控制器/操作/参数/值…

多应用

安装命令

composer require topthink/think-multi-app

​ 通过Composer命令行在项目根目录下输入

部署注意事项

​ 多应用模式部署后,记得删除app目录下的controller目录(系统根据该目录作为判断是否单应用的依据)。

全局与应用配置

​ 全局配置:config目录对整个项目有效。

​ 应用配置:可单独为应用进行配置,相同的配置参数会覆盖全局配置。

URL访问

http://serverName/index.php/应用/控制器/操作/参数/值...

环境变量配置

配置文件

  • 默认安装后的根目录下.example.env环境变量文件,直接改成.env文件后进行修改。
  • 如果部署环境单独配置环境变量( 环境变量的前缀使用PHP_),删除.env配置文件,避免冲突。

配置参数

​ 环境变量配置的参数会全部转换为大写,值为 offno 和 false 等效于 布尔值false,值为 yes 、on和 true 等效于 布尔值的true

读取配置参数的值

//使用Config类,需在类文件中引入   use think\facade\Config;
//读取一级配置的所有参数,(每个配置文件都是独立的一级配置)Config::get('文件名');Config::get('route');
//读取单个配置参数Config::get('文件名.参数名');Config::get('route.url_domain_root');
//读取数组配置Config::get('database.default.host');
//判断是否存在某个设置参数Config::has('template');Config::has('route.route_rule_merge');

环境变量数组参数

  • 环境变量不支持数组参数,如果需要使用数组参数可以,可以使用:
[DATABASE]
USERNAME =  root
PASSWORD =  123456
  • 设置一个没有键值的数组参数
PATHINFO_PATH[] =  ORIG_PATH_INFO
PATHINFO_PATH[] =  REDIRECT_PATH_INFO
PATHINFO_PATH[] =  REDIRECT_URL

获取环境变量值

  • 获取环境变量的值(不区分大小写)
// 必须先引入think\facade\Env
Env::get('database.username');
Env::get('database.password');
Env::get('PATHINFO_PATH');
  • 获取环境变量的值不存在,则使用默认值
// 获取环境变量 如果不存在则使用默认值root
Env::get('database.username', 'root');
  • 使用环境变量进行本地环境和服务器的自动配置
return ['hostname'  =>  Env::get('hostname','127.0.0.1'),
];

应用调试模式

​ 配置参数app_debug

配置文件后缀

​ 配置参数config_ext

多环境变量配置

​ 第一步:定义多个环境变量配置文件,配置文件命名规范为:

.env.example        //生产环境      
.env.testing        //测试环境
.env.develop        //开发环境

​ 第二步:在入口文件(public文件夹下index.php文件)中指定部署使用的环境变量名称

// 执行HTTP应用并响应
$http = (new App())->setEnvName('develop')->http;$response = $http->run();$response->send();$http->end($response);

系统自带配置文件

配置文件名描述
app.php应用配置
cache.php缓存配置
console.php控制台配置
cookie.phpCookie配置
database.php数据库配置
filesystem.php磁盘配置
lang.php多语言配置
log.php日志配置
middleware.php中间件配置
route.php路由和URL配置
session.phpSession配置
trace.php页面Trace配置
view.php视图配置

架构部分

基础认知

入口文件

​ 入口文件位于public目录下面,最常见的入口文件就是index.php6.0支持多应用多入口,可给每个应用增加入口文件。如果开启自动多应用的话,一般只需要一个入口文件index.php

​ 入口文件定义:

// [ 应用入口文件 ]
namespace think;require __DIR__ . '/../vendor/autoload.php';// 执行HTTP应用并响应
$http = (new App())->http;
$response = $http->run();
$response->send();
$http->end($response);

****

控制台入口文件

​ 位于项目根目录的think(注意该文件没有任何的后缀)。

#!/usr/bin/env php
<?php
namespace think;// 加载基础文件
require __DIR__ . '/vendor/autoload.php';// 应用初始化
(new App())->console->run();

​ 控制台入口文件用于执行控制台指令

php think version

应用

​ 每个应用是一个app目录的子目录(或者指定的composer库),每个应用具有独立的路由、配置,以及MVC相关文件,这些应用可以公用框架核心以及扩展。而且可以支持composer应用加载。

容器

​ ThinkPHP使用(对象)容器统一管理对象实例及依赖注入。

路由

  • 访问地址和实际操作方法之间建立一个路由规则 => 路由地址的映射关系
  • ThinkPHP并非强制使用路由,如果没有定义路由,则可以直接使用“控制器/操作”的方式访问
  • 开启强制路由参数,则必须为每个请求定义路由(包括首页)
  • 可实现验证、权限、参数绑定及响应设置等功能

控制器

  • 一个应用有多个控制器负责响应请求,而每个控制器其实就是一个独立的控制器类。
  • 控制器主要负责请求的接收,并调用相关的模型处理,并最终通过视图输出。控制器不应该过多的介入业务逻辑处理。
  • 一般建议继承一个基础的控制器,方便扩展。系统默认提供了一个app\BaseController控制器类。

方法(操作)

  • 一个控制器包含多个操作(方法),操作方法是一个URL访问的最小单元
  • 操作方法可以不使用任何参数
  • 定义了一个必选参数,并且不是对象类型,则该参数必须通过用户请求传入
  • URL请求,则通常是通过当前的请求传入,操作方法的参数支持依赖注入

模型

  • 通常完成实际的业务逻辑和数据封装,并返回和格式无关的数据
  • 模型类并不一定要访问数据库
  • 只有进行实际的数据库查询操作的时候,才会进行数据库的连接
  • 模型类通常需要继承think\Model

中间件

  • 中间件主要用于拦截或过滤应用的HTTP请求,并进行必要的业务处理。

助手函数

  • 使用助手函数和性能并无直接影响
  • 某些时候无法享受IDE自动提醒的便利,但是否使用助手函数看项目自身规范
  • 在应用的公共函数文件中也可以对系统提供的助手函数进行重写

多应用模式

多应用拓展命令

​ thinkphp安装后默认使用单应用模式,若需部署多应用需通过Composer命令行安装多应用模式扩展think-multi-app

composer require topthink/think-multi-app

自动多应用部署

​ 支持在同一个入口文件中访问多个应用,并且支持应用的映射关系以及自定义。

// 访问admin应用
http://serverName/index.php/admin
// 访问shop应用
http://serverName/index.php/shop

配置路由为指定应用

​ http://serverName/index.php访问的其实是index默认应用,可以通过app.php配置文件的default_app配置参数指定默认应用。

// 设置默认应用名称
'default_app' => 'home',

将以上配置修改后可使用http://serverName/index.php进行访问home应用,而并非是之前的默认应用index

URL重写(伪静态)

可以通过URL重写隐藏应用的入口文件index.php(也可以是其它的入口文件,但URL重写通常只能设置一个入口文件),下面是相关服务器的配置参考:

[ Apache路由重写 ]

  1. httpd.conf配置文件中加载了mod_rewrite.so模块
  2. AllowOverride None 将None改为 All
  3. 把下面的内容保存为.htaccess文件放到应用入口文件的同级目录下
<IfModule mod_rewrite.c>Options +FollowSymlinks -MultiviewsRewriteEngine OnRewriteCond %{REQUEST_FILENAME} !-dRewriteCond %{REQUEST_FILENAME} !-fRewriteRule ^(.*)$ 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  ^(.*)$  /public/index.php?s=/$1  last;}}

依赖注入

定义解释

​ 对类的依赖通过构造器完成自动注入(控制器中),简单地说就是解决将类实例不再自行new实例化,而是交给thinkphp容器帮我们自动new实例。

申明依赖

​ 申明依赖输入步骤:

​ 1.容器类的工作由think\Container类完成

​ 2. 构建方法public function __construct(自动实例化参数 $自动实例化参数变量);自动实例化参数变量示例:Request $request

​ 3.$this->$自动实例化参数变量>$自动实例化参数变量方法名(构造方法)

​ 4.$自动实例化参数变量->$自动实例化参数变量方法名(直接在方法中使用)

​ 代码示例:

    //构造函数public function __construct(User $user){$this->user = $user;}public function index(){return json($this->user->find(1));}

依赖注入的场景

支持使用依赖注入的场景包括(但不限于):

  • 控制器架构方法;
  • 控制器操作方法;
  • 路由的闭包定义;
  • 事件类的执行方法;
  • 中间件的执行方法;

容器

定义解释

  • ​ 使用容器来实例化的话,可以自动进行依赖注入
  • ​ 把多个类自动化实例绑定到依赖注入类容器中
  • ​ 支持对类、方法、函数、闭包使用依赖注入

局部容器

  • ​ 在方法中把多个类自动化实例绑定到容器中,且只能在方法内部使用
  • ​ 建议使用bind助手函数进行操作,也可使用构造函数方式操作
  • ​ 局部容器操作时,需使用系统类库的方式申明
bind助手函数 代码示例:// 绑定类库标识
bind('cache', 'think\Cache');
构造函数方式 代码示例://使用系统类库use think\App;public function __construct(App $app){$this->app = $app;}public function index(){$this->app->bind('users','app\index\model\User');}
依赖注入绑定容器中 代码示例:   use think\App;public function index(){bind(['标识名称'        => \命名空间\类名::class'user' => 'app\index\model\User']);$user = app('user');return $user->find(1);}
系统类库容器绑定标识
think\Appapp
think\Cachecache
think\Configconfig
think\Cookiecookie
think\Consoleconsole
think\Dbdb
think\Debugdebug
think\Envenv
think\Eventevent
think\Httphttp
think\Langlang
think\Loglog
think\Middlewaremiddleware
think\Requestrequest
think\Responseresponse
think\Filesystemfilesystem
think\Routeroute
think\Sessionsession
think\Validatevalidate
think\Viewview

全局容器

​ provider.php文件夹内中把多个类自动化实例绑定到容器中

代码示例:return ['route'      => \think\Route::class,'session'    => \think\Session::class,'url'        => \think\Url::class,'标识名称'        => \命名空间\类名::class
];

invoke助手函数

  • 使用容器来实例化的话,可以自动进行依赖注入,使用系统提供的invoke助手函数调用.
  • 使用容器来实例化类的话,可以自动进行依赖注入--------invoke(类名)
  • 使用容器对某个方法依赖注入------- invoke(['类名','方法名'])

调用依赖容器中类

使用app助手函数进行容器中的类解析调用,

调用和绑定的标识必须保持一致(包括大小写)

对于已经绑定的类标识,会自动快速实例化

$cache = app('cache');

带参数实例化调用

$cache = app('cache',['file']);

对于没有绑定的类,也可以直接解析

$arrayItem = app('org\utils\ArrayItem');

系统服务

定义解释

​ 执行框架的某些组件或者功能的时候需要依赖的一些基础服务,开发组件的时候会常用到。

命令生成

​ 默认生成的服务类会继承系统的think\Service,并且自动生成了系统服务类最常用的两个空方法:registerboot方法。

php think make:service  FileSystemService

注册方法

​ register方法通常用于注册系统服务,也就是将服务绑定到容器中,例如:

<?php
namespace app\service;use my\util\FileSystem;class FileSystemService extends Service
{public function register(){$this->app->bind('file_system', FileSystem::class);}
}

register方法不需要任何的参数,如果你只是简单的绑定容器对象的话,可以直接使用bind属性。

<?php
namespace app\service;use my\util\FileSystem;class FileSystemService extends Service
{public $bind = ['file_system'    =>    FileSystem::class,];
}

启动方法

boot方法是在所有的系统服务注册完成之后调用,用于定义启动某个系统服务之前需要做的操作。例如:

<?php
namespace think\captcha;use think\Route;
use think\Service;
use think\Validate;class CaptchaService extends Service
{public function boot(Route $route){$route->get('captcha/[:config]', "\\think\\captcha\\CaptchaController@index");Validate::maker(function ($validate) {$validate->extend('captcha', function ($value) {return captcha_check($value);}, ':attribute错误!');});}
}

boot方法支持依赖注入,你可以直接使用其它的依赖服务。

服务注册

定义好系统服务后,你还需要注册服务到你的应用实例中。

可以在应用的全局公共文件service.php中定义需要注册的系统服务,系统会自动完成注册以及启动。例如:

return ['\app\service\ConfigService','\app\service\CacheService',
];

如果你需要在你的扩展中注册系统服务,首先在扩展中增加一个服务类,然后在扩展的composer.json文件中增加如下定义:

"extra": {"think": {"services": ["think\\captcha\\CaptchaService"]}
},

在安装扩展后会系统会自动执行service:discover指令用于生成服务列表,并在系统初始化过程中自动注册。

内置服务

为了更好的完成核心组件的单元测试,框架内置了一些系统服务类,主要都是用于核心类的依赖注入,包括ModelServicePaginatorServiceValidateService类。这些服务不需要注册,并且也不能卸载。

门面

定义解释

  • ​ 就是使用中间类来操作,把动态类封装成静态调用接口
  • ​ 系统已经为大部分核心类库定义了Facade,可以通过Facade来访问这些系统类
  • ​ 可以为自定义应用类库添加Facade门面
  • ​ Facade功能可以让类无需实例化而直接进行静态方式调用

使用方法

​ 1.使用一个Facade类库的方法,在代码前部分申明:use 命名空间\具体类库

​ 2.自定义方法调用直接\app\自定义目录名\类名::类方法('参数');,若提前申明了use app\自定义目录名\类名,那就可以直接操作为类名::类方法('参数')

自定义门脸方法

​ 在app\自定义目录下,命名空间namespace app\自定义目录;

​ 自定义类库继承think\Facade

​ 先声明use think\Facade;

​ 再通过class类继承 extends Facade

​ 建立静态方法 protected static function 类名()

​ 最后return '路径';

核心Facade类库

系统给内置的常用类库定义了Facade类库,包括:

(动态)类库Facade类
think\Appthink\facade\App
think\Cachethink\facade\Cache
think\Configthink\facade\Config
think\Cookiethink\facade\Cookie
think\Dbthink\facade\Db
think\Envthink\facade\Env
think\Eventthink\facade\Event
think\Filesystemthink\facade\Filesystem
think\Langthink\facade\Lang
think\Logthink\facade\Log
think\Middlewarethink\facade\Middleware
think\Requestthink\facade\Request
think\Routethink\facade\Route
think\Sessionthink\facade\Session
think\Validatethink\facade\Validate
think\Viewthink\facade\View

中间件

定义解释

  • ​ 中间件主要用于拦截或过滤应用的HTTP请求(get/input/post等),并进行必要的业务处理。

  • ​ 如果用户请求过来的数据,进行安全处理,安全则放行,危险则打回请求。

  • ​ 新版的中间件分为全局中间件、应用中间件(多应用模式下有效)、路由中间件以及控制器中间件四个组。执行顺序分别为:

全局中间件->应用中间件->路由中间件->控制器中间件

命令生成

php think make:middleware 中间件名

定义方法

  • 中间件代码编写文件在app\middleware目录

  • 中间件handle方法的返回值必须是一个Response对象。

  • 中间件的入口执行方法必须是handle方法,而且第一个参数是Request对象,第二个参数是一个闭包。

  • 以下代码方法是固定定义方法,不可删除和修改:

     

结束调度

    public function end(\think\Response $response){// 回调行为}

中间件定义方法--示例解释:在这个中间件中我们判断当前请求的name参数等于think的时候进行重定向处理。否则,请求将进一步传递到应用中。要让请求继续传递到应用程序中,只需使用 $request 作为参数去调用回调函数 $next 。

<?phpnamespace app\middleware;class Check
{public function handle($request, \Closure $next){//`name`参数等于`think`的时候进行重定向处if ($request->param('name') == 'think') {//redirect重定向输出助手函数return redirect('index/think');}return $next($request);}
}

应用中间件

多应用模式,则支持应用中间件定义,你可以直接在应用目录下面增加middleware.php文件,只会在该应用下面生效。

middleware.php优先级设置,此数组中的中间件会按照数组中的顺序优先执行

app\应用目录\middleware\类名::class

全局中间件

全局中间件在app目录下面middleware.php文件中定义

middleware.php优先级设置,此数组中的中间件会按照数组中的顺序优先执行

app\middleware\类名::class
<?phpreturn [\app\middleware\Auth::class,'check','Hello',
];

中间件的注册应该使用完整的类名,如果已经定义了中间件别名(或者分组)则可以直接使用。

全局中间件的执行顺序就是定义顺序。可以在定义全局中间件的时候传入中间件参数,支持两种方式传入。

<?phpreturn [[\app\http\middleware\Auth::class, 'admin'],'Check',['hello','thinkphp'],
];

上面的定义表示 给Auth中间件传入admin参数,给Hello中间件传入thinkphp参数。

路由中间件

注册路由中间件

Route::rule('hello/:name','hello')->middleware(\app\middleware\Auth::class);

注册多个中间件

Route::rule('hello/:name','hello')->middleware([\app\middleware\Auth::class, \app\middleware\Check::class]);

然后,直接使用下面的方式注册中间件

Route::rule('hello/:name','hello')->middleware('check');

路由分组注册中间件

Route::group('hello', function(){Route::rule('hello/:name','hello');
})->middleware('auth');

域名注册中间件

Route::domain('admin', function(){// 注册域名下的路由规则
})->middleware('auth');

传入额外参数给中间件

Route::rule('hello/:name','hello')->middleware('auth', 'admin');

数组方式定义多个中间件

Route::rule('hello/:name','hello')->middleware([Auth::class, 'Check']);

统一传入同一个额外参数

Route::rule('hello/:name','hello')->middleware(['auth', 'check'], 'admin');

分开多次调用,指定不同的参数

Route::rule('hello/:name','hello')->middleware('auth', 'admin')->middleware('hello', 'thinkphp');

如果你希望某个路由中间件是全局执行(不管路由是否匹配),可以不需要在路由里面定义,支持直接在路由配置文件中定义,例如在config/route.php配置文件中添加:

'middleware'    =>    [app\middleware\Auth::class,app\middleware\Check::class,
],

这样,所有该应用下的请求都会执行AuthCheck中间件。

控制器中间件

支持为控制器定义中间件,只需要在控制器中protected定义middleware属性,例如:

<?php
namespace app\controller;class Index
{protected $middleware = ['auth'];public function index(){return 'index';}public function hello(){return 'hello';}
}

当执行index控制器的时候就会调用auth中间件,一样支持使用完整的命名空间定义。

如果需要设置控制器中间的生效操作,可以如下定义:

<?php
namespace app\controller;class Index
{protected $middleware = [ 'auth'    => ['except'   => ['hello'] ],'check' => ['only'       => ['hello'] ],];public function index(){return 'index';}public function hello(){return 'hello';}
}

定义中间件别名

在confing目录下的middleware.php中先预定义中间件

return ['alias' => ['auth'  => app\middleware\Auth::class,'check' => app\middleware\Check::class,],
];

可以支持使用别名定义一组中间件,例如:

return ['alias' => ['check' => [app\middleware\Auth::class,app\middleware\Check::class,],],
];

定义别名仍需中间件注册,注册可以直接写为别名;

事件

定义解释

我们通常会遇到用户注册或者登录后需要做一系列操作,通过事件系统可以做到不侵入原有代码完成登录的操作扩展,降低系统的耦合性的同时,也降低了BUG的可能性。

事件调用

事件系统的所有操作都通过think\facade\Event类进行静态调用

定义事件

命令生成

事件

php think make:event 事件名称

事件监听

php think make:listener 事件名称

事件订阅

php think make:subscribe 事件名称

事件绑定事件标识

直接在应用的event.php事件定义文件中批量绑定。

return ['bind'    =>    ['UserLogin' => 'app\event\UserLogin',// 更多事件绑定],
];

需要动态绑定,可以使用

Event::bind(['UserLogin' => 'app\event\UserLogin']);

事件传参

通过event方法中传入一个事件参数

// user是当前登录用户对象实例
event('UserLogin', $user);

如果是定义了事件类,可以直接传入事件对象实例

// user是当前登录用户对象实例
event(new UserLogin($user));

内置事件

内置的系统事件包括:

事件描述参数
AppInit应用初始化标签位
HttpRun应用开始标签位
HttpEnd应用结束标签位当前响应对象实例
LogWrite日志write方法标签位当前写入的日志信息
RouteLoaded路由加载完成
LogRecord日志记录V6.0.8+

AppInit事件定义必须在全局事件定义文件中定义,其它事件支持在应用的事件定义文件中定义。

数据库操作的回调也称为查询事件,是针对数据库的CURD操作而设计的回调方法,主要包括:

事件描述
before_selectselect查询前回调
before_findfind查询前回调
after_insertinsert操作成功后回调
after_updateupdate操作成功后回调
after_deletedelete操作成功后回调

查询事件的参数就是当前的查询对象实例。

模型事件包含:

钩子对应操作
after_read查询后
before_insert新增前
after_insert新增后
before_update更新前
after_update更新后
before_write写入前
after_write写入后
before_delete删除前
after_delete删除后

before_writeafter_write事件无论是新增还是更新都会执行。

模型事件方法的参数就是当前的模型对象实例。

路由

路由定义

路由表达式

Route::rule('路由表达式', '路由地址', '请求类型');

请求类型

类型描述快捷方法
GETGET请求get
POSTPOST请求post
PUTPUT请求put
DELETEDELETE请求delete
PATCHPATCH请求patch
*****任何请求类型any

快捷路由请求方法

Route::快捷方法名('路由表达式', '路由地址');

规则表达式

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

规则表达式的定义以/为参数分割符(无论你的PATH_INFO分隔符设置是什么,请确保在定义路由规则表达式的时候统一使用/进行URL参数分割,除非是使用组合变量的情况)。

每个参数中可以包括动态变量,例如:变量或者<变量>都表示动态变量**(新版推荐使用第二种方式,更利于混合变量定义),并且会自动绑定到操作方法的对应参数。

可选变量:支持对路由参数的可选定义,变量用[ ]包含起来后就表示该变量是路由匹配的可选变量,

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

完全匹配

路由表达式最后使用$符号,URL进行完全匹配,才会匹配成功。

匹配成功,如果希望URL进行完全匹配,可以在路由表达式最后使用$符号,例如:

Route::get('new/:cate$', 'News/category');

这样定义后

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

会匹配成功,而

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

则不会匹配成功。

如果是采用

Route::get('new/:cate', 'News/category');

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

全局URL完全匹配

如果需要全局进行URL完全匹配,可以在路由配置文件中设置

// 开启路由完全匹配
'route_complete_match'   => true,

开启全局完全匹配后,如果需要对某个路由关闭完全匹配,可以使用

Route::get('new/:cate', 'News/category')->completeMatch(false);

额外参数

append()额外参数指的是不在URL里面的参数,隐式传入需要的操作中,有时候能够起到一定的安全防护作用

Route::get('blog/:id','blog/read')->append(['status' => 1, 'app_id' =>5]);

上面的路由规则定义中statusapp_id参数都是URL里面不存在的,属于隐式传值。

路由标识

例如

// 注册路由到News控制器的read操作
Route::rule('new/:id','News/read')->name('new_read');

生成路由地址的时候就可以使用

url('new_read', ['id' => 10]);

如果不定义路由标识的话,系统会默认使用路由地址作为路由标识,例如可以使用下面的方式生成

url('News/read', ['id' => 10]);

强制路由

在路由配置文件中设置

'url_route_must'      =>  true,

将开启强制使用路由,这种方式下面必须严格给每一个访问地址定义路由规则(包括首页),否则将抛出异常。

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

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

变量规则

系统默认的变量规则设置是\w+,只会匹配字母、数字、中文和下划线字符,并不会匹配特殊符号以及其它字符,需要定义变量规则或者调整默认变量规则。

可以在route.php中自定义默认的变量规则,例如增加中划线字符的匹配:

'default_route_pattern'   =>   '[\w\-]+',

局部变量规则

pattern()局部变量规则,仅在当前路由有效:

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

不需要开头添加^或者在最后添加$,也不支持模式修饰符,系统会自动添加。

全局变量规则

设置全局变量规则,全部路由有效:

// 支持批量添加
Route::pattern(['name' => '\w+','id'   => '\d+',
]);

组合变量

例如:

Route::get('item-<name>-<id>', 'product/detail')->pattern(['name' => '\w+', 'id' => '\d+']);

组合变量的优势是路由规则中没有固定的分隔符,可以随意组合需要的变量规则和分割符,例如路由规则改成如下一样可以支持:

Route::get('item<name><id>', 'product/detail')->pattern(['name' => '[a-zA-Z]+', 'id' => '\d+']);
Route::get('item@<name>-<id>', 'product/detail')->pattern(['name' => '\w+', 'id' => '\d+']);

使用组合变量的情况下如果需要使用可选变量,则可以使用下面的方式:

Route::get('item-<name><id?>', 'product/detail')->pattern(['name' => '[a-zA-Z]+', 'id' => '\d+']);

动态路由

可以把路由规则中的变量传入路由地址中,就可以实现一个动态路由,例如:

// 定义动态路由
Route::get('hello/:name', 'index/:name/hello');

name变量的值作为路由地址传入。

动态路由中的变量也支持组合变量及拼装,例如:

Route::get('item-<name>-<id>', 'product_:name/detail')->pattern(['name' => '\w+', 'id' => '\d+']);

路由地址

  • 定义的路由表达式(可能需要外带参数的)最终需要到的实际地址(操作/控制器/类/重定向/路由/模板/闭包/调度对象/);

  • 即通俗的意思就是写的路由表达式中最终跳转的目标位置(不同的分类)

  • 多应用开启路由时需添加应用目录文件/后跟路由

路由到控制器/操作

定义解释

​ 满足条件的路由规则路由到相关的控制器和操作,然后由系统调度执行相关的操作

定义方法

​ Route::get('路由表达式','控制器/操作')

​ 例子:

// 路由到blog控制器
Route::get('blog/:id','Blog/read');

​ 路由地址中支持多级控制器

​ //Route::get('路由表达式','控制器.子控制器/操作')

​ 例子:

//Route::get('路由表达式','控制器.子控制器/操作')
Route::get('blog/:id','group.Blog/read');
// 表示路由到下面的控制器类
index/controller/group/Blog

​ 路由到动态的应用、控制器或者操作

​ //Route::get(':变量A/其他表达式内容', '控制器/:变量A');

​ 例如:

// action变量的值作为操作方法传入
Route::get(':action/blog/:id', 'Blog/:action');

路由到类的方法

路由地址的格式为(动态方法):

\完整类名@方法名

或者(静态方法)

\完整类名::方法名

例如:

Route::get('blog/:id','\app\index\service\Blog@read');

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

Route::get('blog/:id','\app\index\service\Blog::read');

重定向路由

可以直接使用redirect方法注册一个重定向路由

Route::redirect('blog/:id', 'http://blog.thinkphp.cn/read/:id', 302);

路由到模板

支持路由直接渲染模板输出。

// 路由到模板文件
Route::view('hello/:name', 'index/hello');

表示该路由会渲染当前应用下面的view/index/hello.html模板文件输出。

模板文件中可以直接输出当前请求的param变量,如果需要增加额外的模板变量,可以使用:

Route::view('hello/:name', 'index/hello', ['city'=>'shanghai']);

以上路由中在模板中无法输出name,但可以生成city变量。

Hello,{$name}--{$city}!

路由到闭包

通过路由访问路由自行处理请求返回结果,无需经过控制器操作,例如:

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

可以通过闭包的方式支持路由自定义响应输出,例如:

Route::get('hello/:name', function () {return response()->data('Hello,ThinkPHP')->code(999)->contentType('text/plain');
});//打开F12可查看name变量的文件包,以999的状态传递过来的

参数传递

闭包定义的时候支持参数传递,例如:

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

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

因此,如果我们访问的URL地址是:

http://serverName/hello/thinkphp

则浏览器输出的结果是:

Hello,thinkphp

依赖注入

闭包中使用依赖注入

Route::rule('hello/:name', function (Request $request, $name) {//获取请求方法$method = $request->method();//返回请请求方法return '[' . $method . '] Hello,' . $name;
});

路由到调度对象

支持路由到一个自定义的路由调度对象。具体调度类的实现可以参考内置的几个调度类的实现。

// 路由到自定义调度对象
Route::get('blog/:id',\app\route\BlogDispatch::class);namespace app\route;use think\route\Dispatch;
use think\route\Rule;
use think\Request;class BlogDispatch extends Dispatch
{public function exec(){// 自定义路由调度}
}

路由参数

路由分组及规则定义支持指定路由参数,这些参数主要完成路由匹配检测以及后续行为。

路由参数表

路由参数可以在定义路由规则的时候直接传入(批量),推荐使用方法配置更加清晰。

参数说明方法名
extURL后缀检测,支持匹配多个后缀ext
deny_extURL禁止后缀检测,支持匹配多个后缀denyExt
https检测是否https请求https
domain域名检测domain
complete_match是否完整匹配路由completeMatch
model绑定模型model
cache请求缓存cache
ajaxAjax检测ajax
pjaxPjax检测pjax
jsonJSON检测json
validate绑定验证器类进行数据验证validate
append追加额外的参数append
middleware注册路由中间件middleware
filter请求变量过滤filter
match路由闭包检测(V6.0.12+match
mergeRuleRegex路由规则合并解析, 只能用于路由分组或者域名路由mergeRuleRegex()

用法举例

Route::get('new/:id', 'News/read')->ext('html')->https();

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

批量设置路由参数

option方法,用法举例:

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

URL后缀

URL后缀如果是全局统一的话,可以在路由配置文件中设置url_html_suffix参数,如果当前访问的URL地址中的URL后缀是允许的伪静态后缀,那么后缀本身是不会被作为参数值传入的。

配置值描述
false禁止伪静态访问
空字符串允许任意伪静态后缀
html只允许设置的伪静态后缀
html|htm允许多个伪静态后缀
// 定义GET请求路由规则 并设置URL后缀为html的时候有效
Route::get('new/:id', 'News/read')->ext('html');

支持匹配多个后缀,例如:

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

如果ext方法不传入任何值,表示不允许使用任何后缀访问。

禁止访问的URL后缀

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

如果denyExt方法不传入任何值,表示必须使用后缀访问。

域名检测

使用完整域名或者子域名进行检测,例如:

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

如果需要给子域名定义批量的路由规则,建议使用domain方法进行路由定义。

HTTPS检测

支持检测当前是否HTTPS访问

// 必须使用HTTPS访问
Route::get('new/:id', 'News/read')->https();

AJAX/PJAX/JSON检测

可以检测当前是否为AJAX/PJAX/JSON请求。

// 必须是JSON请求访问
Route::get('new/:id', 'News/read')->json();

请求变量检测

可以在匹配路由地址之外,额外检查请求变量是否匹配,只有指定的请求变量也一致的情况下才能匹配该路由。

// 检查type变量
Route::post('new/:id', 'News/save')->filter('type', 1);   // 检查多个请求变量
Route::post('new/:id', 'News/save')->filter([ 'type' => 1,'status'=> 1 ]);       

闭包检测

通过闭包来检测当前路由或分组是否匹配

// 闭包检测
Route::get('new/:id', 'News/read')->match(function(Rule $rule,Request $request) {// 如果返回false 则视为不匹配return false;});

追加额外参数

可以在定义路由的时候隐式追加额外的参数,这些参数不会出现在URL地址中。

Route::get('blog/:id', 'Blog/read')->append(['app_id' => 1, 'status' => 1]);

在路由请求的时候会同时传入app_idstatus两个参数。

路由绑定模型

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

Route::get('hello/:id', 'index/hello')->model('id', '\app\index\model\User');

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

如果你的模型绑定使用的是id作为查询条件的话,还可以简化成下面的方式

Route::get('hello/:id', 'index/hello')->model('\app\index\model\User');

默认情况下,如果没有查询到模型数据,则会抛出异常,如果不希望抛出异常,可以使用

Route::rule('hello/:id', 'index/hello')->model('id', '\app\index\model\User', false);

可以定义模型数据的查询条件,例如:

Route::rule('hello/:name/:id', 'index/hello')->model('id&name', '\app\index\model\User');

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

也可以使用闭包来自定义返回需要的模型对象

Route::rule('hello/:id', 'index/hello')->model(function ($id) {$model = new \app\index\model\User;return $model->where('id', $id)->find();});

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

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

请求缓存

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

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

表示对当前路由请求缓存3600秒,更多内容可以参考请求缓存一节。

动态参数

如果你需要额外自定义一些路由参数,可以使用下面的方式:

Route::get('new/:name$', 'News/read')->option('rule','admin');

或者使用动态方法

Route::get('new/:name$', 'News/read')->rule('admin');

在后续的路由行为后可以调用该路由的rule参数来进行权限检查。

路由中间件

路由中间件注册方式

Route::rule('hello/:name','hello')->middleware(\app\middleware\Auth::class);

路由分组注册中间件

Route::group('hello', function(){Route::rule('hello/:name','hello');
})->middleware(\app\middleware\Auth::class);

传入额外参数给中间件

Route::rule('hello/:name','hello')->middleware(\app\middleware\Auth::class,'admin');

多个中间件使用数组方式

Route::rule('hello/:name','hello')->middleware([\app\middleware\Auth::class,\app\middleware\Check::class]);

统一传入同一个额外参数

Route::rule('hello/:name','hello')->middleware([\app\middleware\Auth::class, \app\middleware\Check::class], 'admin');

某个路由中间件是全局执行(不管路由是否匹配),可以不需要在路由里面定义,支持直接在路由配置文件中定义,例如在config/route.php配置文件中添加:

'middleware'    =>    [app\middleware\Auth::class,app\middleware\Check::class,
],

所有该应用下的请求都会执行AuthCheck中间件。

路由分组

把相同前缀的路由定义合并分组

分组方法

使用Route类的group方法进行注册,给分组路由定义一些公用的路由设置参数,例如:

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

分组路由支持所有的路由参数设置,具体参数的用法请参考路由参数章节内容。

仅仅是用于对一些路由规则设置一些公共的路由参数(也称之为虚拟分组),也可以使用:

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

路由分组支持嵌套,例如:

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

如果使用了嵌套分组的情况,子分组会继承父分组的参数和变量规则,而最终的路由规则里面定义的参数和变量规则为最优先。

可以使用prefix方法简化相同路由地址的定义,例如下面的定义

Route::group('blog', function () {Route::get(':id', 'blog/read');Route::post(':id', 'blog/update');Route::delete(':id', 'blog/delete');
})->ext('html')->pattern(['id' => '\d+']);

可以简化为

Route::group('blog', function () {Route::get(':id', 'read');Route::post(':id', 'update');Route::delete(':id', 'delete');
})->prefix('blog/')->ext('html')->pattern(['id' => '\d+']);

路由完全匹配

如果希望某个分组下面的路由都采用completeMatch()完全匹配,可以使用

Route::group('blog', function () {Route::get(':id', 'read');Route::post(':id', 'update');Route::delete(':id', 'delete');
})->completeMatch()->prefix('blog/')->ext('html')->pattern(['id' => '\d+']);

延迟路由解析

支持延迟路由解析,也就是说你定义的路由规则(主要是分组路由和域名路由规则)在加载路由定义文件的时候并没有实际注册,而是在匹配到路由分组或者域名的情况下,才会实际进行注册和解析,大大提高了路由注册和解析的性能。

默认是关闭延迟路由解析的,你可以在路由配置文件中设置:

// 开启路由延迟解析
'url_lazy_route'         => true,

命令指令

开启延迟路由解析后,如果你需要生成路由反解URL,需要使用

php think optimize:route

来生成路由缓存解析。

通过路由分组或者域名路由来定义路由才能发挥延迟解析的优势。

一旦开启路由的延迟解析,将会对定义的域名路由和分组路由进行延迟解析,也就是说只有实际匹配到该域名或者分组后才会进行路由规则的注册,避免不必要的注册和解析开销。

路由规则合并解析

同一个路由分组下的路由规则支持合并解析,而不需要遍历该路由分组下的所有路由规则,可以大大提升路由解析的性能。

对某个分组单独开启合并规则解析的用法如下:

Route::group('user', function () {Route::rule('hello/:name','hello');Route::rule('think/:name','think');
})->mergeRuleRegex();

这样该分组下的所有路由规则无论定义多少个都只需要匹配检查一次即可(实际上只会合并检查符合当前请求类型的路由规则)。

mergeRuleRegex方法只能用于路由分组或者域名路由(域名路由其实是一个特殊的分组)。

或者在路由配置文件中设置开启全局合并规则(对所有分组有效)

// 开启路由合并解析
'route_rule_merge'    => true,

传入额外参数

可以统一给分组路由传入额外的参数

Route::group('blog', [':id'   => 'Blog/read',':name' => 'Blog/read',
])->ext('html')
->pattern(['id' => '\d+'])
->append(['group_id' => 1]);

上面的分组路由统一传入了group_id参数,该参数的值可以通过Request类的param方法获取。

指定分组调度

V6.0.8+版本开始,可以给路由分组单独指定调度类,例如:

Route::group('blog', [':id'   => 'Blog/read',':name' => 'Blog/read',
])->dispatcher(GroupDispatcher::class);

资源路由

支持设置RESTFul请求的资源路由,方式如下:

Route::resource('blog', 'Blog');

注册资源路由

表示注册了一个名称为blog的资源路由到Blog控制器,系统会自动注册7个路由规则,如下:

标识请求类型生成路由规则对应操作方法(默认)
indexGETblogindex
createGETblog/createcreate
savePOSTblogsave
readGETblog/:idread
editGETblog/:id/editedit
updatePUTblog/:idupdate
deleteDELETEblog/:iddelete

前端HTML操作中只能使用post/get,无法使用PUT/DELETE需要使用js中ajax方式进行提交请求

具体指向的控制器由路由地址决定,你只需要为Blog控制器创建以上对应的操作方法就可以支持下面的URL访问

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

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

<?php
namespace app\controller;class Blog
{public function index(){}public function read($id){}public function edit($id){}
}

命令创建

通过命令行创建一个资源控制器类。

>php think make:controller index@Blog

改变默认的参数名

可以改变默认的id参数名,例如:

Route::resource('blog', 'Blog')->vars(['blog' => 'blog_id']);

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

<?php
namespace app\controller;class Blog
{public function index(){}public function read($blog_id){}public function edit($blog_id){}
}

限定执行的方法

也可以在定义资源路由的时候限定执行的方法(标识),例如:

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

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

更改标识的对应操作

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

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

URL访问

设置之后,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', 'Blog');
Route::resource('blog.comment','Comment');

URL访问地址

就可以访问如下地址:

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控制器对应的操作方法如下:

<?phpnamespace app\controller;class Comment
{public function edit($id, $blog_id){}
}

edit方法中的参数顺序可以随意,但参数名称必须满足定义要求。

改变资源变量名

如果需要改变其中的变量名,可以使用:

// 更改嵌套资源路由的blog资源的资源变量名为blogId
Route::resource('blog.comment', 'index/comment')->vars(['blog' => 'blogId']);

Comment控制器对应的操作方法改变为:

<?php
namespace app\controller;class Comment
{public function edit($id, $blogId){}
}

MISS路由

所有路由的不匹配的情况下,执行MISS路由

全局MISS路由

如果希望在没有匹配到所有的路由规则后执行一条设定的路由,可以注册一个单独的MISS路由:

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

或者使用闭包定义

Route::miss(function() {return '404 Not Found!';
});

一旦设置了MISS路由,相当于开启了强制路由模式

当所有已经定义的路由规则都不匹配的话,会路由到miss方法定义的路由地址。

你可以限制MISS路由的请求类型

// 只有GET请求下MISS路由有效
Route::miss('public/miss', 'get');

分组MISS路由

分组支持独立的MISS路由,例如如下定义:

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

域名MISS路由

支持给某个域名设置单独的MISS路由

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

跨域请求

不同域名请求本网站的数据

跨域请求方法

如果某个路由或者分组需要支持跨域请求,可以使用

Route::get('new/:id', 'News/read')->ext('html')->allowCrossDomain();

跨域请求一般会发送一条OPTIONS的请求,一旦设置了跨域请求的话,不需要自己定义OPTIONS请求的路由,系统会自动加上。

系统默认Header

跨域请求系统会默认带上一些Header,包括:

Access-Control-Allow-Origin:*
Access-Control-Allow-Methods:GET, POST, PATCH, PUT, DELETE
Access-Control-Allow-Headers:Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-Requested-With

更改Header信息

你可以添加或者更改Header信息,使用

Route::get('new/:id', 'News/read')->ext('html')->allowCrossDomain(['Access-Control-Allow-Origin'        => 'thinkphp.cn','Access-Control-Allow-Credentials'   => 'true']);

V6.0.3+版本开始增加了默认的预检缓存有效期(默认为30分钟),你可以自定义有效期,例如:

Route::get('new/:id', 'News/read')->ext('html')->allowCrossDomain(['Access-Control-Allow-Origin'        => 'thinkphp.cn','Access-Control-Allow-Credentials'   => 'true','Access-Control-Max-Age'             => 600,]);

URL地址生成

自动把各种路径转化为客户看到的那种URL形式。

开启了路由延迟解析,需要生成路由映射缓存才能支持全部的路由地址的反转解析。

URL生成使用 \think\facade\Route::buildUrl() 方法即可。该方法会返回一个think\route\Url对象实例,因为使用了__toString方法,因此可以直接输出路由地址。

echo \think\facade\Route::buildUrl();

如果是通过数据返回客户端,你可以先强制转换为字符串类型后再返回。

$url = (string) \think\facade\Route::buildUrl();

使用路由标识

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

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

在没有指定路由标识的情况下,可以直接使用路由地址来生成URL地址:

Route::buildUrl('blog/read', ['id' => 5, 'name' => 'thinkphp']);

如果我们在注册路由的时候指定了路由标识

Route::rule('blog/:id','blog/read')->name('blog_read');

那么必须使用路由标识来生成URL地址

Route::buildUrl('blog_read', ['id' => 5, 'name' => 'thinkphp']);

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

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

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

/blog/5/name/thinkphp.html

如果你配置了:

'url_common_param'=>true

那么生成的URL地址变为:

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

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

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

使用路由地址

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

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

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

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

Route::buildUrl('/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后缀生成,则可以使用:

Route::buildUrl('blog/read', ['id'=>5])->suffix('shtml');

域名生成

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

自动生成域名

Route::buildUrl('index/blog/read',  ['id'=>5])->suffix('shtml')->domain(true);

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

例如,我们注册了域名路由信息如下:

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

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

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

指定域名

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

Route::buildUrl('blog/read', ['id'=>5])->domain('blog');

或者传入完整的域名

Route::buildUrl('index/blog/read', ['id'=>5])->domain('blog.thinkphp.cn');

生成的URL地址为:

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

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

Route::buildUrl('index/blog/read@blog',  ['id'=>5]);
Route::buildUrl('index/blog/read@blog.thinkphp.cn',  ['id'=>5]);

生成锚点

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

Route::buildUrl('index/blog/read#anchor@blog', ['id'=>5]);

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

生成的URL地址为:

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

加上入口文件

有时候我们生成的URL地址可能需要加上index.php或者去掉index.php,大多数时候系统会自动判断,如果发现自动生成的地址有问题,可以使用下面的方法:

Route::buildUrl('index/blog/read', ['id'=>5])->root('/index.php');

助手函数

系统提供了一个url助手函数用于完成相同的功能,例如:

url('index/blog/read', ['id'=>5])->suffix('html')->domain(true)->root('/index.php');

控制器

控制器文件通常放在controller下面,类名和文件名保持大小写一致,并采用驼峰命名(首字母大写)。

改变目录名

如果要改变controller目录名,需要在route.php配置文件中设置:

'controller_layer'    =>    'controllers',

控制器后缀

如果你希望避免引入同名模型类的时候冲突,可以在route.php配置文件中设置

// 使用控制器后缀
'controller_suffix'     => true,

这样,上面的控制器类就需要改成

<?php
namespace app\controller;class UserController
{public function login(){return 'login';}
}

相应的控制器类文件也要改为

app\controller\UserController.php

渲染输出

默认情况下,控制器的输出全部采用return的方式,无需进行任何的手动输出,系统会自动完成渲染内容的输出

下面都是有效的输出方式:

<?php
namespace app\index\controller;class Index 
{public function hello(){// 输出hello,world!return 'hello,world!';}public function json(){// 输出JSONreturn json($data);}public function read(){// 渲染默认模板输出return view();}}

控制器一般不需要任何输出,直接return即可。并且控制器在json请求会自动转换为json格式输出。

不要在控制器中使用包括dieexit在内的中断代码。如果你需要调试并中止执行,可以使用系统提供的halt助手函数。

halt('输出测试');

多级控制器

支持任意层次级别的控制器,并且支持路由,例如:

<?php
namespace app\index\controller\user;class  Blog 
{public function index(){return 'index';}}

该控制器类的文件位置为:

app/index/controller/user/Blog.php

访问地址可以使用

http://serverName/index.php/user.blog/index

由于URL访问不能访问默认的多级控制器(可能会把多级控制器名误识别为URL后缀),因此建议所有的多级控制器都通过路由定义后访问,如果要在路由定义中使用多级控制器,可以使用:

Route::get('user/blog','user.blog/index');

基础控制器

建议将控制器继承一个基础控制器,系统提供了一个app\BaseController基础控制器类,可以对该基础控制器进行修改。

基础控制器的位置可以随意放置,只需要注意更改命名空间即可。

该基础控制器仅仅提供了控制器验证功能,并注入了think\Appthink\Request对象,因此你可以直接在控制器中使用apprequest属性调用think\Appthink\Request对象实例,下面是一个例子:

namespace app\controller;use app\BaseController;class Index extends BaseController
{public function index(){//BaseController基础控制器类注入了`think\App`和`think\Request`对象$action = $this->request->action();$path = $this->app->getBasePath();}
}

控制器验证

基础控制器提供了数据验证功能,使用如下:

namespace app\controller;use app\BaseController;
use think\exception\ValidateException;class Index extends BaseController
{public function index(){try {$this->validate( ['name'  => 'thinkphp','email' => 'thinkphp@qq.com',],  'app\index\validate\User');} catch (ValidateException $e) {// 验证失败 输出错误信息dump($e->getError());}}
}

该示例使用了验证器功能,具体可以参考验证章节的验证器部分,这里暂时不做展开。

如果需要批量验证,可以改为:

namespace app\controller;use app\BaseController;
use think\exception\ValidateException;class Index extends BaseController
{// 开启批量验证protected $batchValidate = true;public function index(){try {$this->validate( ['name'  => 'thinkphp','email' => 'thinkphp@qq.com',],  'app\index\validate\User');} catch (ValidateException $e) {// 验证失败 输出错误信息dump($e->getError());}}
}

错误返回(空)控制器

当系统找不到指定的控制器名称的时候,系统会尝试定位当前应用下的空控制器(Error)类,利用这个机制我们可以用来定制错误页面和进行URL的优化。

例如,下面是单应用模式下,我们可以给项目定义一个Error控制器类。

<?php
namespace app\controller;class Error 
{public function __call($method, $args){return 'error request!';}
}

资源控制器

资源控制器可以让你轻松的创建RESTFul资源控制器,可以通过命令行生成需要的资源控制器,例如生成index应用的Blog资源控制器使用:

php think make:controller index@Blog

或者使用完整的命名空间生成

php think make:controller app\index\controller\Blog

如果只是用于接口开发,可以使用

php think make:controller index@Blog --api

然后你只需要为资源控制器注册一个资源路由:

Route::resource('blog', 'Blog');

设置后会自动注册7个路由规则,对应资源控制器的7个方法,更多内容请参考资源路由章节

控制器中间件

支持为控制器定义中间件,你只需要在你的控制器中定义middleware属性,例如:

<?php
namespace app\controller;use app\middleware\Auth;class Index 
{protected $middleware = [Auth::class];public function index(){return 'index';}public function hello(){return 'hello';}
}

当执行index控制器的时候就会调用Auth中间件,一样支持使用完整的命名空间定义。

如果需要设置控制器中间的生效操作,可以如下定义:

<?php
namespace app\index\controller;class Index 
{protected $middleware = [ Auth::class . ':admin'    => ['except'   => ['hello'] ],'Hello' => ['only'       => ['hello'] ],];public function index(){return 'index';}public function hello(){return 'hello';}
}

中间件传参

如果需要给中间件传参,可以的定义的时候使用

<?php
namespace app\controller;class Index 
{protected $middleware = ['Auth'];public function index(){return 'index';}public function hello(){return 'hello';}
}

控制器传参

可以通过给请求对象赋值的方式传参给控制器(或者其它地方),例如

<?phpnamespace app\http\middleware;class Hello
{public function handle($request, \Closure $next){$request->hello = 'ThinkPHP';return $next($request);}
}

然后在控制器的方法里面可以直接使用

//引用use think\Request;
class 类名 extends BaseController {//protected $middleware = ['所引用的中间件完全名称或别名'];      ---这一步可以省略public function index(Request $request){return $request->hello; // 输出结果为:ThinkPHP}}

验证

可以在控制器中使用validate助手函数(或者封装验证方法)进行验证。

验证器定义

为具体的验证场景或者数据表定义好验证器类,直接调用验证类的check方法即可完成验证,下面是一个例子:

命令生成

可以使用下面的指令快速生成User验证器。

php think make:validate User

$rule属性

验证规则

我们定义一个\app\validate\User验证器类用于User的验证。

namespace app\validate;use think\Validate;class User extends Validate
{protected $rule = ['name'  =>  'require|max:25','email' =>  'email',];}

$message属性

使用message属性定义错误提示信息,例如:

namespace app\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'        => '邮箱格式错误',    ];}

如果没有定义错误提示信息,则使用系统默认的提示信息

控制器引用验证

在需要进行User验证的控制器方法中,添加如下代码即可:

<?php
namespace app\controller;use app\validate\User;
use think\exception\ValidateException;class Index
{public function index(){try {//绑定User验证器,使用check验证方法传入name与email字段与其对应的值向user验证器进行验证validate(User::class)->check(['name'  => 'thinkphp这里会报错','email' => 'thinkphp@qq.com',]);} catch (ValidateException $e) {// 验证失败 输出错误信息dump($e->getError());}}
}

批量验证

默认情况下,一旦有某个数据的验证规则不符合,就会停止后续数据及规则的验证,如果希望批量进行验证(全部验证完后才停止),可以设置:

<?php
namespace app\controller;use app\validate\User;
use think\exception\ValidateException;class Index
{public function index(){try {$result = validate(User::class)->batch(true)->check(['name'  => 'thinkphp','email' => 'thinkphp@qq.com',]);if (true !== $result) {// 验证失败 输出错误信息dump($result);}} catch (ValidateException $e) {// 验证失败 输出错误信息dump($e->getError());}}
}

自定义验证规则

系统内置了一些常用的规则(参考后面的内置规则),如果不能满足需求,可以在验证器重添加额外的验证方法,例如:

<?php
namespace app\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 : '名称错误';}
}

验证方法传参

验证方法可以传入的参数共有5个(后面三个根据情况选用),依次为:

  • 验证数据
  • 验证规则
  • 全部数据(数组)
  • 字段名
  • 字段描述

内置验证规则

验证规则严格区分大小写

格式验证类

格式验证类的验证规则如果在使用静态方法调用的时候需要加上is(以number验证为例,需要使用 isNumber())。

require

验证某个字段必须,例如:

'name'=>'require'

如果验证规则没有添加require就表示没有值的话不进行验证

由于require属于PHP保留字,所以在使用方法验证的时候必须使用isRequire或者must方法调用。

number

验证某个字段的值是否为纯数字(采用ctype_digit验证,不包含负数和小数点),例如:

'num'=>'number'

integer

验证某个字段的值是否为整数(采用filter_var验证),例如:

'num'=>'integer'

float

验证某个字段的值是否为浮点数字(采用filter_var验证),例如:

'num'=>'float'

boolean 或者 bool

验证某个字段的值是否为布尔值(采用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'

chs

验证某个字段的值只能是汉字,例如:

'name'=>'chs'

chsAlpha

验证某个字段的值只能是汉字、字母,例如:

'name'=>'chsAlpha'

chsAlphaNum

验证某个字段的值只能是汉字、字母和数字,例如:

'name'=>'chsAlphaNum'

chsDash

验证某个字段的值只能是汉字、字母、数字和下划线_及破折号-,例如:

'name'=>'chsDash'

cntrl

验证某个字段的值只能是控制字符(换行、缩进、空格),例如:

'name'=>'cntrl'

graph

验证某个字段的值只能是可打印字符(空格除外),例如:

'name'=>'graph'

print

验证某个字段的值只能是可打印字符(包括空格),例如:

'name'=>'print'

lower

验证某个字段的值只能是小写字符,例如:

'name'=>'lower'

upper

验证某个字段的值只能是大写字符,例如:

'name'=>'upper'

space

验证某个字段的值只能是空白字符(包括缩进,垂直制表符,换行符,回车和换页字符),例如:

'name'=>'space'

xdigit

验证某个字段的值只能是十六进制字符串,例如:

'name'=>'xdigit'

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'

mobile

验证某个字段的值是否为有效的手机,例如:

'mobile'=>'mobile'

idCard

验证某个字段的值是否为有效的身份证格式,例如:

'id_card'=>'idCard'

macAddr

验证某个字段的值是否为有效的MAC地址,例如:

'mac'=>'macAddr'

zip

验证某个字段的值是否为有效的邮政编码,例如:

'zip'=>'zip'

长度和区间验证类

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',

该规则可以用于某个后台的访问权限,多个IP用逗号分隔

denyIp:allow1,allow2,...

验证当前请求的IP是否禁止访问,例如:

'name'   => 'denyIp:114.45.4.55',

多个IP用逗号分隔

字段比较类

confirm

验证某个字段是否和另外一个字段的值一致,例如:

'repassword'=>'require|confirm:password'

支持字段自动匹配验证规则,如passwordpassword_confirm是自动相互验证的,只需要使用

'password'=>'require|confirm'

会自动验证和password_confirm进行字段比较是否一致,反之亦然。

different

验证某个字段是否和另外一个字段的值不一致,例如:

'name'=>'require|different:account'

eq 或者 = 或者 same

验证是否等于某个值,例如:

'score'=>'eq:100'
'num'=>'=:100'
'num'=>'same:100'

egt 或者 >=

验证是否大于等于某个值,例如:

'score'=>'egt:60'
'num'=>'>=:100'

gt 或者 >

验证是否大于某个值,例如:

'score'=>'gt:60'
'num'=>'>:100'

elt 或者 <=

验证是否小于等于某个值,例如:

'score'=>'elt:100'
'num'=>'<=:100'

lt 或者 <

验证是否小于某个值,例如:

'score'=>'lt:100'
'num'=>'<:100'

字段比较

验证对比其他字段大小(数值大小对比),例如:

'price'=>'lt:market_price'
'price'=>'<:market_price'

filter验证

支持使用filter_var进行验证,例如:

'ip'=>'filter:validate_ip'

正则验证

支持直接使用正则验证,例如:

'zip'=>'\d{6}',
// 或者
'zip'=>'regex:\d{6}',

如果你的正则表达式中包含有|符号的话,必须使用数组方式定义。

'accepted'=>['regex'=>'/^(yes|on|1)$/i'],

也可以实现预定义正则表达式后直接调用,例如在验证器类中定义regex属性

namespace app\index\validate;use think\Validate;class User extends Validate
{protected $regex = [ 'zip' => '\d{6}'];protected $rule = ['name'  =>  'require|max:25','email' =>  'email',];}

然后就可以使用

'zip' =>   'regex:zip',

上传验证

file

验证是否是一个上传文件

image:width,height,type

验证是否是一个图像文件,width height和type都是可选,width和height必须同时定义。

fileExt:允许的文件后缀

验证上传文件后缀

fileMime:允许的文件类型

验证上传文件类型

fileSize:允许的文件字节大小

验证上传文件大小

其它验证

token:表单令牌名称

表单令牌验证

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'

requireWithout:field

验证某个字段没有值的时候必须,例如:

// mobile和phone必须输入一个
'mobile' => 'requireWithout:phone',
'phone'  => 'requireWithout:mobile'

requireCallback:callable

验证当某个callable为真的时候字段必须,例如:

// 使用check_require方法检查是否需要验证age字段必须
'age'=>'requireCallback:check_require|number'

用于检查是否需要验证的方法支持两个参数,第一个参数是当前字段的值,第二个参数则是所有的数据。

function check_require($value, $data){if(empty($data['birthday'])){return true;}
}

只有check_require函数返回true的时候age字段是必须的,并且会进行后续的其它验证。

验证规则

如果使用了验证器的话,通常通过rule属性定义验证规则,而如果使用的是独立验证的话,则是通过rule方法进行定义。

属性定义

属性定义方式仅限于验证器,通常类似于下面的方式:

<?php
namespace app\validate;use think\Validate;class User extends Validate
{protected $rule = ['name'  => 'require|max:25','age'   => 'number|between:1,120','email' => 'email',];}

系统内置了一些常用的验证规则可以满足大部分的验证需求,具体每个规则的含义参考内置规则一节。

一个字段可以使用多个验证规则,为了避免混淆可以在rule属性中使用数组定义规则

<?php
namespace app\validate;use think\Validate;class User extends Validate
{protected $rule = ['name'  => ['require', 'max' => 25, 'regex' => '/^[\w|\d]\w+/'],'age'   => ['number', 'between' => '1,120'],'email' => 'email',];}

方法定义

独立验证(即手动调用验证类进行验证)方式的话,通常使用rule方法进行验证规则的设置,举例说明如下。独立验证通常使用Facade或者自己实例化验证类。

$validate = \think\facade\Validate::rule('age', 'number|between:1,120')
->rule(['name'  => 'require|max:25','email' => 'email'
]);$data = ['name'  => 'thinkphp','email' => 'thinkphp@qq.com'
];if (!$validate->check($data)) {dump($validate->getError());
}

rule方法传入数组表示批量设置规则。

对象化规则定义

rule方法还可以支持使用对象化的规则定义。

我们把上面的验证代码改为

use think\facade\Validate;
use think\validate\ValidateRule as Rule;$validate = Validate::rule('age', Rule::isNumber()->between([1,120]))
->rule(['name'  => Rule::isRequire()->max(25),'email' => Rule::isEmail(),
]);$data = ['name'  => 'thinkphp','email' => 'thinkphp@qq.com'
];if (!$validate->check($data)) {dump($validate->getError());
}

闭包验证

可以对某个字段使用闭包验证,例如:

$validate = Validate::rule(['name'  => function($value) { return 'thinkphp' == strtolower($value) ? true : false;},
]);

闭包支持传入两个参数,第一个参数是当前字段的值(必须),第二个参数是所有数据(可选)。

如果使用了闭包进行验证,则不再支持对该字段使用多个验证规则。

闭包函数如果返回true则表示验证通过,返回false表示验证失败并使用系统的错误信息,如果返回字符串,则表示验证失败并且以返回值作为错误提示信息。

$validate = Validate::rule(['name'  => function($value) { return 'thinkphp' == strtolower($value) ? true : '用户名错误';},
]);

属性方式定义验证规则不支持使用对象化规则定义和闭包定义

全局扩展

你可以在扩展包或者应用里面全局注册验证规则,使用方法

Validate::maker(function($validate) {$validate->extend('extra', 'extra_validate_callback');
});

错误信息

使用默认的错误提示信息

如果没有定义任何的验证提示信息,系统会显示默认的错误信息,例如:

namespace app\validate;use think\Validate;class User extends Validate
{protected $rule = ['name'  => 'require|max:25','age'   => 'number|between:1,120','email' => 'email',];}
$data = ['name'  => 'thinkphp','age'   => 121,'email' => 'thinkphp@qq.com',
];$validate = new \app\validate\User;
$result = $validate->check($data);if(!$result){echo $validate->getError();
}

会输出

" age只能在 1 - 120 之间"

可以给age字段设置中文名,例如:

namespace app\validate;use think\Validate;class User extends Validate
{protected $rule = ['name'  => 'require|max:25','age|年龄'   => 'number|between:1,120','email' => 'email',];}

会输出

"年龄只能在 1 - 120 之间"

单独定义提示信息

如果要输出自定义的错误信息,可以定义message属性:

namespace app\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'        => '邮箱格式错误',];
}
$data = ['name'  => 'thinkphp','age'   => 121,'email' => 'thinkphp@qq.com',
];$validate = new \app\validate\User;
$result = $validate->check($data);if(!$result){echo $validate->getError();
}

会输出

"年龄必须在1~120之间"

错误信息可以支持数组定义,并且通过JSON方式传给前端。

namespace app\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' => ['code' => 1001, 'msg' => '名称必须'],'name.max'     => ['code' => 1002, 'msg' => '名称最多不能超过25个字符'],'age.number'   => ['code' => 1003, 'msg' => '年龄必须是数字'],'age.between'  => ['code' => 1004, 'msg' => '年龄必须在1~120之间'],'email'        => ['code' => 1005, 'msg' =>'邮箱格式错误'],];
}

使用多语言

验证信息提示支持多语言功能,你只需要给相关错误提示信息定义语言包,例如:

namespace app\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_require','name.max'     => 'name_max','age.number'   => 'age_number','age.between'  => 'age_between','email'        => 'email_error',];
}

你可以在语言包文件中添加下列定义:

'name_require '   =>   '姓名必须',
'name_max'        =>   '姓名最大长度不超过25个字符',
'age_between' =>   '年龄必须在1~120之间',
'age_number'  =>   '年龄必须是数字',
'email_error' =>   '邮箱格式错误',

系统内置的验证错误提示均支持多语言(参考框架目录下的lang/zh-cn.php语言定义文件)。

验证场景

验证场景仅针对验证器有效,独立验证不存在验证场景的概念

验证器支持定义场景,并且验证不同场景的数据,例如:

namespace app\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'],];    
}

然后可以在验证方法中制定验证的场景

$data = ['name'  => 'thinkphp','age'   => 10,'email' => 'thinkphp@qq.com',
];try {validate(app\validate\User::class)->scene('edit')->check($data);
} catch (ValidateException $e) {// 验证失败 输出错误信息dump($e->getError());
}

可以单独为某个场景定义方法(方法的命名规范是scene+场景名),并且对某些字段的规则重新设置,例如:

  • 注意:场景名不区分大小写,且在调用的时候不能将驼峰写法转为下划线
namespace app\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'        => '邮箱格式错误',    ];// edit 验证场景定义public function sceneEdit(){return $this->only(['name','age'])->append('name', 'min:5')->remove('age', 'between')->append('age', 'require|max:100');}    
}

主要方法说明如下:

方法名描述
only场景需要验证的字段
remove移除场景中的字段的部分验证规则
append给场景中的字段需要追加验证规则

如果对同一个字段进行多次规则补充(包括移除和追加),必须使用下面的方式:

remove('field', ['rule1','rule2'])
// 或者
remove('field', 'rule1|rule2')

下面的方式会导致rule1规则remove不成功

remove('field', 'rule1')
->remove('field', 'rule2')

路由验证

可以在路由规则定义的时候调validate方法指定验证器类对请求的数据进行验证。

例如下面的例子表示对请求数据使用验证器类app\validate\User进行自动验证,并且使用edit验证场景:

Route::post('hello/:id', 'index/hello')->validate(\app\validate\User::class,'edit');

或者不使用验证器而直接传入验证规则

Route::post('hello/:id', 'index/hello')->validate(['name'    =>   'min:5|max:50','email'   =>   'email',]);

也支持使用对象化规则定义

Route::post('hello/:id', 'index/hello')->validate(['name'    =>   ValidateRule::min(5)->max(50),'email'   =>   ValidateRule::isEmail(),]);

表单令牌(没搞懂)

表单令牌的作用在于防止数据的重复提交,原理是生成一个token值,用session缓存起来,这个过程是在打开填写表单的页面时就生成了,然后我们填写完数据是提交到php页面,此时的token值会和之前缓存起来的值进行对比,如果不一样就会报错。

注意:在全局中间件,打开 session, TP6 默认不开启 session, _token又是依赖session

添加令牌Token验证

验证规则支持对表单的令牌验证,首先需要在你的表单里面增加下面隐藏域:

<input type="hidden" name="__token__" value="{:token()}" />

也可以直接使用

{:token_field()}

默认的令牌Token名称是__token__,如果需要自定义名称及令牌生成规则可以使用

{:token_field('__hash__', 'md5')}

第二个参数表示token(依赖session)的生成规则,也可以使用闭包。

如果你没有使用默认的模板引擎,则需要自己生成表单隐藏域

namespace app\controller;use think\Request;
use think\facade\View;class Index
{public function index(Request $request){//没有使用默认的模板引擎,则需要自己生成表单隐藏域$token = $request->buildToken('__token__', 'sha1');View::assign('token', $token);return View::fetch();}
}

然后在模板表单中使用:

<input type="hidden" name="__token__" value="{$token}" />

AJAX提交

如果是AJAX提交的表单,可以将token设置在meta

<meta name="csrf-token" content="{:token()}">

或者直接使用

{:token_meta()}

然后在全局Ajax中使用这种方式设置X-CSRF-Token请求头并提交:

$.ajaxSetup({headers: {'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')}
});

路由验证

然后在路由规则定义中,使用

Route::post('blog/save','blog/save')->token();

如果自定义了token名称,需要改成

Route::post('blog/save','blog/save')->token('__hash__');

令牌检测如果不通过,会抛出think\exception\ValidateException异常。

控制器验证

如果没有使用路由定义,可以在控制器里面手动进行令牌验证

namespace app\controller;use think\exception\ValidateException;
use think\Request;class Index
{public function index(Request $request){$check = $request->checkToken('__token__');if(false === $check) {throw new ValidateException('invalid token');}// ...}
}

提交数据默认获取post数据,支持指定数据进行Token验证。

namespace app\controller;use think\exception\ValidateException;
use think\Request;class Index
{public function index(Request $request){$check = $request->checkToken('__token__', $request->param());if(false === $check) {throw new ValidateException('invalid token');}// ...}
}

使用验证器验证

在你的验证规则中,添加token验证规则即可,例如,如果使用的是验证器的话,可以改为:

protected $rule = ['name'  =>  'require|max:25|token','email' =>  'email',];

如果你的令牌名称不是__token__(假设是__hash__),验证器中需要改为:

protected $rule = ['name'  =>  'require|max:25|token:__hash__','email' =>  'email',];

请求

请求对象

请求对象由think\Request类负责,该类不需要单独实例化调用,通常使用依赖注入即可。在其它场合则可以使用think\facade\Request静态类操作。

项目里面应该使用app\Request对象,该对象继承了系统的think\Request对象,但可以增加自定义方法或者覆盖已有方法。项目里面已经在provider.php中进行了定义,所以你仍然可以和之前一样直接使用容器和静态代理操作请求对象。

构造方法注入

一般适用于没有继承系统的控制器类的情况。

<?phpnamespace app\index\controller;use think\Request;class Index 
{/*** @var \think\Request Request实例*/protected $request;/*** 构造方法* @param Request $request Request对象* @access public*/public function __construct(Request $request){$this->request = $request;}public function index(){return $this->request->param('name');}    
}

操作方法注入

另外一种选择是在每个方法中使用依赖注入。

<?phpnamespace app\index\controller;use think\Request;class Index
{public function index(Request $request){return $request->param('name');}    
}

无论是否继承系统的控制器基类,都可以使用操作方法注入。

更多关于依赖注入的内容,请参考依赖注入章节。

静态调用

没有使用依赖注入的场合,可以通过Facade机制来静态调用请求对象的方法(注意use引入的类库区别)。

<?phpnamespace app\index\controller;use think\facade\Request;class Index
{public function index(){return Request::param('name');}    
}

该方法也同样适用于依赖注入无法使用的场合

助手函数

为了简化调用,系统还提供了request助手函数,可以在任何需要的时候直接调用当前请求对象。

<?phpnamespace app\index\controller;class Index
{public function index(){return request()->param('name');}
}

自定义请求对象

在项目里面自定义Request对象,修改已有的方法或者增加新的方法,默认已经在项目app\Request类,直接修改该类就可以为项目单独自定义请求对象

自定义请求对象不支持为多应用的某个应用自定义,只能是全局自定义,如果你需要为某个应用定义不同的请求对象,可以在入口文件里面修改。例如:

// 执行HTTP应用并响应
$request = new app\common\Request();
$http = (new App())->http;
$response = $http->run($request);
$response->send();
$http->end($response);

请求信息

请求方法

Request对象支持获取当前的请求信息,包括:

方法含义
host当前访问域名或者IP
scheme当前访问协议
port当前访问的端口
remotePort当前请求的REMOTE_PORT
protocol当前请求的SERVER_PROTOCOL
contentType当前请求的CONTENT_TYPE
domain当前包含协议的域名
subDomain当前访问的子域名
panDomain当前访问的泛域名
rootDomain当前访问的根域名
url当前完整URL
baseUrl当前URL(不含QUERY_STRING)
query当前请求的QUERY_STRING参数
baseFile当前执行的文件
rootURL访问根地址
rootUrlURL访问根目录
pathinfo当前请求URL的pathinfo信息(含URL后缀)
ext当前URL的访问后缀
time获取当前请求的时间
type当前请求的资源类型
method当前请求类型
rule当前请求的路由对象实例

对于上面的这些请求方法,一般调用无需任何参数,但某些方法可以传入true参数,表示获取带域名的完整地址,例如:

use think\facade\Request;
// 获取完整URL地址 不带域名
Request::url();
// 获取完整URL地址 包含域名
Request::url(true);
// 获取当前URL(不含QUERY_STRING) 不带域名
Request::baseFile();
// 获取当前URL(不含QUERY_STRING) 包含域名
Request::baseFile(true);
// 获取URL访问根地址 不带域名
Request::root();
// 获取URL访问根地址 包含域名
Request::root(true);

注意domain方法的值本身就包含协议和域名

获取当前控制器/操作

可以通过请求对象获取当前请求的控制器/操作名。

方法含义
controller当前请求的控制器名
action当前请求的操作名

获取当前控制器

Request::controller();

返回的是控制器的驼峰形式(首字母大写),和控制器类名保持一致(不含后缀)。

如果需要返回小写可以使用

Request::controller(true);

如果要返回小写+下划线的方式,可以使用

parse_name(Request::controller());

获取当前操作

Request::action();

返回的是当前操作方法的实际名称,如果需要返回小写可以使用

Request::action(true);

如果要返回小写+下划线的方式,可以使用

parse_name(Request::action());

多应用模式下操作

如果使用了多应用模式,可以通过下面的方法来获取当前应用

app('http')->getName();

请求变量

定义解释

可以通过Request对象完成全局输入变量的检测、获取和安全过滤,支持包括$_GET$_POST$_REQUEST$_SERVER$_SESSION$_COOKIE$_ENV等系统变量,以及文件上传信息。

本篇内容的所有示例代码均使用Facade方式,因此需要首先引入

use think\facade\Request;

如果使用的是依赖注入,自行调整代码为动态调用即可。

检测变量是否设置

可以使用has方法来检测一个变量参数是否设置,如下:

Request::has('id','get');
Request::has('name','post');
//返回的是boolean类型

变量检测可以支持所有支持的系统变量,包括get/post/put/request/cookie/server/session/env/file

变量获取公式

变量获取使用\think\Request类的如下方法及参数:

变量类型方法('变量名/变量修饰符','默认值','过滤方法')

变量类型方法

方法描述
param获取当前请求的变量
get获取 $_GET 变量
post获取 $_POST 变量
put获取 PUT 变量
delete获取 DELETE 变量
session获取 SESSION 变量
cookie获取 $_COOKIE 变量
request获取 $_REQUEST 变量
server获取 $_SERVER 变量
env获取 $_ENV 变量
route获取 路由(包括PATHINFO) 变量
middleware获取 中间件赋值/传递的变量
file获取 $_FILES 变量
all V6.0.8+获取包括 $_FILES 变量在内的请求变量,相当于param+file

获取请求参数

PARAM类型变量是框架提供的用于自动识别当前请求的一种变量获取方式,是系统推荐的获取请求参数的方法,用法如下:

// 获取当前请求的name变量
Request::param('name');
// 获取当前请求的所有变量(经过过滤)
Request::param();
// 获取当前请求未经过滤的所有变量
Request::param(false);
// 获取部分变量
Request::param(['name', 'email']);

param方法会把当前请求类型的参数和路由变量以及GET请求合并,并且路由变量是优先的。

其它的输入变量获取方法和param方法用法基本一致。

你无法使用get方法获取路由变量,例如当访问地址是

http://localhost/index.php/index/index/hello/name/thinkphp

下面的用法是错误的

echo Request::get('name'); // 输出为空

正确的用法是

echo Request::param('name'); // 输出thinkphp

除了serverenv方法的变量名不区分大小写(会自动转为大写后获取),其它变量名区分大小写。

默认值

获取输入变量的时候,可以支持默认值,例如当URL中不包含$_GET['name']的时候,使用下面的方式输出的结果比较。

Request::get('name'); // 返回值为null
Request::get('name',''); // 返回值为空字符串
Request::get('name','default'); // 返回值为default

变量类型方法都支持在第二个参数中传入默认值的方式

变量过滤

框架默认没有设置任何全局过滤规则,你可以在app\Request.php对象中设置filter全局过滤属性:

namespace app;class Request extends \think\Request
{protected $filter = ['htmlspecialchars'];
}

也支持使用Request对象进行全局变量的获取过滤,过滤方式包括函数、方法过滤,以及PHP内置的Types of filters,我们可以设置全局变量过滤方法,支持设置多个过滤方法,例如:

Request::filter(['strip_tags','htmlspecialchars']),

也可以在获取变量的时候添加过滤方法,例如:

Request::get('name','','htmlspecialchars'); // 获取get变量 并用htmlspecialchars函数过滤
Request::param('username','','strip_tags'); // 获取param变量 并用strip_tags函数过滤
Request::post('name','','org\Filter::safeHtml'); // 获取post变量 并用org\Filter类的safeHtml方法过滤

可以支持传入多个过滤规则,例如:

Request::param('username','','strip_tags,strtolower'); // 获取param变量 并依次调用strip_tags、strtolower函数过滤

如果当前不需要进行任何过滤的话,可以使用

// 获取get变量 并且不进行任何过滤 即使设置了全局过滤
Request::get('name', '', null);

对于body中提交的json对象,你无需使用php://input去获取,可以直接当做表单提交的数据使用,因为系统已经自动处理过了

获请求的部分参数

如果你只需要获取当前请求的部分参数,可以使用:

// 只获取当前请求的id和name变量
Request::only(['id','name']);

采用only方法能够安全的获取你需要的变量,避免额外变量影响数据处理和写入。

only方法可以支持批量设置默认值,如下:

// 设置默认值
Request::only(['id'=>0,'name'=>'']);

表示id的默认值为0,name的默认值为空字符串。

默认获取的是当前请求参数(PARAM类型变量),如果需要获取其它类型的参数,可以在第二个参数传入,例如:

// 只获取GET请求的id和name变量
Request::only(['id','name'], 'get');
// 等效于
Request::get(['id', 'name']);
// 只获取POST请求的id和name变量
Request::only(['id','name'], 'post');
// 等效于
Request::post(['id', 'name']);

也支持排除某些变量后获取,例如

// 排除id和name变量
Request::except(['id','name']);

同样支持指定变量类型获取:

// 排除GET请求的id和name变量
Request::except(['id','name'], 'get');
// 排除POST请求的id和name变量
Request::except(['id','name'], 'post');

变量修饰符(强制转换)

支持对变量使用修饰符功能,可以一定程度上简单过滤变量,更为严格的过滤请使用前面提过的变量过滤功能。

用法如下:

Request::变量类型('变量名/修饰符');

支持的变量修饰符,包括:

修饰符作用
s强制转换为字符串类型
d强制转换为整型类型
b强制转换为布尔类型
a强制转换为数组类型
f强制转换为浮点类型

下面是一些例子:

Request::get('id/d');
Request::post('name/s');
Request::post('ids/a');

中间件变量

可以在中间件里面设置和获取请求变量的值,这个值的改变不会影响PARAM变量的获取。

<?phpnamespace app\http\middleware;class Check
{public function handle($request, \Closure $next){if ('think' == $request->name) {$request->name = 'ThinkPHP';}return $next($request);}
}

助手函数

为了简化使用,还可以使用系统提供的input助手函数完成上述大部分功能。

判断变量是否定义

input('?get.id');
input('?post.name');

获取PARAM参数

input('param.name'); // 获取单个参数
input('param.'); // 获取全部参数
// 下面是等效的
input('name'); 
input('');

获取GET参数

// 获取单个变量
input('get.id');
// 使用过滤方法获取 默认为空字符串
input('get.name');
// 获取全部变量
input('get.');

使用过滤方法

input('get.name','','htmlspecialchars'); // 获取get变量 并用htmlspecialchars函数过滤
input('username','','strip_tags'); // 获取param变量 并用strip_tags函数过滤
input('post.name','','org\Filter::safeHtml'); // 获取post变量 并用org\Filter类的safeHtml方法过滤

使用变量修饰符

input('get.id/d');
input('post.name/s');
input('post.ids/a');

请求类型

获取请求类型

判断当前操作的请求类型是GETPOSTPUTDELETE或者HEAD

用途:

1.针对请求类型作出不同的逻辑处理

2.需要验证安全性,过滤不安全的请求。

判断请求类型方法

用途方法
获取当前请求类型method
判断是否GET请求isGet
判断是否POST请求isPost
判断是否PUT请求isPut
判断是否DELETE请求isDelete
判断是否AJAX请求isAjax
判断是否PJAX请求isPjax
判断是否JSON请求isJson
判断是否手机访问isMobile
判断是否HEAD请求isHead
判断是否PATCH请求isPatch
判断是否OPTIONS请求isOptions
判断是否为CLI执行isCli
判断是否为CGI模式isCgi

method方法返回的请求类型始终是大写,这些方法都不需要传入任何参数。

没有必要在控制器中判断请求类型再来执行不同的逻辑,完全可以在路由中进行设置

请求类型伪装

支持请求类型伪装,可以在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请求。

你可以设置为任何合法的请求类型,包括GETPOSTPUTDELETE等,但伪装变量_method只能通过POST请求进行提交。

如果要获取原始的请求类型,可以使用

Request::method(true);

在命令行下面执行的话,请求类型返回的始终是GET

如果你需要改变伪装请求的变量名,可以修改自定义Request类的varMethod属性:

AJAX/PJAX伪装

可以对请求进行AJAX请求伪装,如下:

http://localhost/index?_ajax=1

或者PJAX请求伪装

http://localhost/index?_pjax=1

如果你需要改变伪装请求的变量名,可以修改自定义Request类的varAjaxvarPjax属性:

_ajax_pjax可以通过GET/POST/PUT等请求变量伪装。

HTTP头信息

可以使用Request对象的header方法获取当前请求的HTTP请求头信息,例如:

$info = Request::header();  //数组类型
echo $info['accept'];
echo $info['accept-encoding'];
echo $info['user-agent'];

也可以直接获取某个请求头信息,例如:

$agent = Request::header('user-agent');

HTTP请求头信息的名称不区分大小写,并且_会自动转换为-,所以下面的写法都是等效的:

$agent = Request::header('user-agent');
$agent = Request::header('USER_AGENT');

伪静态(SEO)

URL伪静态通常是为了满足更好的SEO效果,ThinkPHP支持伪静态URL设置,可以通过设置url_html_suffix参数随意在URL的最后增加你想要的静态后缀,而不会影响当前操作的正常执行。例如,我们在route.php中设置

'url_html_suffix' => 'shtml'

的话,我们可以把下面的URL

http://serverName/blog/read/id/1

变成

http://serverName/blog/read/id/1.shtml

后者更具有静态页面的URL特征,但是具有和前面的URL相同的执行效果,并且不会影响原来参数的使用。

**默认情况下,伪静态的设置为html,如果我们设置伪静态后缀为空字符串,

'url_html_suffix'=>''

支持所有的静态后缀访问,如果要获取当前的伪静态后缀,可以使用Request对象的ext方法。

例如:

http://serverName/blog/3.html
http://serverName/blog/3.shtml
http://serverName/blog/3.xml
http://serverName/blog/3.pdf

都可以正常访问。

在控制器的操作方法中获取当前访问的伪静态后缀,例如:

$ext = Request::ext();

支持多个伪静态后缀,可以直接设置如下:

// 多个伪静态后缀设置 用|分割
'url_html_suffix' => 'html|shtml|xml'

那么,当访问 http://serverName/blog/3.pdf 的时候会报系统错误。

如果要关闭伪静态访问,可以设置

// 关闭伪静态后缀访问
'url_html_suffix' => false,

关闭伪静态访问后,不再支持伪静态方式的URL访问,并且伪静态后缀将会被解析为最后一个参数的值,例如:

http://serverName/blog/read/id/3.html

最终的id参数的值将会变成 3.html

参数绑定

参数绑定是把当前请求的变量作为操作方法(也包括架构方法)的参数直接传入,参数绑定并不区分请求类型。

参数绑定传入的值会经过全局过滤,如果你有额外的过滤需求可以在操作方法中单独处理。

参数绑定方式默认是按照变量名进行绑定,例如,我们给Blog控制器定义了两个操作方法readarchive方法,由于read操作需要指定一个id参数,archive方法需要指定年份(year)和月份(month)两个参数,那么我们可以如下定义:

<?php
namespace app\controller;class Blog 
{public function read($id){return 'id=' . $id;}public function archive($year, $month='01'){return 'year=' . $year . '&month=' . $month;}
}

注意这里的操作方法并没有具体的业务逻辑,只是简单的示范。

URL的访问地址分别是:

http://serverName/index.php/blog/read/id/5
http://serverName/index.php/blog/archive/year/2016/month/06

两个URL地址中的id参数和yearmonth参数会自动和read操作方法以及archive操作方法的同名参数绑定。

变量名绑定不一定由访问URL决定,路由地址也能起到相同的作用

输出的结果依次是:

id=5
year=2016&month=06

按照变量名进行参数绑定的参数必须和URL中传入的变量名称一致,但是参数顺序不需要一致。也就是说

http://serverName/index.php/blog/archive/month/06/year/2016

和上面的访问结果是一致的,URL中的参数顺序和操作方法中的参数顺序都可以随意调整,关键是确保参数名称一致即可。

如果用户访问的URL地址是:

http://serverName/index.php/blog/read

那么会抛出下面的异常提示: 参数错误:id

报错的原因很简单,因为在执行read操作方法的时候,id参数是必须传入参数的,但是方法无法从URL地址中获取正确的id参数信息。由于我们不能相信用户的任何输入,因此建议你给read方法的id参数添加默认值,例如:

public function read($id = 0)
{return 'id=' . $id;
}

这样,当我们访问 http://serverName/index.php/blog/read/ 的时候 就会输出

id=0

始终给操作方法的参数定义默认值是一个避免报错的好办法(依赖注入参数除外)

为了更好的配合前端规范,支持自动识别小写+下划线的请求变量使用驼峰注入,例如:

http://serverName/index.php/blog/read/blog_id/5

可以使用下面的方式接收blog_id变量,所以请确保在方法的参数使用驼峰(首字母小写)规范。

public function read($blogId = 0)
{return 'id=' . $blogId;
}

请求缓存

在请求一个静态文件的时候(如图片,css,js)等,这些文件的特点是文件不经常变化,将这些不经常变化的文件存储起来,对客户端来说是一个优化用户浏览体验的方法。仅支持对GET请求设置缓存访问,并设置有效期。

请求缓存仅对GET请求有效

路由设置

可以在路由规则里面调用cache方法设置当前路由规则的请求缓存,例如:

// 定义GET请求路由规则 并设置3600秒的缓存
Route::get('new/:id','News/read')->cache(3600);

第二次访问相同的路由地址的时候,会自动获取请求缓存的数据响应输出,并发送304状态码(服务端已经执行了GET,但文件未变化,则返回304)。

默认请求缓存的标识为当前访问的pathinfo地址,可以定义请求缓存的标识,如下:

// 定义GET请求路由规则 并设置3600秒的缓存
Route::get('new/:id','News/read')->cache([ 'new/:id/:page', 3600]
);

:id:page表示使用当前请求的param参数进行动态标识替换,也就是根据idpage变量进行3600秒的请求缓存。

如果cache参数传入false,则表示关闭当前路由的请求缓存(即使开启全局请求缓存)。

// 定义GET请求路由规则 并关闭请求缓存(即使开启了全局请求缓存)
Route::get('new/:id','News/read')->cache(false);

支持给一组路由设置缓存标签

// 定义GET请求路由规则 并设置3600秒的缓存
Route::get('new/:id','News/read')->cache([ 'new/:id/:page', 3600, 'page']
);

这样可以在需要的时候统一清理缓存标签为page的请求缓存。

全局请求缓存

如果需要开启全局请求缓存,只需要在全局(或者应用)的中间件定义文件middleware.php中增加

 'think\middleware\CheckRequestCache',

然后只需要在route.php配置文件中设置全局缓存的有效时间(秒)

'request_cache_expire'    =>   3600,

就会自动根据当前请求URL地址(只针对GET请求类型)进行请求缓存,全局缓存有效期为3600秒。

如果需要对全局缓存设置缓存规则,可以直接设置request_cache_key参数,例如:

'request_cache_key'   =>   '__URL__',
'request_cache_expire'    =>   3600,

缓存标识支持下面的特殊定义

标识含义
__CONTROLLER__当前控制器名
__ACTION__当前操作名
__URL__当前完整URL地址(包含域名)

全局请求缓存支持设置排除规则,使用方法如下:

'request_cache_key'        => true,
'request_cache_expire' => 3600,
'request_cache_except' => ['/blog/index','/user/member',
],

排除规则为不使用请求缓存的地址(不支持变量)开头部分(不区分大小写)。

路由中设置的请求缓存依然有效并且优先,如果需要设置特殊的请求缓存有效期就可以直接在路由中设置。

响应

响应输出

最佳的方式是在控制器最后明确输出类型(毕竟一个确定的请求是有明确的响应输出类型),默认支持的输出类型包括:

输出类型快捷方法对应Response类
HTML输出response\think\Response
渲染模板输出view\think\response\View
JSON输出json\think\response\Json
JSONP输出jsonp\think\response\Jsonp
XML输出xml\think\response\Xml
页面重定向redirect\think\response\Redirect
附件下载download\think\response\File

每一种输出类型其实对应了一个不同的Response子类(response()函数对应的是Response基类),也可以在应用中自定义Response子类满足特殊需求的输出。

例如我们需要输出一个JSON数据给客户端(或者AJAX请求),可以使用:

<?php
namespace app\controller;class Index
{public function hello(){$data = ['name' => 'thinkphp', 'status' => '1'];return json($data);}
}

这些助手函数的返回值都是Response类或者子类的对象实例,所以后续可以调用Response基类或者当前子类的相关方法,后面我们会讲解相关方法。

如果你只需要输出一个html格式的内容,可以直接使用

<?php
namespace app\controller;class Index
{public function hello(){$data = 'Hello,ThinkPHP!';return response($data);}
}

或者使用return直接返回输出的字符串。

<?php
namespace app\controller;class Index
{public function hello(){return 'Hello,ThinkPHP!';}
}

响应参数

Response对象提供了一系列方法用于设置响应参数,包括设置输出内容、状态码及header信息等,并且支持链式调用以及多次调用。

设置数据

Response基类提供了data方法用于设置响应数据。

response()->data($data);
json()->data($data);

不过需要注意的是data方法设置的只是原始数据,并不一定是最终的输出数据,最终的响应输出数据是会根据当前的Response响应类型做自动转换的,例如:

json()->data($data);

最终的输出数据就是json_encode($data)转换后的数据。

如果要获取当前响应对象实例的实际输出数据可以使用getContent方法。

设置状态码

Response基类提供了code方法用于设置响应数据,但大部分情况一般我们是直接在调用助手函数的时候直接传入状态码,例如:

json($data,201);
view($data,401);

或者在后面链式调用code方法是等效的:

json($data)->code(201);

除了redirect函数的默认返回状态码是302之外,其它方法没有指定状态码都是返回200状态码。

如果要获取当前响应对象实例的状态码的值,可以使用getCode方法。

设置头信息

可以使用Response类的header设置响应的头信息

json($data)->code(201)->header(['Cache-control' => 'no-cache,must-revalidate'
]);

除了header方法之外,Response基类还提供了常用头信息的快捷设置方法:

方法名作用
lastModified设置Last-Modified头信息
expires设置Expires头信息
eTag设置ETag头信息
cacheControl设置Cache-control头信息
contentType设置Content-Type头信息

除非你要清楚自己在做什么,否则不要随便更改这些头信息,每个Response子类都有默认的contentType信息,一般无需设置。

你可以使用getHeader方法获取当前响应对象实例的头信息。

写入Cookie

//response()->cookie('cookie的名称', 'cookie的值', 可选参数值);
response()->cookie('name', 'value', 600);

设置额外参数

有些时候,响应输出需要设置一些额外的参数,例如:
在进行json输出的时候需要设置json_encode方法的额外参数,jsonp输出的时候需要设置jsonp_handler等参数,这些都可以使用options方法来进行处理,例如:

jsonp($data)->options(['var_jsonp_handler'     => 'callback','default_jsonp_handler' => 'jsonpReturn','json_encode_param'     => JSON_PRETTY_PRINT,
]);

关闭当前的请求缓存

支持使用allowCache方法动态控制是否需要使用请求缓存。

// 关闭当前页面的请求缓存
json($data)->code(201)->allowCache(false);

自定义响应

如果需要特别的自定义响应输出,可以自定义一个Response子类,并且在控制器的操作方法中直接返回。又或者通过设置响应参数的方式进行响应设置输出。

重定向

可以使用redirect助手函数进行重定向

<?php
namespace app\controller;class Index
{public function hello(){return redirect('http://www.thinkphp.cn');}
}

重定向传参

如果是站内重定向的话,可以支持URL组装,有两种方式组装URL,第一种是直接使用完整地址(/打头)

redirect('/index/hello/name/thinkphp');

如果你需要自动生成URL地址,应该在调用之前调用url函数先生成最终的URL地址。

redirect((string) url('hello',['name' => 'think']));

还可以支持使用with方法附加Session闪存数据重定向。

<?php
namespace app\controller;class Index
{public function index(){return redirect('/hello')->with('name','thinkphp');}public function hello(){$name = session('name');return 'hello,'.$name.'!';}    
}

从示例可以看到重定向隐式传值使用的是Session闪存数据隐式传值,并且仅在下一次请求有效,再次访问重定向地址的时候无效。

记住请求地址

在很多时候,我们重定向的时候需要记住当前请求地址(为了便于跳转回来),我们可以使用remember方法记住重定向之前的请求地址。

下面是一个示例,我们第一次访问index操作的时候会重定向到hello操作并记住当前请求地址,然后操作完成后到restore方法,restore方法则会自动重定向到之前记住的请求地址,完成一次重定向的回归,回到原点!(再次刷新页面又可以继续执行)

<?php
namespace app\controller;class Index
{public function index(){// 判断session完成标记是否存在if (session('?complete')) {// 删除sessionsession('complete', null);return '重定向完成,回到原点!';} else {// 记住当前地址并重定向return redirect('hello')->with('name', 'thinkphp')->remember();}}public function hello(){$name = session('name');return 'hello,' . $name . '! <br/><a href="/index/index/restore">点击回到来源地址</a>';}public function restore(){// 设置session标记完成session('complete', true);// 跳回之前的来源地址return redirect()->restore();}
}

文件下载

支持文件下载功能,可以更简单的读取文件进行下载操作,支持直接下载输出内容。

你可以在控制器的操作方法中添加如下代码:

public function download(){// download是系统封装的一个助手函数return download('image.jpg', 'my.jpg');}

访问download操作就会下载命名为my.jpg的图像文件。

下载文件的路径是服务器路径而不是URL路径,如果要下载的文件不存在,系统会抛出异常。

下载文件名可以省略后缀,会自动判断(后面的代码都以助手函数为例)

public function download(){// 和上面的下载文件名是一样的效果return download('image.jpg', 'my');}

如果需要设置文件下载的有效期,可以使用

public function download(){// 设置300秒有效期return download('image.jpg', 'my')->expire(300);}

除了expire方法外,还支持下面的方法:

下载方法

方法描述
name命名下载文件
expire下载有效期
isContent是否为内容下载
mimeType设置文件的mimeType类型
force是否强制下载(V6.0.3+

助手函数

助手函数提供了内容下载的参数,如果需要直接下载内容,可以在第三个参数传入true

public function download()
{$data = '这是一个测试文件';return download($data, 'test.txt', true);
}

V6.0.3+版本开始,支持设置是否强制下载,例如需要打开图像文件而不是浏览器下载的话,可以使用:

public function download()
{return download('image.jpg', 'my.jpg')->force(false);
}

数据库

连接数据库

如果应用需要使用数据库,必须配置数据库连接信息,数据库的配置文件有多种定义方式。

配置文件

在全局或者应用配置目录(不清楚配置目录位置的话参考配置章节)下面的database.php中(后面统称为数据库配置文件)配置下面的数据库参数:

return ['default'    =>    'mysql','connections'    =>    ['mysql'    =>    [// 数据库类型'type'        => 'mysql',// 服务器地址'hostname'    => '127.0.0.1',// 数据库名'database'    => 'thinkphp',// 数据库用户名'username'    => 'root',// 数据库密码'password'    => '',// 数据库连接端口'hostport'    => '',// 数据库连接参数'params'      => [],// 数据库编码默认采用utf8'charset'     => 'utf8',// 数据库表前缀'prefix'      => 'think_',],],
];

新版采用多类型的方式配置,方便切换数据库。

default配置用于设置默认使用的数据库连接配置。
connections配置具体的数据库连接信息,default配置参数定义的连接配置必须要存在。

type参数用于指定数据库类型

指定数据库类型

type数据库
mysqlMySQL
sqliteSqLite
pgsqlPostgreSQL
sqlsrvSqlServer
mongoMongoDb
oracleOracle

每个应用可以设置独立的数据库连接参数,通常直接更改default参数即可:

return ['default'    =>    'admin', 
];

连接参数

针对不同的连接需要添加数据库的连接参数(具体的连接参数可以参考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,

在数据库配置文件中设置的params参数中的连接配置将会和内置的设置参数合并,如果需要使用长连接,并且返回数据库的小写列名,可以在数据库配置文件中增加下面的定义:

'params' => [\PDO::ATTR_PERSISTENT   => true,\PDO::ATTR_CASE         => \PDO::CASE_LOWER,
],

你可以在params参数里面配置任何PDO支持的连接参数。

切换连接

我们可以在数据库配置文件中定义多个连接信息

return ['default'    =>    'mysql','connections'    =>    ['mysql'    =>    [// 数据库类型'type'        => 'mysql',// 服务器地址'hostname'    => '127.0.0.1',// 数据库名'database'    => 'thinkphp',// 数据库用户名'username'    => 'root',// 数据库密码'password'    => '',// 数据库连接端口'hostport'    => '',// 数据库连接参数'params'      => [],// 数据库编码默认采用utf8'charset'     => 'utf8',// 数据库表前缀'prefix'      => 'think_',],'demo'    =>    [// 数据库类型'type'        => 'mysql',// 服务器地址'hostname'    => '127.0.0.1',// 数据库名'database'    => 'demo',// 数据库用户名'username'    => 'root',// 数据库密码'password'    => '',// 数据库连接端口'hostport'    => '',// 数据库连接参数'params'      => [],// 数据库编码默认采用utf8'charset'     => 'utf8',// 数据库表前缀'prefix'      => 'think_',],],
];

我们可以调用Db::connect方法动态配置数据库连接信息,例如:

\think\facade\Db::connect('demo')->table('user')->find();

connect方法必须在查询的最开始调用,而且必须紧跟着调用查询方法,否则可能会导致部分查询失效或者依然使用默认的数据库连接

动态连接数据库的connect方法仅对当次查询有效。

这种方式的动态连接和切换数据库比较方便,经常用于多数据库连接的应用需求。

模型类定义

如果某个模型类里面定义了connection属性的话,则该模型操作的时候会自动按照给定的数据库配置进行连接,而不是配置文件中设置的默认连接信息,例如:

<?php
namespace app\index\model;use think\Model;class User extends Model
{protected $connection = 'demo';
}

需要注意的是,ThinkPHP的数据库连接是惰性的,所以并不是在实例化的时候就连接数据库,而是在有实际的数据操作的时候才会去连接数据库

配置参数参考

下面是默认支持的数据库连接信息:

参数名描述默认值
type数据库类型
hostname数据库地址127.0.0.1
database数据库名称
username数据库用户名
password数据库密码
hostport数据库端口号
dsn数据库连接dsn信息
params数据库连接参数
charset数据库编码utf8
prefix数据库的表前缀
deploy数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)0
rw_separate数据库读写是否分离 主从式有效false
master_num读写分离后 主服务器数量1
slave_no指定从服务器序号
fields_strict是否严格检查字段是否存在true
fields_cache是否开启字段缓存false
trigger_sql是否开启SQL监听true
auto_timestamp自动写入时间戳字段false
query指定查询对象think\db\Query

常用数据库连接参数(params)可以参考PHP在线手册中的以PDO::ATTR_开头的常量。

如果同时定义了 参数化数据库连接信息 和 dsn信息,则会优先使用dsn信息。

如果是使用pgsql数据库驱动的话,请先导入 thinkphp/library/think/db/connector/pgsql.sql文件到数据库执行。

断线重连

如果你使用的是长连接或者命令行,在超出一定时间后,数据库连接会断开,这个时候你需要开启断线重连才能确保应用不中断。

在数据库连接配置中设置:

// 开启断线重连
'break_reconnect' => true,

开启后,系统会自动判断数据库断线并尝试重新连接。大多数情况下都能自动识别,如果在一些特殊的情况下或者某些数据库驱动的断线标识错误还没有定义,支持配置下面的信息:

// 断线标识字符串
'break_match_str' => ['error with',
],

在 break_match_str配置中加入你的数据库错误信息关键词。

分布式数据库

分布式支持

数据访问层支持分布式数据库,包括读写分离,要启用分布式数据库,需要开启数据库配置文件中的deploy参数:

return ['default'    =>    'mysql','connections'    =>    ['mysql'    =>    [// 启用分布式数据库'deploy'    =>  1,// 数据库类型'type'        => 'mysql',// 服务器地址'hostname'    => '192.168.1.1,192.168.1.2',// 数据库名'database'    => 'demo',// 数据库用户名'username'    => 'root',// 数据库密码'password'    => '',// 数据库连接端口'hostport'    => '',],],
];

启用分布式数据库后,hostname参数是关键,hostname的个数决定了分布式数据库的数量,默认情况下第一个地址就是主服务器

主从服务器支持设置不同的连接参数,包括:

连接参数
username
password
hostport
database
dsn
charset

如果主从服务器的上述参数一致的话,只需要设置一个,对于不同的参数,可以分别设置,例如:

return ['default'    =>    'mysql','connections'    =>    ['mysql'    =>    [// 启用分布式数据库'deploy'   => 1,// 数据库类型'type'     => 'mysql',// 服务器地址'hostname' => '192.168.1.1,192.168.1.2,192.168.1.3',// 数据库名'database' => 'demo',// 数据库用户名'username' => 'root,slave,slave',// 数据库密码'password' => '123456',// 数据库连接端口'hostport' => '',// 数据库字符集'charset'  => 'utf8',],],
];

记住,要么相同,要么每个都要设置。

分布式的数据库参数支持使用数组定义(通常为了避免多个账号和密码的误解析),例如:

return ['default'    =>    'mysql','connections'    =>    ['mysql'    =>    [// 启用分布式数据库'deploy'   => 1,// 数据库类型'type'     => 'mysql',// 服务器地址'hostname' =>[ '192.168.1.1','192.168.1.2','192.168.1.3'],// 数据库名'database' => 'demo',// 数据库用户名'username' => 'root,slave,slave',// 数据库密码'password' => ['123456','abc,def','hello']// 数据库连接端口'hostport' => '',// 数据库字符集'charset'  => 'utf8',],],
];

读写分离

还可以设置分布式数据库的读写是否分离,默认的情况下读写不分离,也就是每台服务器都可以进行读写操作,对于主从式数据库而言,需要设置读写分离,通过下面的设置就可以:

    'rw_separate' => true,

在读写分离的情况下,默认第一个数据库配置是主服务器的配置信息,负责写入数据,如果设置了master_num参数,则可以支持多个主服务器写入(每次随机连接其中一个主服务器)。其它的地址都是从数据库,负责读取数据,数量不限制。每次连接从服务器并且进行读取操作的时候,系统会随机进行在从服务器中选择。同一个数据库连接的每次请求只会连接一次主服务器和从服务器,如果某次请求的从服务器连接不上,会自动切换到主服务器进行查询操作。

如果不希望随机读取,或者某种情况下其它从服务器暂时不可用,还可以设置slave_no 指定固定服务器进行读操作,slave_no指定的序号表示hostname中数据库地址的序号,从0开始。

调用查询类或者模型的CURD操作的话,系统会自动判断当前执行的方法是读操作还是写操作并自动连接主从服务器,如果你用的是原生SQL,那么需要注意系统的默认规则: 写操作必须用数据库的execute方法,读操作必须用数据库的query方法,否则会发生主从读写错乱的情况。

发生下列情况的话,会自动连接主服务器:

  • 使用了数据库的写操作方法(execute/insert/update/delete以及衍生方法);
  • 如果调用了数据库事务方法的话,会自动连接主服务器;
  • 从服务器连接失败,会自动连接主服务器;
  • 调用了查询构造器的lock方法;
  • 调用了查询构造器的master/readMaster方法

主从数据库的数据同步工作不在框架实现,需要数据库考虑自身的同步或者复制机制。如果在大数据量或者特殊的情况下写入数据后可能会存在同步延迟的情况,可以调用master()方法进行主库查询操作。

在实际生产环境中,很多云主机的数据库分布式实现机制和本地开发会有所区别,但通常会采下面用两种方式:

  • 第一种:提供了写IP和读IP(一般是虚拟IP),进行数据库的读写分离操作;
  • 第二种:始终保持同一个IP连接数据库,内部会进行读写分离IP调度(阿里云就是采用该方式)。

主库读取

有些情况下,需要直接从主库读取数据,例如刚写入数据之后,从库数据还没来得及同步完成,你可以使用

Db::name('user')->where('id', 1)->update(['name' => 'thinkphp']);
Db::name('user')->master(true)->find(1);

不过,实际情况远比这个要复杂,因为你并不清楚后续的方法里面是否还存在相关查询操作,这个时候我们可以配置开启数据库的read_master配置参数。

// 开启自动主库读取
'read_master' => true,

开启后,一旦我们对某个数据表进行了写操作,那么当前请求的后续所有对该表的查询都会使用主库读取。

查询构造器

选择数据表

  • 数据表设置表前缀的话,可以使用name方法
  • 没有设置表前缀的话,可以使用table方法
  • 那么nametable方法效果一致。

查询数据

查询单个数据

方法作用返回结果示例
find()查询单个数据使用find方法find方法查询结果不存在,返回 null,否则返回结果数组->find()
findOrFail()在没有找到数据后抛出异常抛出异常->findOrFail()
findOrFail()查询数据不存在的时候返回空数组返回空数组->findOrFail()

查询数据集

方法作用返回结果示例
select()查询多个数据(数据集)数据集对象->select()
toArray()转换为数组数据集对象转换为数组->toArray()
selectOrFail()没有查找到数据后抛出异常抛出异常->selectOrFail()

值和列查询

方法作用返回结果示例
value()查询某个字段的值可以用查询结果不存在,返回 null->value(‘字段名’)
column()查询某一列的值方法查询结果不存在,返回空数组->column('列名')
column()查询某一列的值,指定id字段的值作为索引方法查询结果不存在,返回空数组->column('列名','id的值')
column()指定id字段的值作为索引 返回所有数据方法查询结果不存在,返回空数组->column('*','id的值')

数据分批处理

需要处理成千上百条数据库记录,可以考虑使用chunk方法,该方法一次获取结果集的一小块,然后填充每一小块数据到要处理的闭包,该方法在编写处理大量数据库记录的时候非常有用。

比如,我们可以全部用户表数据进行分批处理,每次处理 100 个用户记录:

Db::table('think_user')->chunk(100, function($users) {foreach ($users as $user) {//}
});

你可以通过从闭包函数中返回false来中止对后续数据集的处理:

Db::table('think_user')->chunk(100, function($users) {foreach ($users as $user) {// 处理结果集...if($user->status==0){return false;}}
});

也支持在chunk方法之前调用其它的查询方法,例如:

Db::table('think_user')
->where('score','>',80)
->chunk(100, function($users) {foreach ($users as $user) {//}
});

chunk方法的处理默认是根据主键查询,支持指定字段,例如:

Db::table('think_user')->chunk(100, function($users) {// 处理结果集...return false;
},'create_time');

并且支持指定处理数据的顺序。

Db::table('think_user')->chunk(100, function($users) {// 处理结果集...return false;
},'create_time', 'desc');

chunk方法一般用于命令行操作批处理数据库的数据,不适合WEB访问处理大量数据,很容易导致超时。

游标查询

如果你需要处理大量的数据,可以使用新版提供的游标查询功能,该查询方式利用了PHP的生成器特性,可以大幅减少大量数据查询的内存开销问题。

$cursor = Db::table('user')->where('status', 1)->cursor();
foreach($cursor as $user){echo $user['name'];
}

cursor方法返回的是一个生成器对象,user变量是数据表的一条数据(数组)。

添加数据

添加一条数据

方法作用返回结果示例
save()自动判断是新增还是更新数据(以写入数据中是否存在主键数据为依据)成功则返回为1->save($数组变量)
insert()向数据库提交数据返回添加成功的条数->insert($数组变量)
strict(false)不希望抛出异常不存在字段的值将会直接抛弃->strict(false)
replace()mysql数据库支持写入方法->replace()
insertGetId()返回新增数据的自增主键返回主键值->insertGetId($data)
$数组变量:$data=['字段名A'=>'字段值A','字段名B'=>'字段值B',...]

添加多条数据

方法作用返回结果示例
insertAll()传入需要添加的数据(通常是二维数组)返回添加成功的条数insertAll($二位数组变量名)
replace()mysql数据库支持写入方法确保要批量添加的数据字段是一致的(注意)->replace()
limit()每次插入的数量限制,指定分批插入->limit(数据条数)

二位数组变量名示例:

$data = [['foo' => 'bar', 'bar' => 'foo'],['foo' => 'bar1', 'bar' => 'foo1'],['foo' => 'bar2', 'bar' => 'foo2']
];

更新数据

更新方法表

方法作用返回结果示例
save()自动判断是新增还是更新数据(以写入数据中是否存在主键数据为依据)成功则返回为1->save($数组变量)
update()更新数据返回影响数据的条数,没修改任何数据返回 0->update(['字段名' => '字段值'])
data()传入要更新的数据->data(['字段名' => '字段值'])
raw()数据更新

如果update方法和data方法同时传入更新数据,则以update方法为准。

更新方法使用

支持使用data方法传入要更新的数据

Db::name('user')->where('id', 1)->data(['name' => 'thinkphp'])->update();

如果update方法和data方法同时传入更新数据,则以update方法为准。

如果数据中包含主键,可以直接使用:

Db::name('user')->update(['name' => 'thinkphp','id' => 1]);

实际生成的SQL语句和前面用法是一样的:

UPDATE `think_user`  SET `name`='thinkphp'  WHERE  `id` = 1

如果要更新的数据需要使用SQL函数或者其它字段,可以使用下面的方式:

Db::name('user')->where('id',1)->exp('name','UPPER(name)')->update();

实际生成的SQL语句:

UPDATE `think_user`  SET `name` = UPPER(name)  WHERE  `id` = 1

支持使用raw方法进行数据更新。

Db::name('user')->where('id', 1)->update(['name'        =>   Db::raw('UPPER(name)'),'score'       =>   Db::raw('score-3'),'read_time'   =>   Db::raw('read_time+1')]);

删除数据

方法作用返回结果示例
delete()根据主键删除返回影响数据的条数,没有删除返回 0->delete(主键值)
delete()根据条件删除返回影响数据的条数,没有删除返回 0->where(条件)->delete();
delete()需要删除所有数据返回影响数据的条数,没有删除返回 0->delete(true)
useSoftDelete()软删除机制指定软删除字段为delete_time,写入数据为当前的时间戳。->useSoftDelete('delete_time',time())

一般情况下,业务数据不建议真实删除数据,系统提供了软删除机制(模型中使用软删除更为方便)。

查询表达式

公式样式

查询表达式支持大部分的SQL查询语法,也是ThinkPHP查询语言的精髓,查询表达式的使用格式:

where('字段名','查询表达式','查询条件');

表达式清单

表达式含义快捷查询方法
=等于
<>不等于
>大于
>=大于等于
<小于
<=小于等于
[NOT] LIKE模糊查询whereLike/whereNotLike
[NOT] BETWEEN(不在)区间查询whereBetween/whereNotBetween
[NOT] IN(不在)IN 查询whereIn/whereNotIn
[NOT] NULL查询字段是否(不)是NULLwhereNull/whereNotNull
[NOT] EXISTSEXISTS查询whereExists/whereNotExists
[NOT] REGEXP正则(不)匹配查询(仅支持Mysql)
[NOT] BETWEEN TIME时间区间比较whereBetweenTime
> TIME大于某个时间whereTime
< TIME小于某个时间whereTime
>= TIME大于等于某个时间whereTime
<= TIME小于等于某个时间whereTime
EXP表达式查询,支持SQL语法whereExp
find in setFIND_IN_SET查询whereFindInSet

表达式查询的用法

等于(=)

例如:

Db::name('user')->where('id','=',100)->select();

和下面的查询等效

Db::name('user')->where('id',100)->select();

最终生成的SQL语句是:

SELECT * FROM `think_user` WHERE  `id` = 100

不等于(<>)

例如:

Db::name('user')->where('id','<>',100)->select();

最终生成的SQL语句是:

SELECT * FROM `think_user` WHERE  `id` <> 100

大于(>)

例如:

Db::name('user')->where('id','>',100)->select();

最终生成的SQL语句是:

SELECT * FROM `think_user` WHERE  `id` > 100

大于等于(>=)

例如:

Db::name('user')->where('id','>=',100)->select();

最终生成的SQL语句是:

SELECT * FROM `think_user` WHERE  `id` >= 100

小于(<)

例如:

Db::name('user')->where('id', '<', 100)->select();

最终生成的SQL语句是:

SELECT * FROM `think_user` WHERE  `id` < 100

小于等于(<=)

例如:

Db::name('user')->where('id', '<=', 100)->select();

最终生成的SQL语句是:

SELECT * FROM `think_user` WHERE  `id` <= 100

[NOT] LIKE: 同sql的LIKE

例如:

Db::name('user')->where('name', 'like', 'thinkphp%')->select();

最终生成的SQL语句是:

SELECT * FROM `think_user` WHERE  `name` LIKE 'thinkphp%'

like查询支持使用数组

Db::name('user')->where('name', 'like', ['%think','php%'],'OR')->select();

实际生成的SQL语句为:

SELECT * FROM `think_user` WHERE  (`name` LIKE '%think' OR `name` LIKE 'php%')

为了更加方便,应该直接使用whereLike方法

Db::name('user')->whereLike('name','thinkphp%')->select();
Db::name('user')->whereNotLike('name','thinkphp%')->select();

[NOT] BETWEEN :同sql的[not] between

查询条件支持字符串或者数组,例如:

Db::name('user')->where('id','between','1,8')->select();

和下面的等效:

Db::name('user')->where('id','between',[1,8])->select();

最终生成的SQL语句都是:

SELECT * FROM `think_user` WHERE  `id` BETWEEN 1 AND 8

或者使用快捷查询方法:

Db::name('user')->whereBetween('id','1,8')->select();
Db::name('user')->whereNotBetween('id','1,8')->select();

[NOT] IN: 同sql的[not] in

查询条件支持字符串或者数组,例如:

Db::name('user')->where('id','in','1,5,8')->select();

和下面的等效:

Db::name('user')->where('id','in',[1,5,8])->select();

最终的SQL语句为:

SELECT * FROM `think_user` WHERE  `id` IN (1,5,8)

或者使用快捷查询方法:

Db::name('user')->whereIn('id','1,5,8')->select();
Db::name('user')->whereNotIn('id','1,5,8')->select();

[NOT] IN查询支持使用闭包方式

[NOT] NULL :

查询字段是否(不)是Null,例如:

Db::name('user')->where('name', null)
->where('email','null')
->where('name','not null')
->select();

实际生成的SQL语句为:

SELECT * FROM `think_user` WHERE  `name` IS NULL  AND `email` IS NULL  AND `name` IS NOT NULL

如果你需要查询一个字段的值为字符串null或者not null,应该使用:

Db::name('user')->where('title','=', 'null')
->where('name','=', 'not null')
->select();

推荐的方式是使用whereNullwhereNotNull方法查询。

Db::name('user')->whereNull('name')
->whereNull('email')
->whereNotNull('name')
->select();

EXP:表达式

支持更复杂的查询情况 例如:

Db::name('user')->where('id','in','1,3,8')->select();

可以改成:

Db::name('user')->where('id','exp',' IN (1,3,8) ')->select();

exp查询的条件不会被当成字符串,所以后面的查询条件可以使用任何SQL支持的语法,包括使用函数和字段名称。

推荐使用whereExp方法查询

Db::name('user')->whereExp('id', 'IN (1,3,8) ')->select();

链式操作

链式方法清单

方法作用
where完成包括普通查询、表达式查询、快捷查询、区间查询、组合查询在内的查询操作
table指定操作的数据表
alias设置当前数据表的别名
field标识要返回或者操作的字段,可以用于查询和写入操作
strict设置是否严格检查字段名
limit用于指定查询和操作的数量
page用于分页查询
order用于对操作的结果排序或者优先级限制
group用于结合合计函数,根据一个或多个列对结果集进行分组
having用于配合group方法完成从分组的结果中筛选(通常是聚合条件)数据
join用于根据两个或多个表中的列之间的关系,从这些表中查询数据。
union用于合并两个或多个 SELECT 语句的结果集
distinct用于返回唯一不同的值
lock用于数据库的锁机制,如果在查询或者执行操作的时候
cache用于查询缓存操作,也是连贯操作方法之一
comment用于在生成的SQL语句中添加注释内容
fetchSql用于直接返回SQL而不是执行查询,适用于任何的CURD操作方法
force用于数据集的强制索引操作
partition用于MySQL数据库的分区查询
failException设置查询数据为空时是否需要抛出异常,用于selectfind方法
sequence用于pgsql数据库指定自增序列名,其它数据库不必使用,
replace用于设置MySQL数据库insert方法或者insertAll方法写入数据的时候是否适用REPLACE方式
extra可以用于CURD查询
duplicate设置DUPLICATE查询
procedure用于设置当前查询是否为存储过程查询

具体方法的使用,请参考网络教程或者thinphp手册

数据统计

聚合查询方法

方法说明
count统计数量,参数是要统计的字段名(可选)
max获取最大值,参数是要统计的字段名(必须)
min获取最小值,参数是要统计的字段名(必须)
avg获取平均值,参数是要统计的字段名(必须)
sum获取总分,参数是要统计的字段名(必须)

聚合方法如果没有数据,默认都是0,聚合查询都可以配合其它查询条件

数据查询方式

分页查询

分批向数据库请求数据,并支持渲染到模型的操作方法

时间查询

框架内置了常用的时间查询方法,并且可以自动识别时间字段的类型,所以无论采用什么类型的时间字段,都可以统一使用本章的时间查询用法。

高级查询

支持快捷查询、批量查询、闭包查询、动态查询,条件查询

视图查询

可以实现不依赖数据库视图的多表查询,并不需要数据库支持视图,是JOIN方法的推荐替代方法

JSON字段

json字段的写入、查询、更新

子查询

构造子查询SQL,使用fetchSql方法,使用buildSql构造子查询,使用闭包构造子查询

原生查询

Db类支持原生SQL查询操作,主要包括下面两个方法:query方法和execute方法

获取查询参数

可以通过getOptions()获取最终的查询条件.

模型

模型定义

定义方法

模型会自动对应数据表,模型类的命名规则是除去表前缀的数据表名称,采用驼峰法命名,并且首字母大写,例如:

模型名约定对应数据表(假设数据库的前缀定义是 think_
Userthink_user
UserTypethink_user_type

如果你的规则和上面的系统约定不符合,那么需要设置Model类的数据表名称属性,以确保能够找到对应的数据表。

模型自动对应的数据表名称都是遵循小写+下划线规范,如果你的表名有大写的情况,必须通过设置模型的table属性。

如果你希望给模型类添加后缀,需要设置name属性或者table属性。

<?php
namespace app\model;use think\Model;class UserModel extends Model
{protected $name = 'user';
}

模型设置

定义主键

默认主键为id,如果你没有使用id作为主键名,需要在模型中设置属性:

<?php
namespace app\model;use think\Model;class User extends Model
{protected $pk = 'uid';
}

指定数据表

如果你想指定数据表甚至数据库连接的话,可以使用:

<?php
namespace app\model;use think\Model;class User extends Model
{// 设置当前模型对应的完整数据表名称protected $table = 'think_user';// 设置当前模型的数据库连接protected $connection = 'db_config';
}

connection属性使用用配置参数名(需要在数据库配置文件中的connections参数中添加对应标识)。

常用的模型设置属性

属性描述
name模型名(相当于不带数据表前后缀的表名,默认为当前模型类名)
table数据表名(默认自动获取)
suffix数据表后缀(默认为空)
pk主键名(默认为id
connection数据库连接(默认读取数据库配置)
query模型使用的查询类名称
field模型允许写入的字段列表(数组)
schema模型对应数据表字段及类型
type模型需要自动转换的字段及类型
strict是否严格区分字段大小写(默认为true)
disuse数据表废弃字段(数组)

模型不支持对数据表的前缀单独设置,并且也不推荐使用数据表的前缀设计,应该用不同的库区分。当你的数据表没有前缀的时候,nametable属性的定义是没有区别的,定义任何一个即可。

模型初始化

模型支持初始化,只需要定义init方法,例如:

<?php
namespace app\model;use think\Model;class User extends Model
{// 模型初始化protected static function init(){//TODO:初始化内容}
}

init必须是静态方法,并且只在第一次实例化的时候执行,并且只会执行一次

模型操作

模型的操作方法无需调用table或者name方法,例如Db操作方式时

Db::name('user')->where('id','>',10)->select();

改成模型操作的话就变成

User::where('id','>',10)->select();

模型方法依赖注入

需要对模型的方法支持依赖注入,可以把模型的方法改成闭包的方式,例如,你需要对获取器方法增加依赖注入

public function getTestFieldAttr($value,$data) {return $this->invoke(function(Request $request)  use($value,$data) {return $data['name'] . $request->action();});
}

不仅仅是获取器方法,在任何需要依赖注入的方法都可以改造为调用invoke方法的方式,invoke方法第二个参数用于传入需要调用的(数组)参数。

如果你需要直接调用某个已经定义的模型方法(假设已经使用了依赖注入),可以使用

protected function bar($name, Request $request) {// ...
}protected function invokeCall(){return $this->invoke('bar',['think']);
}

模型字段

定义解释

模型的数据字段==对应数据表的字段,默认会自动获取(包括字段类型),但自动获取会导致增加一次查询,因此你可以在模型中明确定义字段信息避免多一次查询的开销。

<?php
namespace app\model;use think\Model;class User extends Model
{// 设置字段信息protected $schema = ['id'          => 'int','name'        => 'string','status'      => 'int','score'       => 'float','create_time' => 'datetime','update_time' => 'datetime',];
}

字段类型的定义可以使用PHP类型或者数据库的字段类型都可以,字段类型定义的作用主要用于查询的参数自动绑定类型

时间字段尽量采用实际的数据库类型定义,便于时间查询的字段自动识别。如果是json类型直接定义为json即可。

如果你没有定义schema属性的话,可以在部署完成后运行如下指令。

php think optimize:schema

运行后会自动生成数据表的字段信息缓存。使用命令行缓存的优势是Db类的查询仍然有效。

需要在数据库配置文件中设置fields_cachetrue才能生成缓存

字段类型

schema属性一旦定义,就必须定义完整的数据表字段类型。
如果你只希望对某个字段定义需要自动转换的类型,可以使用type属性,例如:

<?php
namespace app\model;use think\Model;class User extends Model
{// 设置字段自动转换类型protected $type = ['score'       => 'float',];
}

type属性定义的不一定是实际的字段,也有可能是你的字段别名。

废弃字段

如果因为历史遗留问题 ,你的数据表存在很多的废弃字段,你可以在模型里面定义这些不再使用的字段。

<?php
namespace app\model;use think\Model;class User extends Model
{// 设置废弃字段protected $disuse = [ 'status', 'type' ];
}

在查询和写入的时候会忽略定义statustype废弃字段。

获取数据

在模型外部 (比如在控制器里) 获取数据的方法如下

$user = User::find(1);
echo $user->create_time;  
echo $user->name;

由于模型类实现了ArrayAccess接口,所以可以当成数组使用。

$user = User::find(1);
echo $user['create_time'];  
echo $user['name'];

如果你是在模型内部获取数据的话,需要改成:

$user = $this->find(1);
echo $user->getAttr('create_time');  
echo $user->getAttr('name');

否则可能会出现意想不到的错误。

模型赋值(控制器操作)

模型对象赋值

$user = new User();
$user->name = 'thinkphp';
$user->score = 100;

该方式赋值会自动执行模型的修改器,如果不希望执行修改器操作,可以使用

$data['name'] = 'thinkphp';
$data['score'] = 100;
$user = new User($data);

或者使用

$user = new User();
$data['name'] = 'thinkphp';
$data['score'] = 100;
$user->data($data);

data方法使用修改器

$user = new User();
$data['name'] = 'thinkphp';
$data['score'] = 100;
$user->data($data, true);

数据过滤

$user = new User();
$data['name'] = 'thinkphp';
$data['score'] = 100;
$user->data($data, true, ['name','score']);

表示只设置data数组的namescore数据。

注意:data方法会清空调用前设置的数据

追加数据赋值

$user = new User();
$user->group_id = 1;
$data['name'] = 'thinkphp';
$data['score'] = 100;
$user->appendData($data);    // 如果调用 data ,则清空group_id字段数据

可以通过传入第二个参数 true 来使用修改器 ,比如:appendData($data,true)

严格区分字段大小写

默认情况下,模型数据名称和数据表字段应该保持严格一致,也就是说区分大小写

$user = User::find(1);
echo $user->create_time;  // 正确
echo $user->createTime;  // 错误

严格区分字段大小写的情况下,如果你的数据表字段是大写,模型获取的时候也必须使用大写。

如果你希望在获取模型数据的时候不区分大小写(前提是数据表的字段命名必须规范,即小写+下划线),可以设置模型的strict属性。

<?php
namespace app\model;use think\Model;class User extends Model 
{// 模型数据不区分大小写protected $strict = false,
}

你现在可以使用

$user = User::find(1);
// 下面两种方式都有效
echo $user->createTime; 
echo $user->create_time; 

模型数据转驼峰

V6.0.4+版本开始,可以设置convertNameToCamel属性使得模型数据返回驼峰方式命名(前提也是数据表的字段命名必须规范,即小写+下划线)。

<?php
namespace app\model;use think\Model;class User extends Model 
{// 数据转换为驼峰命名protected $convertNameToCamel = true,
}

然后在模型输出的时候可以直接使用驼峰命名的方式获取。

$user = User::find(1);
$data = $user->toArray();
echo $data['createTime']; // 正确 
echo $user['create_time'];  // 错误

新增数据

新增数据的最佳实践原则使用create方法新增数据,使用saveAll批量新增数据。

添加一条数据

第一种是实例化模型对象后赋值并保存:

$user           = new User;
$user->name     = 'thinkphp';
$user->email    = 'thinkphp@qq.com';
$user->save();

save方法成功会返回true,并且只有当before_insert事件返回false的时候返回false,一旦有错误就会抛出异常。所以无需判断返回类型。

也可以直接传入数据到save方法批量赋值:

$user = new User;
$user->save(['name'  =>  'thinkphp','email' =>  'thinkphp@qq.com'
]);

默认只会写入数据表已有的字段,如果你通过外部提交赋值给模型,并且希望指定某些字段写入,可以使用:

$user = new User;
// post数组中只有name和email字段会写入
$user->allowField(['name','email'])->save($_POST);

最佳的建议是模型数据赋值之前就进行数据过滤,例如:

$user = new User;
// 过滤post数组中的非数据表字段数据
$data = Request::only(['name','email']);
$user->save($data);

save方法新增数据返回的是写入的记录数(通常是1),而不是自增主键值。

Replace写入

save方法可以支持replace写入。

$user           = new User;
$user->name     = 'thinkphp';
$user->email    = 'thinkphp@qq.com';
$user->replace()->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;

不要在同一个实例里面多次新增数据,如果确实需要多次新增,可以使用后面的静态方法处理。

saveAll批量增加数据

支持批量新增,可以使用:

$user = new User;
$list = [['name'=>'thinkphp','email'=>'thinkphp@qq.com'],['name'=>'onethink','email'=>'onethink@qq.com']
];
$user->saveAll($list);

saveAll方法新增数据返回的是包含新增模型(带自增ID)的数据集对象。

saveAll方法新增数据默认会自动识别数据是需要新增还是更新操作,当数据中存在主键的时候会认为是更新操作。

create静态方法

还可以直接静态调用create方法创建并写入:

$user = User::create(['name'  =>  'thinkphp','email' =>  'thinkphp@qq.com'
]);
echo $user->name;
echo $user->email;
echo $user->id; // 获取自增ID

save方法不同的是,create方法返回的是当前模型的对象实例。

create方法默认会过滤不是数据表的字段信息,可以在第二个参数可以传入允许写入的字段列表,例如:

// 只允许写入name和email字段的数据
$user = User::create(['name'  =>  'thinkphp','email' =>  'thinkphp@qq.com'
], ['name', 'email']);
echo $user->name;
echo $user->email;
echo $user->id; // 获取自增ID

支持replace操作,使用下面的方法:

$user = User::create(['name'  =>  'thinkphp','email' =>  'thinkphp@qq.com'
], ['name','email'], true);

更新数据

最佳实践原则是:如果需要使用模型事件,那么就先查询后更新,如果不需要使用事件或者不查询直接更新,直接使用静态的Update方法进行条件更新,如非必要,尽量不要使用批量更新。

save查找并更新

在取出数据后,更改字段内容后使用save方法更新数据。这种方式是最佳的更新方式

$user = User::find(1);
$user->name     = 'thinkphp';
$user->email    = 'thinkphp@qq.com';
$user->save();

save方法成功返回true,并只有当before_update事件返回false的时候返回false,有错误则会抛出异常。

对于复杂的查询条件,也可以使用查询构造器来查询数据并更新

$user = User::where('status',1)->where('name','liuchen')->find();
$user->name     = 'thinkphp';
$user->email    = 'thinkphp@qq.com';
$user->save();

save方法更新数据,只会更新变化的数据,对于没有变化的数据是不会进行重新更新的。如果你需要强制更新数据,可以使用下面的方法:

$user = User::find(1);
$user->name     = 'thinkphp';
$user->email    = 'thinkphp@qq.com';
$user->force()->save();

这样无论你的修改后的数据是否和之前一样都会强制更新该字段的值。

如果要执行SQL函数更新,可以使用下面的方法

$user = User::find(1);
$user->name     = 'thinkphp';
$user->email    = 'thinkphp@qq.com';
$user->score =  Db::raw('score+1');
$user->save();

字段过滤

默认情况下会过滤非数据表字段的数据,如果你通过外部提交赋值给模型,并且希望指定某些字段写入,可以使用:

$user = User::find(1);
// post数组中只有name和email字段会写入
$user->allowField(['name', 'email'])->save($_POST);

最佳用法是在传入模型数据之前就进行过滤,例如:

$user = User::find(1);
// post数组中只有name和email字段会写入
$data = Request::only(['name','email']);
$user->save($data);

批量更新数据

可以使用saveAll方法批量更新数据,只需要在批量更新的数据中包含主键即可,例如:

$user = new User;
$list = [['id'=>1, 'name'=>'thinkphp', 'email'=>'thinkphp@qq.com'],['id'=>2, 'name'=>'onethink', 'email'=>'onethink@qq.com']
];
$user->saveAll($list);

批量更新方法返回的是一个数据集对象。

批量更新仅能根据主键值进行更新,其它情况请自行处理。

update静态更新

使用模型的静态update方法更新:

User::update(['name' => 'thinkphp'], ['id' => 1]);

模型的update方法返回模型的对象实例

如果你的第一个参数中包含主键数据,可以无需传入第二个参数(更新条件)

User::update(['name' => 'thinkphp', 'id' => 1]);

如果你需要只允许更新指定字段,可以使用

User::update(['name' => 'thinkphp', 'email' => 'thinkphp@qq.com'], ['id' => 1], ['name']);

上面的代码只会更新name字段的数据。

自动识别新增或更新

我们已经看到,模型的新增和更新方法都是save方法,系统有一套默认的规则来识别当前的数据需要更新还是新增。

  • 实例化模型后调用save方法表示新增;
  • 查询数据后调用save方法表示更新;

不要在一个模型实例里面做多次更新,会导致部分重复数据不再更新,正确的方式应该是先查询后更新或者使用模型类的update方法更新。

不要调用save方法进行多次数据写入。

删除数据

​ 最佳实践原则是:如果删除当前模型数据,用delete方法,如果需要直接删除数据,使用destroy静态方法。

delete删除当前模型

删除模型数据,可以在查询后调用delete方法。

$user = User::find(1);
$user->delete();

delete方法返回布尔值

destroy根据主键删除

或者直接调用静态方法(根据主键删除)

User::destroy(1);
// 支持批量删除多个数据
User::destroy([1,2,3]);

destroy方法传入空值(包括空字符串和空数组)的时候不会做任何的数据删除操作,但传入0则是有效的

条件删除

还支持使用闭包删除,例如:

User::destroy(function($query){$query->where('id','>',10);
});

或者通过数据库类的查询条件删除

User::where('id','>',10)->delete();

直接调用数据库的delete方法的话无法调用模型事件。

查询数据

最佳实践原则是:在模型外部使用静态方法进行查询,内部使用动态方法查询,包括使用数据库的查询构造器。

获取单个数据

获取单个数据的方法包括:

// 取出主键为1的数据
$user = User::find(1);
echo $user->name;// 使用查询构造器查询满足条件的数据
$user = User::where('name', 'thinkphp')->find();
echo $user->name;

模型使用find方法查询,如果数据不存在返回Null,否则返回当前模型的对象实例。如果希望查询数据不存在则返回一个空模型,可以使用。

$user = User::findOrEmpty(1);

你可以用isEmpty方法来判断当前是否为一个空模型。

$user = User::where('name', 'thinkphp')->findOrEmpty();
if (!$user->isEmpty()) {echo $user->name;
}

如果你是在模型内部获取数据,请不要使用$this->name的方式来获取数据,请使用$this->getAttr('name') 替代。

获取多个数据

取出多个数据:

// 根据主键获取多个数据
$list = User::select([1,2,3]);
// 对数据集进行遍历操作
foreach($list as $key=>$user){echo $user->name;
}

要更多的查询支持,一样可以使用查询构造器:

// 使用查询构造器查询
$list = User::where('status', 1)->limit(3)->order('id', 'asc')->select();
foreach($list as $key=>$user){echo $user->name;
}

查询构造器方式的查询可以支持更多的连贯操作,包括排序、数量限制等。

自定义数据集对象

模型的select方法返回的是一个包含多个模型实例的数据集对象(默认为\think\model\Collection),支持在模型中单独设置查询数据集的返回对象的名称,例如:

<?php
namespace app\index\model;use think\Model;class User extends Model
{// 设置返回数据集的对象名protected $resultSetType = '\app\common\Collection';
}

resultSetType属性用于设置自定义的数据集使用的类名,该类应当继承系统的think\model\Collection类。

使用查询构造器

在模型中仍然可以调用数据库的链式操作和查询方法,可以充分利用数据库的查询构造器的优势。

例如:

User::where('id',10)->find();
User::where('status',1)->order('id desc')->select();
User::where('status',1)->limit(10)->select();

使用查询构造器直接使用静态方法调用即可,无需先实例化模型。

获取某个字段或者某个列的值

// 获取某个用户的积分
User::where('id',10)->value('score');
// 获取某个列的所有值
User::where('status',1)->column('name');
// 以id为索引
User::where('status',1)->column('name','id');

valuecolumn方法返回的不再是一个模型对象实例,而是纯粹的值或者某个列的数组。

动态查询

支持数据库的动态查询方法,例如:

// 根据name字段查询用户
$user = User::getByName('thinkphp');// 根据email字段查询用户
$user = User::getByEmail('thinkphp@qq.com');

聚合查询

同样在模型中也可以调用数据库的聚合方法查询,例如:

User::count();
User::where('status','>',0)->count();
User::where('status',1)->avg('score');
User::max('score');

注意,如果你的字段不是数字类型,是使用max/min的时候,需要加上第二个参数。

User::max('name', false);

数据分批处理

模型也可以支持对返回的数据分批处理,这在处理大量数据的时候非常有用,例如:

User::chunk(100, function ($users) {foreach($users as $user){// 处理user模型对象}
});

使用游标查询

模型也可以使用数据库的cursor方法进行游标查询,返回生成器对象

foreach(User::where('status', 1)->cursor() as $user){echo $user->name;
}

user变量是一个模型对象实例。

查询范围

可以对模型的查询和写入操作进行封装,例如:

<?php
namespace app\model;use think\Model;class User extends Model
{public function scopeThinkphp($query){$query->where('name','thinkphp')->field('id,name');}public function scopeAge($query){$query->where('age','>',20)->limit(10);}    }

就可以进行下面的条件查询:

// 查找name为thinkphp的用户
User::scope('thinkphp')->find();
// 查找年龄大于20的10个用户
User::scope('age')->select();
// 查找name为thinkphp的用户并且年龄大于20的10个用户
User::scope('thinkphp,age')->select();

查询范围的方法可以定义额外的参数,例如User模型类定义如下:

<?php
namespace app\model;use think\Model;class User extends Model
{public function scopeEmail($query, $email){$query->where('email', 'like', '%' . $email . '%');}public function scopeScore($query, $score){$query->where('score', '>', $score);}}

在查询的时候可以如下使用:

// 查询email包含thinkphp和分数大于80的用户
User::email('thinkphp')->score(80)->select();

可以直接使用闭包函数进行查询,例如:

User::scope(function($query){$query->where('age','>',20)->limit(10);
})->select();

使用查询范围后,只能使用find或者select查询。

全局查询范围

支持在模型里面设置globalScope属性,定义全局的查询范围

<?php
namespace app\model;use think\Model;class User extends Model
{// 定义全局的查询范围protected $globalScope = ['status'];public function scopeStatus($query){$query->where('status',1);}
}

然后,执行下面的代码:

$user = User::find(1);

最终的查询条件会是

status = 1 AND id = 1

如果需要动态关闭所有的全局查询范围,可以使用:

// 关闭全局查询范围
User::withoutGlobalScope()->select();

可以使用withoutGlobalScope方法动态关闭部分全局查询范围。

User::withoutGlobalScope(['status'])->select();

Json字段

可以更为方便的操作模型的JSON数据字段。

这里指的JSON数据包括JSON类型以及JSON格式(但并不是JSON类型字段)的数据

定义json字段

我们修改下User模型类

<?php
namespace app\model;use think\Model;
class User extends Model
{// 设置json类型字段protected $json = ['info'];
}

定义后,可以进行如下JSON数据操作。

写入JSON数据

使用数组方式写入JSON数据:

$user = new User;
$user->name = 'thinkphp';
$user->info = ['email'    => 'thinkphp@qq.com','nickname '=> '流年',
];
$user->save();

使用对象方式写入JSON数据

$user = new User;
$user->name = 'thinkphp';
$info = new \StdClass();
$info->email = 'thinkphp@qq.com';
$info->nickname = '流年';
$user->info = $info;
$user->save();

查询JSON数据

$user = User::find(1);
echo $user->name; // thinkphp
echo $user->info->email; // thinkphp@qq.com
echo $user->info->nickname; // 流年

查询条件为JSON数据

$user = User::where('info->nickname','流年')->find();
echo $user->name; // thinkphp
echo $user->info->email; // thinkphp@qq.com
echo $user->info->nickname; // 流年

如果你需要查询的JSON属性是整型类型的话,可以在模型类里面定义JSON字段的属性类型,就会自动进行相应类型的参数绑定查询。

<?php
namespace app\model;use think\Model;class User extends Model
{// 设置json类型字段protected $json = ['info'];// 设置JSON字段的类型protected $jsonType = ['info->user_id'    =>   'int'];
}

没有定义类型的属性默认为字符串类型,因此字符串类型的属性可以无需定义。

可以设置模型的JSON数据返回数组,只需要在模型设置jsonAssoc属性为true

<?php
namespace app\model;use think\Model;class User extends Model
{// 设置json类型字段protected $json = ['info'];// 设置JSON数据返回数组protected $jsonAssoc = true;
}

设置后,查询代码调整为:

$user = User::find(1);
echo $user->name; // thinkphp
echo $user->info['email']; // thinkphp@qq.com
echo $user->info['nickname']; // 流年

更新JSON数据

$user = User::find(1);
$user->name = 'kancloud';
$user->info->email = 'kancloud@qq.com';
$user->info->nickname = 'kancloud';
$user->save();

如果设置模型的JSON数据返回数组,那么更新操作需要调整如下。

$user = User::find(1);
$user->name = 'kancloud';
$info['email'] = 'kancloud@qq.com';
$info['nickname'] = 'kancloud';
$user->info = $info;
$user->save();

获取器

定义解释

获取器的作用是对模型实例的(原始)数据做出自动处理

一个获取器对应模型的一个特殊方法(该方法必须为public类型),方法命名规范为:

get`FieldName`Attr

FieldName数据表字段的驼峰转换,定义了获取器之后会在下列情况自动触发:

  • 模型的数据对象取值操作$model->field_name);
  • 模型的序列化输出操作$model->toArray()toJson());
  • 显式调用getAttr方法$this->getAttr('field_name'));

使用场景

获取器的场景包括:

  • 时间日期字段的格式化输出;
  • 集合或枚举类型的输出;
  • 数字状态字段的输出;
  • 组合字段的输出;

状态值进行转换

<?php
namespace app\model;use think\Model;class User extends Model 
{public function getStatusAttr($value){$status = [-1=>'删除',0=>'禁用',1=>'正常',2=>'待审核'];return $status[$value];}
}

数据表的字段会自动转换为驼峰法,一般status字段的值采用数值类型,我们可以通过获取器定义,自动转换为字符串描述。

$user = User::find(1);
echo $user->status; // 例如输出“正常”

定义数据表中不存在的字段

<?php
namespace app\model;use think\Model;class User extends Model 
{public function getStatusTextAttr($value,$data){$status = [-1=>'删除',0=>'禁用',1=>'正常',2=>'待审核'];return $status[$data['status']];}
}

获取器方法的第二个参数传入的是当前的所有数据数组。

我们就可以直接使用status_text字段的值了,例如:

$user = User::find(1);
echo $user->status_text; // 例如输出“正常”

获取原始数据

如果你定义了获取器的情况下,希望获取数据表中的原始数据,可以使用:

$user = User::find(1);
// 通过获取器获取字段
echo $user->status;
// 获取原始字段数据
echo $user->getData('status');
// 获取全部原始数据
dump($user->getData());

动态获取器

定义解释

可以支持对模型使用动态获取器,无需在模型类中定义获取器方法。

User::withAttr('name', function($value, $data) {return strtolower($value);
})->select();

withAttr方法支持多次调用,定义多个字段的获取器。另外注意,withAttr方法之后不能再使用模型的查询方法必须使用Db类的查询方法

如果同时还在模型里面定义了相同字段的获取器,则动态获取器优先,也就是可以临时覆盖定义某个字段的获取器

支持对关联模型的字段使用动态获取器,例如:

User::with('profile')->withAttr('profile.name', function($value, $data) {return strtolower($value);
})->select();

注意:对于MorphTo关联不支持使用动态获取器。

JSON字段使用获取器

<?php
namespace app\index\model;use think\Model;class User extends Model
{// 设置json类型字段protected $json = ['info'];
}

可以使用下面的代码定义JSON字段的获取器。

User::withAttr('info.name', function($value, $data) {return strtolower($value);
})->select();

可以在查询之后使用withAttr方法,例如:

User::select()->withAttr('name', function($value, $data) {return strtolower($value);
});

查询结果处理

如果你需要处理多个字段的数据或者增加虚拟字段的话,可以使用新版本增加的查询结果处理机制更方便对模型对象实例进行统一的数据处理

User::filter(function($user) {$user->name  = 'new value';$user->test = 'test';
})->select();

注意:filter方法的数据处理和获取器并不冲突filter处理的数据会改变模型的原始数据,获取器只有在取值或输出的时候才会进行处理

修改器

修改器的主要作用是对模型设置数据对象值进行处理。

修改器方法的命名规范为:

setFieldNameAttr

使用场景

修改器的使用场景和读取器类似:

  • 时间日期字段的转换写入;
  • 集合或枚举类型的写入;
  • 数字状态字段的写入;
  • 某个字段涉及其它字段的条件或者组合写入;

触发情况

定义了修改器之后会在下列情况下触发:

  • 模型对象赋值;
  • 调用模型的data方法,并且第二个参数传入true
  • 调用模型的appendData方法,并且第二个参数传入true
  • 调用模型的save方法,并且传入数据;
  • 显式调用模型的setAttr方法;
  • 显式调用模型的setAttrs方法,效果与appendData并传入true的用法相同;

例如:

<?php
namespace app\model;use think\Model;class User extends Model 
{public function setNameAttr($value){//字符串转化为小写return strtolower($value);}
}

如下代码实际保存到数据库中的时候会转为小写

$user = new User();
$user->name = 'THINKPHP';
$user->save();
echo $user->name; // thinkphp

也可以进行序列化字段的组装

namespace app\model;use think\Model;class User extends Model 
{public function setSerializeAttr($value,$data){return serialize($data);}
}

修改器方法的第二个参数会自动传入当前的所有数据数组。

如果你需要在修改器中修改其它数据,可以使用

<?php
namespace app\model;use think\Model;class User extends Model 
{public function setTestFieldAttr($value, $data){$this->set('other_field', $data['some_field']);}
}

上面的例子,在test_field字段的修改器中修改了other_field字段数据,并且没有返回值(表示不对test_field字段做任何修改)。

批量修改

除了赋值的方式可以触发修改器外,还可以用下面的方法批量触发修改器:

$user = new User();
$data['name'] = 'THINKPHP';
$data['email'] = 'thinkphp@qq.com';
$user->data($data, true);
$user->save();
echo $user->name; // thinkphp

如果为nameemail字段都定义了修改器的话,都会进行处理。

或者直接使用save方法触发,例如:

$user = new User();
$data['name'] = 'THINKPHP';
$data['email'] = 'thinkphp@qq.com';
$user->save($data);
echo $user->name; // thinkphp

修改器方法仅对模型的写入方法有效,调用数据库的写入方法写入无效,例如下面的方式修改器无效。

$user = new User();
$data['name'] = 'THINKPHP';
$data['email'] = 'thinkphp@qq.com';
$user->insert($data);

搜索器

搜索器的作用是用于封装字段(或者搜索标识)的查询条件表达式,一个搜索器对应一个特殊的方法(该方法必须是public类型),方法命名规范为:

searchFieldNameAttr

FieldName为数据表字段的驼峰转换,搜索器仅在调用withSearch方法的时候触发。

使用场景

搜索器的场景包括:

  • 限制和规范表单的搜索条件;
  • 预定义查询条件简化查询;

例如,我们需要给User模型定义name字段和时间字段的搜索器,可以使用:

<?php
namespace app\model;use think\Model;class User extends Model 
{public function searchNameAttr($query, $value, $data){$query->where('name','like', $value . '%');}public function searchCreateTimeAttr($query, $value, $data){$query->whereBetweenTime('create_time', $value[0], $value[1]);}    
}

然后,我们可以使用下面的查询

User::withSearch(['name','create_time'], ['name'            =>   'think','create_time' =>   ['2018-8-1','2018-8-5'],'status'      =>   1])->select();

最终生成的SQL语句类似于

SELECT * FROM `think_user` WHERE  `name` LIKE 'think%' AND `create_time` BETWEEN '2018-08-01 00:00:00' AND '2018-08-05 00:00:00' 

可以看到查询条件中并没有status字段的数据,因此可以很好的避免表单的非法查询条件传入,在这个示例中仅能使用namecreate_time条件进行查询。

事实上,除了在搜索器中使用查询表达式外,还可以使用其它的任何查询构造器以及链式操作。

例如,你需要通过表单定义的排序字段进行搜索结果的排序,可以使用

<?php
namespace app\model;use think\Model;class User extends Model 
{public function searchNameAttr($query, $value, $data){$query->where('name','like', $value . '%');if (isset($data['sort'])) {$query->order($data['sort']);}        }public function searchCreateTimeAttr($query, $value, $data){$query->whereBetweenTime('create_time', $value[0], $value[1]);}      
}

然后,我们可以使用下面的查询

User::withSearch(['name','create_time', 'status'], ['name'            =>   'think','create_time' =>   ['2018-8-1','2018-8-5'],'status'      =>   1,'sort'            =>   ['status'=>'desc'],])->select();

最终查询的SQL可能是

SELECT * FROM `think_user` WHERE  `name` LIKE 'think%' AND `create_time` BETWEEN '2018-08-01 00:00:00' AND '2018-08-05 00:00:00' ORDER BY `status` DESC

你可以给搜索器定义字段别名,例如:

User::withSearch(['name'=>'nickname','create_time', 'status'], ['nickname'        =>   'think','create_time' =>   ['2018-8-1','2018-8-5'],'status'      =>   1,'sort'            =>   ['status'=>'desc'],])->select();

搜索器通常会和查询范围进行比较,搜索器无论定义了多少,只需要一次调用,查询范围如果需要组合查询的时候就需要多次调用。

数据集

模型的select查询方法返回数据集对象 think\model\Collection,该对象继承自think\Collection,因此具有数据库的数据集类的所有方法,而且还提供了额外的模型操作方法。

基本用法和数组一样,例如可以遍历和直接获取某个元素。

// 模型查询返回数据集对象
$list = User::where('id', '>', 0)->select();
// 获取数据集的数量
echo count($list);
// 直接获取其中的某个元素
dump($list[0]);
// 遍历数据集对象
foreach ($list as $user) {dump($user);
}
// 删除某个元素
unset($list[0]);

需要注意的是,如果要判断数据集是否为空,不能直接使用empty判断,而必须使用数据集对象的isEmpty方法判断,例如:

$users = User::select();
if($users->isEmpty()){echo '数据集为空';
}

你可以使用模型的hidden/visible/append/withAttr方法进行数据集的输出处理,例如:

// 模型查询返回数据集对象
$list = User::where('id', '>', 0)->select();
// 对输出字段进行处理
$list->hidden(['password']) ->append(['status_text'])->withAttr('name', function($value, $data) {return strtolower($value);});
dump($list);

如果需要对数据集的结果进行筛选,可以使用:

// 模型查询返回数据集对象
$list = User::where('id', '>', 0)->select()->where('name', 'think')->where('score', '>', 80);
dump($list);

支持whereLike/whereIn/whereBetween等快捷方法。

// 模型查询返回数据集对象
$list = User::where('id', '>', 0)->select()->whereLike('name', 'think%')->whereBetween('score', [80,100]);
dump($list);

支持数据集的order排序操作。

// 模型查询返回数据集对象
$list = User::where('id', '>', 0)->select()->where('name', 'think')->where('score', '>', 80)->order('create_time','desc');
dump($list);

支持数据集的diff/intersect操作。

// 模型查询返回数据集对象
$list1 = User::where('status', 1)->field('id,name')->select();
$list2 = User::where('name', 'like', 'think')->field('id,name')->select();
// 计算差集
dump($list1->diff($list2));
// 计算交集
dump($list1->intersect($list2));

批量删除和更新数据

支持对数据集的数据进行批量删除和更新操作,例如:

$list = User::where('status', 1)->select();
$list->update(['name' => 'php']);$list = User::where('status', 1)->select();
$list->delete();

自动时间戳

系统支持自动写入创建和更新的时间戳字段(默认关闭),有两种方式配置支持。

全局开启

第一种方式是全局开启,在数据库配置文件中进行设置:

// 开启自动写入时间戳字段
'auto_timestamp' => true,

单独开启

第二种是在需要的模型类里面单独开启:

<?php
namespace app\model;use think\Model;class User extends Model
{protected $autoWriteTimestamp = true;
}

单独关闭

又或者首先在数据库配置文件中全局开启,然后在个别不需要使用自动时间戳写入的模型类中单独关闭:

<?php
namespace app\model;use think\Model;class User extends Model
{protected $autoWriteTimestamp = false;
}

一旦配置开启的话,会自动写入create_timeupdate_time两个字段的值,默认为整型(int),如果你的时间字段不是int类型的话,可以直接使用:

// 开启自动写入时间戳字段
'auto_timestamp' => 'datetime',

或者

<?php
namespace app\model;use think\Model;class User extends Model
{protected $autoWriteTimestamp = 'datetime';
}

默认的创建时间字段为create_time,更新时间字段为update_time,支持的字段类型包括timestamp/datetime/int

写入数据的时候,系统会自动写入create_timeupdate_time字段,而不需要定义修改器,例如:

$user = new User();
$user->name = 'thinkphp';
$user->save();
echo $user->create_time; // 输出类似 2016-10-12 14:20:10
echo $user->update_time; // 输出类似 2016-10-12 14:20:10

时间字段的自动写入仅针对模型的写入方法,如果使用数据库的更新或者写入方法无效

时间字段输出的时候会自动进行格式转换,如果不希望自动格式化输出,可以把数据库配置文件的 datetime_format 参数值改为false

datetime_format参数支持设置为一个时间类名,这样便于你进行更多的时间处理,例如:

// 设置时间字段的格式化类
'datetime_format' => '\org\util\DateTime',

该类应该包含一个__toString方法定义以确保能正常写入数据库

数据表字段非默认值

如果你的数据表字段不是默认值的话,可以按照下面的方式定义:

<?php
namespace app\model;use think\Model;class User extends Model 
{// 定义时间戳字段名protected $createTime = 'create_at';protected $updateTime = 'update_at';
}

统一定义时间字段

或者在数据库配置文件中统一定义时间字段

'datetime_field'    =>    'create_at,update_at',

下面是修改字段后的输出代码:

$user = new User();
$user->name = 'thinkphp';
$user->save();
echo $user->create_at; // 输出类似 2016-10-12 14:20:10
echo $user->update_at; // 输出类似 2016-10-12 14:20:10

单独关闭某个字段

如果你只需要使用create_time字段而不需要自动写入update_time,则可以单独关闭某个字段,例如:

namespace app\model;use think\Model;class User extends Model 
{// 关闭自动写入update_time字段protected $updateTime = false;
}

动态关闭时间戳写入

支持动态关闭时间戳写入功能,例如你希望更新阅读数的时候不修改更新时间,可以使用isAutoWriteTimestamp方法:

$user = User::find(1);
$user->read +=1;
$user->isAutoWriteTimestamp(false)->save();

自读字段

只读字段用来保护某些特殊的字段值不被更改,这个字段的值一旦写入,就无法更改。 要使用只读字段的功能,我们只需要在模型中定义readonly属性:

<?php
namespace app\model;use think\Model;class User extends Model
{protected $readonly = ['name', 'email'];
}

例如,上面定义了当前模型的nameemail字段为只读字段,不允许被更改。也就是说当执行更新方法之前会自动过滤掉只读字段的值,避免更新到数据库。

下面举个例子说明下:

$user = User::find(5);// 更改某些字段的值
$user->name = 'TOPThink';
$user->email = 'Topthink@gmail.com';
$user->address = '上海静安区';// 保存更改后的用户数据
$user->save();

事实上,由于我们对nameemail字段设置了只读,因此只有address字段的值被更新了,而nameemail的值仍然还是更新之前的值。

支持动态设置只读字段,例如:

$user = User::find(5);// 更改某些字段的值
$user->name = 'TOPThink';
$user->email = 'Topthink@gmail.com';
$user->address = '上海静安区';// 保存更改后的用户数据
$user->readonly(['name','email'])->save();

只读字段仅针对模型的更新方法,如果使用数据库的更新方法则无效,例如下面的方式无效。

$user = new User;// 要更改字段值
$data['name'] = 'TOPThink';
$data['email'] = 'Topthink@gmail.com';
$data['address'] = '上海静安区';// 保存更改后的用户数据
$user->where('id', 5)->update($data);

软删除

在实际项目中,对数据频繁使用删除操作会导致性能问题,软删除的作用就是把数据加上删除标记,而不是真正的删除,同时也便于需要的时候进行数据的恢复

要使用软删除功能,需要引入SoftDelete方法 ,例如User模型按照下面的定义就可以使用软删除功能:

<?php
namespace app\model;use think\Model;
//引入SoftDelete方法 
use think\model\concern\SoftDelete;class User extends Model
{use SoftDelete;protected $deleteTime = 'delete_time';
}

deleteTime属性用于定义你的软删除标记字段ThinkPHP的软删除功能使用时间戳类型(数据表默认值为Null),用于记录数据的删除时间。

可以支持defaultSoftDelete属性来定义软删除字段的默认值,在此之前的版本,软删除字段的默认值必须为null

<?php
namespace app\model;use think\Model;
use think\model\concern\SoftDelete;class User extends Model
{use SoftDelete;protected $deleteTime = 'delete_time';protected $defaultSoftDelete = 0;
}

可以用类型转换指定软删除字段的类型,建议数据表的所有时间字段统一为一种类型

定义好模型后,我们就可以使用:

// 软删除
User::destroy(1);
// 真实删除
User::destroy(1,true);$user = User::find(1);
// 软删除
$user->delete();
// 真实删除
$user->force()->delete();

默认情况下查询的数据不包含软删除数据,如果需要包含软删除的数据,可以使用下面的方式查询:

User::withTrashed()->find();
User::withTrashed()->select();

如果仅仅需要查询软删除的数据,可以使用:

User::onlyTrashed()->find();
User::onlyTrashed()->select();

恢复被软删除的数据

$user = User::onlyTrashed()->find(1);
$user->restore();

软删除的删除操作仅对模型的删除方法有效,如果直接使用数据库的删除方法则无效,例如下面的方式无效

$user = new User;
$user->where('id',1)->delete();

类型转化

支持给字段设置类型自动转换,会在写入和读取的时候自动进行类型转换处理,例如:

<?php
namespace app\model;use think\Model;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,如果希望改变其他格式,可以定义如下:

<?php
namespace app\model;use think\Model;class User extends Model 
{protected $dateFormat = 'Y/m/d';protected $type = ['status'    =>  'integer','score'     =>  'float','birthday'  =>  'timestamp',];
}

或者在类型转换定义的时候使用:

<?php
namespace app\model;use think\Model;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的格式。

模型输出

模型数据的模板输出可以直接把模型对象实例赋值给模板变量,在模板中可以直接输出,例如:

<?php
namespace app\controller;use app\model\User;
use think\facade\View;class Index
{public function index(){$user = User::find(1);View::assign('user', $user);return View::fetch();}
}

在模板文件中可以使用

{$user.name}
{$user.email}

模板中的模型数据输出一样会调用获取器。

数组转换

可以使用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());

对于数据集结果一样可以直接使用(包括appendvisiblehidden方法)

$list = User::select();
$list = $list->toArray();

可以在查询之前定义hidden/visible/append方法,例如:

dump(User::where('id',10)->hidden(['create_time','update_time'])->append(['status_text'])->find()->toArray());

注意,必须要首先调用一次Db类的方法后才能调用hidden/visible/append方法。

追加关联属性

支持追加关联模型的属性到当前模型,例如:

$user = User::find(1);
dump($user->append(['profile' => ['email', 'nickname']])->toArray());

profile是关联定义方法名,emailnicknameProfile模型的属性。

模型的visiblehiddenappend方法支持关联属性操作,例如:

$user = User::with('profile')->find(1);
// 隐藏profile关联属性的email属性
dump($user->hidden(['profile'=>['email']])->toArray());
// 或者使用
dump($user->hidden(['profile.email'])->toArray());

hiddenvisibleappend方法同样支持数据集对象。

JSON序列化

可以调用模型的toJson方法进行JSON序列化,toJson方法的使用和toArray一样。

$user = User::find(1);
echo $user->toJson();

可以设置需要隐藏的字段,例如:

$user = User::find(1);
echo $user->hidden(['create_time','update_time'])->toJson();

或者追加其它的字段(该字段必须有定义获取器):

$user = User::find(1);
echo $user->append(['status_text'])->toJson();

设置允许输出的属性:

$user = User::find(1);
echo $user->visible(['id','name','email'])->toJson();

模型对象可以直接被JSON序列化,例如:

echo json_encode(User::find(1));

输出结果类似于:

{"id":"1","name":"","title":"","status":"1","update_time":"1430409600","score":"90.5"}

如果直接echo 一个模型对象会自动调用模型的toJson方法输出,例如:

echo User::find(1);

输出的结果和上面是一样的。

模型事件

模型事件是指在进行模型的查询和写入操作的时候触发的操作行为。

模型事件只在调用模型的方法生效,使用查询构造器操作是无效的

模型支持如下事件:

事件描述事件方法名
after_read查询后onAfterRead
before_insert新增前onBeforeInsert
after_insert新增后onAfterInsert
before_update更新前onBeforeUpdate
after_update更新后onAfterUpdate
before_write写入前onBeforeWrite
after_write写入后onAfterWrite
before_delete删除前onBeforeDelete
after_delete删除后onAfterDelete
before_restore恢复前onBeforeRestore
after_restore恢复后onAfterRestore

注册的回调方法支持传入一个参数(当前的模型对象实例),但支持依赖注入的方式增加额外参数。

如果before_writebefore_insert、 before_update 、before_delete事件方法中返回false或者抛出think\exception\ModelEventException异常的话,则不会继续执行后续的操作。

模型事件定义

最简单的方式是在模型类里面定义静态方法来定义模型的相关事件响应

<?php
namespace app\model;use think\Model;
use app\model\Profile;class User extends Model
{public static function onBeforeUpdate($user){if ('thinkphp' == $user->name) {return false;}}public static function onAfterDelete($user){Profile::destroy($user->id);}
}

参数是当前的模型对象实例,支持使用依赖注入传入更多的参数。

写入事件

onBeforeWriteonAfterWrite事件会在新增操作和更新操作都会触发.

具体的触发顺序:

// 执行 onBeforeWrite
// 如果事件没有返回`false`,那么继续执行
// 执行新增或更新操作(onBeforeInsert/onAfterInsert或onBeforeUpdate/onAfterUpdate)
// 新增或更新执行成功
// 执行 onAfterWrite

复制

注意:模型的新增或更新是自动判断的.

虚拟模型

虚拟模型不会写入数据库,数据只能保存在内存中,而且只能通过实例化的方式来创建数据,虚拟模型可以保留模型的大部分功能,包括获取器、模型事件,甚至是关联操作。

要使用虚拟模型,只需要在模型定义的时候引入Virtual trait,例如:

<?php
namespace app\model;use think\Model;
use think\model\concern\Virtual;class User extends Model
{use Virtual;public function blog(){return $this->hasMany('Blog');}
}

你不需要在数据库中定义user表,但仍然可以进行相关数据操作,下面是一些例子。

// 创建数据
$user = User::create($data);
// 修改数据
$user->name = 'thinkphp';
$user->save();
// 获取关联数据
$blog = $user->blog()->limit(3)->select();
// 删除数据(同时删除关联blog数据)
$user->together(['blog'])->delete();

由于虚拟模型没有实际的数据表,所以你不能进行查询操作,下面的代码就会抛出异常

User::find(1);
// 会抛出下面的异常
// virtual model not support db query

另外,注意,虚拟模型不再支持自动时间戳功能,如果需要时间字段需要在实例化的时候传入。

模型关联

一个账户下面有一个资料库

一对一关联

关联定义

定义一对一关联,例如,一个用户都有一个个人资料,我们定义User模型如下:

<?php
namespace app\model;use think\Model;class User extends Model
{//profile  个人资料public function profile(){return $this->hasOne(Profile::class);}
}

hasOne方法的参数包括:

hasOne('关联模型类名', '外键', '主键');

参数

除了关联模型外,其它参数都是可选。

  • 关联模型(必须):关联模型类名
  • 外键:默认的外键规则是当前模型名(不含命名空间,下同)+_id ,例如user_id
  • 主键:当前模型主键,默认会自动获取也可以指定传入

额外的方法

一对一关联定义的时候还支持额外的方法,包括:

方法名描述
bind绑定关联属性到父模型
joinTypeJOIN方式查询的JOIN方式,默认为INNER

如果使用了JOIN方式的关联查询方式,你可以在额外的查询条件中使用关联对象名(不含命名空间)作为表的别名。

关联查询

定义好关联之后,就可以使用下面的方法获取关联数据:

$user = User::find(1);
// 输出Profile关联模型的email属性
echo $user->profile->email;

默认情况下, 我们使用的是user_id 作为外键关联,如果不是的话则需要在关联定义的时候指定,例如:

<?php
namespace app\model;use think\Model;class User extends Model 
{public function profile(){return $this->hasOne(Profile::class, 'uid');}
}

有一点需要注意的是,关联方法的命名规范是驼峰法,而关联属性则一般是小写+下划线的方式,系统在获取的时候会自动转换对应,读取user_profile关联属性则对应的关联方法应该是userProfile

根据关联数据查询

可以根据关联条件来查询当前模型对象数据,例如:

// 查询用户昵称是think的用户
// 注意第一个参数是关联方法名(不是关联模型名)
$users = User::hasWhere('profile', ['nickname'=>'think'])->select();
dump($users->toArray());// 可以使用闭包查询
$users = User::hasWhere('profile', function($query) {$query->where('nickname', 'like', 'think%');
})->select();

关联保存

以下方法无限执行会无限增加数据:

$user = User::find(1);
// 如果还没有关联数据 则进行新增
$res = $user->profile()->save(['email' => 'thinkphp']);
dump($res->toArray());

系统会自动把当前模型的主键传入Profile模型。

以下方法必须在已有数据上才能更新成功,返回true:

和新增一样使用save方法进行更新关联数据

$user = User::find(1);
$user->profile->email = 'thinkphp';
$user->profile->save();
// 或者
$user->profile->save(['email' => 'thinkphp']);

预载入查询

可以使用预载入查询解决典型的N+1查询问题,使用:

$users = User::with('profile')->select();
foreach ($users as $user) {echo $user->profile->name;
}

上面的代码使用的是IN查询,只会产生2条SQL查询。

如果要对关联模型进行约束,可以使用闭包的方式。

$users = User::with(['profile'    => function($query) {$query->field('id,user_id,name,email');
}])->select();
foreach ($users as $user) {echo $user->profile->name;
}

with方法可以传入数组,表示同时对多个关联模型(支持不同的关联类型)进行预载入查询。

$users = User::with(['profile','book'])->select();
foreach ($users as $user) {echo $user->profile->name;foreach($user->book as $book) {echo $book->name;}
}

如果需要使用JOIN方式的查询,直接使用withJoin方法,如下:

$users = User::withJoin('profile')->select();
foreach ($users as $user) {echo $user->profile->name;
}

withJoin方法默认使用的是INNER JOIN方式,如果需要使用其它的,可以改成

$users = User::withJoin('profile', 'LEFT')->select();
foreach ($users as $user) {echo $user->profile->name;
}

需要注意的是withJoin方式不支持嵌套关联,通常你可以直接传入多个需要关联的模型。

如果需要约束关联字段,可以使用下面的简便方法。

$users = User::withJoin(['profile' =>   ['user_id', 'name', 'email']
])->select();
foreach ($users as $user) {echo $user->profile->name;
}

定义相对关联

可以理解为关联查询的反向关联,比如关联查询是user模型关联Profile模型,现在是反过来Profile模型主动去关联user模型;

我们可以在Profile模型中定义一个相对的关联关系,例如:

<?php
namespace app\model;use think\Model;class Profile extends Model 
{public function user(){return $this->belongsTo(User::class);}
}

belongsTo的参数包括:

belongsTo('关联模型','外键', '关联主键');

除了关联模型外,其它参数都是可选。

  • 关联模型(必须):关联模型类名
  • 外键:当前模型外键,默认的外键名规则是关联模型名+_id
  • 关联主键:关联模型主键,一般会自动获取也可以指定传入

默认的关联外键是user_id,如果不是,需要在第二个参数定义

<?php
namespace app\model;use think\Model;class Profile extends Model 
{public function user(){return $this->belongsTo(User::class, 'uid');}
}

我们就可以根据档案资料来获取用户模型的信息

$profile = Profile::find(1);
// 输出User关联模型的属性
echo $profile->user->account;

绑定属性到父模型

定义理解:A模型是父模型,关联B模型为子模型,将子模型中的数据字段绑定到A模型中

可以在定义关联的时候使用bind方法绑定属性到父模型,例如:

<?php
namespace app\model;use think\Model;class User extends Model 
{public function profile(){return $this->hasOne(Profile::class, 'uid')->bind(['nickname', 'email']);}
}

或者指定绑定属性别名

<?php
namespace app\model;use think\Model;class User extends Model 
{public function profile(){return $this->hasOne(Profile::class, 'uid')->bind(['email','truename'    => 'nickname',]);}
}

然后使用关联预载入查询的时候,可以使用

$user = User::with('profile')->find(1);
// 直接输出Profile关联模型的绑定属性
echo $user->email;
echo $user->truename;

绑定关联模型的属性支持读取器。

如果不是预载入查询,请使用模型的appendRelationAttr方法追加属性。

也可以使用动态绑定关联属性(放弃这个方法,BUG)

$user = User::find(1)->bindAttr('profile',['email','nickname']);
// 输出Profile关联模型的email属性
echo $user->email;
echo $user->nickname;

同样支持指定属性别名

$user = User::find(1)->bindAttr('profile',['email','truename'    => 'nickname',
]);
// 输出Profile关联模型的email属性
echo $user->email;
echo $user->truename;

关联自动写入

定义解释:在A模型中新增,在B模型中就直接自动操作了

我们可以使用together方法更方便的进行关联自动写入操作,(这个方法非常有用)

以下操纵皆是在控制器中进行操作

写入

$blog = new Blog;
$blog->name = 'thinkphp';
$blog->title = 'ThinkPHP5关联实例';
$content = new Content;
$content->data = '实例内容';
$blog->content = $content;
$blog->together(['content'])->save();

如果绑定了子模型的属性到当前模型,可以指定子模型的属性

public function index()
{$user = new User;$user->name = '吴限明';//写入User表$user->nickname = 'ThinkPHP5关联实例';//写入profile表$user->email = 'wifiming@wifiming.com';//写入profile表// nickname和email是profile(子)模型的属性$user->together(['profile' => ['nickname', 'email']])->save();//$user->together(['关联模型名称' => ['绑定字段A', '绑定字段B']])->save();
}

更新

// 查询
$blog = Blog::find(1);
$blog->title = '更改标题';
$blog->content->data = '更新内容';
// 更新当前模型及关联模型
$blog->together(['content'])->save();

删除

// 查询
$blog = Blog::with('content')->find(1);
// 删除当前及关联模型
$blog->together(['content'])->delete();

一对多关联

一个博客,有多条评论

关联定义

一对多关联的情况也比较常见,使用hasMany方法定义,参数包括:

hasMany('关联模型','外键','主键');

除了关联模型外,其它参数都是可选。

  • 关联模型(必须):关联模型类名
  • 外键:关联模型外键,默认的外键名规则是当前模型名+_id
  • 主键:当前模型主键,一般会自动获取也可以指定传入

例如一篇文章可以有多个评论

<?php
namespace app\model;use think\Model;class Article extends Model 
{public function comments(){return $this->hasMany(Comment::class);}
}

同样,也可以定义外键的名称

<?php
namespace app\model;use think\Model;class Article extends Model 
{public function comments(){return $this->hasMany(Comment::class,'art_id');}
}

关联查询

我们可以通过下面的方式获取关联数据

//一对多的关联查询,输出城市的用户
$city = CityModel::find(1);
dump($city->user->toArray());
// 也可以进行条件搜索--根据用户模型搜索id为1的数据
dump($city->user()->where('id',1)->select()->toArray());

根据关联条件查询

可以根据关联条件来查询当前模型对象数据,例如:

// 查询用户超过10个的城市
$list = CityModel::has('user','>',10)->select()->toArray();
dump($list);
// 查询用户中status状态为1的用户
$list = CityModel::hasWhere('user',['status'=>1])->select()->toArray();

如果需要更复杂的关联条件查询,可以使用

$where = Comment::where('status',1)->where('content', 'like', '%think%');
$list = Article::hasWhere('comments', $where)->select();

关联新增

$article = Article::find(1);
// 增加一个关联数据
$article->comments()->save(['content'=>'test']);
// 批量增加关联数据
$article->comments()->saveAll([['content'=>'thinkphp'],['content'=>'onethink'],
]);

定义相对的关联

要在 Comment 模型定义相对应的关联,可使用 belongsTo 方法:

<?php
name app\model;use think\Model;class Comment extends Model 
{public function article(){return $this->belongsTo(Article::class);}
}

关联删除

在删除文章的同时删除下面的评论

$article = Article::with('comments')->find(1);
$article->together(['comments'])->delete();

扩展库

验证码

composer require topthink/think-captcha

使用

扩展包内定义了一些常见用法方便使用,可以满足大部分常用场景,以下示例说明。

在模版内添加验证码的显示代码

<div>{:captcha_img()}</div>

或者

<div><img src="{:captcha_src()}" alt="captcha" /></div>

上面两种的最终效果是一样的,根据需要调用即可。

然后使用框架的内置验证功能(具体可以参考验证章节),添加captcha验证规则即可

$this->validate($data,['captcha|验证码'=>'require|captcha'
]);

如果没有使用内置验证功能,则可以调研内置的函数手动验证

if(!captcha_check($captcha)){// 验证失败
};

如果是多应用模式下,你需要自己注册一个验证码的路由。

Route::get('captcha/[:config]','\\think\\captcha\\CaptchaController@index');

配置

Captcha类带有默认的配置参数,支持自定义配置。这些参数包括:

参数描述默认
codeSet验证码字符集合
expire验证码过期时间(s)1800
math使用算术验证码false
useZh使用中文验证码false
zhSet中文验证码字符串
useImgBg使用背景图片false
fontSize验证码字体大小(px)25
useCurve是否画混淆曲线true
useNoise是否添加杂点true
imageH验证码图片高度,设置为0为自动计算0
imageW验证码图片宽度,设置为0为自动计算0
length验证码位数5
fontttf验证码字体,不设置是随机获取
bg背景颜色[243, 251, 254]
reset验证成功后是否重置true

直接在应用的config目录下面的captcha.php文件中进行设置即可,例如下面的配置参数用于输出4位数字验证码。

return ['length'    =>  4,'codeSet'   =>  '0123456789',
];

自定义验证码

如果需要自己独立生成验证码,可以调用Captcha类(think\captcha\facade\Captcha)操作。

在控制器中使用下面的代码进行验证码生成:

<?php
namespace app\index\controller;use think\captcha\facade\Captcha;class Index 
{public function verify(){return Captcha::create();    }
}

然后访问下面的地址就可以显示验证码:

http://serverName/index/index/verify

输出效果如图

通常可以给验证码地址注册路由

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

在模板中就可以使用下面的代码显示验证码图片

<div><img src="{:url('index/verify')}" alt="captcha" /></div>

可以用Captcha类的check方法检测验证码的输入是否正确,

// 检测输入的验证码是否正确,$value为用户输入的验证码字符串
$captcha = new Captcha();
if( !$captcha->check($value))
{// 验证失败
}

或者直接调用封装的一个验证码检测的函数captcha_check

// 检测输入的验证码是否正确,$value为用户输入的验证码字符串
if( !captcha_check($value ))
{// 验证失败
}

如果你需要生成多个不同设置的验证码,可以使用下面的配置方式:

<?php
return ['verify'=>['codeSet'=>'1234567890']
];

使用指定的配置生成验证码:

return Captcha::create('verify');

默认情况下,验证码的字体是随机使用扩展包内 think-captcha/assets/ttfs目录下面的字体文件,我们可以指定验证码的字体,例如:
修改或新建配置文件如下:

<?php
return ['verify'=>['fontttf'=>'1.ttf']
];

复制

return Captcha::create('verify');

默认的验证码字符已经剔除了易混淆的1l0o等字符

tp助手函数集

助手函数描述
abort中断执行并发送HTTP状态码
app快速获取容器中的实例 支持依赖注入
bind快速绑定对象实例
cache缓存管理
class_basename获取类名(不包含命名空间)
class_uses_recursive获取一个类里所有用到的trait
config获取和设置配置参数
cookieCookie管理
download获取\think\response\File对象实例
dump浏览器友好的变量输出
env获取环境变量
event触发事件
halt变量调试输出并中断执行
input获取输入数据 支持默认值和过滤
invoke调用反射执行callable 支持依赖注入
jsonJSON数据输出
jsonpJSONP数据输出
lang获取语言变量值
parse_name字符串命名风格转换
redirect重定向输出
request获取当前Request对象
response实例化Response对象
sessionSession管理
token生成表单令牌输出
trace记录日志信息
trait_uses_recursive获取一个trait里所有引用到的trait
urlUrl生成
validate实例化验证器
view渲染模板输出
display渲染内容输出
xmlXML数据输出
app_path当前应用目录
base_path应用基础目录
config_path应用配置目录
public_pathweb根目录
root_path应用根目录
runtime_path应用运行时目录

tp常用方法大全

请求变量

use think\facade\Request;
Request::param('name');
Request::param();全部请求变量 返回数组
Request::param(['name', 'email']); 多个变量
Request::param('a','1') $a不存在使用默认值1
Request::param('username','','strip_tags'); 参数过滤 去掉html标签 htmlspecialchars转换成实体入库 strtolower小写
Request::header(); 请求头数组,支持单个 cookie
input("name");
Request::session();获取 $_SESSION 变量
Request::cookie();获取 $_COOKIE 变量
Request::server();获取 $_SERVER 变量
Request::env();返回env数组
Request::file();获取 $_FILES 变量Request::baseUrl(); /index/index
Request::host(true); 域名:www.baidu.com,默认无参数包含端口:80
Request::url(1); 完整域名和地址 http://tp6.api.shanliwawa.top:80/index/index
Request::domain(1)   http://tp6.api.shanliwawa.top
Request::time()  请求时间戳
Request::app() 应用名 index
Request::controller() 控制器 Index 参数true小写
Request::action() 操作 index 参数true 小写
Request::method(true); 请求类型获取  GET
isGet isPost isPut isDelete isAjax isMobile isHead 判断是否某种类型
Request::has('id','get'); 检测变量id是否存在
url('index/hello', ['id'=>5,'name'=>'李白'],'do');  http://tp6.api.shanliwawa.top/index/hello/李白.do?id=5
url('index/hello#aa'); 锚点Cache::set('name', $value, 3600); 1小时后过期
Cache::get('name'); 获取缓存
多缓存类型配置
return [// 缓存类型为File'type'  =>  'redis',// 全局缓存有效期(0为永久有效)开发下一定要设置-1 否在刷新后 还在'expire'=>  -1,// 缓存前缀'prefix'=>  'think',// 缓存目录'host'       => '127.0.0.1',
];
return [// 使用复合缓存类型'type'  =>  'complex',// 默认使用的缓存'default'   =>  [// 驱动方式'type'   => 'file',// 缓存保存目录'path'   => '../runtime/default',],// 文件缓存'file'   =>  [// 驱动方式'type'   => 'file',// 设置不同的缓存保存目录'path'   => '../runtime/file/',],// redis缓存'redis'   =>  [// 驱动方式'type'   => 'redis',// 服务器地址'host'       => '127.0.0.1',],
];
use think\facade\Cache;
Cache::store('file')->set('name','123',0);
$v =   Cache::store('redis')->get('name');Cache::store('default')->get('name');文件缓存
Cache::delete('name');
Cache::clear();
Cache::set('name', [1,2,3]);
Cache::push('name', 4);
Cache::remember('start_time', time()); 不存在则创建
Cache::inc('name',1); 自增1
Cache::dec('name',1); 自减1
$redis = Cache::handler(); redis对象
配置redis session
return ['type'       => 'redis','prefix'     => 'think','auto_start' => true,// redis主机'host'       => '127.0.0.1',// redis端口'port'       => 6379,// 密码'password'   => '',
]
session('name', ['thinkphp']); 设置支持字符串 数组
session('name');获取
session('name', null);删除
session(null);清空
cookie('name', 'value', 3600);//设置不支持数组,序列化后存储
cookie('name');
cookie('name', null);cookie('think_lang','en-us');//设置语言类型
lang('add user error');//翻译
config('cache.type') //读取配置

验证

{:token_field()} 模板中输出令牌
{:token_meta()} ajax提交
$.ajaxSetup({headers: {'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')}
});
Route::post('blog/save','blog/save')->token(); 路由中使用验证think\facade\Validate
$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   = Validate::rule($rule)->message($msg);
$result = $validate->check($data);
if(!$result) {dump($validate->getError());
}

路由

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'); // 所有请求都支持的路由规则
->allowCrossDomain();跨域

输出响应

$data=['code'=>200,'msg'=>'信息提示','list'=>['中国']];
json($data);
jsonp($data);
xml($data);
redirect('http://www.thinkphp.cn');
redirect('/index/hello/name'); //站内跳转
download('./static/2.xlsx'); 下载

数据库

use think\facade\Db;
$rs =Db::name('user')->where('id',1)->find();  查询一条记录 name不含前缀
$rs =Db::table('ims_user')->where('sex', 2)->select(); 多条数据 table含前缀
$rs1 =Db::name('user')->where('id', 1)->value('name'); 查询某个字段值
$rs =Db::table('ims_user')->where('sex', 2)->column('name,id','id'); 返回name,id列,后面是key
$userId = Db::name('user')->insertGetId($data);//插入数据返回id
Db::name('user')
->limit(100)
->insertAll($data); 插入多条数据,分每次100
Db::name('user')
->where('id', 1)
->update(['name' => 'thinkphp']); 更新
Db::table('think_user')->delete(1);
Db::table('think_user')->delete([1,2,3]);
Db::table('think_user')->where('id',1)->delete();
Db::name('user')->delete(true);//清空数据
where('id','<>',1)  不等于1  >  >=   like 
where("id=:id and username=:name", ['id' => 1 , 'name' => 'thinkphp'])
field('id,title,content') 指定字段
limit(10,25) 第十条开始25条  单数字返回数据条数
page(1,10) 第一页十条
order(['id'=>'desc','sex'=>'desc']) 排序
group('user_id,test_time') 分组
count() max('id') min() avg() sum() 聚合函数
whereTime('birthday', '>=', '1970-10-1')  支持< = 
whereTime('create_time','-2 hours') 查询2小时
whereBetweenTime('create_time', '2017-01-01', '2017-06-30') 查询时间段
whereYear('create_time') 今年 whereYear('create_time','2018')  last year 去年
whereMonth('create_time') last month上月 2018-06 具体月份
whereWeek('create_time') last week 上周
whereDay('create_time')今天 yesterday昨天 2018-11-1具体Db::query("select * from think_user where status=1"); 原生查询
Db::execute("update think_user set name='thinkphp' where status=1");//更新插入删除
Db::query("select * from think_user where id=? AND status=?", [8, 1]);//绑定
$list = Db::name('user')->where('status',1)->paginate(10); 分页每页10条

模型

定义全局常量

define('__URL__',\think\facade\Request::domain(1)); http://tp6.api.shanliwawa.top
define('__ROOT__',\think\facade\app::getRootPath());  系统根目录 C:\www\tp6\
define("PRE",config('database.prefix')); 表前缀

绝对路径获取

\think\facade\app::getRootPath() 根目录C:\www\tp6\
\think\facade\app::getAppPath()  应用路径  C:\www\tp6\app\index\
\think\facade\app::getConfigPath() 配置路径C:\www\tp6\config\
\think\facade\app::version() 核心版本

模板视图

use think\facade\View;View::assign(['name'  => 'ThinkPHP','email' => 'thinkphp@qq.com']);View::assign('data',['name'  => 'ThinkPHP','email' => 'thinkphp@qq.com']);
View::fetch('index');//助手函数
view('index', ['name'  => 'ThinkPHP','email' => 'thinkphp@qq.com'
]);//模板输出
{$name}
{$data.name} 等价 {$data['name']}
{:dump($data)} 使用函数 :开头
{$user.nickname|default="这家伙很懒,什么也没留下"}
{$Think.cookie.name}  // 输出$_COOKIE['name']变量
{$Think.server.script_name} // 输出$_SERVER['SCRIPT_NAME']变量
{$Think.session.user_id} // 输出$_SESSION['user_id']变量
{$Think.get.page} // 输出$_GET['page']变量
{$Request.param.name} 获取name
{$data.name|raw} 不转义输出
{$data.create_time|date='Y-m-d H:i'}
{literal}Hello,{$name}!//原样输出
{/literal}
{load href="/static/js/common.js,/static/js/common.css" /} 加载js,css
{php}echo 'Hello,world!';{/php}{/* 注释内容 */ } 或 {// 注释内容 }
{include file="public/header" /} 模板包含
{include file="Public/header" title="$title" keywords="开源WEB开发框架" /} 传入参数{foreach $list as $key=>$vo } {$vo.id}:{$vo.name}
{/foreach}{for start="开始值" end="结束值" comparison="" step="步进值" name="循环变量名" }
{/for}{if 表达式}value1
{elseif 表达式 /}value2
{else /}value3
{/if}

记录日志

log.php 可添加  'json'   =>   1 表示json格式
trace("日志信息")//app.php中'app_trace'             => true,
//trace.php改为默认html
'type' => 'Console',

上传

$file = request()->file('image');//移动到框架应用根目录/uploads/ 目录下$info = $file->move( '../uploads');if($info){//  成功上传后 获取上传信息// 输出 jpgecho $info->getExtension();// 输出 20160820/42a79759f284b767dfcb2a0197904287.jpgecho $info->getSaveName();//输出 42a79759f284b767dfcb2a0197904287.jpgecho $info->getFilename(); }else{//上传失败获取错误信息echo $file->getError();}// 多文件xphrforeach($files as $file){}//验证,生成带md5文件名$info = $file->rule('md5')->validate(['size'=>15678,'ext'=>'jpg,png,gif'])->move( '../uploads');

常见错误集

variable type error

<---将变量return报错-->变量类型错误:数组//解决方案:json($变量)

Driver [Think] not supported.

<---没安装视图扩展--->驱动程序[认为]不受支持//composer输入composer require topthink/think-view即可

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/48898.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

国际化技术参考

一、概述 国际化就是用户可以选择对应的语言,页面展示成对应的语言; 一个系统的国际化按照信息的所在位置,可以分为三种国际化信息: 前端页面信息后端提示信息数据库的字典类信息二、前端页面国际化 使用i18n库实现国际化 i18n国际化库思路:通过jquery或者dom操作拿到需…

推荐4款简单高效的视频转文字工具。

最近我要将很多的以前的培训视频转换成笔记&#xff0c;觉得很麻烦&#xff0c;于是就搜索有没有什么工具可以帮助。结果就真的找到了很多将视频转换成文字的软件和网站。解决了一个大工程&#xff0c;后来发现其实很多人都会碰到像我这样的问题&#xff0c;于是在这里将我使用…

类和对象:完结

1.再深构造函数 • 之前我们实现构造函数时&#xff0c;初始化成员变量主要使⽤函数体内赋值&#xff0c;构造函数初始化还有⼀种⽅ 式&#xff0c;就是初始化列表&#xff0c;初始化列表的使⽤⽅式是以⼀个冒号开始&#xff0c;接着是⼀个以逗号分隔的数据成 员列表&#xf…

通信原理-思科实验三:无线局域网实验

实验三 无线局域网实验 一&#xff1a;无线局域网基础服务集 实验步骤&#xff1a; 进入物理工作区&#xff0c;导航选择 城市家园; 选择设备 AP0&#xff0c;并分别选择Laptop0、Laptop1放在APO范围外区域 修改笔记本的网卡&#xff0c;从以太网卡切换到无线网卡WPC300N 切…

力扣Hot100-543二叉树的直径

给你一棵二叉树的根节点&#xff0c;返回该树的 直径 。 二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。 两节点之间路径的 长度 由它们之间边数表示。 示例 1&#xff1a; 输入&#xff1a;root [1,2,3,4,5] 输出&a…

C++ 基础(类和对象下)

目录 一. 再探构造函数 1.1. 初始化列表&#xff08;尽量使用列表初始化&#xff09; 二. static成员 2.1static成员初始化 三.友元 3.1友元&#xff1a;提供了⼀种 突破类访问限定符封装的方式. 四.内部类 4.1如果⼀个类定义在另⼀个类的内部&#xff0c;这个内部类就叫…

2024.7.24 作业

1.二叉树的创建、遍历自己实现一遍 bitree.h #ifndef BITREE_H #define BITREE_H#include <myhead.h>typedef char datatype;typedef struct Node {datatype data;struct Node *left_child;struct Node *right_child; }Node,*BiTreePtr;//创建二叉树 BiTreePtr tree_cr…

我在百科荣创企业实践——简易函数信号发生器(5)

对于高职教师来说,必不可少的一个任务就是参加企业实践。这个暑假,本人也没闲着,报名参加了上海市电子信息类教师企业实践。7月8日到13日,有幸来到美丽的泉城济南,远离了上海的酷暑,走进了百科荣创科技发展有限公司。在这短短的一周时间里,我结合自己的教学经验和企业的…

【Java语法基础】9.异常处理

9. 异常处理 Error是程序无法处理的错误&#xff0c;出现时线程被JVM终止。 Exception&#xff0c;指的是程序运行时可以处理的异常。其继承关系如下表&#xff1a; 运行时异常&非运行时异常 运行时异常 都是RuntimeException类及其子类异常&#xff0c;如NullPointerE…

模拟实现c++中的string

c内置string库的相关函数&#xff1a;string - C Reference 目录 一string类构造&#xff0c;拷贝构造和析构&#xff1a; 二string内正向迭代器实现&#xff1a; 三赋值运算符重载实现&#xff1a; 四reserve&#xff0c;empty&#xff0c;clear实现&#xff1a; 五push_b…

动手学深度学习——6.循环神经网络

1.序列模型 处理序列数据需要统计工具和新的深度神经网络架构。 为了简单起见&#xff0c;我们以 图8.1.1所示的股票价格&#xff08;富时100指数&#xff09;为例。 图8.1.1 近30年的富时100指数 其中&#xff0c;用&#x1d465;&#x1d461;表示价格&#xff0c;即在时间…

LIS检验信息软件源码,适合二级医院的应用

LIS系统主要面向医院检验科&#xff0c;包含检验医生日常处理、报告处理、质量控制、条码管理、仪器双工通讯、无人值守等诸多功能模块&#xff0c;能与HIS系统、体检系统和电子病历信息系统实现无缝连接&#xff0c;已成功应用于多家各种规模的医院&#xff0c;满足客户各方面…

Git之repo sync -c与repo sync -dc用法区别(四十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

InternLM学习笔记

入门岛 1. Linux基础知识 2. Python 基础知识 from collections import Countertext """ Got this panda plush toy for my daughters birthday, who loves it and takes it everywhere. Its soft and super cute, and its face has a friendly look. Its a …

论文阅读【检测】:Facebook ECCV2020 | DETR

文章目录 论文地址AbstractMotivation模型框架详细结构小结 论文地址 DETR Abstract 提出了一种将目标检测视为直接集预测问题的新方法。简化了检测pipeline&#xff0c;有效地消除了许多手工设计的组件的需求&#xff0c;例如非最大抑制过程或锚生成&#xff0c;这些组件明…

设计模式|观察者模式

观察者模式是一种行为设计模式&#xff0c;它定义了一种一对多的依赖关系&#xff0c;让多个观察者对象同时监听某一个主题对象。当主题对象发生变化时&#xff0c;它的所有观察者都会收到通知并更新。观察者模式常用于实现事件处理系统、发布-订阅模式等。在项目中&#xff0c…

磁盘管理与磁盘卷--红帽Linux操作系统<>

分区的两种格式 1、MBR分区 MBR(Master Boot Record&#xff0c;主引导记录)是传统的分区机制&#xff0c;使用BI0S引导PC设备&#xff0c;寻址空间只有32bit长。 分区空间最大支持2.2TB 支持的分区数量:4个主分区或者3个主分区1个扩展分区 为什么MBR最多只能有4个主分区?…

云服务部署项目(Spring + Vue)

云计算&#xff1a;腾讯云 操作系统&#xff1a;Ubuntu 22.04.4 LTS 项目&#xff1a;若依前后端分离项目&#xff08;SpringBoot Vue&#xff09; 首先要安装基本的一些依赖环境&#xff0c;大家可以看一下我往期的文章&#xff1a; Ubuntu在线JDK Ubuntu在线安装Nginx Ubunt…

文件解析的终极工具:Apache Tika

文件解析的终极工具&#xff1a;Apache Tika Apache Tika 简介 Apache Tika 是一个开源的、跨平台的库&#xff0c;用于检测、提取和解析各种类型文件的元数据。 它支持多种文件格式&#xff0c;包括文档、图片、音频和视频。 Tika是一个底层库&#xff0c;经常用于搜索引擎…

Android 列表或网格形式展示大量数据:RecyclerView

目录 RecyclerView是什么如何使用RecyclerView 涉及到的类LayoutManager为Item设置不同的布局样式制作拖动的RecyclerView 一、RecyclerView是什么 RecyclerView是Android支持库中的一个控件&#xff0c;用于在列表或网格形式展示大量数据。它是ListView的升级版&#xff0c…