首先抛出我的疑问:
- Laravel的底层是如何处理HTTP请求的?
- Laravel的Request是如何实现的?
- 为什么不需要配置Nginx的url解析,也不需要在Laravel的router中配置参数名称,却可以通过Request接收到参数呢?实现原理是什么?
下面开始进入查源码之旅:
- 首先调研了一下Laravel的request是基于什么实现的?
- 知识点如下:Laravel的很多底层组件是基于Symfony实现的,比如:请求、响应、cookie、命令行,文件等。其中HttpFoundation组件是http请求中比较重要的基础组件,它可以独立于Symfony使用的,Laravel基于此做了进一步的封装,使用示例如下
$request->input('param'); $request->cookie('cookie'); $request->file('file');
- 考虑到Laravel项目所有的HTTP请求都会转发到Laravel的入口文件(public/index.php),所以我们查看这个文件一探究竟:
//创建Application实例,作为服务容器 $app = new Illuminate\Foundation\Application( realpath(__DIR__.'/../') ); //以单例方式在服务容器中将 App\Http\Kernel 实例绑定到 Illuminate\Contracts\Http\Kernel 接口 $app->singleton( Illuminate\Contracts\Http\Kernel::class, App\Http\Kernel::class ); //服务容器创建处理 HTTP 请求的内核实例(Kernel) $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); //核心方法:处理 HTTP 请求的核心代码(核心代码会在下一部分展开来讲,先从上而下理清流程) $response = $kernel->handle( $request = Illuminate\Http\Request::capture() ); //发送响应给发送请求的客户端 $response->send(); //内核实例终止程序,基于传入的request和response做一些收尾工作 $kernel->terminate($request, $response);
- 上面的注释已经比较清楚的解释了Laravel处理HTTP请求的内部实现流程。
- 我们再继续深入核心代码,研究一下“Laravel为什么在路由中无需指定参数是什么,在控制器方法中传入Request就可以获得参数了呢?”
比如:
//路由 Route::resource('/index', 'IndexController'); //controller中的方法 function index(Request $request) { switch ($request->type) { . . . } }
- 下面的非核心代码用竖着的3个.省略,避免误导
//laravel项目的入口文件 index.php . . . //重点1:handle()方法 $response = $kernel->handle( $request = Illuminate\Http\Request::capture() ); . . . //深入源码1 capture() public static function capture() { static::enableHttpMethodParameterOverride(); //重点2:createFromBase() 方法 return static::createFromBase(SymfonyRequest::createFromGlobals()); } //我们再深入源码2 createFromBase() public static function createFromBase(SymfonyRequest $request) { if ($request instanceof static) { return $request; } $content = $request->content; //这是解决疑问的关键代码 duplicate 意为复制一份 $request = (new static)->duplicate( $request->query->all(), $request->request->all(), $request->attributes->all(), $request->cookies->all(), $request->files->all(), $request->server->all() ); $request->content = $content; $request->request = $request->getInputSource(); return $request; }
- 发现了一个神奇的方法:duplicate(),意为复制一份。
- duplicate()方法的参数,包括request−>query−>all(),request->query->all(), request−>query−>all(),request->request->all()....等,即底层已经实现了所有query和request的接收,我们不需要单独声明也是可以接收到所有请求参数的。
$request = (new static)->duplicate( $request->query->all(), $request->request->all(), $request->attributes->all(), $request->cookies->all(), $request->files->all(), $request->server->all() );
- 上面就是我查询源码的流程:
- 明确自己的问题(Request的底层实现)
- 明确自己排查问题的边界(从入口文件入手)
- 一层一层的深入查询源码(源码1->源码2-->源码3)
- 排除非核心代码,找到最核心代码(duplicate()方法)
9.下面是另外一位博主的思考总结,个人觉得总结的更系统和专业,Laravel底层如何处理HTTP请求,而我的总结更体现出我带着问题查看源码的思路、流程、反思、总结。
10.还有一个小插曲: 源码这么写的原因是什么?不是重复操作吗?
11.不认真呀,仔细看一下,2中的request是通过 (new static)->duplicate()克隆的新对象,和1中的request并不是相同的实例。
文章结尾,放两个小题供大家消遣一下。
- 请设计一个函数,实现以下功能:将字符串"wang_zhong_yang"转换成"WangZhongYang";
1.1 我的解题思路:
public function actionCreate() { $str1 = "wang_zhong_yang"; $arr1 = explode("_",$str1); foreach ($arr1 as $value){ $str2 .=ucfirst($value); } var_dump($str2); }
- 请再设计一个函数,将字符串"wang_zhong_yang"转换成"wAngzHongyAng";
2.2 我的解题思路:
public function actionCreate() { $str1 = "wang_zhong_yang"; $arr1 = explode("_",$str1); foreach ($arr1 as $value){ //拆分 $first = substr($value,0,1); $sec = substr($value,1,1); $sec = strtoupper($sec); $third = substr($value,2,strlen($value)); $final = $first . $sec.$third; $str2 .= $final; } var_dump($str2); }
我上面2道题的解法思路肯定不是最优解,欢迎大家不吝赐教。