版本 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 请求生命周期的所有步骤!