老王,你給我說說 Laravel 的請求是怎麼到達控制器的

寫PHP的老王發表於2019-09-18

老王啊,你總在說Laravel的東西,能不能給我大概簡單的說一下一個請求是怎麼樣到達我寫在控制的程式碼中去的。中間都經歷了哪些東西啊。

入口

Laravel5.8 入口檔案為public/index.php


$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

$response = $kernel->handle(

    $request = Illuminate\Http\Request::capture()

);

$response->send();

$kernel->terminate($request, $response);

建立了一個Kernel物件,呼叫handler處理請求,獲取返回結果。將返回結果輸出到客戶端,處理terminate操作。

Kernel中如何處理請求

容器裡繫結的是App\Http\Kernel,繼承於Illuminate\Foundation\Http\Kernel


    public function handle($request)

    {

        try {

            $request->enableHttpMethodParameterOverride();

            $response = $this->sendRequestThroughRouter($request);

        } catch (Exception $e) {

            $this->reportException($e);

            $response = $this->renderException($request, $e);

        } catch (Throwable $e) {

            $this->reportException($e = new FatalThrowableError($e));

            $response = $this->renderException($request, $e);

        }

        $this->app['events']->dispatch(

            new Events\RequestHandled($request, $response)

        );

        return $response;

    }

Kernel中呼叫sendRequestThroughRouter方法,將請求傳遞到路由處理當中。


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先經過全域性的中介軟體,然後在透過路由分發。


    protected function dispatchToRouter()

    {

        return function ($request) {

            $this->app->instance('request', $request);

            return $this->router->dispatch($request);

        };

    }

因為Piepline呼叫鏈都是一個個的回撥方法,所以在dispatchToRouter返回了一個匿名回撥函式。使用Kernelroute屬性進行排程。

Kernelroute是一個Illuminate\Routing\Router物件。

路由排程


//Illuminate\Routing\Router

public function dispatch(Request $request)

{

    $this->currentRequest = $request;

    return $this->dispatchToRoute($request);

}

public function dispatchToRoute(Request $request)

{

    return $this->runRoute($request, $this->findRoute($request));

}

protected function runRoute(Request $request, Route $route)

{

    $request->setRouteResolver(function () use ($route) {

        return $route;

    });

    $this->events->dispatch(new Events\RouteMatched($route, $request));

    return $this->prepareResponse($request,

        $this->runRouteWithinStack($route, $request)

    );

}

從上面的方法可以看出,最終透過findRoute查詢當前匹配的路由物件,並呼叫runRoute處理請求返回結果。

怎麼找到路由的


//Illuminate\Routing\Router

protected function findRoute($request)

{

    $this->current = $route = $this->routes->match($request);

    $this->container->instance(Route::class, $route);

    return $route;

}

對路由的匹配,是透過routes這個路由Collections去匹配的。


//Illuminate\Routing\RouteCollection

    public function match(Request $request)

    {

        $routes = $this->get($request->getMethod());

        $route = $this->matchAgainstRoutes($routes, $request);

        if (! is_null($route)) {

            return $route->bind($request);

        }

        $others = $this->checkForAlternateVerbs($request);

        if (count($others) > 0) {

            return $this->getRouteForMethods($request, $others);

        }

        throw new NotFoundHttpException;

    }

    protected function matchAgainstRoutes(array $routes, $request, $includingMethod = true)

    {

        [$fallbacks, $routes] = collect($routes)->partition(function ($route) {

            return $route->isFallback;

        });

        return $routes->merge($fallbacks)->first(function ($value) use ($request, $includingMethod) {

            return $value->matches($request, $includingMethod);

        });

    }

先透過請求的方法獲取當前方法下可用的路由集合,在從這些集合中去遍歷獲取第一個匹配的路由。集合中每個item是一個Illuminate\Routing\Router物件。因此最終判斷路由與請求是否匹配呼叫的是Illuminate\Routing\Router中的matches方法。


//Illuminate\Routing\Router

public function matches(Request $request, $includingMethod = true)

{

    $this->compileRoute();

    foreach ($this->getValidators() as $validator) {

        if (! $includingMethod && $validator instanceof MethodValidator) {

            continue;

        }

        if (! $validator->matches($this, $request)) {

            return false;

        }

    }

    return true;

}

public static function getValidators()

{

    if (isset(static::$validators)) {

        return static::$validators;

    }

    return static::$validators = [

        new UriValidator, new MethodValidator,

        new SchemeValidator, new HostValidator,

    ];

}

Illuminate\Routing\Router提供了四個預設的驗證器,當四個驗證器透過的時候才會匹配成功。四個驗證器分別是UriValidator驗證訪問路徑,MethodValidator驗證請求方法,SchemeValidator驗證訪問協議,HostValidator驗證域名。其中對uri的驗證內部是使用正規表示式驗證。

路由排程怎麼處理請求


//Illuminate\Routing\Router

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()

                        );

                    });

}

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();

    }

}

路由對請求的處理也是返回一個Pipeline,先將請求透過中介軟體,然後在執行路由的run方法。在run方法裡面判斷當前是執行控制器方法還是回撥方法,根據不同型別分開執行。

怎麼執行


protected function isControllerAction()

{

    return is_string($this->action['uses']);

}

protected function runCallable()

{

    $callable = $this->action['uses'];

    return $callable(...array_values($this->resolveMethodDependencies(

        $this->parametersWithoutNulls(), new ReflectionFunction($this->action['uses'])

    )));

}

protected function runController()

{

    return $this->controllerDispatcher()->dispatch(

        $this, $this->getController(), $this->getControllerMethod()

    );

}

透過當前路由的action配置判斷是否是控制器或者回撥方法。從程式碼中可以看到,其實就是我們路由配置中的第二個引數對應到action['user']。當我們第二引數是一個字串的時候則認為是控制器方法,將請求轉發到控制器裡去處理。否則執行回撥函式處理。

到這裡,我們的請求就真的到達了我們的控制器的方法中,開始執行我們寫的程式碼了。

本作品採用《CC 協議》,轉載必須註明作者和本文連結
寫PHP的老王

相關文章