一个请求如何跨过山和大海来到控制器的地盘。php
这块代码是在 Application
的构造函数中加载的web
public function __construct($basePath = null)
{
...
$this->registerBaseServiceProviders();
...
}
复制代码
protected function registerBaseServiceProviders()
{
...
$this->register(new RoutingServiceProvider($this));
...
}
复制代码
展开完整的服务提供者bootstrap
<?php
namespace Illuminate\Routing;
use Illuminate\Support\ServiceProvider;
use Psr\Http\Message\ResponseInterface;
use Zend\Diactoros\Response as PsrResponse;
use Psr\Http\Message\ServerRequestInterface;
use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
use Illuminate\Contracts\View\Factory as ViewFactoryContract;
use Illuminate\Contracts\Routing\ResponseFactory as ResponseFactoryContract;
use Illuminate\Routing\Contracts\ControllerDispatcher as ControllerDispatcherContract;
class RoutingServiceProvider extends ServiceProvider
{
public function register()
{
$this->registerRouter();
$this->registerUrlGenerator();
$this->registerRedirector();
$this->registerPsrRequest();
$this->registerPsrResponse();
$this->registerResponseFactory();
$this->registerControllerDispatcher();
}
protected function registerRouter()
{
$this->app->singleton('router', function ($app) {
return new Router($app['events'], $app);
});
}
protected function registerUrlGenerator()
{
$this->app->singleton('url', function ($app) {
$routes = $app['router']->getRoutes();
$app->instance('routes', $routes);
$url = new UrlGenerator(
$routes, $app->rebinding(
'request', $this->requestRebinder()
), $app['config']['app.asset_url']
);
$url->setSessionResolver(function () {
return $this->app['session'];
});
$url->setKeyResolver(function () {
return $this->app->make('config')->get('app.key');
});
$app->rebinding('routes', function ($app, $routes) {
$app['url']->setRoutes($routes);
});
return $url;
});
}
protected function requestRebinder()
{
return function ($app, $request) {
$app['url']->setRequest($request);
};
}
protected function registerRedirector()
{
$this->app->singleton('redirect', function ($app) {
$redirector = new Redirector($app['url']);
if (isset($app['session.store'])) {
$redirector->setSession($app['session.store']);
}
return $redirector;
});
}
protected function registerPsrRequest()
{
$this->app->bind(ServerRequestInterface::class, function ($app) {
return (new DiactorosFactory)->createRequest($app->make('request'));
});
}
protected function registerPsrResponse()
{
$this->app->bind(ResponseInterface::class, function () {
return new PsrResponse;
});
}
protected function registerResponseFactory()
{
$this->app->singleton(ResponseFactoryContract::class, function ($app) {
return new ResponseFactory($app[ViewFactoryContract::class], $app['redirect']);
});
}
protected function registerControllerDispatcher()
{
$this->app->singleton(ControllerDispatcherContract::class, function ($app) {
return new ControllerDispatcher($app);
});
}
}
复制代码
后面在使用中会涉及这里注册的对象,红框内就是注册的绑定关系。api
启动在这里并无完成,这仅仅是启动系统的基础路由,在 app.php
中还有一个路由服务提供者 RouteServiceProvider
数组
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
class RouteServiceProvider extends ServiceProvider
{
protected $namespace = 'App\Http\Controllers';
public function boot()
{
// "boot() 方法是在服务提供者全部 register() 方法执行完成以后在统一执行的"
// "这段代码最后会调用 $this->map();"
parent::boot();
}
public function map()
{
$this->mapApiRoutes();
$this->mapWebRoutes();
}
// "这一块的逻辑很是复杂就不展开了,主要功能就是优先加载 cache/routes.php,若是不存在 则从给定的路径加载路由文件"
protected function mapWebRoutes()
{
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
}
protected function mapApiRoutes()
{
Route::prefix('api')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
}
}
复制代码
注册完成以后就是开始处理,是从内核的 handle()
方法开始处理请求缓存
protected function sendRequestThroughRouter($request)
{
...
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
复制代码
这段代码在 【Laravel-海贼王系列】第七章,Pipeline 类解析 解析过了 不了解执行逻辑请先看上一篇哦~bash
这里会在运行完中间件以后最后运行 $this->dispatchToRouter()
这个方法。session
$this->router
对象是在内核的构造函数注入的\Illuminate\Routing\Router
对象闭包
protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);
return $this->router->dispatch($request);
};
}
复制代码
那么咱们接着看 dispatch
方法app
public function dispatch(Request $request)
{
$this->currentRequest = $request;
return $this->dispatchToRoute($request);
}
复制代码
转发一个请求给路由返回一个响应对象
public function dispatchToRoute(Request $request)
{
return $this->runRoute($request, $this->findRoute($request));
}
复制代码
个人理解:router
表明路由器,route
则是表明一次路由的对象,
全部路由器的功能就是执行,派发路由对象。因此咱们须要先经过请求来拿到一个路由对象
protected function findRoute($request)
{
$this->current = $route = $this->routes->match($request);
// "绑定最新的 $route 对象到容器"
$this->container->instance(Route::class, $route);
// "返回路由"
return $route;
}
复制代码
继续分析 $this->routes->match($request);
,
这里的 $this->routes
是构造函数注入的 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;
}
复制代码
$routes
对象这里面的值来自与路由缓存文件或者路由文件解析结果
继续看 $route = $this->matchAgainstRoutes($routes, $request);
执行结果从请求中匹配对应路由并返回
若是没有匹配的路由则使用请求方法之外的方法继续匹配
public static $verbs = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'];
protected function checkForAlternateVerbs($request)
{
$methods = array_diff(Router::$verbs, [$request->getMethod()]);
$others = [];
foreach ($methods as $method) {
if (! is_null($this->matchAgainstRoutes($this->get($method), $request, false))) {
$others[] = $method;
}
}
return $others;
}
复制代码
执行完成返回 $other
数组,若是仍是没有则抛出throw new NotFoundHttpException;
这里不详细叙述了,若是匹配成功咱们将获得一个 Illuminate\Routing\Route
对象传递下去。
当咱们获得路由对象以后就是派发它了,根据给定的路由返回响应对象
protected function runRoute(Request $request, Route $route)
{
// "将这个闭包设置到 request 对象的 $this->routeResolver 成员上"
$request->setRouteResolver(function () use ($route) {
return $route;
});
// "执行路由匹配的事件,框架刚启动的时候这里什么都不作"
$this->events->dispatch(new Events\RouteMatched($route, $request));
return $this->prepareResponse($request,
$this->runRouteWithinStack($route, $request)
);
}
复制代码
执行到这里就已经到了最后的部分了
return $this->prepareResponse($request,
$this->runRouteWithinStack($route, $request)
);
复制代码
这个方法就是将 $request
和 $response
根据里面的属性封装好数据返回而已。
public function prepareResponse($request, $response)
{
return static::toResponse($request, $response);
}
复制代码
重点看 $this->runRouteWithinStack($route, $request)
这段话才是将请求传递到控制器关键!
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()
);
});
}
复制代码
又到了这种用法,不理解执行逻辑请看第七章,
根据 Pipeline
的使用原理,咱们在经过全部 $middleware
以后会将 $requeset
传递给闭包来结束
因此这是终点!
function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run()
);
}
复制代码
刚才说过了 $this->prepareResponse()
这个方法没什么亮点就是
将请求和响应对象封装返回,全部咱们应该知道了,$route->run() 将返回 response
对象!
来吧,经历了无数使人发指的封装但愿后面一片坦途,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();
}
}
复制代码
总算看到了 runController()
方法了,想必路由跨过山和大海最总的归宿也到这儿了
$this->isControllerAction()
是判断路由是闭包仍是字符串
若是是字符串向上图红框中的内容则执行
protected function runController()
{
return $this->controllerDispatcher()->dispatch(
$this, $this->getController(), $this->getControllerMethod()
);
}
复制代码
这里是调用 Illuminate\Routing\ControllerDispatcher
的 dispatch
方法
public function dispatch(Route $route, $controller, $method)
{
// "从容器获取当前类构造函数依赖和方法依赖参数"
$parameters = $this->resolveClassMethodDependencies(
$route->parametersWithoutNulls(), $controller, $method
);
if (method_exists($controller, 'callAction')) {
return $controller->callAction($method, $parameters);
}
return $controller->{$method}(...array_values($parameters));
}
复制代码
callAction
来自全部控制器基础的 Illuminate\Routing\Controller
public function callAction($method, $parameters)
{
return call_user_func_array([$this, $method], $parameters);
}
复制代码
没什么好讲的其实就是调用控制器对应的方法。
若是路由是闭包形式,则直接抽取路由对象中的闭包进行调用
protected function runCallable()
{
$callable = $this->action['uses'];
// "经过容器抽取依赖的参数传入闭包运行"
return $callable(...array_values($this->resolveMethodDependencies(
$this->parametersWithoutNulls(), new ReflectionFunction($this->action['uses'])
)));
}
复制代码
总算结束了,Laravel
路由在启动阶段注册了很是多的类,
1.Application
构造阶段 $this->register(new RoutingServiceProvider($this));
2.Kernel handle() bootstrap()
阶段加载服务提供者的时候包含了 App\Providers\RouteServiceProvider::class,
这两个阶段注册加上加载的逻辑是很是复杂,可是目的也很简单从就是从路由文件转成路由对象的过程,没有力气分析进去。
其余的就是最后一直调用到控制器的过程,其中最后的 resolveClassMethodDependencies
, resolveMethodDependencies
也是很是值得研究的代码。