1前言
在FastCGI协议下工作的php-fpm, 使用持续的进程来处理一连串的请求, 具体到某个请求的解析流程的时候, 如果不考虑扩展的方式, 基本上都是顺序的解析处理.
所以就算复杂如Laravel框架, 他也是一个顺序的加载解析过程.
本地打断点可以完整的走一遍框架的加载逻辑. 但是你可能并不一定十分理解为什么框架要这么做? 或者他这一步究竟是干嘛的?
本文就尽可能的提取Laravel处理http请求过程中一些关键的路径跟大家分享一下Laravel框架工作的原理, 也方便后面大家自己排错或者对于框架进行改造.
2预备知识
首先跟大家说的是后续在解析源码的时候比较核心的两个东西(某种意义上我觉得也是Laravel框架的骨架)——Container和Pipeline, 一个负责各种类实例的管理, 另一个负责流程的配置, 理解清楚了这两个东西, Laravel框架的主体逻辑就会很好理解了. (如果之前已经有了解过的就可以略过了哈~)
3大总管-Container
后面会介绍到的Application类就是继承自Container的一个框架运行过程中的实例的大总管.
大概的意思就类似如下的代码:
所以后面在看源码的时候凡是看到类似$app->make等都是在向大总管要一个对应的类示例.
4流水线-Pipeline
这里说的是Laravel作者taylor自己开发的Pipeline 不是thephpleague的Pipeline
但是这两个pipeline的库都是很好的抽象库——用来装配定义对于某个数据源的解析流程
我们看下laravel框架里的Pipeline的核心类Pipeline:可以看到该类实现了PipelineContract, 而该接口源码如下
那么这个接口主要描述的就是一个流水线需要实现的方法:
- send方法: 将$traveler摆上这个流水线
- through方法: 中间会经过几个车间$stops
- via方法: 经过每个车间的时候会被怎么处理$method
- then方法: 最终会被什么车间处理$destination
那么我们Pipeline类最终实现之后又有哪些调整呢?
- send方法: 变量替换为$passable
- through方法: 变量替换为$pipes
- via方法: 变量$method默认赋值为handle
- [重点!!!]then方法明确的定义Laravel框架里面的车间处理流程
5核心逻辑-then方法的实现
先看下源码:
第一行: 调用getInitialSlice传入$destination得到最终车间对于$passable的处理方法$firstSlice
第二行: 倒转中间的$pipes的顺序
第三行的代码分开解析:
[第三行]$this->getSlice()
这里得到的是一个车间的堆栈$stack以及当前车间$pipe对于$passable的一个处理逻辑:
如果$pipe是个闭包, 则直接调用传入($passable, $stack)
如果$pipe是个对象, 则向大总管初始化, 然后调用handle方法处理($passable, $stack)
[第三行]array_reduce($pipes, $this->getSlice(), $firstSlice)
核心的概要可以看如下的代码:
那么很明显这里我们就能得到一个已经装配好的流水线——$this->getSlice()不断的将最终的目的处理逻辑$firstSlice以及$pipes压入这个处理流程的堆栈里面, 最后得到这个按照顺序的对于$passable的处理流程.
[第三行]return call_user_func( array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable);
最终将$passable放到这个流水线上处理并返回处理的结果
理解清楚了Pipeline之后我们下面分析源代码就会快了很多, 因为核心的逻辑都是依赖Pipeline来装配的.
6源码分析
这里就顺序的逐层来带大家看下Laravel的http请求处理流程.
入口——public/index.php
对你没有看错, 其实Laravel处理http请求就这么”几行”代码:
$app = require_once __DIR__.'/../bootstrap/app.php';
这里引用这个文件得到一个Container的子类Application的示例,
同时app.php里面还声明了3个重要的抽象的实现
http请求的处理器Illuminate\Contracts\Http\Kernel::class实现为App\Http\Kernel::class
console(命令行)请求的处理器Illuminate\Contracts\Console\Kernel::class实现为App\Console\Kernel::class
异常Exception的处理器
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
之前只是配置, 这里是真正得到一个new好的实例
下面就是比较关键的代码了——处理器Kernel调用handle方法处理当前的请求$request得到\$response
我们从这个断点深入看下处理器的handle方法
全局处理请求的handle方法——Illuminate\Foundation\Http\Kernel
App\Http\Kernel没有handle方法, handle方法逻辑在他的父类Illuminate\Foundation\Http\Kernel里面
大概扫了一眼, 正常逻辑的话其实就是这句——$response = $this->sendRequestThroughRouter($request);//将请求放到Router上面过一遍
所以我们继续挖:
这里就看到我们之前说的Pipiline的使用了——将请求$request放在可能有的$middleware中间件车间上处理, 最终送到$this->dispatchToRouter()上面处理, 那么这个最终的目的车间不难想到应该就是当前请求匹配到的路由规则了, 所以我们继续深挖:
这里利用了本身的router示例来进一步分发处理$request, 所以我们再新开一节看下Router是怎么处理请求的
小结:
sendRequestThroughRouter中使用的中间件就是配置在App\Http\Kernel的全局中间件
然后我们看下文档中关于中间件的用法说明就很清楚了:
不难发现, handle方法其实就是一个处理$request数据的车间, 调用$next($request)之后就进入到了下一个车间, 所以中间件其实就是嵌套处理的逻辑, 通过划分不同的层次实现逻辑解耦.
匹配特定路由规则的dispatch——Illuminate\Routing\Router
Router主要的职责就是找出匹配当前$request规则的路由并执行然后返回
继续看正常流程逻辑:$response = $this->dispatchToRoute($request);
附:
在$route = $this->findRoute($request);有一个我觉得可以优化的逻辑, 大家有兴趣可以去看下
通过$route = $this->findRoute($request);我们得到了匹配当前请求的路由规则,
然后调用runRouteWithinStack来实际解析当前的请求
没有眼花!又一个Pipeline的处理逻辑, 这里的逻辑的就是:
真正的进入到了大家平时可能编写的Controller的特定方法里面去解析了
这里的middleware就是大家路由里面可能会配置的非全局的middleware了
收尾——回到public/index.php
正常的逻辑下就从这两个Pipeline顺利返回了$response
然后调用$response->send()将响应返回给客户端, 之后处理器也停止运行.
一次http请求顺利走完了整个Laravel框架
7总结
这就是我眼中Laravel的骨架——Container+PipeLine 以及 在这个骨架上搭建起来的一个处理http请求的流水线!
Laravel简单吗?——简单, 他把框架搭好, 只需要我们填充自己的车间进去
Laravel复杂吗?——复杂, Container后面的实现, PipeLine的沉淀, 都不是一下子凭空就能构造出来的.
通过Laravel框架的设计回过头来看业务开发其实也是一样的道理, 利用尽可能的高度简单的抽象来概括某个领域, 然后将灵活多变的需求限定在这一个个”车间”里面.