目录
介绍
官方介绍
ThinkPHP
是一个免费开源的,快速、简单的面向对象的轻量级PHP开发框架,是为了敏捷WEB应用开发和简化企业应用开发而诞生的。
ThinkPHP
诞生十七年来一直秉承简洁实用的设计原则,在保持出色的性能和至简代码的同时,更注重易用性。
遵循Apache2
开源许可协议发布,意味着你可以免费使用ThinkPHP
,甚至允许把你基于ThinkPHP开发的应用开源或商业产品发布/销售。
本文章主要是针对已有其他后端开发框架基础的人群,如SpringBoot(Java),Express(NodeJS) 等。
这样,阅读本文章你就可以快速了解ThinkPHP框架如何ORM的与数据库做交互,提供访问接口。
ThinkPHP为MVC架构,但本文章不说明视图层(V),使用接口测试工具apifox来测试ThinkPHP创造出的接口。
安装(windows环境)
安装Composer
我们安装ThinkPHP之前,要先去安装Composer。
为什么要安装composer?
因为ThinkPHP的下载需要通过Composer来进行
Composer是一个PHP的依赖管理工具,可以用于管理项目中的PHP库和包的依赖关系。
类似于NodeJS中的npm包管理器,也类似前端脚手架构建vue项目的Vite。
安装步骤
1. 下载Composer安装包
访问官方下载地址:Composer下载https://doc.thinkphp.cn/v8_0/setup.html
2. 执行下载的.exe安装引导程序,无脑下一步。
其中需要注意的勾选:
3. 检查安装结果:命令行输入composer。
看见输出就好了。
安装ThinkPHP
安装好了composer之后,我们就能使用composer"脚手架"来创建ThinkPHP基础项目了。
使用脚手架去构建TP(ThinkPHP)基本项目需要下载代码、依赖包等文件,而Composer的下载服务器在国外,直接下载会很慢,因此我们可以配置一下国内的镜像源。
1. 安装前配置国内镜像:
命令:composer config -g repo.packagist composer 镜像源地址
阿里云: https://mirrors.aliyun.com/composer/
华为云: https://repo.huaweicloud.com/repository/php/
例如执行命令: 配置阿里国内镜像源
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
2. 切换到你PHP网站根目录(www目录)下执行安装命令:
composer create-project topthink/think tp6
命令是创建一个ThinkPHP项目。(tp6为根目录名称,可自定义)
这个操作类似于前端我们使用vite脚手架构建vue项目类似 。
3. 运行测试
cd进入刚创建的tp6目录,执行运行命令:php think run
看到输出信息:项目运行在了“内置服务器”的8000端口。
通过访问URL:localhost:8000 即可访问项目首页。
这样我们的ThinkPHP基本项目的构造就完成了。
上面提到了“内置服务器”,是基于 PHP 内置的 CLI 开发服务器。
在本地开发调试的时候我们直接使用内置服务器是可行的。
但是在部署到生产环境的时候,建议还是要部署到Apache或Nginx上。
目录结构
上面说了Composer将像是一个脚手架帮我们去构建特定结构的ThinkPHP项目。
那么接下来我们就可以看看生成的这个项目结构长啥样。
目录还是不少,下面我会说明几个较为常用的说明:
1. app目录:是应用目录,我们就在这个目录进行编码,例如接口的编写。
2. config是配置目录,里面存放着各类配置文件,如数据库的配置。
3. public是公共目录,对外访问目录,像上面我们通过访问localhost:8000显示的页面就是这个目录中的index.php文件。
4. route是定义路由的目录,就是定义我们的接口URL以什么样子暴露出去让前端进行调用。
更详细的目录和文件说明,我们可以访问官方文档进行查看:ThinkPHP目录结构
配置文件
脚手架构建的这个基础项目中的配置文件很多,目前我们可以就关注一个
就是“database.php”,顾名思义就是数据库连接的配置。
我们展开/config目录看看其中的各种配置文件。
总之,后续我们要配置一些参数,如数据库的连接参数,我们就到/config下找就好。
第一个接口(Controller层)
接下来,我们进行正题吧。
让我们一起来写出ThinkPHP的第一个"Hello World"接口 。
Hello World
打开文件controller/Index.php
<?php
namespace app\controller;
use app\BaseController;
class Index extends BaseController
{
// 输出hello world
public function hi()
{
return 'hello world';
}
}
php think run 启动项目,apifox访问接口localhost:8000/index/hi
可以看到,我们通过访问方式为:类名/方法名
通过上面实践,我们就能知道一种接口访问的方式(后面会讲到路由访问)。
自定义Controller
值得注意的是:如果我们要在controller目录下新建新的Controller,如UserController,需要文件名和类名保持一致。
例如,我们下面创建自定义User控制器,专门用来处理用户数据相关的请求。
BaseController
我们通过继承BaseController (基础控制器)使得我们自定义的控制器功能得到扩展。
例如继承后我们就能通过$this去获取当前请求的一些参数。
我们可以看看BaseController中的内容:
请求参数
这里说的是如何获取前端调用接口时传入的请求参数。
下面说明获取:查询参数,对象参数,至于"路径参数"在下面的路由章节会提到。
获取查询参数(Get请求)
获取参数的方式有很多种,下面介绍两种:1.Request对象获取 2.助手函数获取
获取指定请求参数
访问:/UserController/test1?name=tom
接口代码:
use think\facade\Request; // 引入Request
public function test1(){
// 方法一:使用 Request 类获取请求参数
halt(Request::param("name"));
// 方法二:助手函数获取
halt(input("name"));
}
halt() 输出显示:
获取所有URL的请求参数
当我们同时传递多个参数的情况
访问:/UserController/test1?name=tom&age=18
接口代码:
public function test1(){
// 方法一:使用 Request 类获取请求参数
halt(Request::param());
// 方法二:助手函数获取请求参数
halt(input("param."));
}
halt()输出
获取json数据(Post)
获取JSON数据
我们通过apifox模拟发送一段json数据(post请求)
接口代码:
use think\facade\Request;
public function index(){
// 方法一:使用 Request 类获取请求参数
halt(Request::param());
// 方法二:助手函数获取请求参数
halt(input("param."));
}
获取JSON数据中的属性
例如我们想要获取传入json对象中的name属性值,可以这样:
public function index(){
// 方法一:使用 Request 类获取请求参数
halt(Request::param()["name"]);
// 方法二:助手函数获取请求参数
halt(input("param.name"));
}
一些问题
在上面获取传入json数据的时候其实接口可读性是有点差的,因为我们没有将接口该传入的参数在方法上体现出来。
我想说的是:
// 显示声明name 和 age
public function index(String name, int age){
// 函数体
}
// 显示接收用户信息Json数据
public function addUser(User user){
// 函数体
}
例如上面的index()方法其中就显示的定义调用该接口需要的参数,以及addUser()方法中需要User用户类型的一个对象参数。
我想这样的定义方式有SpringBoot使用经验的并不陌生。
这样的可读性是很好的,虽然规范性强了些。
关于对象类型的参数说明:
在上面的addUser方法中我们定义了(User user)作为参数,表示我们想要接收一个User用户类型的对象/json数据。
但是需要知道ThinkPHP中并不像SpringBoot那样会自动将json对象转为我们的PHP对象,所以,如果我们以上面的方式去定义参数,我们需要手动的先将json数据转为对象。
所以就推荐直接使用请求对象Request::param() 或助手函数input("param.")去获取传入的参数好了,不必去那么严格的定义接口参数的定义。
如果就是想要定义对象参数的自动转换,可以去了解“封装中间件或者创建自定义请求处理器”,或者考虑更“大”一点的PHP框架,比如Laravel或者Symfony。
响应参数
大多数情况,我们不需要关注Response
对象本身,只需要在控制器的操作方法中返回数据即可。
为了规范和清晰起见,最佳的方式是在控制器最后明确输出类型。
ThinkPHP中支持的输出类型:
例如想要返回json数据给客户端:
public function hello()
{
$data = ['name' => 'thinkphp', 'status' => '1'];
return json($data);
}
想要返回html格式:
response($data);
返回字符串:
return 'Hello,ThinkPHP!';
响应参数
我们还可以设置响应的状态码和响应头等信息,可以这样写:
json($data)->code(201)->header([
'Cache-control' => 'no-cache,must-revalidate'
]);
使用链式调用设置状态码和响应头。
路由
在上面的控制器或说是接口的定义中,我们并没有像SpringBoot或Express那样去显示的声明路由。
例如接口使用什么请求方式(Get还是Post),和显示的把URL和处理函数进行绑定等。
这一节就是说明这些问题。
在SpringBoot中我们通过注解来“隐式”绑定路由,而在ThinkPHP中的路由系统独立于Controller存在,需要显式配置。
基础路由映射
下面我们使用路由来映射控制器演示写法。
待路由的控制器:
<?php
namespace app\controller;
use app\BaseController;
class UserController extends BaseController{
public function test3(){
return "我是路由访问的控制器方法";
}
}
定义路由,打开/route/app.php文件
use think\facade\Route; // 引入Route
Route::get('user/test3', 'UserController/test3');
测试路由访问:
路由写法解析
上面的路由语法:Route::快捷方法名('路由表达式', '路由地址');
关于快捷方法名有:
关于路由表达式:
说的就是我们自定义的请求URL的地址。
当然表达式定义上还可以说明路径参数,可以这样写:
Route::get('user/test3/:name', 'UserController/test3');
控制器代码:
值得注意的是:在路由上定义的路径参数名要和控制器参数名相同。
模型(Model)
这里的模型在SpringBoot中体现为实体类。
我们能使用模型来和数据库表的映射关系,也就是ORM的一个重要组成。
模型的定义
1.模型目录创建:app/model
2.创建的模型名称要和数据表名一致(大驼峰映射)
例如创建一个User模型:
User模型对应user数据表:
模型使用测试
测试前到配置文件config/database.php中配置好数据库连接信息。
我们通过这个数据表映射出的模型去做查询测试
use app\model\User; // 导入User类/模型
// 获取用户表数据
public function getUserData(){
$users = User::select(); // 直接用User模型快捷查询数据
halt(json($users));
}
访问控制器:
发现可以获取到user表的所有数据,至于User::select()的写法先不用纠结,这是使用模型查询数据的一种方式,下面会介绍到。
模型的设置
模型对应数据表是我们已经明确了的,在上面的模型测试访问中也是成功了的。
可以查询到数据是因为框架根据我们的类名去查询数据表名,然后获取数据。
设置表名
如果我们的类名和表明不一致,我们可以在模型中进行设置:
class ABC extends Model
{
protected $table = 'user'; // 数据表名称
}
上面代码可以看到,我们的模型名为ABC,而我们的表名为user,直接查询必然会映射失败导致数据无法查询,我们可以通过$table属性对表名进行定义。
设置主键
默认会识别主键名称为“id”,如果你的主键名称是其他需要设置$pk属性值。
use think\Model;
class User extends Model
{
protected $pk = 'uid'; // 主键名称
}
模型字段的设置
这使得我们可以在模型中设置字段名,就是显示的设置类的成员属性。
为什么要显示设置字段名?
在上面的模型测试中我们没有设置模型的字段名依然可以查询出数据。
那么为什么我们还要显示的声明字段名呢?
1. 一方面是可读性,使我们看到模型就知道数据表有哪些字段。
2. 提升性能,当我们默认使用模型映射查询数据时,首先框架会根据数据表名先查询出表的所有字段,再使用查询出来的字段查询数据,这必然就会导致多执行一次sql查询,所以如果我们能在模型中先定义,就可以省去一次sql查询。
使用$schema设置字段名
<?php
namespace app\model;
use think\Model;
class User extends Model
{
// protected $table = 'user'; // 数据表名称
// 设置表字段
protected $schema = [
'id' => 'int',
'name' => 'string',
'status' => 'int',
'score' => 'float',
'create_time' => 'datetime',
'update_time' => 'datetime',
];
}
通过$schema去定义字段我们还能显示看到字段的类型。
当然,模型中的字段要和数据表的字段名对上的。
废弃字段
当我们在操作数据表想要忽略某个/些字段时,我们可以将字段进行设置:
class User extends Model
{
// 设置废弃字段
protected $disuse = [ 'status', 'type' ];
}
这样设置之后,在查询和写入的时候会忽略定义的status
和type
废弃字段。
访问数据库
下面只会说明使用模型直接ORM访问数据表,而不会说明Db:table()的原始方式去查询数据。
如果对Db::table()的方式感兴趣,可以访问官方手册。
数据库的连接
访问config/database.php文件:
可以看到在数据库的连接信息中,是优先使用环境变量中的连接信息的,如果环境变量中没有连接信息的参数,才会使用第二个参数。
所以,我们在本地测试中优先到.env文件中进行连接信息的配置。
这个文件名原先为.example.env ,可以改为.env。
然后进行.env配置文件配置连接信息:
关于.env文件,一般用于本地环境测试,如果后续要部署到服务器需要将.env内容注释,使用database.php文件进行配置。
增删改查(CURD)
新增
定义路由
<?php
use think\facade\Route;
Route::post("user/add", "UserController/addUser"); // 新增用户
控制器代码:
// 新增用户
public function addUser(){
// 获取请求参数
$data = Request::param();
// 创建用户模型实例
$user = new User();
// 设置用户数据
$user->data($data);
// 保存用户数据到数据库
if($user->save()){
return "新增成功";
}else{
return "新增失败";
}
}
测试访问
save
方法支持传入模型实例或实体对象实例。
使用::create方法
public function addUser(){
// 获取请求参数
$data = Request::param();
// 执行添加,失败会返回null,成功返回模型对象
$user = User::create($data);
if($user){
// 成功逻辑
}else{
// 失败逻辑
}
}
新增多条
使用saveAll()方法。
$user = new User;
$list = [
['name'=>'thinkphp','email'=>'thinkphp@qq.com'],
['name'=>'onethink','email'=>'onethink@qq.com']
];
$user->saveAll($list);
删除
定义路由:
Route::delete('user/del/:id', 'UserController/deleteUser'); // 删除用户
控制器代码:
// 删除用户
public function deleteUser($id){
// 方法一、
$user = User::find($id);
$user->delete();
// 方法二、
$res = User::destroy($id);
}
删除多条:
// 方法1:
User::destroy([1,2,3]);
// 方法2:
User::where('id','>',10)->delete();
更新/修改
在取出数据后,更改字段内容后使用save
方法更新数据。这种方式是最佳的更新方式。
// 方法1
$user = User::find(1);
$user->name = 'thinkphp';
$user->email = 'thinkphp@qq.com';
$user->save();
// 方法2:
User::update(['name' => 'thinkphp'], ['id' => 1]);
// 如果你的第一个参数中包含主键数据,可以无需传入第二个参数(更新条件)
User::update(['name' => 'thinkphp', 'id' => 1]);
查询
下面的内容并不完整,仅演示常用的一些查询。
可以往下过一遍看看语法,其他的方法用法都大差不差。
完整的查询方法可以到官方手册,或用的时候直接问题AI就行。
查询单条
// 根据主键查询单条数据
$user = User::find(1);
// 使用查询构造器查询满足条件的数据
$user = User::where('name', 'thinkphp')->find();
执行find()方法后,如果没有找到数据返回null,否则返回当前模型的对象实例。
获取多条数据
// 根据主键获取多个数据
$list = User::select([1,2,3]);
// 对数据集进行遍历操作
foreach($list as $key=>$user){
echo $user->name;
}
带有条件的查询多条
$users = User::where('id', '>',10)->select();
模糊查询
$user = User::whereLike("name", "%mao%")->select();
多条件
如果有多条件,我们可以多次调用where()进行拼接。
$users = User::where('id', '>',10)
->where("name", "like", "mao")
->select();
多个where之间的连接为AND。
如果想要where之间的关系为OR,我们可以把where换成whereOr:
$users = User::where('id', '>',10)
->whereOr("name", "like", ""mao)
->select();
排序
在调用链上添加上order("排序字段", "排序规则(asc , desc)")
users = User::where("id" , ">", 1)->order("age", "desc")->select();
聚合函数的使用
// 获取用户表年龄最大值
$user = User::max("age");
其他的如min(), avg(), sum()是一样的用法。
分页查询
// 获取分页参数,默认每页 10 条,第 1 页
$page = Request::param('page', 1);
$limit = Request::param('limit', 10);
// 查询用户数据并分页
$users = User::paginate([
'list_rows' => $limit,
'page' => $page
]);
原生查询
原生查询说的是我们可以手写sql语句去执行查询。
public function getUsers(){
$user = Db::query("select * from user");
halt($user);
}
测试访问
部署
在我们本地开发完ThinkPHP项目后,如果想要让项目能线上访问,我们需要将项目部署到线上服务器上。这是我们要说明的问题。
我们将项目部署到服务器上,然后使用浏览器或apifox访问服务器的接口进行测试。
部署前准备
1.关闭调试模式
将APP_DEBUG设置为false
2.数据库的配置
把数据库的连接配置信息填好(线上可访问)。
3.服务器环境
部署的服务器需要安装:
- PHP环境
- Composer
- Nginx
上面三个服务器的环境安装配置过程就不演示了。
部署方式一:内置服务器直接启动
最直接的部署方式是直接将thinkphp项目上传到服务器,然后通过composer安装依赖后直接使用php think run将项目启动。
这种方式 仅适合本地开发调试,不推荐在生产环境中使用。
但是如果你的项目仅用来上线演示,不供大众使用,直接使用这种方式也无妨。
部署步骤
1. 上传开发好的ThinkPHP项目到服务器
2. 到项目根目录下安装依赖,命令:composer install --no-dev -o
3. 使用命令启动项目: php think run --host=0.0.0.0 --port=9878
- 通过--port参数临时指定启动的端口(自定义)。
- 如果部署在云服务器上,记得将服务器防火墙和云服务器的安全组端口给打开。
4. 访问测试就行:服务器域名或IP:自定义端口/接口路径
这种方式为什么不推荐生产环境使用?
性能较差
php think run
使用的是 PHP 自带的开发服务器,单线程、无并发处理能力,无法承受多用户访问,容易崩溃。稳定性不足
它不是为长时间运行设计的,容易在高负载或错误情况下宕掉,不具备错误自动恢复机制。安全性较低
内建服务器没做太多安全隔离,比如目录穿越、恶意请求防护等都非常薄弱。日志、连接管理能力差
没有日志轮转、访问日志、慢日志、连接池等功能,排查问题不方便。
部署方式二:使用Nginx服务器
1. 上传开发好的ThinkPHP项目到服务器
2. 到上传好的项目根目录下安装依赖,执行安装命令:composer install --no-dev -o
3. 配置nginx,打开你自己的nginx目录下的配置文件nginx.conf
# 配置示例,请根据自己的服务器参数进行填写
server {
listen 8000; # 端口,自定义
server_name 你域名或服务器IP;
root 你的项目路径/public;
index index.php index.html;
location / {
if (!-e $request_filename) {
rewrite ^(.*)$ /index.php?s=$1 last;
break;
}
}
location ~ \.php$ {
# 这里的配置需要按照实际情况填写,看下面说明
fastcgi_pass unix:/run/php-fpm/www.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
配置完nginx之后,记得执行命令nginx -s reload 命令重新加载一下配置文件。
需要注意!!
上面配置中的:fastcgi_pass unix:/run/php-fpm/www.sock; 一行中,需要正确配置php-fpm服务目录名称。
怎么找到该填的值呢?
我们可以通过命令 “grep "listen =" /etc/php-fpm.d/*.conf” 进行查询。
然后将查询结果填到上面的nginx配置中。
例如图片输出:/run/php-fpm/www.sock。
php-fpm是什么?
- 是PHP在Web服务器(如 Nginx)中运行的核心组件。
- 例如当我们用Nginx访问一个
.php
页面时,Nginx本身不会执行PHP,它会把请求转交给php-fpm
去处理,处理完再把结果返回给用户。- 简单来说就是PHP的解释器。
所以,我们在nginx配置中需要正确配置fpm的路径,以便使nginx能找到fpm。
4. 开启php-fpm服务
// 查看php-fpm服务状态
systemctl status php-fpm
// 如果是关闭状态就启动他
systemctl start php-fpm
5. 开放文件权限
给上传的ThinkPHP项目根目录下的public 和 runtime目录开放权限
chmod -R 775 runtime
chmod -R 775 public
以及,需要给上面执行命令grep "listen =" /etc/php-fpm.d/*.conf后输出(php-fpm)的目录开放权限。
这就是刚才在nginx中配置的php解释器,给www.sock开放权限。
这是必要的,否则会出现502报错攻击!!
6. 测试访问
测试前记得把你在nginx配置的端口在服务器防火墙和云服务器安全组打开。
最后
完整教程请访问官方手册:序言 - ThinkPHP官方手册