【Yii系列】处理请求
缘起
这一章是Yii系列的第三章,前两章给大伙讲解了Yii2.0的安装与Yii2.0的基本框架及基础概念,传送门:
【Yii2.0的安装与调试】:http://www.cnblogs.com/riverdubu/p/6439680.html
【Yii2.0基础框架】:http://www.cnblogs.com/riverdubu/p/6607373.html
相信学习过上两章的内容,你们是不是对Yii有个大致的了解了呢,Yii2.0基础框架这一章很重要,不仅仅是因为它的长度,而是里面讲解了整个Yii2.0的基础概念,以及如何基于MVC模型构建的它的整套框架,只是浮于表面的一些基础只是,如果大家对更深层次的Yii的底层感兴趣,可以持续关注我的博客,后面我会对于Yii底层的一些关键组件和代码做更加详细的讲解。
接上一章的话,我们这章带大伙了解下一个用户的请求从发起到收到响应的整个过程是如何实现,这对于统筹全局至关重要,如果说上一章是基础,是打根基和搭架子的一章,那么这一章就是给你将整个房子建起来啦,会把所有的门窗房间规划好,让你能够轻松自如的在房子里面穿梭。
运行机制
首先,我们来看下整个请求的运行机制
用户发送请求给入口脚本,入口脚本加载配置,运行application,application会创建一个request组件去处理这次用户请求,Request组件会去路由里面查找用户想要请求的那个Controller,找到Controller后实例化它,调用对应的action执行操作,action会调用对应model层的函数进行数据处理,处理完成之后返回给对应的action,action会将数据格式化或者不格式化渲染View,为其提供填充所需要的数据,渲染完成的结果会返回给response组件发送给用户浏览器。
这里面有几个关键的组件,request和response我们先不用去管他们,这是application会自动搞定的,我们要关心关心这个被称为路由的东西。
路由
有几个关键概念是需要大家理解下的,引导路由。
当入口脚本在调用 yii\web\Application::run() 方法时,它进行的第一个操作就是解析输入的请求,然后实例化对应的Controller处理这个请求。 该过程就被称为引导路由(routing)。
我们之前可能看过类似于下面的请求URL
http://服务器IP/index.php?r=post/view&id=100
这边的r后面的就是后面会被实例化的Controller去处理这次请求,这是标准的写法,但是,为了让URL看上去更pretty一些,比方说下面的URL
http://服务器IP/post/view?id=100
这样是不是能够更清晰的看出是哪个业务单元的逻辑,这样的写法需要通过配置应用主体Components中一个叫urlManager的配置项来完成路由的解析。
这两种写法之间通过配置urlManager里面的enablePrettyUrl属性来实现切换,true的话是下面一种写法,false的话是上面一种写法。
引导路由包含两步,第一步,请求会被解析成对应的路由和请求参数,第二步,一个路由对应的Controller的action会被实例化去处理这个请求。
当你使用pretty方式去解析你的URL时,urlManager会在当前的规则中寻找你想要找的规则,如果找不到,会抛出一个 yii\web\NotFoundHttpException,这就是大名鼎鼎的404啦。
一旦找到对应的路由规则,URL会被拆分成很多部分,就像上面的post/view一样,从前往后可能分别对应了一个module,一个controller,一个action。application回一个个去尝试的,如果没有找到对应的action去执行这个请求,还是会像上面一样,抛出大名鼎鼎的404。
我们这边对基础的路由不做解析,想要了解的朋友可以去官网详细的查看下。我们来具体的来看下pretty的路由规则。
为了能够使用pretty路由解析,我们得在应用主体,也就是上一章的web.php中配置一下。
[ 'components' => [ 'urlManager' => [ 'enablePrettyUrl' => true, 'showScriptName' => false, 'enableStrictParsing' => false, 'rules' => [ // ... ], ], ], ]
具体的规则就要配置在rules,这里建议一下,这个rules配置在一个文件中,然后这边require一下即可。
这里面有几个参数
enablePrettyUrl:和上面的意思一样,打开pretty url规则的,这个必须得打开,要不然的话默认的路由解析会是基础路由解析。
showScriptName:是否将入口脚本写入用户请求的链接,如果为true,请求链接为:/index.php/post/100,如果为false,请求链接为:/post/100
enableStrictParsing:是否需要按照rules里的规则严格解析,如果为true,请求的链接必须符合rules里面的规则,如果不满足,抛出404,如果为false,请求链接可以是rules规则的一部分。
url rules
下面我们就来详细的讲一讲需要配置的这个rules文件。
我们先来看个例子
[ 'posts' => 'post/index', 'post/<id:\d+>' => 'post/view', ]
这边定义了两条URL rules
第一条将URL中的posts对应到post/index这条路由;
第二条规则匹配一个正则式post/(\d+)对应到post/view这条路由,id是它的参数。
当URL被规则解析,他后面的参数会被收录进application的request组件,后面我们会说到这个request组件。在我看来,处女座的人比较满意的一个url是这样的:http://example.com/psot/action?a=1&b=2,这是比较常规的
我这边给出的一个例子适用于大多数情况
'v1/<module:\w+>/<controller:\w+>/<action:\w+>' => 'api/v1/<module>/<controller>/<action>', //模块相关规则
前面部分是用户访问时的链接格式,后面部分是项目代码中对应的action位置。
通过正则式去解析路由,这回大大减少规则数量,大大提升urlManager的性能。
Request
讲完了路由,我们再来看下一个请求是如何被应用主体接受和反馈的吧。
首先,我们来看下在应用中,用户的请求是被实例化成啥的。
一个应用的请求是用 yii\web\Request 对象来表示的,该对象提供了诸如 请求参数(译者注:通常是GET参数或者POST参数)、HTTP头、cookies等信息。
调用方式
Yii::$app->request
$request->get()这段代码等价于$_GET,之前和大伙说过,不能直接使用$_GET这个值,这使你更容易编写测试用例,因为你可以伪造数据来创建一个模拟请求组件。
你可以通过 Yii::$app->request->method
表达式来获取当前请求使用的HTTP方法。
$request = Yii::$app->request; if ($request->isAjax) { /* 该请求是一个 AJAX 请求 */ } if ($request->isGet) { /* 请求方法是 GET */ } if ($request->isPost) { /* 请求方法是 POST */ } if ($request->isPut) { /* 请求方法是 PUT */ }
我这边一般的是使用一个Web基类去获取请求的body和head
$requestBody = $this->getParam('body'); $requestHeader = $this->getParam('header');
Web基类
<?php namespace ext\controller; use Yii; use yii\web\Controller; class Web extends Controller { public function init() { parent::init(); $this->setDefaultCrumbs(); } public function beforeaction($action) { return parent::beforeaction($action); } ...... /** * 获取参数 * @param null $key * @param null $val * @return array|mixed|null */ public function getParam($key = null, $val = null) { $request = Yii::$app->request; $data = []; /** * 如果是GET请求 */ if ($request->getIsGet()) { $data = $request->get('request'); } elseif ($request->getIsPost()) { if($request->contentType == 'application/json') { $rawBody = $request->rawBody; if($rawBody){ $requestData = json_decode($rawBody,true); $data = $requestData['request']; unset($requestData); } }else{ $data = $request->post('request'); } } elseif ($request->getIsDelete()) { $data = $request->get('request'); } if (isset($data[$key])) { if (is_null($data[$key])) { return $val; } else { return $data[$key]; } } elseif (is_null($key)) { return $data; } else { return $val; } } }
详细的代码待我完成Yii系列最佳时间贡献到githun供大伙下载研究呢。
Response
看完了请求的引导,我们来看下我们如何反馈给用户吧。
Yii是通过response这个组件来反馈给用户我们想要给到他的信息的。
Response对象包含的信息有HTTP状态码,HTTP头和主体内容等, 网页应用开发的最终目的本质上就是根据不同的请求构建这些响应对象。
响应中首当其冲的应该就是状态码了,也就是我们之前提到的404啊,503啊,301啊,这些都是服务器的状态码。
Yii提供了一些常量去定义这些状态码
yii\web\BadRequestHttpException:状态码 400。 yii\web\ConflictHttpException:状态码 409。 yii\web\ForbiddenHttpException:状态码 403。 yii\web\GoneHttpException:状态码 410。 yii\web\MethodNotAllowedHttpException:状态码 405。 yii\web\NotAcceptableHttpException:状态码 406。 yii\web\NotFoundHttpException:状态码 404。 yii\web\ServerErrorHttpException:状态码 500。 yii\web\TooManyRequestsHttpException:状态码 429。 yii\web\UnauthorizedHttpException:状态码 401。 yii\web\UnsupportedMediaTypeHttpException:状态码 415。
yii\web\Response::statusCode 状态码默认为200, 如果需要指定请求失败,可抛出对应的上诉的HTTP异常。
如果想抛出的异常不在如上列表中,可创建一个yii\web\HttpException异常, 带上状态码抛出,如下:
throw new \yii\web\HttpException(402);
可在 response
组件中操控yii\web\Response::headers来发送HTTP头部信息, 例如:
$headers = Yii::$app->response->headers; // 增加一个 Pragma 头,已存在的Pragma 头不会被覆盖。 $headers->add('Pragma', 'no-cache'); // 设置一个Pragma 头. 任何已存在的Pragma 头都会被丢弃 $headers->set('Pragma', 'no-cache'); // 删除Pragma 头并返回删除的Pragma 头的值到数组 $values = $headers->remove('Pragma');
响应主体
大多是响应应有一个主体存放你想要显示给终端用户的内容。
如果已有格式化好的主体字符串,可赋值到响应的yii\web\Response::$content属性, 例如:
$response = Yii::$app->response; $response->format = \yii\web\Response::FORMAT_JSON; $response->data = ['message' => 'hello world'];
Yii可以使用如下的几种格式
HTML: 通过 yii\web\HtmlResponseFormatter 来实现. XML: 通过 yii\web\XmlResponseFormatter来实现. JSON: 通过 yii\web\JsonResponseFormatter来实现. JSONP: 通过 yii\web\JsonResponseFormatter来实现. RAW: use this format if you want to send the response directly without applying any formatting.
一般的话,我们不会这么麻烦的去操作,而是直接和前端商量好,我们会以哪种形式返回给你,然后直接在action里面直接返回数据给用户。
Web基类中对应的方法:
public function sendSuccess($message = null, $data = [], $code = "0") { return $this->formatJson([ 'response' => [ 'header' => [ 'code' => $code, 'msg' => $message ], 'body' => $data ?: [] ] ] ); } public function sendError($message = null, $code = "1") { return $this->formatJson([ 'response' => [ 'header' => [ 'code' => (string)$code, 'msg' => $message ? $message : '未知错误' ], ] ] ); }
session
除了Request和Response这两个基本概念,还有个概念比较重要,如果你想让你用户的数据在你后端构成一个map,你可以使用session这个组件,相信我,这个组件绝对能够满足大部分用户登录系统,用他来保存用户的数据非常方便,每次用户来请求,只需要将sessionId传给你,你就可以获取到这个用户的状态,虽然方便,但不能完全保证安全,如果sessionId被截取了,这也是相当蛋疼的事。这就要看系统的健壮性了,用户的敏感信息是否会在前端有权限拿到,如果要拿敏感信息,你的系统又当如何。
这里不多说,会在安全那一章和大伙好好唠唠后台应用中提高安全系数的一些做法呢。
下面直接上session相关的代码,session的操作也很简单,key-value模型。
$session = Yii::$app->session; // 检查session是否开启 if ($session->isActive) ... // 开启session $session->open(); // 关闭session $session->close(); // 销毁session中所有已注册的数据 $session->destroy();
比较重要的一个方法是设置session存在的时间:
$session->setTimeout(...)
session数据的存储和提取
$session = Yii::$app->session; // 获取session中的变量值,以下用法是相同的: $language = $session->get('language'); $language = $session['language']; $language = isset($_SESSION['language']) ? $_SESSION['language'] : null; // 设置一个session变量,以下用法是相同的: $session->set('language', 'en-US'); $session['language'] = 'en-US'; $_SESSION['language'] = 'en-US'; // 删除一个session变量,以下用法是相同的: $session->remove('language'); unset($session['language']); unset($_SESSION['language']); // 检查session变量是否已存在,以下用法是相同的: if ($session->has('language')) ... if (isset($session['language'])) ... if (isset($_SESSION['language'])) ... // 遍历所有session变量,以下用法是相同的: foreach ($session as $name => $value) ... foreach ($_SESSION as $name => $value) ...
Flash数据是一种特别的session数据,它一旦在某个请求中设置后, 只会在下次请求中有效,然后该数据就会自动被删除。喜欢的朋友可以去官方文当中详查。
关于cookie,后端使用的较少,前端用的时候经常用它来存储一些数据。这里不多说,感兴趣的朋友可以去官方文当中详查。
至此,所有的关于用户的请求Yii是如何处理的就讲完啦,包括从URL解析,Request和Response组件,Session组件这一整套内容,其实,官网将错误处理,日志系统归类到这章,我感觉这两章对于开发者来讲还是比较重要的,所以,我下面会分两个小章来详细讲讲这两个系统。^_^