1. 路由(Route)
所有的用户请求都是发送给入口脚本 index.php 来处理的。那么,Yii 需要提供一种高效的分派请求的方法,来判断请求应当采用哪个 controller 哪个 action 进行处理。Yii 提供了路由和 URL 管理组件。
所谓路由是指 URL 中用于标识用于处理用户请求的 module, controller, action 的部分,一般情况下 由 r 查询参数来指定。(如 http://www.test.com/index.php?r=post/view&id=100 ,表示这个请求将由 PostController 的 actionView 来处理)
Yii 也提供了一种美化 URL 的功能,使得上面的 URL 可以用一个比较整洁、美观的形式表现出来,如 http://www.test.com/post/view/100 。这个功能的实现是依赖于一个称为 urlManager 的应用组件。urlManger 具有解析请求以便确定指派谁来处理请求和根据路由规则生成 URL 2 个功能。
1.1 美化URL
省略 index.php?
,因为所有请求都会进入到index.php?。在nginx配置:
location / {
try_files $uri $uri/ /index.php?$args;
}
URL就会变成:http://www.test.com/post/view?id=100
Yii 有专门的 yii\web\UrlManager 来进行处理,其中:
- 隐藏入口脚本可以通过
yii\web\UrlManager::showScriptName = false
来实现 - 路由的路径化可以通过
yii\web\UrlManager::enablePrettyUrl = true
来实现 - 参数的路径化可以通过路由规则来实现
假后缀 (fake suffix) .html 可以通过 yii\web\UrlManager::suffix = ‘.html’ 来实现
1.2 路由规则
路由规则是指 urlManager 用于解析请求或生成 URL 的规则。一个路由规则必须实现
yii\web\UrlRuleInterface
接口,这个接口定义了两个方法:用于解析请求的 yii\web\UrlRuleInterface::parseRequest()
- 用于生成 URL 的 yii\web\UrlRuleInterface::createUrl()
Yii 中,使用 yii\web\UrlRule
来表示路由规则,一般这个类是足够使用。但是,想自己实现解析请求或生成 URL 的逻辑,可以以这个类为基类进行派生,并重载 parseRuquest()
和 createUrl()
。
2. URL管理
3. 请求(Request)
对于 Yii 应用而言,初始化后第一件正事,就是获取用户请求。在 yii\base\Application::run()
中:
<?
public function run() {
try {
$this->state = self::STATE_BEFORE_REQUEST;
$this->trigger(self::EVENT_BEFORE_REQUEST);
$this->state = self::STATE_HANDLING_REQUEST;
// 获取用户请求,并进行处理,处理的过程也是产生响应内容的过程
$response = $this->handleRequest($this->getRequest());
$this->state = self::STATE_AFTER_REQUEST;
$this->trigger(self::EVENT_AFTER_REQUEST);
$this->state = self::STATE_SENDING_RESPONSE;
// 将响应内容发送回用户
$response->send();
$this->state = self::STATE_END;
return $response->exitStatus;
} catch (ExitException $e) {
$this->end($e->statusCode, isset($response) ? $response : null);
return $e->statusCode;
}
}
$this->getRequest()
就是用于获取用户请求的。这是一个 getter,用于获取 Application 的 request 组件 (component) 。Yii 用这个组件来代表用户请求,他承载着所有的用户输入信息。
Request 类其实涉及到了以下的类:
- yii\base\Request Request 类基类
- yii\console\Request 表示 Console 应用的的 Request
- yii\web\Request 表示 Web 应用的 Request
3.2 基类 Request
基类是对 Console 应用和 Web 应用 Request 的抽象,他仅仅定义了两个属性和一个虚函数: ```php <?php namespace yii\base;
use Yii;
abstract class Request extends Component { // 属性 scriptFile,用于表示入口脚本 private $_scriptFile; // 属性 isConsoleRequest,用于表示是否是命令行应用 private $_isConsoleRequest;
// 这个函数的功能主要是为了把 Request 解析成路由和相应的参数
abstract public function resolve();
// isConsoleRequest 属性的 getter 函数
// 使用 PHP_SAPI 常量判断当前应用是否是命令行应用
public function getIsConsoleRequest() {
// 一切 PHP_SAPI 不为 'cli' 的,都不是命令行
return ($this->_isConsoleRequest !== null)
? $this->_isConsoleRequest : PHP_SAPI === 'cli';
}
// isConsoleRequest 属性的 setter 函数
public function setIsConsoleRequest($value) {
$this->_isConsoleRequest = $value;
}
// scriptFile 属性的 getter 函数
// 通过 $_SERVER['SCRIPT_FILENAME'] 来获取入口脚本名
public function getScriptFile() {
if ($this->_scriptFile === null) {
if (isset($_SERVER['SCRIPT_FILENAME'])) {
$this->setScriptFile($_SERVER['SCRIPT_FILENAME']);
} else {
throw new InvalidConfigException(
'Unable to determine the entry script file path.');
}
}
return $this->_scriptFile;
}
// scriptFile 属性的 setter 函数
public function setScriptFile($value) {
$scriptFile = realpath(Yii::getAlias($value));
if ($scriptFile !== false && is_file($scriptFile)) {
$this->_scriptFile = $scriptFile;
} else {
throw new InvalidConfigException(
'Unable to determine the entry script file path.');
}
}
}
<a name="KTRbK"></a>
## 3.3 命令行应用 Request
命令行应用 Request 由 `yii\console\Request` 负责实现,相比较于 yii\base\Request 稍有丰富:
```php
<?php
namespace yii\console;
class Request extends \yii\base\Request {
// 属性 params,用于表示命令行参数
private $_params;
// params 属性的 getter 函数,通过 $_SERVER['argv'] 来获取命令行参数
public function getParams() {
if ($this->_params === null) {
if (isset($_SERVER['argv'])) {
$this->_params = $_SERVER['argv'];
// 删除数组的第一个元素,这个元素是 PHP 脚本名。
// 因此,属性 params 中全部是参数,不带脚本名
array_shift($this->_params);
} else {
$this->_params = [];
}
}
return $this->_params;
}
public function setParams($params) {
$this->_params = $params;
}
// 父类虚函数的实现
public function resolve() {
// 获取全部的命令行参数
$rawParams = $this->getParams();
$endOfOptionsFound = false;
// 第一个命令行参数作为路由
if (isset($rawParams[0])) {
$route = array_shift($rawParams);
if ($route === '--') {
$endOfOptionsFound = true;
$route = array_shift($rawParams);
}
} else {
$route = '';
}
$params = [];
$prevOption = null;
// 遍历剩余的全部命令行参数
foreach ($rawParams as $param) {
if ($endOfOptionsFound) {
$params[] = $param;
} elseif ($param === '--') {
$endOfOptionsFound = true;
// 正则匹配每一个参数
} elseif (preg_match('/^--([\w-]+)(?:=(.*))?$/', $param, $matches)) {
$name = $matches[1];
if (is_numeric(substr($name, 0, 1))) {
throw new Exception('Parameter "' . $name . '" is not valid');
}
// yii\console\Application::OPTION_APPCONFIG = 'appconfig'
if ($name !== Application::OPTION_APPCONFIG) {
$params[$name] = isset($matches[2]) ? $matches[2] : true;
$prevOption = &$params[$name];
}
} elseif (preg_match('/^-([\w-]+)(?:=(.*))?$/', $param, $matches)) {
$name = $matches[1];
if (is_numeric($name)) {
$params[] = $param;
} else {
$params['_aliases'][$name] = isset($matches[2]) ? $matches[2] : true;
$prevOption = &$params['_aliases'][$name];
}
} elseif ($prevOption === true) {
$prevOption = $param;
// 无名参数,直接作为参数值
} else {
$params[] = $param;
}
}
return [$route, $params];
}
}
yii\console\Request 实现了父类的 resolve()
虚函数,这个函数主要做了这么几件事:
- 将 params 属性的第一个元素作为路由。如果入口脚本未提供任何参数,也即 params 是个空数组,那么将路由置为一个空字符串。
- 遍历 params 中剩余的参数,使用正则匹配 Yii 应用的参数名和参数值,看看是不是 —参数名 = 参数值形式。其中,以 — 打头的任意字母、数字、下划线的组合,就是参数名。紧跟参数名的 = 后面的内容,则为参数值。对于仅有参数名,没有参数值的,视参数值为 true 。
- 如果正则匹配不成功,则将这个命令行参数作为 Yii 应用的一个无名参数的值。
- 如果第二步中的参数名为 appconfig 则忽略该参数,Console Application 会专门针对该参数进行处理。
- 上面步骤中的参数和参数值,被保存进一个数组中。数组的键表示参数名,数组的值表示参数值。
- 最终 resolve() 返回一个数组,第一个元素是一个表示路由的字符串,第二元素则是参数数组。该方法 由 Application 在处理 Request 时调用。
关于 appconfig 参数的问题,只要在调用 yii 时,指定了 appconfig 参数,就表明不使用默认的参数配置文件,而使用该参数所指定的配置文件。相关的代码在 yii\console\Application 中:
<?
class Application extends \yii\base\Application {
// 定义一个常量
const OPTION_APPCONFIG = 'appconfig';
public function __construct($config = []) {
// 调用 loadConfig() 成员函数
$config = $this->loadConfig($config);
parent::__construct($config);
}
// 配置文件存在 ? 返回其配置数组 : 返回构造函数调用时的数组
protected function loadConfig($config) {
if (!empty($_SERVER['argv'])) {
// 设定了一个字符串 "--appconfig="
$option = '--' . self::OPTION_APPCONFIG . '=';
// 遍历所有命令行参数,看看能不能找到 $option 字符串
foreach ($_SERVER['argv'] as $param) {
if (strpos($param, $option) !== false) {
// 截取参数值部分
$path = substr($param, strlen($option));
if (!empty($path) && is_file($file = Yii::getAlias($path))) {
// 将指定文件的内容引入进来
return require $file;
}
exit("The configuration file does not exist: $path\n");
}
}
}
return $config;
}
}
4. Web应用Request
Web 应用 Request 由 yii\web\Request 实现。这个类涉及到许多 HTTP 的有关知识。还涉及到 PHP 运行于不同的环境和模式下的一些细微差别。
4.1 请求的方法
HTTP 的请求可以有:GET, POST, PUT 等 8 种方法 (Request Method)。除了用不到的 CONNECT 外,Yii 支持全部的 HTTP 请求方法。
要获取当前用户请求的方法,可以使用 yii\web\Request::getMethod():
<?
// 返回当前请求的方法,方法名称是大小写敏感的,按规范应转换为大写字母
public function getMethod() {
if (
isset($_POST[$this->methodParam])
&& !in_array(strtoupper($_POST[$this->methodParam]), ['GET', 'HEAD', 'OPTIONS'], true)
) {
return strtoupper($_POST[$this->methodParam]);
}
if ($this->headers->has('X-Http-Method-Override')) {
return strtoupper($this->headers->get('X-Http-Method-Override'));
}
if (isset($_SERVER['REQUEST_METHOD'])) {
return strtoupper($_SERVER['REQUEST_METHOD']);
}
return 'GET';
}