- laravel版本5.8
入口
当你想要弄清楚任何一件事物的内部构造时,一定需要先找到入口,因此第一件要做的事情就是找到入口文件 public/index.php
<?php
define('LARAVEL_START', microtime(true));
require __DIR__.'/../vendor/autoload.php';
$app = require_once __DIR__.'/../bootstrap/app.php';
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
$response->send();
$kernel->terminate($request, $response);
精简一下,只有9行代码,非常的简洁,包括<?php在内只有10行代码
- 定义常量 表示laravel运行的开始时间
- 包含composer的自动加载 曾经有psr0 和psr4规范 但是在我写下这个文档的时间(2019-06-04)已经废弃psr0很久了
- 加载laravel引导 (把laravel想象成一个操作系统 此处的引导就是操作系统的那512M字节的数据),并得到laravel的应用变量
- 使用$app变量,制作一个Http内核$kernel (没错 就是操作系统的那个内核)
- 使用内核($kernel)处理 $request 并得到一个$response
- 发送$response
- $kernel内核终止请求与响应
至此一次完整的laravel的生命周期就结束了,同时思考一下http协议,一个完整的HTTP事务应该是这个样子
客户端发送请求->服务器处理请求并响应->客户端收到响应
对于一个http请求来说,只有请求和响应两个过程,而laravel的入口恰好完美的诠释了这个神圣而又复杂的过程.
写代码虽然很简单,但是能把代码写的像散文一样一定是非常困难的,而这个入口文件就让人心旷心神
引导
$app = require_once DIR.'/../bootstrap/app.php';
<?php
$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
return $app;
大多数的其他框架都倾向于把引导放在入口里,但是laravel是单独放了一个文件,并且这个文件也非常的简洁
以代码量计算只有17行,以功能计算只有5个
- 根据路径new出一个Application类
- 绑定一个Http内核单例
- 绑定一个Console内核单例
- 绑定一个异常处理类单例
- 返回$app实例
不需要知道Application内部做了什么 也不需要知道singleton内部做了什么,仅仅只阅读这几行代码,就能窥豹一斑
laravel的app实例仅仅只有三个功能
- http请求处理 由http内核提供
- 控制台命令处理 由Console内核提供
- app异常处理 由Exceptions\Handler提供
最后返回一个app实例,这个app实例是继承于Container类的,这里运用反射技术来提供了一个根据类名生成类实例的功能,具体实现可以查看laravel/framework/src/illuminate/Container/Container.php
的第790行代码
Container容器的具体用法在后面的篇章中会详细的进行解释,此处不多做介绍
http 内核
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
使用$app实例制作一个实现了Http/Kernel契约的内核实例
还记得引导文件中的这一行代码么
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
没错,这个地方返回的内核就是绑定的App\Http\Kernel::class
实例,并且仅初始化了基本的中间件而已
这里make的是一个imterface,因此只要你的Kernel实现了
Illuminate\Contracts\Http\Kernel::class
这个接口,那么在上面的文件中你完全可以自定义属于自己的内核,虽然没有什么必要
处理请求
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
先看这一行代码 ,捕获http请求
$request = Illuminate\Http\Request::capture()
暂时不深入剖析源代码,根据文档中的描述来看以及经验来看,都应该想到这里的request就是
$_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER 中携带的信息,虽然会做一些处理,但是一个基本的http请求中都必须包含这些信息,这是Http协议决定了
接着是内核处理这个请求
$response = $kernel->handle($request);
这个地方就勉为其难的看一下源代码好了,目光定位到Illuminate\Foundation\Http\Kernel
的111行
顺便我们再精简一下,把异常处理去掉,得到代码如下
public function handle($request)
{
$request->enableHttpMethodParameterOverride();
$response = $this->sendRequestThroughRouter($request);
$this->app['events']->dispatch(
new Events\RequestHandled($request, $response)
);
return $response;
}
做了三件事情
- 开启http 方法重写 (这个地方其实仅仅是为了支持 传统表单页面中 用
_method=put
来代替PUT请求这样的功能的)
具体代码可以查看
Symfony\Component\HttpFoundation\Request
1229行 - sendRequestThroughRouter发送请求到路由器并得到响应$response
-
分发事件处理
发送请求到路由器
这里是发送请求到路由器 那么接下来肯定是路由分发了
目光定位到Illuminate\Foundation\Http\Kernel
149行代码
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
$this->bootstrap();
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
只需要看最后一行代码就可以了,这里有两个关键词
- Pipeline 管道
- dispatchToRouter 路由分发
记得最开始make内核的时候,在App\Http\Kernel
文件里有许多的中间件数组配置,那么这里的Pipeline做的事情只有一件,让这些左右的中间件变成一个队列一样,这些请求依次穿过(through)这些中间件, 注意这里的用词是through 穿过,然后then 路由分发dispatchToRouter , 读起来很有感觉
dispatchToRouter路由分发就更简单了,在laravel中所有的路由都是预先配置好的,通过request请求中的path找到配置好的route,如果命中则交给route中定义的控制器或者闭包来进行处理,如果没有命中,那么则抛出异常
当然 抛出异常这个过程是在上一步处理请求中的 handle中执行的,因为我精简掉了异常处理代码 所以看不到
最后route的dispatch过程,可以把代码定位到Illuminate\Routing\Roouter
730行
经过了一些列的操作,甚至包括中间件
在laravel中 请求和响应是都要经过中间件的 代码分别在
请求:
Illuminate\Foundation\Http\Kernel
149行代码响应:
Illuminate\Routing\Roouter
656行
public static function toResponse($request, $response)
{
if ($response instanceof Responsable) {
$response = $response->toResponse($request);
}
if ($response instanceof PsrResponseInterface) {
$response = (new HttpFoundationFactory)->createResponse($response);
} elseif ($response instanceof Model && $response->wasRecentlyCreated) {
$response = new JsonResponse($response, 201);
} elseif (! $response instanceof SymfonyResponse &&
($response instanceof Arrayable ||
$response instanceof Jsonable ||
$response instanceof ArrayObject ||
$response instanceof JsonSerializable ||
is_array($response))) {
$response = new JsonResponse($response);
} elseif (! $response instanceof SymfonyResponse) {
$response = new Response($response);
}
if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) {
$response->setNotModified();
}
return $response->prepare($request);
}
这里最后一定会得到一个Illuminate\Http\Response
的实例
发送响应
$response->send();
根据上面所有的流程,我们最终得出, 返回了一个Response实例,那么接下来我们一起看看laravel 是怎么把这个发送这个response的
注意看这里 这个响应是自己发送的 并不是内核发送的 内核仅仅是根据请求 并且通过了管道内的中间件 最后得到了一个Response 实例
目光定位到Symfony\Component\HttpFoundation
的 373行代码
精简一下变成这样
public function send()
{
$this->sendHeaders();
$this->sendContent();
}
- $this->sendHeaders(); 这行代码输出头信息
Symfony\Component\HttpFoundation
330行代码
header($name.': '.$value, $replace, $this->statusCode);
header('Set-Cookie: '.$cookie->getName().strstr($cookie, '='), false, $this->statusCode);
header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);
这个地方仅仅只是利用header 输出了一些头部信息
- $this->sendContent(); 这行代码输出文本
public function sendContent()
{
echo $this->content;
return $this;
}
和预想中的一样,这个地方仅仅是echo了一些东西而已 没有特殊的地方
通过代码分析 我们可以知道 $response->send();只做了两件事情 输出头信息以及内容, 和输出所有的网页一样,头部和内容
内核终止请求与响应
$kernel->terminate($request, $response);
目光定位到Illuminate\Foundation\Application
中的1006行代码
public function terminate()
{
foreach ($this->terminatingCallbacks as $terminating) {
$this->call($terminating);
}
}
这里的代码很简单,在响应成功之后,执行一些回调函数
总结
抛开所有的类库 框架 mvc等等各种各样的概念来看,laravel其实是一个非常简单的东西,当然http协议也是一样
创建应用实例->接受请求->得到响应->发送响应(header 一些东西 echo 一些东西)->结束