请求

今天我们来谈一谈yii2对请求的封装处理 —— request 组件。

在yii2中,用request组件来处理应用的请求。诸如请求参数,请求头,cookie等都与之有不解的关系。

request组件,如无特殊说明,本文以及后面要介绍的request组件指的就是 yii\web\Request ,这是一个核心组件。

我们可以用 Yii::$app->request 或者 Yii::$app->get(‘request’) 来获取request实例对象。

yii\web\Request 的结构如下

1
2
3
4
5
6
class Request extends \yii\base\Request
{
}
abstract class Request extends \yii\base\Component
{
}

可以看出,request 组件的整体结构并不复杂,当然,这只是表面现象。

为什么这么说呢?

后期涉及到路由、cookie等问题你就发现问题了。

我们准备从几个小问题来看一看request组件的实现。

如何获取请求参数?
如何获取请求头?
如何获取cookie?
cookie的加密又是怎么回事?
首先第一个问题,我们知道,在php中,可以使用全局数组 $_GET、$_POST或者$_REQUEST获取请求参数。

在yii2中,我们把请求参数的获取分为两部分。

get参数的获取

在yii2中,调用 Yii::$app->request->get 方法可以获取get类型的参数,来看一下相关代码的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public function get($name = null, $defaultValue = null)
{
if ($name === null) {
return $this->getQueryParams();
}
return $this->getQueryParam($name, $defaultValue);
}
public function getQueryParams()
{
if ($this->_queryParams === null) {
return $_GET;
}
return $this->_queryParams;
}
public function getQueryParam($name, $defaultValue = null)
{
$params = $this->getQueryParams();
return isset($params[$name]) ? $params[$name] : $defaultValue;
}

我们注意到:

默认的,yii\web\Request::getQueryParams 方法返回$_GET数组,当然,你也可以通过 yii\web\Request::setQueryParams 方法把所有的参数和参数值赋值给 yii\web\Request::_queryParams 属性
如果调用 yii\web\Request::get 方法时没有指定要获取的参数,则返回所有的参数和参数值,用 $_GET 或者 yii\web\Request::_queryParams 属性表示
在指定了要获取的参数名和参数的默认值(get方法的第二个参数)的情况下,如果 $_GET 或者 yii\web\Request::_queryParams 属性中未设置该参数,则返回调用 get 方法中指定的默认值,这样有助于避免获取不到参数但是又得判断给其默认值的情况
这部分的代码实现相对比较简单。

请求体参数

下面我们重点介绍一下如何获取和解析请求体中的参数。

为什么要说获取和解析请求体中的参数呢?

现在移动端那么盛行,相信很多同学都为客户端比如Android、IOS等写过接口。这部分的实现过程中,经常会涉及诸如POST、PUT、PATCH等请求方法传递的不同类型的参数,如果根据请求方法来获取参数,后面一旦修改就显得异常繁琐。

request组件为我们封装了完美的解决方案 —— yii\web\Request::getBodyParams 方法。

yii\web\Request::getBodyParams ,可以根据请求的内容类型获取不同请求方法的参数并解析多种格式类型的请求参数。

解释一下:

请求内容类型,指的是请求头携带的 content-type 参数,可以通过 yii\web\Request::getContentType 方法获取
获取不同请求方法的参数,不同的请求方法,可以通过 yii\web\Request::getMethod 方法获取
不同格式类型的请求参数,比如key value键值对,json等格式的参数
想一想,如果单独做判断获取并解析是不是很麻烦?

看一下 getBodyParams 方法的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public function getBodyParams()
{
// 判断请求体参数是否是null,可以通过 yii\web\Request::setBodyParams 方法修改并非实际意义的请求体参数集合
if ($this->_bodyParams === null) {
// 可以在post请求中添加 yii\web\Request::methodParam 对应的key,来修改当前的请求方法,但是请求体参数还是$_POST
if (isset($_POST[$this->methodParam])) {
$this->_bodyParams = $_POST;
unset($this->_bodyParams[$this->methodParam]);
return $this->_bodyParams;
}
// 获取请求头的content_type,不同的请求可以设置不同的content_type,同时这也是解析的关键
// 比如说客户端可以设置请求头content_type等于 'application/json; charset=UTF-8',也可以让其等于 'application/json',具体以分号";"前面的为准,紧接着下面的if else是这句话的实现
$rawContentType = $this->getContentType();
if (($pos = strpos($rawContentType, ';')) !== false) {
// e.g. application/json; charset=UTF-8
$contentType = substr($rawContentType, 0, $pos);
} else {
$contentType = $rawContentType;
}
// yii\web\Request::parsers 属性,我们可以在配置request组件的时候,配置该属性
// 这个属性的值,是content_type和解析方法的映射,比如我们可以设定凡是 content_type=application/json 的请求,用 yii\web\JsonParser 解析。我们也可以自定义content_type, 让解析的类继承 RequestParserInterface 接口即可
if (isset($this->parsers[$contentType])) {
$parser = Yii::createObject($this->parsers[$contentType]);
if (!($parser instanceof RequestParserInterface)) {
throw new InvalidConfigException("The '$contentType' request parser is invalid. It must implement the yii\\web\\RequestParserInterface.");
}
$this->_bodyParams = $parser->parse($this->getRawBody(), $rawContentType);
} elseif (isset($this->parsers['*'])) {
$parser = Yii::createObject($this->parsers['*']);
if (!($parser instanceof RequestParserInterface)) {
throw new InvalidConfigException("The fallback request parser is invalid. It must implement the yii\\web\\RequestParserInterface.");
}
$this->_bodyParams = $parser->parse($this->getRawBody(), $rawContentType);
}
// 如果没有定义parsers, 这里会以POST为准
elseif ($this->getMethod() === 'POST') {
// PHP has already parsed the body so we have all params in $_POST
$this->_bodyParams = $_POST;
}
// 以上都不符合的情况下,则使用 mb_parse_str 函数解析
else {
$this->_bodyParams = [];
mb_parse_str($this->getRawBody(), $this->_bodyParams);
}
}
return $this->_bodyParams;
}

有同学云里雾里,我们举一个简单的例子

客户端发起POST请求,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var settings = {
"async": true,
"crossDomain": true,
"url": "http://api.analysis.com/",
"method": "POST",
"headers": {
"content-type": "application/json"
},
"processData": false,
"data": "{\n\t\"username\": \"白狼\"\n}"
}
$.ajax(settings).done(function (response) {
console.log(response);
});

注意客户端传递的参数类型是json格式的。

在不做任何配置的情况下,调用 yii\web\Request::getBodyParams方法,我们看一些结果

1
print_r(Yii::$app->request->getBodyParams()

结果

1
Array ()

从getBodyParams方法的实现上,我们可以看出,如果我们对 request组件配置parsers解析json格式的数据,是不是就可以了?

1
2
3
4
5
6
'request' => [
// ......
'parsers' => [
'application/json' => 'yii\web\JsonParser',
],
],

再次打印下请求体参数,结果如下

1
2
3
4
Array
(
[username] => 白狼
)

rest api开发中一个很常见的例子,希望各位能体会到 yii\web\Request::getBodyParams函数的意义。

请求头

请求头,指的是HTTP头信息。

request 组件并没有直接增\删\改请求头信息的实现,request 组件封装的 getHeaders方法可以间接获取请求头信息,请求头信息的操作主要由 yii\web\HeaderCollection 实现。

yii\web\Request::getHeaders 方法的实现并不复杂,获取header信息,调用 yii\web\HeaderCollection::add 方法添加header信息到 yii\web\HeaderCollection::_headers属性,仅此而已。

既然有添加header头,便少不了获取\删除等操作,这一切取决于 yii\web\HeaderCollection 的实现。注意操作前提是调用 yii\web\HeaderCollection::getHeaders方法把请求头信息全部赋值给 yii\web\HeaderCollection::_headers属性保存起来。

yii\web\HeaderCollection::add // 添加header
yii\web\HeaderCollection::has // 判断是否存在指定的header
yii\web\HeaderCollection::remove // 移除指定header
关于 request 我们没做过多介绍,比如 csrf,cookie等,后面找一下时机,跟session一起介绍吧。

当然,request 组件还有很多涉及到与URL有关的实现,大家简单熟悉下,至少用的时候能想起来有这个方法就好了。