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配置:

  1. location / {
  2. try_files $uri $uri/ /index.php?$args;
  3. }

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() 中:

  1. <?
  2. public function run() {
  3. try {
  4. $this->state = self::STATE_BEFORE_REQUEST;
  5. $this->trigger(self::EVENT_BEFORE_REQUEST);
  6. $this->state = self::STATE_HANDLING_REQUEST;
  7. // 获取用户请求,并进行处理,处理的过程也是产生响应内容的过程
  8. $response = $this->handleRequest($this->getRequest());
  9. $this->state = self::STATE_AFTER_REQUEST;
  10. $this->trigger(self::EVENT_AFTER_REQUEST);
  11. $this->state = self::STATE_SENDING_RESPONSE;
  12. // 将响应内容发送回用户
  13. $response->send();
  14. $this->state = self::STATE_END;
  15. return $response->exitStatus;
  16. } catch (ExitException $e) {
  17. $this->end($e->statusCode, isset($response) ? $response : null);
  18. return $e->statusCode;
  19. }
  20. }

$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;

  1. // 这个函数的功能主要是为了把 Request 解析成路由和相应的参数
  2. abstract public function resolve();
  3. // isConsoleRequest 属性的 getter 函数
  4. // 使用 PHP_SAPI 常量判断当前应用是否是命令行应用
  5. public function getIsConsoleRequest() {
  6. // 一切 PHP_SAPI 不为 'cli' 的,都不是命令行
  7. return ($this->_isConsoleRequest !== null)
  8. ? $this->_isConsoleRequest : PHP_SAPI === 'cli';
  9. }
  10. // isConsoleRequest 属性的 setter 函数
  11. public function setIsConsoleRequest($value) {
  12. $this->_isConsoleRequest = $value;
  13. }
  14. // scriptFile 属性的 getter 函数
  15. // 通过 $_SERVER['SCRIPT_FILENAME'] 来获取入口脚本名
  16. public function getScriptFile() {
  17. if ($this->_scriptFile === null) {
  18. if (isset($_SERVER['SCRIPT_FILENAME'])) {
  19. $this->setScriptFile($_SERVER['SCRIPT_FILENAME']);
  20. } else {
  21. throw new InvalidConfigException(
  22. 'Unable to determine the entry script file path.');
  23. }
  24. }
  25. return $this->_scriptFile;
  26. }
  27. // scriptFile 属性的 setter 函数
  28. public function setScriptFile($value) {
  29. $scriptFile = realpath(Yii::getAlias($value));
  30. if ($scriptFile !== false && is_file($scriptFile)) {
  31. $this->_scriptFile = $scriptFile;
  32. } else {
  33. throw new InvalidConfigException(
  34. 'Unable to determine the entry script file path.');
  35. }
  36. }

}

  1. <a name="KTRbK"></a>
  2. ## 3.3 命令行应用 Request
  3. 命令行应用 Request 由 `yii\console\Request` 负责实现,相比较于 yii\base\Request 稍有丰富:
  4. ```php
  5. <?php
  6. namespace yii\console;
  7. class Request extends \yii\base\Request {
  8. // 属性 params,用于表示命令行参数
  9. private $_params;
  10. // params 属性的 getter 函数,通过 $_SERVER['argv'] 来获取命令行参数
  11. public function getParams() {
  12. if ($this->_params === null) {
  13. if (isset($_SERVER['argv'])) {
  14. $this->_params = $_SERVER['argv'];
  15. // 删除数组的第一个元素,这个元素是 PHP 脚本名。
  16. // 因此,属性 params 中全部是参数,不带脚本名
  17. array_shift($this->_params);
  18. } else {
  19. $this->_params = [];
  20. }
  21. }
  22. return $this->_params;
  23. }
  24. public function setParams($params) {
  25. $this->_params = $params;
  26. }
  27. // 父类虚函数的实现
  28. public function resolve() {
  29. // 获取全部的命令行参数
  30. $rawParams = $this->getParams();
  31. $endOfOptionsFound = false;
  32. // 第一个命令行参数作为路由
  33. if (isset($rawParams[0])) {
  34. $route = array_shift($rawParams);
  35. if ($route === '--') {
  36. $endOfOptionsFound = true;
  37. $route = array_shift($rawParams);
  38. }
  39. } else {
  40. $route = '';
  41. }
  42. $params = [];
  43. $prevOption = null;
  44. // 遍历剩余的全部命令行参数
  45. foreach ($rawParams as $param) {
  46. if ($endOfOptionsFound) {
  47. $params[] = $param;
  48. } elseif ($param === '--') {
  49. $endOfOptionsFound = true;
  50. // 正则匹配每一个参数
  51. } elseif (preg_match('/^--([\w-]+)(?:=(.*))?$/', $param, $matches)) {
  52. $name = $matches[1];
  53. if (is_numeric(substr($name, 0, 1))) {
  54. throw new Exception('Parameter "' . $name . '" is not valid');
  55. }
  56. // yii\console\Application::OPTION_APPCONFIG = 'appconfig'
  57. if ($name !== Application::OPTION_APPCONFIG) {
  58. $params[$name] = isset($matches[2]) ? $matches[2] : true;
  59. $prevOption = &$params[$name];
  60. }
  61. } elseif (preg_match('/^-([\w-]+)(?:=(.*))?$/', $param, $matches)) {
  62. $name = $matches[1];
  63. if (is_numeric($name)) {
  64. $params[] = $param;
  65. } else {
  66. $params['_aliases'][$name] = isset($matches[2]) ? $matches[2] : true;
  67. $prevOption = &$params['_aliases'][$name];
  68. }
  69. } elseif ($prevOption === true) {
  70. $prevOption = $param;
  71. // 无名参数,直接作为参数值
  72. } else {
  73. $params[] = $param;
  74. }
  75. }
  76. return [$route, $params];
  77. }
  78. }

yii\console\Request 实现了父类的 resolve() 虚函数,这个函数主要做了这么几件事:

  • 将 params 属性的第一个元素作为路由。如果入口脚本未提供任何参数,也即 params 是个空数组,那么将路由置为一个空字符串。
  • 遍历 params 中剩余的参数,使用正则匹配 Yii 应用的参数名和参数值,看看是不是 —参数名 = 参数值形式。其中,以 — 打头的任意字母、数字、下划线的组合,就是参数名。紧跟参数名的 = 后面的内容,则为参数值。对于仅有参数名,没有参数值的,视参数值为 true 。
  • 如果正则匹配不成功,则将这个命令行参数作为 Yii 应用的一个无名参数的值。
  • 如果第二步中的参数名为 appconfig 则忽略该参数,Console Application 会专门针对该参数进行处理。
  • 上面步骤中的参数和参数值,被保存进一个数组中。数组的键表示参数名,数组的值表示参数值。
  • 最终 resolve() 返回一个数组,第一个元素是一个表示路由的字符串,第二元素则是参数数组。该方法 由 Application 在处理 Request 时调用。

关于 appconfig 参数的问题,只要在调用 yii 时,指定了 appconfig 参数,就表明不使用默认的参数配置文件,而使用该参数所指定的配置文件。相关的代码在 yii\console\Application 中:

  1. <?
  2. class Application extends \yii\base\Application {
  3. // 定义一个常量
  4. const OPTION_APPCONFIG = 'appconfig';
  5. public function __construct($config = []) {
  6. // 调用 loadConfig() 成员函数
  7. $config = $this->loadConfig($config);
  8. parent::__construct($config);
  9. }
  10. // 配置文件存在 ? 返回其配置数组 : 返回构造函数调用时的数组
  11. protected function loadConfig($config) {
  12. if (!empty($_SERVER['argv'])) {
  13. // 设定了一个字符串 "--appconfig="
  14. $option = '--' . self::OPTION_APPCONFIG . '=';
  15. // 遍历所有命令行参数,看看能不能找到 $option 字符串
  16. foreach ($_SERVER['argv'] as $param) {
  17. if (strpos($param, $option) !== false) {
  18. // 截取参数值部分
  19. $path = substr($param, strlen($option));
  20. if (!empty($path) && is_file($file = Yii::getAlias($path))) {
  21. // 将指定文件的内容引入进来
  22. return require $file;
  23. }
  24. exit("The configuration file does not exist: $path\n");
  25. }
  26. }
  27. }
  28. return $config;
  29. }
  30. }

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():

  1. <?
  2. // 返回当前请求的方法,方法名称是大小写敏感的,按规范应转换为大写字母
  3. public function getMethod() {
  4. if (
  5. isset($_POST[$this->methodParam])
  6. && !in_array(strtoupper($_POST[$this->methodParam]), ['GET', 'HEAD', 'OPTIONS'], true)
  7. ) {
  8. return strtoupper($_POST[$this->methodParam]);
  9. }
  10. if ($this->headers->has('X-Http-Method-Override')) {
  11. return strtoupper($this->headers->get('X-Http-Method-Override'));
  12. }
  13. if (isset($_SERVER['REQUEST_METHOD'])) {
  14. return strtoupper($_SERVER['REQUEST_METHOD']);
  15. }
  16. return 'GET';
  17. }