请求(Reqeust)

获取用户请求

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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用这个组件来代表用户请求, 他承载着所有的用户输入信息。

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

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

下面我们逐一进行讲解。

基类Request

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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.');
        }
    }
}

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

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

命令行应用Request

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
class Request extends \yii\base\Request
{
    // 属性 params,用于表示命令行参数
    private $_params;

    // params属性的getter函数
    // 通过 $_SERVER['argv'] 来获取命令行参数
    public function getParams()
    {
        if (!isset($this->_params)) {
            if (isset($_SERVER['argv'])) {
                $this->_params = $_SERVER['argv'];

                // 删除数组的第一个元素,这个元素是PHP脚本名。
                // 因此,属性params中全部是参数,不带脚本名
                array_shift($this->_params);
            } else {
                $this->_params = [];
            }
        }

        return $this->_params;
    }

    // params属性的setter函数
    public function setParams($params)
    {
        $this->_params = $params;
    }

    // 父类虚函数的实现
    public function resolve()
    {
        // 获取全部的命令行参数
        $rawParams = $this->getParams();

        // 第一个命令行参数作为路由
        if (isset($rawParams[0])) {
            $route = $rawParams[0];
            array_shift($rawParams);
        } else {
            $route = '';
        }

        $params = [];

        // 遍历剩余的全部命令行参数
        foreach ($rawParams as $param) {

            // 正则匹配每一个参数
            if (preg_match('/^--(\w+)(=(.*))?$/', $param, $matches)) {

                // 参数名
                $name = $matches[1];

                // yii\console\Application::OPTION_APPCONFIG = 'appconfig'
                if ($name !== Application::OPTION_APPCONFIG) {
                    $params[$name] = isset($matches[3]) ? $matches[3] : true;
                }

            // 无名参数,直接作为参数值
            } else {
                $params[] = $param;
            }
        }

        return [$route, $params];
    }
}

相比较于 yii\base\Requestyii\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
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 定义一个常量
const OPTION_APPCONFIG = 'appconfig';

// yii\console\Application类的构造函数
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 . '=';

        // 遍历所有命令行参数,看看能不能找到上面说的这个字符串
        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);
                } else {
                    die("The configuration file does not exist: $path\n");
                }
            }
        }
    }

    return $config;
}

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

如果觉得《深入理解Yii2.0》对您有所帮助,也请帮助《深入理解Yii2.0》。 谢谢!