运行机制

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>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容