yii处理http请求时,执行的流程是这样的:
1、服务器接收到来自客户端(比如浏览器)的请求,服务器(比如nginx)根据一些转发规则把请求转发给入口脚本web/index.php
2、入口加班加载配置文件,实例化一个应用。
3、应用通过request应用组件解析路由。
4、应用创建controller实例处理请求。
5、controller创建action实例并为这个action执行相关的filter操作。
6、如果任何一个filter验证失败,就不执行这个动作,如果全部的filter通过验证,就会继续执行
7、动作会加载数据模型,一般从数据库中读取
8、加载完数据渲染一个view,并且把需要的数据一起填充到这个view中。
9、渲染得到的结果会返回给response组件。
10、response组件把渲染结果返回给浏览器。
下面是yii官方文档中的示意图。
启动引导
在应用开始解析并处理接受请求之前,要预先准备环境。启动引导会在两个地方执行,入口脚本和应用主体。在入口脚本里,需要注册各个类库的类文件自动加载器,加载配置文件,实例化应用主体。
实例化应用主体,会执行应用主体的构造函数,在构造函数中做这些事:
1、调用 yii\base\Application::preInit()方法,配置高优先级的应用属性。
2、注册errorHandler函数
3、调用init方法,运行引导组件。init方法做的事:1、加载vendor/yiisoft/extensions.php扩展清单文件,创建运行各个扩展的引导组件,创建并运行各个组件和在应用的bootstrap属性中声明的各个模块组件。
因为引导工作必须在处理每一次请求之前都进行一遍,因此尽量不要注册太多引导组件。
路由引导
在入口脚本中调用yii\web\Application::run() 方法时,首先要解析输入的请求,用request组件的 yii\web\Request::resolve() 方法实现,确定该用哪个controller处理请求,然后实例化对应的controller处理请求,如果请求实在不能被确定该由哪个controller处理,request组件会抛出yii\web\NotFoundHttpException 异常。
缺省路由
如果请求没有提供具体的路由,这个时候就会启用yii\web\Application::defaultRoute 属性所指定的缺省路由。该属性的默认值为 site/index,它指向 site 控制器的 actionIndex,可以在配置文件中设置该属性。
catchAll路由
如果你的web服务整在维护,不想让用户访问,这个时候可以设置catchAll属性,它可以把所有的请求都解析到同一个路由。
创建action
一旦请求路由被确定了,紧接着就要创建action对象,用来响应这个路由。
路由可以用斜线分割成多个组成片段,栗子:site/index可以分解为site和index,它们指向某一个module、controller或action。
从路由的第一个片段开始,按下面的流程一次创建module、controller或action,如果这些步骤有任何错误发生,就会抛出yii\web\NotFoundHttpException。
先找到site对应的controller。
在controller里面找到index的action,如果找的到,就创建一个action对象,如果找不到,controller会尝试创建一个action和它相对应
请求
一个应用的请求是用yii\web\Request 对象来表示的,该对象提供了请求参数、HTTP头、cookies等信息,可以通过request组件获得相应的请求对象。
这个组件提供了一些方法可以获得相关信息:
获取请求的参数和方法
yii\web\Request::get()
yii\web\Request::post()
yii\web\Request::getBodyParam()
Yii::$app->request->method,
获取Url信息
假设被请求的URL是 http://example.com/admin/index.php/product?id=100, 可以像下面描述的那样获取URL的各个部分:
yii\web\Request::url:返回 /admin/index.php/product?id=100, 此URL不包括host info部分。
yii\web\Request::absoluteUrl:返回 http://example.com/admin/index.php/product?id=100, 包含host info的整个URL。
yii\web\Request::hostInfo:返回 http://example.com, 只有host info部分。
yii\web\Request::pathInfo:返回 /product, 这个是入口脚本之后,问号之前(查询字符串)的部分。
yii\web\Request::queryString:返回 id=100,问号之后的部分。
yii\web\Request::baseUrl:返回 /admin, host info之后, 入口脚本之前的部分。
yii\web\Request::scriptUrl:返回 /admin/index.php, 没有path info和查询字符串部分。
yii\web\Request::serverName:返回 example.com, URL中的host name。
yii\web\Request::serverPort:返回 80, 这是web服务中使用的端口。
获取HTTP头信息
Yii::$app->request->header: 返回yii\web\HeaderCollection 对象
yii\web\Request::userAgent:返回 User-Agent 头。
yii\web\Request::contentType:返回 Content-Type 头的值, Content-Type 是请求体中MIME类型数据。
yii\web\Request::acceptableContentTypes:返回用户可接受的内容MIME类型。 返回的类型是按照他们的质量得分来排序的,得分最高的类型将被最先返回。
yii\web\Request::acceptableLanguages:返回用户可接受的语言。 返回的语言是按照他们的偏好层次来排序的。第一个参数代表最优先的语言。
获取客户端信息
yii\web\Request::userHost 获取host name
yii\web\Request::userIP 获取客户机的IP地址
响应
当应用完成处理一个请求后, 会生成一个yii\web\Response响应对象并发送给客户端,响应对象包含的信息有HTTP状态码,HTTP头和主体内容等, web应用开发的最终目的本质上就是根据不同的请求构建这些响应对象。
HTTP状态码设置方法
Yii::$app->response->statusCode = 200;
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::$app->response->content = 'hello world!';
如果在发送给终端用户之前需要格式化,应设置 yii\web\Response::format 和 yii\web\Response::data 属性,yii\web\Response::format 属性指定yii\web\Response::data中数据格式化后的样式,栗子:
<pre>
$response = Yii::$app->response;
$response->format = \yii\web\Response::FORMAT_JSON;
$response->data = ['message' => 'hello world'];
</pre>
Yii支持以下可直接使用的格式
yii\web\Response::FORMAT_HTML
yii\web\Response::FORMAT_XML
yii\web\Response::FORMAT_JSON
yii\web\Response::FORMAT_JSONP
如果不指定,在action中调用render方法渲染视图默认yii\web\Response::FORMAT_HTML,
上面介绍的这些方法可以被显式地设置,但是大多数情况下通过操作方法的返回值隐式设置,如果想使用其他格式,只需要在返回数据之前设置$response->format即可。
浏览器跳转
浏览器跳转依赖于发送一个Location
HTTP 头,Yii是这样支持的:可以调用yii\web\Response::redirect() 方法将用户浏览器跳转到一个URL地址,在action中,可以调用缩写版的redirect,栗子:
return $this->redirect('http://example.com/new', 301);
这行代码会返回一个响应对象发送给终端用户,除了这个,还可以在redirect()之后再直接调用send()方法来确保没有其他内容追加到响应中。栗子:
\Yii::$app->response->redirect('http://example.com/new', 301)->send();
yii\web\Response::redirect() 方法默认会设置响应状态码为302,该状态码会告诉浏览器请求的资源 临时 放在另一地址上,可传递一个301状态码告知浏览器请求的资源已经 永久 重定向到新的地址。
如果当前请求是ajax请求,这样发送location是不会跳转的,解决办法:yii\web\Response::redirect() 方法设置一个值为要跳转的URL的X-Redirect 头,在浏览器用JavaScript 代码读取该头部值然后让浏览器跳转对应的URL。
发送文件
Yii提供这些方法支持各种文件的发送需求:
yii\web\Response::sendFile(): 发送一个已存在的文件到客户端
yii\web\Response::sendContentAsFile(): 发送一个文本字符串作为文件到客户端
yii\web\Response::sendStreamAsFile(): 发送一个已存在的文件流作为文件到客户端
发送响应####
用yii\web\Response::send() 方法发送响应,在这个方法调用前响应的内容不会返回给客户端,默认在yii\base\Application::run() 结尾自动调用,也可以手动调用这个方法强制立刻发送。这个方法使用这些步骤发送响应:
触发 yii\web\Response::EVENT_BEFORE_SEND 事件.
调用 yii\web\Response::prepare() 来格式化 yii\web\Response::data 为 yii\web\Response::content.
触发 yii\web\Response::EVENT_AFTER_PREPARE 事件.
调用 yii\web\Response::sendHeaders() 来发送注册的HTTP头
调用 yii\web\Response::sendContent() 来发送响应主体内容
触发 yii\web\Response::EVENT_AFTER_SEND 事件.
一旦yii\web\Response::send() 方法被执行后,在其他地方调用这个方法就会被忽略, 就是说一旦响应发出后,就不能再追加其他内容。
Sessions
session可以通过session 应用组件访问sessions,它是
yii\web\Session 的实例。session组件提供了这些属性和方法:
<pre>
$session = Yii::$app->session;
if ($session->isActive) ...// 检查session是否开启
$session->open(); // 开启session
$session->close(); // 关闭session,多次调用open和close不会产生错误,因为方法内部会先检查session是否已经开启。
$session->destroy(); // 销毁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) ...
</pre>
tips:使用session组件访问session数据的时候,如果session没有自动开启,和通过$_SESSION不同,使用$_SESSION之前需要先执行session_start().
当session数据是数组时,session组件只允许修改第一层数据的值,但是可以读取第二层以后的数据,栗子:
<pre>
$session = Yii::$app->session;
// 如下代码不会生效
$session['captcha']['number'] = 5;
$session['captcha']['lifetime'] = 3600;
// 如下代码会生效:
$session['captcha'] = [
'number' => 5,
'lifetime' => 3600,
];
// 如下代码也会生效
echo $session['captcha']['lifetime'];
</pre>
自定义Session存储
yii\web\Session默认把session数据保存为文件,Yii提供下面这些类可以保存为不同的形式:
yii\web\DbSession: 保存到数据库
yii\web\CacheSession: 保存到缓存,需要在配置文件中配置相关的缓存组件
yii\redis\Session: 保存到redis
这些session类都支持相同的api,因此切换不同的session类不需要修改使用session的代码。
tips:如果通过$_SESSION访问使用自定义存储介质的session,需要确保session已经用yii\web\Session::open() 开启, 因为只有在这个方法中会注册自定义session存储处理器。
Cookies
Yii使用 yii\web\Cookie对象来代表每个cookie,yii\web\Request(维护请求提交的cookies) 和 yii\web\Response(维护返回给用户的cookies) 通过名为'cookies'的属性维护一个cookie集合。
读取cookies,栗子:
<pre>
$cookies = Yii::$app->request->cookies;
// 获取名为 "language" cookie 的值,如果不存在,返回默认值"en"
$language = $cookies->getValue('language', 'en’);
// 另一种方式获取名为 "language" cookie 的值
if (($cookie = $cookies->get('language')) !== null) {
$language = $cookie->value;
}
// 可将 $cookies当作数组使用
if (isset($cookies['language'])) {
$language = $cookies['language']->value;
}
// 判断是否存在名为"language" 的 cookie
if ($cookies->has('language')) ...
if (isset($cookies['language'])) ...
</pre>
发送cookies,栗子:
<pre>
$cookies = Yii::$app->response->cookies;
// 在要发送的响应中添加一个新的cookie
$cookies->add(new \yii\web\Cookie([
'name' => 'language',
'value' => 'zh-CN',
]));
// 删除一个cookie
$cookies->remove('language');
// 等同于以下删除代码
unset($cookies['language']);
</pre>