基础部分
安装
环境要求
*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)和文件名保持一致,统一采用驼峰法命名(首字母大写);
函数和类、属性命名
- 类的命名采用驼峰法(首字母大写),例如 User、UserType;
- 函数的命名使用小写字母和下划线(小写字母开头)的方式,例如 get_client_ip;
- 方法的命名使用驼峰法(首字母小写),例如 getUserName;
- 属性的命名使用驼峰法(首字母小写),例如 tableName、instance;
- 特例:以双下划线__打头的函数或方法作为魔术方法,例如__call和__autoload;
常量和配置
- 常量以大写字母和下划线命名,例如 APP_PATH;
- 配置参数以小写字母和下划线命名,例如 url_route_on和url_convert;
- 环境变量定义使用大写字母和下划线命名,例如APP_DEBUG;
数据表和字段
- 数据表和字段采用小写加下划线方式命名,并注意字段名不要以下划线开头,例如 think_user表和user_name字段,不建议使用驼峰和中文作为数据表及字段命名。
避免使用PHP保留字
 编译时常量
CLASS DIR FILE FUNCTION LINE METHOD NAMESPACE TRAIT 
应用模式
单应用
基础认知
- 
框架默认安装的模式为单应用 
- 
根目录下的 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配置文件,避免冲突。
配置参数
环境变量配置的参数会全部转换为大写,值为 off,no 和 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.php | Cookie配置 | 
| database.php | 数据库配置 | 
| filesystem.php | 磁盘配置 | 
| lang.php | 多语言配置 | 
| log.php | 日志配置 | 
| middleware.php | 中间件配置 | 
| route.php | 路由和URL配置 | 
| session.php | Session配置 | 
| trace.php | 页面Trace配置 | 
| view.php | 视图配置 | 
架构部分
基础认知
入口文件
入口文件位于public目录下面,最常见的入口文件就是index.php,6.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路由重写 ]
- httpd.conf配置文件中加载了- mod_rewrite.so模块
- AllowOverride None将- None改为- All
- 把下面的内容保存为.htaccess文件放到应用入口文件的同级目录下
`<IfModule mod_rewrite.c>
  Options +FollowSymlinks -Multiviews
  RewriteEngine On
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L]
</IfModule>`[ IIS 路由重写]
如果你的服务器环境支持ISAPI_Rewrite的话,可以配置httpd.ini文件,添加下面的内容:
`RewriteRule (.*)$ /index\.php\?s=$1 [I]`在IIS的高版本下面可以配置web.Config,在中间添加rewrite节点:
`<rewrite>
 <rules>
 <rule name="OrgPage" stopProcessing="true">
 <match url="^(.*)$" />
 <conditions logicalGrouping="MatchAll">
 <add input="{HTTP_HOST}" pattern="^(.*)$" />
 <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
 <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
 </conditions>
 <action type="Rewrite" url="index.php/{R:1}" />
 </rule>
 </rules>
 </rewrite>`[ Nginx路由重写 ]
在Nginx低版本中,是不支持PATHINFO的,但是可以通过在Nginx.conf中配置转发规则实现:
`location / {
        if (!-e $request_filename) {
            rewrite  ^(.*)$  /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\App | app | 
| think\Cache | cache | 
| think\Config | config | 
| think\Cookie | cookie | 
| think\Console | console | 
| think\Db | db | 
| think\Debug | debug | 
| think\Env | env | 
| think\Event | event | 
| think\Http | http | 
| think\Lang | lang | 
| think\Log | log | 
| think\Middleware | middleware | 
| think\Request | request | 
| think\Response | response | 
| think\Filesystem | filesystem | 
| think\Route | route | 
| think\Session | session | 
| think\Validate | validate | 
| think\View | view | 
全局容器
在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,并且自动生成了系统服务类最常用的两个空方法:register和boot方法。
`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指令用于生成服务列表,并在系统初始化过程中自动注册。
内置服务
为了更好的完成核心组件的单元测试,框架内置了一些系统服务类,主要都是用于核心类的依赖注入,包括ModelService、PaginatorService和ValidateService类。这些服务不需要注册,并且也不能卸载。
门面
定义解释
- 就是使用中间类来操作,把动态类封装成静态调用接口
- 系统已经为大部分核心类库定义了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\App | think\facade\App | 
| think\Cache | think\facade\Cache | 
| think\Config | think\facade\Config | 
| think\Cookie | think\facade\Cookie | 
| think\Db | think\facade\Db | 
| think\Env | think\facade\Env | 
| think\Event | think\facade\Event | 
| think\Filesystem | think\facade\Filesystem | 
| think\Lang | think\facade\Lang | 
| think\Log | think\facade\Log | 
| think\Middleware | think\facade\Middleware | 
| think\Request | think\facade\Request | 
| think\Route | think\facade\Route | 
| think\Session | think\facade\Session | 
| think\Validate | think\facade\Validate | 
| think\View | think\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 。
``<?php
namespace 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``<?php
return [
    \app\middleware\Auth::class,
    'check',
    'Hello',
];`中间件的注册应该使用完整的类名,如果已经定义了中间件别名(或者分组)则可以直接使用。
全局中间件的执行顺序就是定义顺序。可以在定义全局中间件的时候传入中间件参数,支持两种方式传入。
`<?php
return [
    [\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,
],`这样,所有该应用下的请求都会执行Auth和Check中间件。
控制器中间件
支持为控制器定义中间件,只需要在控制器中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_select | select查询前回调 | 
| before_find | find查询前回调 | 
| after_insert | insert操作成功后回调 | 
| after_update | update操作成功后回调 | 
| after_delete | delete操作成功后回调 | 
查询事件的参数就是当前的查询对象实例。
模型事件包含:
| 钩子 | 对应操作 | 
|---|---|
| after_read | 查询后 | 
| before_insert | 新增前 | 
| after_insert | 新增后 | 
| before_update | 更新前 | 
| after_update | 更新后 | 
| before_write | 写入前 | 
| after_write | 写入后 | 
| before_delete | 删除前 | 
| after_delete | 删除后 | 
before_write和after_write事件无论是新增还是更新都会执行。
模型事件方法的参数就是当前的模型对象实例。
路由
路由定义
路由表达式
Route::rule('路由表达式', '路由地址', '请求类型');
请求类型
| 类型 | 描述 | 快捷方法 | 
|---|---|---|
| GET | GET请求 | get | 
| POST | POST请求 | post | 
| PUT | PUT请求 | put | 
| DELETE | DELETE请求 | delete | 
| PATCH | PATCH请求 | 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]);`上面的路由规则定义中``status``和``app_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()
    {
        // 自定义路由调度
    }
}`路由参数
路由分组及规则定义支持指定路由参数,这些参数主要完成路由匹配检测以及后续行为。
路由参数表
路由参数可以在定义路由规则的时候直接传入(批量),推荐使用方法配置更加清晰。
| 参数 | 说明 | 方法名 | 
|---|---|---|
| ext | URL后缀检测,支持匹配多个后缀 | ext | 
| deny_ext | URL禁止后缀检测,支持匹配多个后缀 | denyExt | 
| https | 检测是否https请求 | https | 
| domain | 域名检测 | domain | 
| complete_match | 是否完整匹配路由 | completeMatch | 
| model | 绑定模型 | model | 
| cache | 请求缓存 | cache | 
| ajax | Ajax检测 | ajax | 
| pjax | Pjax检测 | pjax | 
| json | JSON检测 | 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_id``和``status``两个参数。
路由绑定模型
路由规则和分组支持绑定模型数据,例如:
`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');`表示查询``id``和``name``的值等于当前路由变量的模型数据。
也可以使用闭包来自定义返回需要的模型对象
`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,
],`所有该应用下的请求都会执行Auth和Check中间件。
路由分组
把相同前缀的路由定义合并分组
分组方法
使用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个路由规则,如下:
| 标识 | 请求类型 | 生成路由规则 | 对应操作方法(默认) | 
|---|---|---|---|
| index | GET | blog | index | 
| create | GET | blog/create | create | 
| save | POST | blog | save | 
| read | GET | blog/:id | read | 
| edit | GET | blog/:id/edit | edit | 
| update | PUT | blog/:id | update | 
| delete | DELETE | blog/:id | delete | 
前端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控制器对应的操作方法如下:
`<?php
namespace 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()
    {
        // 输出JSON
        return json($data);
    }
    public function read()
    {
        // 渲染默认模板输出
        return view();
    }
}`
控制器一般不需要任何输出,直接``return``即可。并且控制器在``json``请求会自动转换为``json``格式输出。
不要在控制器中使用包括``die``、``exit``在内的中断代码。如果你需要调试并中止执行,可以使用系统提供的``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\App``和``think\Request``对象,因此你可以直接在控制器中使用``app``和``request``属性调用``think\App``和``think\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';
    }
}`控制器传参
可以通过给请求对象赋值的方式传参给控制器(或者其它地方),例如
`<?php
namespace 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地址(采用``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'`验证某个字段的值只能是可打印字符(包括空格),例如:
`'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'`支持字段自动匹配验证规则,如``password``和``password_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``中进行了定义,所以你仍然可以和之前一样直接使用容器和静态代理操作请求对象。
构造方法注入
一般适用于没有继承系统的控制器类的情况。
`<?php
namespace app\index\controller;
use think\Request;
class Index 
{
    /**
     * @var \think\Request Request实例
     */
    protected $request;
    /**
     * 构造方法
     * @param Request $request Request对象
     * @access public
     */
    public function __construct(Request $request)
    {
        $this->request = $request;
    }
    public function index()
    {
        return $this->request->param('name');
    }    
}`操作方法注入
另外一种选择是在每个方法中使用依赖注入。
`<?php
namespace app\index\controller;
use think\Request;
class Index
{
    public function index(Request $request)
    {
        return $request->param('name');
    }    
}`无论是否继承系统的控制器基类,都可以使用操作方法注入。
更多关于依赖注入的内容,请参考依赖注入章节。
静态调用
在没有使用依赖注入的场合 ,可以通过``Facade``机制来静态调用请求对象的方法(注意``use``引入的类库区别)。
`<?php
namespace app\index\controller;
use think\facade\Request;
class Index
{
    public function index()
    {
        return Request::param('name');
    }    
}`该方法也同样适用于依赖注入无法使用的场合。
助手函数
为了简化调用,系统还提供了``request``助手函数,可以在任何需要的时候直接调用当前请求对象。
`<?php
namespace 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 | 当前执行的文件 | 
| root | URL访问根地址 | 
| rootUrl | URL访问根目录 | 
| 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`
除了``server``和``env``方法的变量名不区分大小写(会自动转为大写后获取),其它变量名区分大小写。
默认值
获取输入变量的时候,可以支持默认值,例如当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``变量的获取。
`<?php
namespace app\http\middleware;
class Check
{
    public function handle($request, \Closure $next)
    {
        if ('think' == $request->name) {
            $request->name = 'ThinkPHP';
        }
        return $next($request);
    }
}`助手函数
为了简化使用,还可以使用系统提供的``input``助手函数完成上述大部分功能。
判断变量是否定义
`input('?get.id');
input('?post.name');`获取PARAM参数
`input('param.name'); // 获取单个参数
input('param.'); // 获取全部参数
// 下面是等效的
input('name'); 
input('');`获取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');`请求类型
获取请求类型
判断当前操作的请求类型是``GET``、``POST``、``PUT``、``DELETE``或者``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``请求。
你可以设置为任何合法的请求类型,包括``GET``、``POST``、``PUT``和``DELETE``等,但伪装变量``_method``只能通过POST请求进行提交。
如果要获取原始的请求类型,可以使用
`Request::method(true);`
在命令行下面执行的话,请求类型返回的始终是``GET``。
如果你需要改变伪装请求的变量名,可以修改自定义Request类的``varMethod``属性:
AJAX/PJAX``伪装
可以对请求进行``AJAX``请求伪装,如下:
`http://localhost/index?_ajax=1`或者``PJAX``请求伪装
`http://localhost/index?_pjax=1`如果你需要改变伪装请求的变量名,可以修改自定义Request类的``varAjax``和``varPjax``属性:
_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``控制器定义了两个操作方法``read``和``archive``方法,由于``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``参数和``year``和``month``参数会自动和``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``参数进行动态标识替换,也就是根据``id``和``page``变量进行``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')) {
            // 删除session
            session('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 | 数据库 | 
|---|---|
| mysql | MySQL | 
| sqlite | SqLite | 
| pgsql | PostgreSQL | 
| sqlsrv | SqlServer | 
| mongo | MongoDb | 
| oracle | Oracle | 
每个应用可以设置独立的数据库连接参数 ,通常直接更改``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方法
- 那么``name``和``table``方法效果一致。
查询数据
查询单个数据
| 方法 | 作用 | 返回结果 | 示例 | 
|---|---|---|---|
| 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 | 查询字段是否(不)是NULL | whereNull/whereNotNull | 
| [NOT] EXISTS | EXISTS查询 | whereExists/whereNotExists | 
| [NOT] REGEXP | 正则(不)匹配查询(仅支持Mysql) | |
| [NOT] BETWEEN TIME | 时间区间比较 | whereBetweenTime | 
| > TIME | 大于某个时间 | whereTime | 
| < TIME | 小于某个时间 | whereTime | 
| >= TIME | 大于等于某个时间 | whereTime | 
| <= TIME | 小于等于某个时间 | whereTime | 
| EXP | 表达式查询,支持SQL语法 | whereExp | 
| find in set | FIND_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();`推荐的方式是使用``whereNull``和``whereNotNull``方法查询。
`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 | 设置查询数据为空时是否需要抛出异常,用于``select``和``find``方法 | 
| 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_``) | 
|---|---|
| User | think_user | 
| UserType | think_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 | 数据表废弃字段(数组) | 
模型不支持对数据表的前缀单独设置,并且也不推荐使用数据表的前缀设计,应该用不同的库区分。当你的数据表没有前缀的时候,``name``和``table``属性的定义是没有区别的,定义任何一个即可。
模型初始化
模型支持初始化,只需要定义``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_cache``为``true``才能生成缓存
字段类型
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' ];
}`在查询和写入的时候会忽略定义 的``status``和``type``废弃字段。
获取数据
在模型外部 (比如在控制器里) 获取数据的方法如下
`$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``数组的``name``和``score``数据。
注意:``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');`
value``和``column``方法返回的不再是一个模型对象实例,而是纯粹的值或者某个列的数组。
动态查询
支持数据库的动态查询方法,例如:
`// 根据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``处理的数据会改变模型的原始数据,获取器只有在取值或输出的时候才会进行处理。
修改器
修改器的主要作用是对模型设置 的数据对象值进行处理。
修改器方法的命名规范为:
set``FieldName``Attr
使用场景
修改器的使用场景和读取器类似:
- 时间日期字段的转换写入;
- 集合或枚举类型的写入;
- 数字状态字段的写入;
- 某个字段涉及其它字段的条件或者组合写入;
触发情况
定义了修改器之后会在下列情况下触发:
- 模型对象赋值;
- 调用模型的``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`如果为``name``和``email``字段都定义了修改器的话,都会进行处理。
或者直接使用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``类型),方法命名规范为:
search``FieldName``Attr
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``字段的数据,因此可以很好的避免表单的非法查询条件传入,在这个示例中仅能使用``name``和``create_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_time``和``update_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_time``和``update_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'];
}`例如,上面定义了当前模型的``name``和``email``字段为只读字段,不允许被更改。也就是说当执行更新方法之前会自动过滤掉只读字段的值,避免更新到数据库。
下面举个例子说明下:
`$user = User::find(5);
 // 更改某些字段的值
$user->name = 'TOPThink';
$user->email = 'Topthink@gmail.com';
$user->address = '上海静安区';
 // 保存更改后的用户数据
$user->save();`事实上,由于我们对``name``和``email``字段设置了只读,因此只有``address``字段的值被更新了,而``name``和``email``的值仍然还是更新之前的值。
支持动态设置只读字段,例如:
`$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());`对于数据集结果一样可以直接使用(包括``append``、``visible``和``hidden``方法)
`$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``是关联定义方法名,``email``和``nickname``是``Profile``模型的属性。
模型的``visible``、``hidden``和``append``方法支持关联属性操作,例如:
`$user = User::with('profile')->find(1);
// 隐藏profile关联属性的email属性
dump($user->hidden(['profile'=>['email']])->toArray());
// 或者使用
dump($user->hidden(['profile.email'])->toArray());`hidden``、``visible``和``append``方法同样支持数据集对象。
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_write``、``before_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);
    }
}`参数是当前的模型对象实例,支持使用依赖注入传入更多的参数。
写入事件
onBeforeWrite``和``onAfterWrite``事件会在``新增``操作和``更新``操作都会触发.
具体的触发顺序:
``// 执行 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 | 绑定关联属性到父模型 | 
| joinType | JOIN方式查询的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 | 获取和设置配置参数 | 
| cookie | Cookie管理 | 
| download | 获取\think\response\File对象实例 | 
| dump | 浏览器友好的变量输出 | 
| env | 获取环境变量 | 
| event | 触发事件 | 
| halt | 变量调试输出并中断执行 | 
| input | 获取输入数据 支持默认值和过滤 | 
| invoke | 调用反射执行callable 支持依赖注入 | 
| json | JSON数据输出 | 
| jsonp | JSONP数据输出 | 
| lang | 获取语言变量值 | 
| parse_name | 字符串命名风格转换 | 
| redirect | 重定向输出 | 
| request | 获取当前Request对象 | 
| response | 实例化Response对象 | 
| session | Session管理 | 
| token | 生成表单令牌输出 | 
| trace | 记录日志信息 | 
| trait_uses_recursive | 获取一个trait里所有引用到的trait | 
| url | Url生成 | 
| validate | 实例化验证器 | 
| view | 渲染模板输出 | 
| display | 渲染内容输出 | 
| xml | XML数据输出 | 
| app_path | 当前应用目录 | 
| base_path | 应用基础目录 | 
| config_path | 应用配置目录 | 
| public_path | web根目录 | 
| 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){
       //  成功上传后 获取上传信息
        // 输出 jpg
        echo $info->getExtension();
      // 输出 20160820/42a79759f284b767dfcb2a0197904287.jpg
        echo $info->getSaveName();
        //输出 42a79759f284b767dfcb2a0197904287.jpg
        echo $info->getFilename(); 
    }else{
        //上传失败获取错误信息
        echo $file->getError();
    }
   // 多文件xphr
     foreach($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即可`