Thinkphp5视频教程
通过本文你可以学到:
-
Request
对象的使用 - 参数的获取
-
Request
自动注入的原理讲解(选读)
Request
对象
Request
是 thinkphp5
对于请求的做的一个统一的封装,我们基本上可以通过 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
方法(可以忽略) - 第三步:调用
$className
的instance
方法或直接new
,那就是这里咯,request
在这里执行了instance
方法并返回对象作为参数咯。
源头分析到了,那就原路返回就可以啦,本次分析到此结束了。
好了今天的教程就到这里啦。此篇是小滕的《Thinkphp5入门系列课程》第十课:Request。
喜欢的给个订阅呗!
由于作者水平有限,如有错误请欢迎指正。