加载中...

请求(Reqeust)


获取用户请求

PHP并未提供集中的、统一的界面以获取用户请求,而是分散在 $_SERVER $_POST 等变量和其他代码中。 万能的Yii怎么会允许群雄割据这种局面出现呢?他肯定是要一统江湖的。 那么对于任何Yii应用而言,初始化后第一件正事,就是获取用户请求。 这个代码在 yii\base\Application::run() 中:

  1. public function run()
  2. {
  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用这个组件来代表用户请求, 他承载着所有的用户输入信息。

我们知道,Yii应用有命令行(Console)应用和Web应用之分。因此,这个Request类其实涉及到了以下的类:

  • yii\base\Request Request类基类
  • yii\console\Request 表示Console应用的的Request
  • yii\web\Request 表示Web应用的Request

下面我们逐一进行讲解。

基类Request

基类是对Console应用和Web应用Request的抽象,他仅仅定义了两个属性和一个虚函数:

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

yii\base\Request 通过getter和setter提供了两个可读写的属性, isConsoleRequest 和 scriptFile 。 同时,要求子类实现一个 resolve() 方法。

基类的代码相对简单,主要涉及到PHP的一些知识,如 PHP_SAPI $_SERVER['SCRIPT_FILENAME'] 等, 读者朋友们可以通过搜索引擎或PHP手册了解下相关的知识,相信上面的代码难不倒你们的。

命令行应用Request

命令行应用Request由 yii\console\Request 负责实现,相比较于 yii\base\Request 稍有丰富:

  1. class Request extends \yii\base\Request
  2. {
  3. // 属性 params,用于表示命令行参数
  4. private $_params;
  5. // params属性的getter函数
  6. // 通过 $_SERVER['argv'] 来获取命令行参数
  7. public function getParams()
  8. {
  9. if (!isset($this->_params)) {
  10. if (isset($_SERVER['argv'])) {
  11. $this->_params = $_SERVER['argv'];
  12. // 删除数组的第一个元素,这个元素是PHP脚本名。
  13. // 因此,属性params中全部是参数,不带脚本名
  14. array_shift($this->_params);
  15. } else {
  16. $this->_params = [];
  17. }
  18. }
  19. return $this->_params;
  20. }
  21. // params属性的setter函数
  22. public function setParams($params)
  23. {
  24. $this->_params = $params;
  25. }
  26. // 父类虚函数的实现
  27. public function resolve()
  28. {
  29. // 获取全部的命令行参数
  30. $rawParams = $this->getParams();
  31. // 第一个命令行参数作为路由
  32. if (isset($rawParams[0])) {
  33. $route = $rawParams[0];
  34. array_shift($rawParams);
  35. } else {
  36. $route = '';
  37. }
  38. $params = [];
  39. // 遍历剩余的全部命令行参数
  40. foreach ($rawParams as $param) {
  41. // 正则匹配每一个参数
  42. if (preg_match('/^--(\w+)(=(.*))?$/', $param, $matches)) {
  43. // 参数名
  44. $name = $matches[1];
  45. // yii\console\Application::OPTION_APPCONFIG = 'appconfig'
  46. if ($name !== Application::OPTION_APPCONFIG) {
  47. $params[$name] = isset($matches[3]) ? $matches[3] : true;
  48. }
  49. // 无名参数,直接作为参数值
  50. } else {
  51. $params[] = $param;
  52. }
  53. }
  54. return [$route, $params];
  55. }
  56. }

相比较于 yii\base\Request , yii\console\Request 提供了一个 params 属性, 该属性以数组形式保存了入口脚本 Yii 的命令行参数。这是通过 $_SERVER['argv'] 获取的。 注意 params 属性不保存入口脚本名。入口脚本名由基类的 scriptName 属性保存。

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

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

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

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

讲完了Request基类和命令行应用的Request只是热身而已,接下来要讲的Web应用Request才是重头。 毕竟最最主要的,还是Web开发嘛。考虑到Web Request的内容较多,还是单独成 Web应用Request 来讲吧。


还没有评论.