illuminate/routing 源码分析之注册路由

451 阅读3分钟

我们知道,在 Laravel 世界里,外界传进来一个 Request 时,会被 Kernel 处理并返回给外界一个 Response。Kernel 在处理 Request 时,会调用 illuminate/routing 包提供的路由功能,来根据当前的 Request,转发到对应的执行逻辑(执行逻辑的形式可以为 Closure 或 Controller@Action)。同时,在进入执行逻辑之前和之后,还会依次进入 Middlewares 的前置和后置处理。所以,一个 Request 由 Kernel 处理为一个 Response 的一个生命周期图如下:

illuminate/routing

根据上面的流程,理解路由系统的内部工作原理是非常重要的!当然,它也是非常复杂的。想要深入理解一个工具的使用,学习它的内部设计原理才是画龙点睛。在理解 illuminate/routing 如何工作之前,先设想如何去设计一个路由系统呢?一起想个三分钟吧。

  • 注册路由 :想想一个 Request 进入程序时,携带的请求信息类似为 GET https://localhost/api/v2/people/1/accounts?type=bank,所以我们需要定义一个 Route 对象来表示这个信息,同时还得定义 RouteCollection(Route 的集合)来添加、获取和匹配出一个 Route。程序启动时,开发者定义的所有路由(Route)列表都会被注册到 RouteCollection 内。
  • 查找路由 :有了整个程序的路由列表,这样当一个 Request 进来时,再根据当前 Request 的信息匹配出一个合适的 Route,所以可以设计一个类似 RouterManager 对象,作用类似开发经理 Manager 的总体统筹,来调用 $router->findRoute($request): Route 匹配出合适的 Route。
  • 运行路由 :既然匹配出了对应 Route,那可以调用 RouterManager->runRoute($route): Response 得到对应的 Response 返回给外界。

所以,如果自己去设计一个路由系统,就可以按照上面三步去做,思路也很好理解。实际上,Laravel 的路由模块 illuminate/routing 也是按照这三个步骤来设计的。本文将会源码分析下 Laravel 是如何把开发者在 routes/*.php 中写的路由列表注册到 RouteCollection 对象内的。

我们知道,Laravel 在启动时第一步会去实例化 \Illuminate\Foundation\Application 对象,这个容器对象会去调用 \Illuminate\Routing\RoutingServiceProvider::register() 往容器对象的 $bindings 数组属性key-value 形式注册进来,注册的对象主要包括 Router(就是上文的 RouterManager 角色,把它比作为开发小组的开发经理角色)等。

然后会去调用 \App\Providers\RouteServiceProvider::boot() 方法默认加载 routes/api.php 和 routes/web.php 文件中注册的路由列表,并且以 Facade 模式去注册路由列表:

Route::prefix($prefix)->middleware($middleware)->group('xxx/web.php');

实际上就是调用 \Illuminate\Routing\RouteRegistrar 类里的 attribute(key, value) 方法以 key-value 形式注册到 attributes 数组属性里。最主要的 group(string) 方法调用的是 Router::group() 方法,然后调用 loadRoutes(routes) 去执行在 routes/api.php 和 routes/web.php 文件中定义的路由。对于每一种方法(如 GET、POST 等等方法)的路由,Router 对象内都有对应的方法来添加 Route 注册到 RouteCollection 中,比如常见的 get(uri, action) 方法,就是调用的 RouteCollection::add(route) 方法把 Route 注册到 RouteCollection 中。而 route 的创建,调用的是 Router::createRoute(methods, uri, action),其中由于 $action 可能是 Closure 或者 Controller@Action,如果是 Controller@Action 形式,则需要把字符串切割为数组形式,再传入 Route 类的构造函数里。

总结下注册路由所需要用到的对象:使用 Route 来表示路由信息,使用 RouteCollection 来表示路由集合列表,并且提供了添加删除方法来把 Route 注册到 RouteCollection 内,而 Router 才是纵览全局的角色,注册路由是通过该对象发起的,它会调用 RouteCollection 去注册路由,路由的元数据信息如路由名称等是用 RouteRegistrar 对象表示。从上文可知道,所有对象中,Router 才是画龙点睛的对象。

通过以上的分析,就能对 illuminate/routing 路由系统的基本设计越来越清晰。一个 Request 进来后,Application 首先开始启动并按照以上逻辑开始注册路由列表,然后就是根据当前 Request 信息查找对应的 Route 对象。

那如何根据当前 Request 信息查找出对应的 Route 的呢?见本系列第二篇文章。