版本 8.x
入口
Laravel 应用程序的所有请求的入口点都是 public/index.php 文件。
所有请求都由 web 服务器(Apache/Nginx)配置定向到此文件。
该 index.php 文件将加载 Composer 的自动加载,然后从 bootstrap/app.php 中检索 Laravel 应用程序的实例。
Laravel 本身采取的第一个操作是创建应用 / 服务容器 的实例。
在初始化 Application (启动容器)时, Laravel 主要做了三件事情
1.注册基础绑定
2.注册基础服务提供者
3.注册容器核心别名
注册完成以后,我们就能直接从容器中获取需要的对象(如Illuminate\\Contracts\\Http\\Kernel),即使它是一个 Interface 。
<?php
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Http\Request;
define('LARAVEL_START', microtime(true)); //请求开始执行时间
//维护状态
if (file_exists(__DIR__.'/../storage/framework/maintenance.php')) {
require __DIR__.'/../storage/framework/maintenance.php';
}
//自动加载
require __DIR__.'/../vendor/autoload.php';
//第一步 创建app服务容器的实例,app.php 中,注册了Kernel等容器
$app = require_once __DIR__.'/../bootstrap/app.php';
//实力化Kernel
$kernel = $app->make(Kernel::class);
//请求传入kernel->输出响应
$response = tap($kernel->handle(
$request = Request::capture()
))->send();
//结束所有操作
$kernel->terminate($request, $response);
Kernel是如何处理请求的?
容器里绑定的是 App\Http\Kernel
, 继承于 Illuminate\Foundation\Http\Kernel
我们来看看 handle
方法
/**
* Handle an incoming HTTP request. 处理传入的HTTP请求
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function handle($request)
{
try {
$request->enableHttpMethodParameterOverride();
$response = $this->sendRequestThroughRouter($request);
} catch (Throwable $e) {
$this->reportException($e);
$response = $this->renderException($request, $e);
}
$this->app['events']->dispatch(
new RequestHandled($request, $response)
);
return $response;
}
enableHttpMethodParameterOverride
方法开启方法参数覆盖,即可以在 POST 请求中添加_method 参数来伪造 HTTP 方法(如post中添加_method=DELETE来构造 HTTP DELETE 请求)。
sendRequestThroughRouter
通过路由来发送请求,我们来看看这个方法。
/**
* Send the given request through the middleware / router.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
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());
}
在 sendRequestThroughRouter 当中,在 app 中绑定了 request 实例,并解绑掉其他 request 实例对象。这样在程序其他地方都能通过 app()->make(‘request’) 获取到 request 实例对象。
调用 bootstrap 方法,加载引导类。
创建一个 Pipeline 对象,将路由调度与中间件放入调用链当中。所有 request 先经过全局的中间件,然后在通过路由分发,dispatchToRouter
返回了一个匿名函数
如何进行路由匹配和映射到控制器方法?
Illuminate/Routing/Router.php
/**
* Dispatch the request to the application.
*
* @param \Illuminate\Http\Request $request
* @return \Symfony\Component\HttpFoundation\Response
*/
public function dispatch(Request $request)
{
$this->currentRequest = $request;
return $this->dispatchToRoute($request);
}
/**
* Dispatch the request to a route and return the response.
*
* @param \Illuminate\Http\Request $request
* @return \Symfony\Component\HttpFoundation\Response
*/
public function dispatchToRoute(Request $request)
{
return $this->runRoute($request, $this->findRoute($request));
}
通过 findRoute
找到匹配的路由,对路由的匹配,是通过 routes
这个路由 Collections
去匹配的。
先通过请求的方法获取当前方法下可用的路由集合,在从这些集合中去遍历获取第一个匹配的路由。集合中每个 item 是一个 Illuminate\Routing\Router 对象。因此最终判断路由与请求是否匹配调用的是 Illuminate\Routing\Router 中的 matches 方法。
/**
* Find the first route matching a given request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Routing\Route
*
* @throws \Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
public function match(Request $request)
{
$routes = $this->get($request->getMethod());
// First, we will see if we can find a matching route for this current request
// method. If we can, great, we can just return it so that it can be called
// by the consumer. Otherwise we will check for routes with another verb.
$route = $this->matchAgainstRoutes($routes, $request);
return $this->handleMatchedRoute($request, $route);
}
/**
* Determine if a route in the array matches the request.
*
* @param \Illuminate\Routing\Route[] $routes
* @param \Illuminate\Http\Request $request
* @param bool $includingMethod
* @return \Illuminate\Routing\Route|null
*/
protected function matchAgainstRoutes(array $routes, $request, $includingMethod = true)
{
[$fallbacks, $routes] = collect($routes)->partition(function ($route) {
return $route->isFallback;
});
return $routes->merge($fallbacks)->first(function (Route $route) use ($request, $includingMethod) {
return $route->matches($request, $includingMethod);
});
}
/**
* Handle the matched route.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Routing\Route|null $route
* @return \Illuminate\Routing\Route
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
protected function handleMatchedRoute(Request $request, $route)
{
if (! is_null($route)) {
return $route->bind($request);
}
// If no route was found we will now check if a matching route is specified by
// another HTTP verb. If it is we will need to throw a MethodNotAllowed and
// inform the user agent of which HTTP verb it should use for this route.
$others = $this->checkForAlternateVerbs($request);
if (count($others) > 0) {
return $this->getRouteForMethods($request, $others);
}
throw new NotFoundHttpException;
}
/**
* Get the route validators for the instance.
*
* @return array
*/
public static function getValidators()
{
if (isset(static::$validators)) {
return static::$validators;
}
// To match the route, we will use a chain of responsibility pattern with the
// validator implementations. We will spin through each one making sure it
// passes and then we will know if the route as a whole matches request.
return static::$validators = [
new UriValidator, new MethodValidator,
new SchemeValidator, new HostValidator,
];
}
在 Illuminate\Routing\Router 提供了四个默认的验证器,当四个验证器通过的时候才会匹配成功。四个验证器分别是 UriValidator 验证访问路径,MethodValidator 验证请求方法,SchemeValidator 验证访问协议,HostValidator 验证域名。其中对 uri 的验证内部是使用正则表达式验证。
步骤完成后执行路由映射到具体的控制器方法
/**
* Run the given route within a Stack "onion" instance.
*
* @param \Illuminate\Routing\Route $route
* @param \Illuminate\Http\Request $request
* @return mixed
*/
protected function runRouteWithinStack(Route $route, Request $request)
{
$shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
$this->container->make('middleware.disable') === true;
$middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run()
);
});
}
/**
* Run the route action and return the response.
*
* @return mixed
*/
public function run()
{
$this->container = $this->container ?: new Container;
try {
if ($this->isControllerAction()) {
return $this->runController();
}
return $this->runCallable();
} catch (HttpResponseException $e) {
return $e->getResponse();
}
}
/**
* Checks whether the route's action is a controller.
*
* @return bool
*/
protected function isControllerAction()
{
return is_string($this->action['uses']) && ! $this->isSerializedClosure();
}
通过当前路由的 action 配置判断是否是控制器或者回调方法。从代码中可以看到,其实就是我们路由配置中的第二个参数对应到 action[‘uses’]。当我们第二参数是一个字符串的时候则认为是控制器方法,将请求转发到控制器里去处理。
否则执行回调函数处理。
最后,一旦路由或控制器方法返回一个响应,该响应将通过路由的中间件返回,从而使应用程序有机会修改或检查传出的响应。
一旦响应通过中间件传回,HTTP 内核的 handle 方法将返回响应对象,并且 index.php 文件对返回的响应调用 send 方法。send 方法将响应内容发送到用户的 web 浏览器。
至此,我们已经完成了整个 Laravel 请求生命周期的所有步骤!