路由

一个请求如何跨过山和大海来到控制器的地盘。

注册路由

这块代码是在 Application 的构造函数中加载的

public function __construct($basePath = null)
{...$this->registerBaseServiceProviders();...
}
复制代码
protected function registerBaseServiceProviders()
{...$this->register(new RoutingServiceProvider($this));...
}
复制代码

展开完整的服务提供者

<?phpnamespace 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);});}
}复制代码

后面在使用中会涉及这里注册的对象,红框内就是注册的绑定关系。

启动在这里并没有完成,这仅仅是启动系统的基础路由,在 app.php 中还有一个路由服务提供者 RouteServiceProvider

<?phpnamespace 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 类解析 解析过了 不了解执行逻辑请先看上一篇哦~

这里会在运行完中间件之后最后运行 $this->dispatchToRouter() 这个方法。

$this->router 对象是在内核的构造函数注入的 \Illuminate\Routing\Router 对象

protected function dispatchToRouter(){return function ($request) {$this->app->instance('request', $request);return $this->router->dispatch($request);};}
复制代码

那么我们接着看 dispatch 方法

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\ControllerDispatcherdispatch 方法

 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,

这两个阶段注册加上加载的逻辑是非常复杂,但是目的也很简单从就是从路由文件转成路由对象的过程,没有力气分析进去。

其他的就是最后一直调用到控制器的过程,其中最后的 resolveClassMethodDependenciesresolveMethodDependencies

也是非常值得研究的代码。

【Laravel-海贼王系列】第十三章,路由控制器解析相关推荐

  1. CCNP精粹系列之十三-----OSPF路由汇总

    一.路由器中OSPF的汇总 1)各个路由器的配置 R1 R1(config)#int lo0 R1(config-if)#ip addr 172.16.8.1 255.255.255.0 R1(con ...

  2. WP8.1学习系列(第二十三章)——到控件的数据绑定

    在本文中 先决条件 将控件绑定到单个项目 将控件绑定到对象的集合 通过使用数据模板显示控件中的项目 添加详细信息视图 转换数据以在控件中显示 相关主题 本主题介绍了如何在使用 C++.C# 或 Vis ...

  3. HTML5 2D游戏引擎研发系列 第五章

    HTML5 2D游戏引擎研发系列 第五章 <Canvas技术篇-画布技术-纹理集复杂动画> 作者:HTML5游戏开发者社区-白泽 转载请注明出处:http://html5gamedev.o ...

  4. HTML5 2D游戏引擎研发系列 第四章 Canvas技术篇-画布技术-基于手动切片动画

    作者:HTML5游戏开发者社区-白泽 转载请注明出处:http://html5gamedev.or HTML5 2D游戏引擎研发系列 第四章 <Canvas技术篇-画布技术-基于手动切片动画&g ...

  5. WEBGL 2D游戏引擎研发系列 第三章 正交视口

    WEBGL 2D游戏引擎研发系列 第三章 <正交视口> 作者:HTML5游戏开发者社区-白泽 转载请注明出处:http://html5gamedev.org/ 目录 HTML5 2D游戏引 ...

  6. WEBGL 2D游戏引擎研发系列 第四章 感想以及矩阵

    WEBGL 2D游戏引擎研发系列 第四章 <感想以及矩阵> HTML5游戏开发者社区(群号:326492427) 转载请注明出处:http://html5gamedev.org/ HTML ...

  7. 扩展中断控制器8259实验_「正点原子FPGA连载」第十三章双核AMP实验

    1)摘自[正点原子]领航者 ZYNQ 之嵌入式开发指南 2)实验平台:正点原子领航者ZYNQ开发板 3)平台购买地址:https://item.taobao.com/item.htm?&id= ...

  8. 《操作系统真象还原》第十三章 ---- 编写硬盘驱动软件 行百里者半九十终成时喜悦溢于言表

    文章目录 专栏博客链接 相关查阅博客链接 本书中错误勘误 部分缩写熟知 闲聊时刻 提前需要准备编写的函数 实现printk 实现sprintf函数 创建从盘 创建从盘的步骤 修改后的bochsrc.d ...

  9. 【正点原子MP157连载】第二十三章 Linux设备树-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7

    1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频 ...

最新文章

  1. OpenGL 地形LOD的镶嵌细分的用法
  2. iphone NSNotificationCenter
  3. 【面向对象】面向对象程序设计测试题14-Java文件测试题
  4. hadoop单节点配置并且统计单词
  5. tensorflow实战学习笔记(1)
  6. 【转载】spring.net 学习系列目录
  7. 计算机系统集成 行业标准,行业标准信息服务平台
  8. 手机QQ Hybrid 的架构演进
  9. firebug的调试,console
  10. R语言自然语言处理:关键词提取与文本摘要(TextRank)
  11. spring-第十八篇之spring AOP基于XML配置文件的管理方式
  12. PFQ: a Linux kernel module for packet capturing on multi-core architectures
  13. vb读取mysql数据库数据_VB读取ORACLE数据库的两种方法
  14. vs2013编译ffmpeg之三十五 xavs、xvidcore
  15. 卸载oracle10g教程,卸载Oracle10g步骤
  16. mac如何打开/bin等目录
  17. 3dmax常见的八十个问题汇总
  18. 谭浩强《C程序设计》书后习题 第八章
  19. 从实例来看DAO:权力分散的伟大尝试
  20. 电风扇维修 记录-成功复原3台电风扇

热门文章

  1. python全栈开发 * 31知识点汇总 * 180716
  2. 讨论MySQL丢失数据的几种情况
  3. 深入研究Servlet线程安全性问题
  4. LVS+Heartbeat+Ipvsadm+Ldirectord安装(四)
  5. 不服来战!PHP 是世界上最好的语言!
  6. 前端三大技术 HTML、CSS、JavaScript 快速入门手册
  7. android小闹钟课程设计,《小闹钟》教学设计
  8. SpringSecurity 权限控制准备之IOC容器结构说明
  9. Nginx解决跨域问题的具体实现
  10. ArrayBlockingQueue原理分析-put方法