ThinkPHP8学习篇(四):请求和响应

在请求流程中,请求与响应构成了应用与客户端交互的核心纽带 ------ 请求负责捕获并解析客户端传递的各类数据(如参数、头信息、请求方式等),响应则承担着将处理结果以合适形式(如页面、JSON、文件等)返回给客户端的重任,是完成一次完整交互的关键闭环。本篇文章将记录 ThinkPHP 请求与响应的学习过程。


一、请求

1、请求对象

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

在项目里面应该使用 app\Request 对象,该对象继承了系统的 think\Request 对象,但可以增加自定义方法或者覆盖已有方法。

继承 app\BaseController 基础控制器类可以直接使用 request 属性调用 think\Request 对象实例。

当然,继承基础控制器类并不是必须的,接下来说明不继承基础控制器类如何使用请求对象。

1.1、构造方法注入

只需要在构造方法的参数中声明 Request 对象参数就可以在构造方法中使用 Request 对象了。例如:

php 复制代码
<?php
namespace app\controller;

use think\Request;

class Index 
{
    // 在类中声明 Request 实例
    protected $request;

    /**
     * 构造方法,通过向构造方法中传入 Request 对象完成注入
     * @param Request $request
     */
    public function __construct(Request $request)
    {
        // 将注入的 Request 对象赋值给当前类中的 $request
        $this->request = $request;
    }

    public function index()
    {
        return $this->request->param('name');
    }
}

1.2、操作方法注入

Request 对象可以通过构造方法注入,同样的,也可以通过普通方法进行注入。注入的方式是一样的,只需要在需要注入 Request 对象的方法上声明该参数就可以了。例如:

php 复制代码
<?php
namespace app\controller;

use think\Request;

class Index
{
    public function index(Request $request)
    {
        return $request->param('name');
    }
}

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

1.3、静态调用

除了使用依赖注入的方式使用 Request,也可以通过 Facade 机制来静态调用请求对象的方法(注意 use 引入的类库区别)。

示例

php 复制代码
<?php
namespace app\controller;

use think\facade\Request;

class Index
{
    public function index()
    {
        return Request::param('name');
    }
}

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

1.4、助手函数

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

示例

php 复制代码
<?php
namespace app\controller;

class Index
{

    public function index()
    {
        return request()->param('name');
    }
}

1.5、自定义请求对象

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

2、请求信息

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 参数,表示获取带域名的完整地址,例如:

php 复制代码
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);

2.1、获取当前控制器/操作

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

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

示例

php 复制代码
// 获取当前控制器,返回的是控制器的驼峰形式(首字母大写),和控制器类名保持一致(不含后缀)。
Request::controller();
// 获取当前操作,返回的是当前操作方法的实际名称。
Request::action();

3、输入变量

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

3.1、检测变量是否设置

has(变量名, 请求类型) 方法来检测一个变量参数是否设置。设置返回 true,未设置返回 false。

php 复制代码
Request::has('id','get'); // GET 请求中是否设置 id 变量
Request::has('name','post'); // POST 请求中是否设置 name 变量

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

3.2、变量获取

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

php 复制代码
变量类型方法('变量名/变量修饰符', '默认值', '过滤方法');

变量类型方法包括:

|------------|--------------------------------------|
| 方法 | 描述 |
| 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 | 获取包括 _FILES 变量在内的请求变量,相当于param+file |

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

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

param 方法会把当前请求类型的参数和 GET 请求合并。

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

3.3、变量修饰符

支持对变量使用修饰符功能,可以一定程度上简单过滤变量。

php 复制代码
Request::变量类型('变量名/修饰符');

支持的变量修饰符有:

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

示例

php 复制代码
Request::get('id/d'); // 变量 id 为整型
Request::post('name/s'); // 变量 name 为字符串
Request::post('ids/a'); // 变量 ids 为数组

4、请求类型

4.1、获取请求类型

在有些情况下,我们需要判断当前操作的请求类型是GET、POST、PUT、DELETE或者HEAD,一方面可以针对请求类型作出不同的逻辑处理,另外一方面有些情况下面需要验证安全性,过滤不安全的请求。

请求对象 Request 类提供了下列方法来获取或判断当前请求类型:

|---------------|-----------|
| 用途 | 方法 |
| 获取当前请求类型 | 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 方法返回的请求类型始终是大写,这些方法都不需要传入任何参数。

4.2、请求类型伪装

ThinkPHP 支持请求类型伪装,可以在POST表单里面提交 _method 变量,传入需要伪装的请求类型,例如:

html 复制代码
<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 请求进行提交。

5、HTTP头信息

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

示例

php 复制代码
// 获取的信息为数组,通过信息头名称获取值
$info = Request::header();
echo $info['accept'];
echo $info['accept-encoding'];
echo $info['user-agent'];

// 也可以直接获取某个请求头信息
$agent = Request::header('user-agent');

HTTP 请求头信息的名称不区分大小写,并且 _ 会自动转换为 - 。

6、参数绑定

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

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

php 复制代码
<?php
namespace app\controller;

class Blog
{
    public function read($year, $month='01')
    {
        return 'year=' . $year . '&month=' . $month;
    }
}

URL的访问地址是:

php 复制代码
http://serverName/blog/read/year/2025/month/12
或者
http://serverName/blog/read?year=2021&month=02

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

二、响应

ThinkPHP的 Response 响应对象由 think\Response 类或者子类完成,ThinkPHP的响应输出是自动的,最终会调用 Response 对象的 send 方法完成输出。

1、响应输出

大多数情况下,我们不需要关注 Response 对象本身,只需要在控制器的操作方法中返回数据即可。最简单的响应输出是直接在控制器操作方法中返回一个字符串,例如:

php 复制代码
<?php
namespace app\controller;

class Index
{
    public function hello($name='thinkphp')
    {
        return 'Hello,' . $name . '!';
    }
}

默认是输出 html。如果发起一个 JSON 请求的话,输出就会自动使用 JSON 格式响应输出。

为了规范和清晰起见,最佳的方式是在控制器最后明确输出类型。默认支持的输出类型包括:

|----------|----------|-----------------------------|
| 输出类型 | 快捷方法 | 对应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 |

2、响应参数

Response 对象提供了一系列方法用于设置响应参数,包括设置输出内容、状态码及 header 信息等,并且支持链式调用以及多次调用。

2.1、设置数据

Response 基类提供的 data 方法用于设置响应数据。

示例

php 复制代码
response()->data($data);
json()->data($data);

需要注意的是 data 方法设置的只是原始数据,并不一定是最终的输出数据,最终的响应输出数据是会根据当前的 Response 响应类型做自动转换。例如:

php 复制代码
json()->data($data);

最终的输出数据就是 json_encode($data) 转换后的数据。

2.2、设置状态码

Response 基类提供的 code 方法用于设置响应的状态码,但一般情况下,都是在调用助手函数的时候直接传入状态码。

示例

php 复制代码
json($data, 201);
view($data, 401);

// 或者在后面链式调用code方法:
json($data)->code(201);

除了 redirect 函数的默认返回状态码是302之外,其它方法没有指定状态码都是返回200状态码。

2.3、设置头信息

Response 基类提供的 header 方法用于设置响应的头信息。

示例

php 复制代码
json($data)->code(201)->header([
    'Cache-control' => 'no-cache,must-revalidate'
]);

3、重定向

使用 redirect(URL) 助手函数进行重定向。

示例

php 复制代码
<?php
namespace app\controller;

class Index
{
    public function hello()
    {
        return redirect('http://www.baidu.com');
    }
}

还可以支持使用 with 方法附加 Session 闪存数据重定向。

php 复制代码
<?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 闪存的数据仅在下一次请求有效,再次访问重定向地址的时候无效。

4、文件下载

支持文件下载功能,可以更简单的读取文件进行下载操作,支持直接下载输出内容。

助手函数 download(要下载的文件, 显示的文件名, 是否为内容, 有效期(秒)) 用于文件下载。

示例

php 复制代码
public function download($year, $month='01')
{
    return download('image.jpg', 'my.jpg');
}

访问 download 操作就会下载命名为 my.jpg 的图像文件。

下载文件的路径是服务器路径而不是URL路径,如果要下载的文件不存在,系统会抛出异常。

支持设置是否强制下载,例如需要打开图像文件而不是浏览器下载的话,可以使用 force 方法:

php 复制代码
public function download()
{
    // force()方法:是否强制下载
    return download('image.jpg', 'my.jpg')->force(false);
}

除了 force 方法外,还支持下面的方法:

|-----------|-----------------|
| 方法 | 描述 |
| name | 命名下载文件 |
| expire | 下载有效期 |
| isContent | 是否为内容下载 |
| mimeType | 设置文件的mimeType类型 |
| force | 是否强制下载 |