《Thinkphp5入门系列课程》第十课:Request

Thinkphp5视频教程

通过本文你可以学到:

  • Request 对象的使用
  • 参数的获取
  • Request 自动注入的原理讲解(选读)

Request 对象

Requestthinkphp5 对于请求的做的一个统一的封装,我们基本上可以通过 Request 获取所有来自用户端的数据。例如:post,cookie等。Request 的使用方式有三种:

  • 自动注入
  • 手动获取
  • 函数获取

自动注入

Controller 中,我们可以在方法中这样使用:

param());
    }
}

如上面方式,在我们访问 login 的时候,系统会自动实例化 Request 并以参数的形式自动诸如到当前的 login 方法中。这就是自动注入。这样就不需要手动的去实例化 Request 直接使用就可以咯。

文章结尾给出了 thinkphp5 对于 Request 自动注入的分析,感兴趣的小伙伴们可以自行选择阅读。

手动获取

除了自动获取实例之外,我们也可以:

param());
    }
}

函数获取

param());
    }
}

Q:方法多了,有的小伙伴就不好选择咯,我该使用哪个方式较好?
A:没有最好的方法也没有最坏的方法,只有最适合你的方法,你想怎么使用就怎么使用咯。

参数的获取

Request 对象提供了非常的多的方法供我们获取多样化的数据,具体如下:

$request = request();
行为 代码
当前域名 $request->domain()
当前 URL 地址(不包含域名) $request->url()
当前完整地址 $request->url(true)
当前模块 $request->module()
当前控制器 $request->controller()
当前行为 $request->action()
当前请求方法 $request->method()
当前IP $request->ip()
全部参数 $request->param($name = '', $default = null, $filter = '')
GET 参数 $request->get($name = '', $default = null, $filter = '')
POST 参数 $request->post($name = '', $default = null, $filter = '')
session 参数 $request->session($name = '', $default = null, $filter = '')
cookie 参数 $request->cookie($name = '', $default = null, $filter = '')
server 参数 $request->server($name = '', $default = null, $filter = '')
file 参数 $request->file($name = '')
参数 解释
$name 参数名
$default 如果参数不存在返回这个值
$filter 参数过滤,可以使闭包函数,字符串,数组等

param(),get(),post(),session(),cookie(),server(),file() 如果不指定参数那就默认获取该域下全部的参数。

Request 自动注入的原理讲解(选读)

分析一个框架之前,最好是对框架的生命周期做个大概的了解,做到心中有数,这样在后面阅读框架源代码的时候会有很大的助益。

在了解 thinkphp5 的整个生命周期之后,结合本文的需求,初步定为 request 的注入是发生在框架生命周期的第八步:分发请求,接下来带着这个线索去查看源代码。

首先 public/index.php

require __DIR__ . '/../thinkphp/start.php';

追踪到 thinkphp/start.php

namespace think;

// ThinkPHP 引导文件
// 加载基础文件
require __DIR__ . '/base.php';
// 执行应用
App::run()->send();

thinkphp/library/think/App.php

baseFile(), PATHINFO_FILENAME);
                if ($name && 'index' != $name && is_dir(APP_PATH . $name)) {
                    Route::bind($name);
                }
            }

            $request->filter($config['default_filter']);

            // 默认语言
            Lang::range($config['default_lang']);
            if ($config['lang_switch_on']) {
                // 开启多语言机制 检测当前语言
                Lang::detect();
            }
            $request->langset(Lang::range());

            // 加载系统语言包
            Lang::load([
                THINK_PATH . 'lang' . DS . $request->langset() . EXT,
                APP_PATH . 'lang' . DS . $request->langset() . EXT,
            ]);

            // 获取应用调度信息
            $dispatch = self::$dispatch;
            if (empty($dispatch)) {
                // 进行URL路由检测
                $dispatch = self::routeCheck($request, $config);
            }
            // 记录当前调度信息
            $request->dispatch($dispatch);

            // 记录路由和请求信息
            if (self::$debug) {
                Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info');
                Log::record('[ HEADER ] ' . var_export($request->header(), true), 'info');
                Log::record('[ PARAM ] ' . var_export($request->param(), true), 'info');
            }

            // 监听app_begin
            Hook::listen('app_begin', $dispatch);
            // 请求缓存检查
            $request->cache($config['request_cache'], $config['request_cache_expire'], $config['request_cache_except']);

            $data = self::exec($dispatch, $config);
        } catch (HttpResponseException $exception) {
            $data = $exception->getResponse();
        }

        // 清空类的实例化
        Loader::clearInstance();

        // 输出数据到客户端
        if ($data instanceof Response) {
            $response = $data;
        } elseif (!is_null($data)) {
            // 默认自动识别响应输出类型
            $isAjax   = $request->isAjax();
            $type     = $isAjax ? Config::get('default_ajax_return') : Config::get('default_return_type');
            $response = Response::create($data, $type);
        } else {
            $response = Response::create();
        }

        // 监听app_end
        Hook::listen('app_end', $response);

        return $response;
    }

run() 方法是 thinkphp5 的核心方法,该方法完成了框架周期的第五步:应用初始化第十步:应用结束。而根据我们先前的判断,request 的自动注入发生在第八步:分发请求,那么我们想要的追踪的代码就在这一个方法里面完成咯。仔细阅读代码只有,锁定到下面的代码:

$data = self::exec($dispatch, $config);

那么锁定到 exec 方法:

    protected static function exec($dispatch, $config)
    {
        switch ($dispatch['type']) {
            case 'redirect':
                // 执行重定向跳转
                $data = Response::create($dispatch['url'], 'redirect')->code($dispatch['status']);
                break;
            case 'module':
                // 模块/控制器/操作
                $data = self::module($dispatch['module'], $config, isset($dispatch['convert']) ? $dispatch['convert'] : null);
                break;
            case 'controller':
                // 执行控制器操作
                $vars = array_merge(Request::instance()->param(), $dispatch['var']);
                $data = Loader::action($dispatch['controller'], $vars, $config['url_controller_layer'], $config['controller_suffix']);
                break;
            case 'method':
                // 执行回调方法
                $vars = array_merge(Request::instance()->param(), $dispatch['var']);
                $data = self::invokeMethod($dispatch['method'], $vars);
                break;
            case 'function':
                // 执行闭包
                $data = self::invokeFunction($dispatch['function']);
                break;
            case 'response':
                $data = $dispatch['response'];
                break;
            default:
                throw new \InvalidArgumentException('dispatch type not support');
        }
        return $data;
    }

代码的结构非常简单,其主要功能是负责路由地址调度,这里除了 case 'redirect'case 'response' 都需要我们关注,这里的话我么就选择 case controller 的情况进行下一步的分析,该情况下执行了:

Loader::action()

所以,视线转移到与 App.php 文件同目录下的 Loader.php 文件,找到 action 方法:

    /**
     * 远程调用模块的操作方法 参数格式 [模块/控制器/]操作
     * @param string       $url          调用地址
     * @param string|array $vars         调用参数 支持字符串和数组
     * @param string       $layer        要调用的控制层名称
     * @param bool         $appendSuffix 是否添加类名后缀
     * @return mixed
     */
    public static function action($url, $vars = [], $layer = 'controller', $appendSuffix = false)
    {
        $info   = pathinfo($url);
        $action = $info['basename'];
        $module = '.' != $info['dirname'] ? $info['dirname'] : Request::instance()->controller();
        $class  = self::controller($module, $layer, $appendSuffix);
        if ($class) {
            if (is_scalar($vars)) {
                if (strpos($vars, '=')) {
                    parse_str($vars, $vars);
                } else {
                    $vars = [$vars];
                }
            }
            return App::invokeMethod([$class, $action . Config::get('action_suffix')], $vars);
        }
    }

方法结尾执行了:App::invokeMethod() 方法,于是视线还是转移到了 App.php 文件,找到 invokeMethod() 方法:

    /**
     * 调用反射执行类的方法 支持参数绑定
     * @access public
     * @param string|array $method 方法
     * @param array        $vars   变量
     * @return mixed
     */
    public static function invokeMethod($method, $vars = [])
    {
        if (is_array($method)) {
            $class   = is_object($method[0]) ? $method[0] : self::invokeClass($method[0]);
            $reflect = new \ReflectionMethod($class, $method[1]);
        } else {
            // 静态方法
            $reflect = new \ReflectionMethod($method);
        }
        $args = self::bindParams($reflect, $vars);

        self::$debug && Log::record('[ RUN ] ' . $reflect->class . '->' . $reflect->name . '[ ' . $reflect->getFileName() . ' ]', 'info');
        return $reflect->invokeArgs(isset($class) ? $class : null, $args);
    }

不知道小伙伴有没有发现,正主出现啦。invokeArgs() 执行了 $class 类,$args 作为参数,而在此时执行类的话会执行什么类呢?肯定是 Controller 类啊,很明显的嘛,那参数干嘛用?肯定是 Action 的参数吗!!!我们一直研究 request 的参数绑定,既然这里执行了 controller 里面的 action,那么 request 肯定在这里 $args 中咯,于是我们锁定到这行代码:

$args = self::bindParams($reflect, $vars);

看来,request 是在这个方法中注入了 ^ - ^,于是锁定到 bindParams() 方法:

    /**
     * 绑定参数
     * @access private
     * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类
     * @param array                                 $vars    变量
     * @return array
     */
    private static function bindParams($reflect, $vars = [])
    {
        if (empty($vars)) {
            // 自动获取请求变量
            if (Config::get('url_param_type')) {
                $vars = Request::instance()->route();
            } else {
                $vars = Request::instance()->param();
            }
        }
        $args = [];
        if ($reflect->getNumberOfParameters() > 0) {
            // 判断数组类型 数字数组时按顺序绑定参数
            reset($vars);
            $type   = key($vars) === 0 ? 1 : 0;
            $params = $reflect->getParameters();
            foreach ($params as $param) {
                $args[] = self::getParamValue($param, $vars, $type);
            }
        }
        return $args;
    }

这里通过 getParameters 方法获取了当前 action 方法的参数列表,然后对这些参数做 self::getParamValue() 处理,所以我们锁定到 getParamValue() 方法中:

可能这里有的小伙伴看的一脸懵B,没有关系,这里的话需要一点 PHP 的反射基础,如果看不懂的话去看下文档就可以啦。这里就不做强调说明啦。

    /**
     * 获取参数值
     * @access private
     * @param \ReflectionParameter  $param
     * @param array                 $vars    变量
     * @param string                $type
     * @return array
     */
    private static function getParamValue($param, &$vars, $type)
    {
        $name  = $param->getName();
        $class = $param->getClass();
        if ($class) {
            $className = $class->getName();
            $bind      = Request::instance()->$name;
            if ($bind instanceof $className) {
                $result = $bind;
            } else {
                if (method_exists($className, 'invoke')) {
                    $method = new \ReflectionMethod($className, 'invoke');
                    if ($method->isPublic() && $method->isStatic()) {
                        return $className::invoke(Request::instance());
                    }
                }
                $result = method_exists($className, 'instance') ? $className::instance() : new $className;
            }
        } elseif (1 == $type && !empty($vars)) {
            $result = array_shift($vars);
        } elseif (0 == $type && isset($vars[$name])) {
            $result = $vars[$name];
        } elseif ($param->isDefaultValueAvailable()) {
            $result = $param->getDefaultValue();
        } else {
            throw new \InvalidArgumentException('method param miss:' . $name);
        }
        return $result;
    }

request 是类,所以会在 if ($class) { 条件下执行,该条件下执行步骤是这样的:

  • 第一步:分析当前 $className 是否已经绑定到 Request 实例上(可以忽略)
  • 第二步:检测当前 $className 是否存在invoke 方法,经过分析 Request 类,并没有 invoke 方法(可以忽略)
  • 第三步:调用 $classNameinstance 方法或直接 new,那就是这里咯,request 在这里执行了 instance 方法并返回对象作为参数咯。

源头分析到了,那就原路返回就可以啦,本次分析到此结束了。

好了今天的教程就到这里啦。此篇是小滕的《Thinkphp5入门系列课程》第十课:Request。
喜欢的给个订阅呗!
由于作者水平有限,如有错误请欢迎指正。

你可能感兴趣的:(《Thinkphp5入门系列课程》第十课:Request)