Thinkphp6学习笔记,持续记录
- PHP笔记
- 2019-01-16
- 1300热度
- 0评论
官方文档
由此开始:https://www.kancloud.cn/manual/thinkphp6_0/1037479
目录结构:https://www.kancloud.cn/manual/thinkphp6_0/1037483
伪静态设置:https://www.kancloud.cn/manual/thinkphp6_0/1037488
Thinkphp网址导航:http://sites.thinkphp.cn/1556332
1.安装lnmp之后做些什么?
open_basedir = /:/tmp/:/proc/,调整PHP目录访问权限;
display_errors = On,打开默认不显示的错误提示;
安装Redis,安装Swoole;
2.安装Thinkphp之后要做些什么?
- 安装think-view模板库: "topthink/think-view":"*";
- Nginx解析的根目录改为Thinkphp项目所在的public目录;
- Thinkphp伪静态修改好,并重启Nginx;
- 将runtime、session等需要写入的目录给予可写的权限;
- 启用Thinkphp的session:app/middleware.php写入;
thinkmiddlewareSessionInit::class
- config/session.php调整session的有效期;
- config/middleware.php新增中间件类的别名
'alias' => [ "Token"=>appmiddlewareToken::class, "BusinessToken"=>appmiddlewareBusinessToken::class ],
- configdatabase.php,修改数据库相关的配置信息;
- configfilesystem.php,修改自带的文件管理库相关配置;
- configapp.php ,修改应用相关配置(是否显示错误信息等等);
- configcache.php,配置redis等缓存数据库;
- .env,修改调试的相关配置信息;
- appExceptionHandle.php,修改默认错误信息输出的默认模板;
细节总结
1.基本
- 助手函数:https://www.kancloud.cn/manual/thinkphp6_0/1037653
- 门面Facade:https://www.kancloud.cn/manual/thinkphp6_0/1037491
- URL访问模式:域名/入口/控制器/操作/参数名/参数...,入口、控制器不填的时候默认index。单个参数无需指定参数名。(参数绑定:https://www.kancloud.cn/manual/thinkphp6_0/1037523)
- Request请求对象:https://www.kancloud.cn/manual/thinkphp6_0/1037516
- Response响应对象:https://www.kancloud.cn/manual/thinkphp6_0/1037525
- 在Tp6里不能通过header函数设置响应头,只能通过response对象。
2.控制器以及多应用
- 官方文档:https://www.kancloud.cn/manual/thinkphp6_0/1037510
- Tp6支持任意的多级路由器,访问时URl格式为:host/one.two.three/methods
- 可以通过路由的方式精简URL。路由不能针对整个控制器路由。
- 跨域访问:https://www.kancloud.cn/manual/thinkphp6_0/1037507
在mac或者linux环境下面,注意需要设置runtime目录权限为777。
3.视图
- 视图的目录规范:https://www.kancloud.cn/manual/thinkphp6_0/1037611
- 视图模板赋值与输出:https://www.kancloud.cn/manual/thinkphp6_0/1037609
- 模板开发文档:https://www.kancloud.cn/manual/think-template
-
// 模板变量赋值 View::assign('name','ThinkPHP'); // 或者批量赋值 View::assign([ 'name' => 'ThinkPHP', 'email' => 'thinkphp@qq.com' ]); // 使用视图输出过滤 return View::filter(function($content){ return str_replace("rn",'',$content); })->fetch();
- 可以使用View静态方法或者view()助手函数返回的对象;
- 模板路径:默认情况下,框架会自动定位你的模板文件路径,优先定位应用目录下的
view
目录,这种方式的视图目录下就是应用的控制器目录。(app/(多应用时存在的目录)/controller同级目录的view目录/控制器目录/视图.html);第二种方式是视图文件和应用类库文件完全分离,统一放置在根目录下的view
目录。 - 表示系统会按照默认规则自动定位视图目录下的模板文件,其规则是:控制器名(小写+下划线)/操作名.html。如果有更改模板引擎的view_depr,改为_后就变成控制器_视图.html;
/* 指定模板输出 */ return View::fetch('edit'); /*表示调用当前控制器下面的edit模板*/ return View::fetch('member/read'); /*表示调用Member控制器下面的read模板。*/ return View::fetch('admin@member/edit'); /*表示跨应用调用模板*/
4.数据库
- 原生查询:https://www.kancloud.cn/manual/thinkphp6_0/1037570
$user=Db::table('$tablename'); /*含前缀*/ $user=Db::name('$name');/*不含前缀*/
- 在Db类的方法中,name(),会自动加上表前缀,table(),则不会。
- 链式查询:https://www.kancloud.cn/manual/thinkphp6_0/1037538,如field指定部分字段等等。
- select 方法查询结果是一个数据集对象,如果需要转换为数组可以使用(toArray)
- find 方法:查询符合条件的第一条数据。select 方法:查询符合条件的所有数据。find只是取一条记录;打印出来的区别只是有个limit 1的限制;
- 字符串条件查询:whereRaw('type=1 AND status=1'),以及whereOr、whereIn等等
- find查询未找到数据是返回null,findOrEmpty未找到数据时返回空数组。insertGetId,插入数据时返回Id
- update方法返回影响数据的条数,没修改任何数据返回 0;
- fieldRaw方法中可直接使用Mysql函数,fieldRaw('id,SUM(score)');
- 如果要更新的数据需要使用SQL函数或者其它字段,exp('name','UPPER(name)');
- getOptions(),$model_list = User::order();$options = $model_list->getOptions();获取本次查询的条件;
- fetchSql(),fetchSql用于直接返回SQL而不是执行查询,适用于任何的CURD操作方法。
- buildSql(),返回用于子查询的sql语句
- 'break_reconnect' => true,用于开启断线重连。
- 配置中的params参数可设置PDO连接参数
5.引入自定义类库、函数
- 如果你需要在核心之外扩展和使用第三方类库,并且该类库不是通过Composer安装使用,那么可以直接放入应用根目录下面的extend目录下面,该目录是官方建议的第三方扩展类库目录。
- 官方文档说明:https://www.kancloud.cn/manual/thinkphp5/177200
- 强烈建议使用Composer安装和更新扩展类库,ThinkPHP5.0+的扩展类库都采用Composer方式进行安装。
6.扩展和自定义配置目录:
- 5.0.1开始增加了扩展配置目录的概念,在应用配置目录或者模块配置目录下面增加extra子目录,下面的配置文件都会自动加载,无需任何配置。
- Tp6.x的config目录下的所有配置文件系统都会自动读取,不需要手动加载。如果存在子目录,你可以通过Config类的load方法手动加载
- 官方说明文档:https://www.kancloud.cn/manual/thinkphp5/215848
- 自定义函数直接写在application 或app目录下的common.php 即可。
7.数据模型
- 模型会自动对应数据表,模型类的命名规则是除去表前缀的数据表名称,采用驼峰法命名,并且首字母大写。
- 模型可使用数据库Db的所有方法。
- 模型类定义在app/model目录内,官方文档:https://www.kancloud.cn/manual/thinkphp6_0/1037483。
- 模型在控制器中的引入方式有三种,在控制器的开始部分use进来(推荐使用)、使用Loader类加载模型、使用助手函数model(不推荐使用,以防助手函数被覆盖掉)。
- select 方法查询结果是一个数据集对象,如果需要转换为数组可以使用(toArrray()方法)。
- V6.0.3+版本开始,原生查询仅支持Db类操作,不支持在模型中调用原生查询方法(包括query和execute方法)。
8.异常处理
<?php
/* 全局错误处理 */
if ($e instanceof HttpException) {
if ($e->getStatusCode() == 404) {
return json(["code" => 0, "errMsg" => $e->getMessage()])->code(404);
} else {
return json(["code" => 0, "errMsg" => $e->getMessage()]);
}
} else {
return json(["code" => 0, "errMsg" => $e->getMessage()]);
}
}
9.中间件
- 中间件主要用于拦截或过滤应用的HTTP请求,并进行必要的业务处理。
- 全局中间件->应用中间件->路由中间件->控制器中间件;
- 官方文档:https://www.kancloud.cn/manual/thinkphp6_0/1037493
- 直接使用中间件时需要指定完整的包名+类名,通过middleware.php配置中间件的别名后可直接指定别名。
- 直接传递参数到控制器;
public function handle($request, Closure $next) { if ('think' == $request->name) { $request->name = 'ThinkPHP'; } return $next($request); }
10.事件
- 事件相比较中间件的优势是事件比中间件更加精准定位(或者说粒度更细),并且更适合一些业务场景的扩展。例如,我们通常会遇到用户注册或者登录后需要做一系列操作,通过事件系统可以做到不侵入原有代码完成登录的操作扩展,降低系统的耦合性的同时,也降低了BUG的可能性。
- 官方文档:https://www.kancloud.cn/manual/thinkphp6_0/1037492
11.数据库事务
什么情况下应该使用事务,当数据库操作结束后,还有其它的非数据库业务流程,失败时数据库操作不应该存在时使用。
Thinkphp6 模型使用事务同DB类一致,实例化后直接调用startTrans、commit、rollback;
回滚只能在Commit之前,Commit之后将无法回滚。
12.数据库加锁
排它锁:Db::name('user')->where('id',1)->lock(true)->find();
共享锁:Db::name('user')->where('id',1)->lock('lock in share mode')->find();
13.Redis的使用
官方文档:https://www.kancloud.cn/manual/thinkphp6_0/1037634
官方提供的方法都比较简单易用,当然也可以自己对Redis类进行封装;
路由相关
1.基础知识
官方文档:https://www.kancloud.cn/manual/thinkphp6_0/1037494
路由解析的过程一般包含:
- 路由定义:完成路由规则的定义和参数设置;
- 路由检测:检查当前的URL请求是否有匹配的路由;
- 路由解析:解析当前路由实际对应的操作(方法或闭包);
- 路由调度:执行路由解析的结果调度;
- 掌握路由主要是要掌握路由定义及参数设置,其它环节是由系统自动完成的。
1.1 单应用路由
├─route 路由定义目录
│ ├─route.php 路由定义
│ ├─api.php 路由定义
│ └─... 更多路由定义
1.2 多应用路由
多应用模式,路由定义文件需要放入应用目录下:
├─app 应用目录
│ ├─app_name 应用目录
│ │ ├─common.php 函数文件
│ │ ├─controller 控制器目录
│ │ ├─model 模型目录
│ │ ├─view 视图目录
│ │ ├─config 配置目录
│ │ ├─route 路由目录
│ │ │ ├─route.php 路由定义
│ │ │ ├─api.php 路由定义
│ │ │ └─... 更多路由定义
2.注册路由
要使用Route类注册路由必须首先在路由定义文件开头添加引用(定义路由之后,原来的访问地址会自动失效)
use thinkfacadeRoute;
Route::rule('路由表达式', '路由地址', '请求类型');
// 注册路由到News控制器的read操作
Route::rule('new/:id','News/read');
// 单独指定请求类型鹅快捷方法
Route::快捷方法名('路由表达式', '路由地址');
3.路由类型
- 路由到控制器/操作,路由到blog控制器,Route::get('blog/:id','Blog/read');
- 路由到类的方法,完整类名@方法名,Route::get('blog/:id','appindexserviceBlog@read');
- 重定向路由,Route::redirect('blog/:id', 'http://blog.thinkphp.cn/read/:id', 302);
- 路由到模板,路由到模板文件,Route::view('hello/:name', 'index/hello',[携带的变量]);
- 路由到闭包,Route::get('hello', function () {return 'hello,world!';});
4.路由参数
官方文档:https://www.kancloud.cn/manual/thinkphp6_0/1037499
Route::get('new/:id', 'News/read')
->ext('html')
->https();
路由参数可以混合使用,只要有任何一条参数检查不通过,当前路由就不会生效,继续检测后面的路由规则。
<?php
Route::get('chat/:user/:id','Index/chat');
public function chat($user, $id)
5.路由中间件
单个路由注册中间件:
Route::rule('hello/:name','hello')
->middleware(appmiddlewareAuth::class);
路由分组注册中间件:
Route::group('hello', function(){
Route::rule('hello/:name','hello');
})->middleware(appmiddlewareAuth::class);
使用数组方式,定义多个中间件:
Route::rule('hello/:name','hello')
->middleware([appmiddlewareAuth::class,appmiddlewareCheck::class]);
6.注解路由
ThinkPHP支持使用注解方式定义路由(也称为注解路由),如果需要使用注解路由需要安装额外的扩展:
composer require topthink/think-annotation
使用注解定义路由:
class Index
{
/**
* @param string $name 数据名称
* @return mixed
* @Route("hello/:name")
*/
public function hello($name)
{
return 'hello,'.$name;
}
}
7.路由绑定
把当前的URL绑定到控制器/操作,最多支持绑定到操作级别。
7.1绑定到控制器/操作
// 绑定当前的URL到 Blog控制器
Route::bind('blog');
// 绑定当前的URL到 Blog控制器的read操作
Route::bind('blog/read');
8.多级目录路由
<?php
//多级目录的匹配
Route::any(':start/:controller/:action', ':start.:controller/:action');
Route::any(':start/:controller', ':start.:controller/index');
Route::any(':start', ':start.index/index');
Request对象
Request对象可通过 thinkfacadeRequest;t或者request()助手函数两种方式获取;
1.Request::has('id','get');
判断参数是否传递,等同于isset(request()->get("id"));
- 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;
2.默认值
Request::get('name','default'); // 返回值为default
3.变量过滤
框架默认没有设置任何全局过滤规则,你可以在appRequest对象中设置filter全局过滤属性;
全局变量过滤方法:Request::filter(['strip_tags','htmlspecialchars']),
获取变量的时候过滤:Request::param('username','','strip_tags,strtolower'); // 获取param变量 并依次调用strip_tags、strtolower函数过滤
4.获取JSON数据
对于body中提交的json对象,你无需使用php://input去获取,可以直接当做表单提交的数据使用,因为系统已经自动处理过了;
5.获取部分参数、排除参数
/* 设置默认值 */
Request::only(['id'=>0,'name'=>'']);
/* 只获取当前请求的id和name变量*/
Request::only(['id','name']);
/* 只获取GET请求的id和name变量*/
Request::only(['id','name'], 'get');
/* 排除id和name变量*/
Request::except(['id','name']);
6.修饰符
Request::变量类型('变量名/修饰符');
- s 强制转换为字符串类型
- d 强制转换为整型类型
- b 强制转换为布尔类型
- a 强制转换为数组类型
- f 强制转换为浮点类型
7.助手函数
为了简化使用,还可以使用系统提供的input助手函数完成上述大部分功能。https://www.kancloud.cn/manual/thinkphp6_0/1037519#_89
- input('?get.id');//是否存在
- input('param.name'); // 获取单个参数
- input('param.'); // 获取全部参数
- input('');// 获取全部参数input('get.id');// 获取单个参数
- input('get.name','','htmlspecialchars'); // 获取get变量 并用htmlspecialchars函数过滤
- input('post.name','','orgFilter::safeHtml'); // 获取post变量 并用orgFilter类的safeHtml方法过滤
- input('post.ids/a');//变量修饰符
事件
事件系统相比行为系统强大的地方在于事件本身可以是一个类,并且可以更好的支持事件订阅者。
事件相比较中间件的优势是事件比中间件更加精准定位(或者说粒度更细),并且更适合一些业务场景的扩展。例如,我们通常会遇到用户注册或者登录后需要做一系列操作,通过事件系统可以做到不侵入原有代码完成登录的操作扩展,降低系统的耦合性的同时,也降低了BUG的可能性。
1.观察者模式
观察者模式是一种对象行为模式。它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。在观察者模式中,主体是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅并接收通知。观察者模式不仅被广泛应用于软件界面元素之间的交互,在业务对象之间的交互、权限管理等方面也有广泛的应用。
观察者模式(Observer)完美的将观察者和被观察的对象分离开。举个例子,用户界面可以作为一个观察者,业务数据是被观察者,用户界面观察业务数据的变化,发现数据变化后,就显示在界面上。
面向对象设计的一个原则是:系统中的每个类将重点放在某一个功能上,而不是其他方面。一个对象只做一件事情,并且将他做好。观察者模式在模块之间划定了清晰的界限,提高了应用程序的可维护性和重用性。
2.事件机制
事件机制涉及三个类:事件类(event)、监听类(listener)、订阅类(subscribe)
- 通过监听的方式,每个事件都需要定义一个监听类来处理监听逻辑,并且在event.php的listen数组中配置
- 通过订阅的方式,如果采用自动绑定,则需要在订阅类中为每个事件定义一个监听方法(方法名不能自己定义),不需要定义subscribe方法;如果采用手动绑定,则定义subscribe方法,并为每一个事件绑定负责处理业务逻辑的具体方法(方法名可以自己定义)
- 通过监听的方式中,事件类基本上可有可无,因为创建的监听类本来就是专门处理对应的事件的业务逻辑的。并且监听类可以用事件类代替。
- 通过订阅的方式中,如果采用自动绑定,事件类也没有必要,因为在订阅类中为每个事件定义的监听方法本来就是专门处理对应的事件的业务逻辑的。如果采用手动绑定,也可以把监听方法写在订阅类中,那么事件类也就没必要了。但是个人认为手动绑定还是使用事件类比较好。订阅类只负责绑定,事件类负责处理具体业务逻辑,这样结构上比较清晰
- 其实通过监听的方式中,监听类(例子中的UserListener)可以替换成其他命名空间下的其他类,只要该类有一个handle方法就行,比如事件类。替换之后记得要在event.php配置文件中做对应修改。同理,通过订阅的方式中,也可以将订阅类(例子中的UserSubscribe)直接替换为其他类,但是要记得自动绑定需要在该类下有符合命名规则的方法,手动绑定需要在该类下有一个subscribe方法
功能总览
- 日志相关功能:https://www.kancloud.cn/manual/thinkphp6_0/1037616
- 错误和调试:https://www.kancloud.cn/manual/thinkphp6_0/1037615、https://www.kancloud.cn/manual/thinkphp6_0/1037618
- 数据验证:https://www.kancloud.cn/manual/thinkphp6_0/1037623
- 系统安全:https://www.kancloud.cn/manual/thinkphp5/268461
- 实用小功能:https://www.kancloud.cn/manual/thinkphp6_0/1037633
- 扩展功能的相关说明:https://www.kancloud.cn/manual/thinkphp5/118129
- Thinkphp部署相关的细节:https://www.kancloud.cn/manual/thinkphp5/129745
使用心得
1.快捷获取当前时间
/*
* 按照格式化字符获取当前时间
* */
function now($format="Y-m-d H:i:s"){
return date($format,time());
}
3.分页输出
QdAccount::order('belong', 'desc')
->paginate([
'list_rows' => $json['size'],
'page' => $json['page'],
])->toArray();
//最终输出
{
"total": 2,
"per_page": 30,
"current_page": 1,
"last_page": 1,
"data": [
{
"id": 1,
"mobile": "18273411374",
"password": "123",
"register_time": "2022-07-14 13:31:04"
},
{
"id": 2,
"mobile": "18273411374",
"password": "123",
"register_time": "2022-07-14 13:32:04"
}
]
}
问题总结
1.日常报错
- Tp6.0报错Driver [Think] not supported.是因为没有安装tp视图库,安装即可。
composer require topthink/think-view
- 文件上传:https://www.kancloud.cn/manual/thinkphp6_0/1037639,文件系统类:https://github.com/ctfang/think-flysystem
- 在Linux系统上,Thinkphp是区分大小写的。模型名、控制器等必须大小写一致。
- Session保存的目录不可写时,将导致session失效。
- 通过助手函数直接让用户响应下载文件:https://kancloud.cn/manual/thinkphp6_0/1037529
- 控制器进行响应时,可以携带一个状态码。
- Thinkphp通过伪静态重定向,会导致正常访问不存在的资源时也会经过PHP处理。
2. Tp6.1
更新6.1移除了filesystem的库,导致文件上传报错了。安装的时候又报错了。
require ext-fileinfo * -> it is missing from your system. Install or enable PHP's fileinfo extension.
出现此错误的原因是php.ini中的fileinfo扩展没有开启,安装fileinfo拓展,开启 extension=fileinfo
3.tp6.1问题记录
更新6.1移除了filesystem的库,导致文件上传报错了。好不容易安装了,6.1版本用不了。
最后升级thinkphp到6.1.x-dev版本才解决。
4.字段缓存
ORM:https://www.kancloud.cn/manual/think-orm/1258072
// 开启字段缓存
'fields_cache' => true,
// 字段缓存路径
'schema_cache_path' => 'path/to/cache',
开启后,会自动生成使用过的数据表字段缓存,如果你更改了数据表的字段及类型,需要清空字段缓存文件。字段缓存采用文件方式保存,路径由schema_cache_path配置参数设置。
5. group和paginate一起使用时,分页异常
tp中使用paginate时会使用Db里面的count函数来统计数据总行数,count函数则会使用count(当前查询的所有字段) limit 1 进行查询,当使用了group by时count统计的是分组后的每行的数量,然后limit 1返回第一行的统计值 所以会出现总行数不对的情况。
//原语句
$list=Db::name(‘sms_log’)->field(‘mobile’)->group(‘mobile’)->paginate(10);//此时总页数和分页的html异常
//修改后
$buildSql=Db::name(‘sms_log’)->field(‘mobile’)->group(‘mobile’)->buildSql();
$list=Db::table($buildSql)->alias(‘bs’)->paginate(10);//总页数和分页的html正确.