3.1.0 - Laravel - 5.6 - Route - RoutingServiceProvider注册

RoutingServiceProvider 对路由来说是第一个需要加载的服务。提供了很多必要的绑定。

加载路径步骤如下

  1. index.php 引入 app.php

    require_once __DIR__.'/../bootstrap/app.php';
  2. bootstrap/app.php 文件中会创建容器对象 Application

    $app = new Illuminate\Foundation\Application(
     realpath(__DIR__.'/../')
    );
  3. 在 namespace Illuminate\Foundation\Application 下的 Apllication 的构造函数中会调用 registerBaseServiceProviders 方法。

    public function __construct($basePath = null)
    {
     ...
     $this->registerBaseServiceProviders();
     ...
    }
  4. registerBaseServiceProviders 方法如下,会调用 register 方法注册 RoutingServiceProvider 方法。

调用 register 方法会去执行这个 RoutingServiceProvider 中的 register 方法中的逻辑。可以把需要的逻辑比如绑定一些必要的对象等等写在这个 register 方法中。
(register 方法的逻辑参考上一篇 application 的 register)

protected function registerBaseServiceProviders()
{
    $this->register(new EventServiceProvider($this));
    $this->register(new LogServiceProvider($this));
    $this->register(new RoutingServiceProvider($this));
}
  1. 主要看下 RoutingServiceProvider 的 register 方法,内容如下。
    public function register()
    {
     $this->registerRouter();
     $this->registerUrlGenerator();
     $this->registerRedirector();
     $this->registerPsrRequest();
     $this->registerPsrResponse();
     $this->registerResponseFactory();
     $this->registerControllerDispatcher();
    }

5.1 $this->registerRouter();
这个方法就是通过 singleton 方法注册 router 实例到容器中。(router 对象提供很多必要的方法帮助我们操作后面会生成的 route 对象)。代码如下。

protected function registerRouter()
{
    $this->app->singleton('router', function ($app) {
        return new Router($app['events'], $app);
    });
}

很简单,就是绑定一个闭包,闭包会创建 router 对象返回。

5.2 $this->registerUrlGenerator(); 这一步就是绑定 url 和其产生 UrlGenerator 对象的回调方法。就是注册一个 url 产生器。

protected function registerUrlGenerator()
{
    $this->app->singleton('url', function ($app) {
        $routes = $app['router']->getRoutes();
        $app->instance('routes', $routes);
        return new UrlGenerator($routes, $app->rebinding(
            'request', 
            $this->requestRebinder()), 
            $app['config']['app.asset_url']
        );
    });

    $this->app->extend('url', function (UrlGeneratorContract $url, $app) {
        $url->setSessionResolver(function () {
        return $this->app['session'] ?? null;});

        $url->setKeyResolver(function () {
            return $this->app->make('config')->get('app.key');
        });

        $app->rebinding('routes', function ($app, $routes) {
            $app['url']->setRoutes($routes);
        });
        return $url;
    });
}

这里主要分两步,分别使用 singleton 和 extend 方法绑定字段名为 url 对应的闭包。

singleton 会把 url 和其闭包绑定。但是不会执行闭包,要在 make 解析的时候才执行,执行完会存入 instance 数组中。

extend 绑定对前面这个 url 绑定的扩展。如果 url 已经解析过了就会把 extend 返回值替换 instance 中的值,如果没有就会把方法存入 extender 数组以备后用。简单说就是 extend 是对 singleton 的 url 的扩展。

1.1 $routes = $app['router']->getRoutes();
获取 router 对象的所有 routes 对象。(route 对象就是我们产生的一个一个路由对象,用来和请求 request 匹配)
1.2 把 route 对象集合以名字 routes 存入 instance 数组中。
1.2 返回一个 UrlGenerator 对象。可以生成 url 的对象。
1.2.1 $app->rebinding('request', $this->requestRebinder()
requestRebinder 方法返回一个设置 request 到 $app['url'] 的闭包。代码如下

protected function requestRebinder()
{
    return function ($app, $request) {
        $app['url']->setRequest($request);
    };
}

rebinding 通常对已经解析过的 abstruct,重新绑定的时候触发回调函数。
但是他还有个特性,如果当前 abstruct 以前被绑定过,他会马上解析当前的 abstruct。
(abstruct 这里就是’url’)。
这里就是,如果 request 被绑定过就会马上解析这个 request 对象。

整句的意思就是如果 request 被绑定过,就会马上解析 request 返回,同时还把 rebinding 的回调函数存入 reboundCallbacks 数组中等待再次绑定的时候触发。很巧妙。

1.2.2 $app['config']['app.asset_url'] 是 asset 静态文件路径 url

2.1 第二个 extend 绑定,返回一个方法,触发的时候需要两个参数:app 和 UrlGeneratorContract 的实现类 (就是上面的 UrlGenerator 对象)。

2.2 设置一些必要的特性以便以后使用

$url->setSessionResolver(function () {
    return $this->app['session'] ?? null;
});
$url->setKeyResolver(function () {
    return $this->app->make('config')->get('app.key');
});

这里设置了一些回调函数给 UrlGenerator 对象,到时候可以触发获取一些信息,比如 session 以及配置文件中的 app.key 的值。

2.3 和前面同理。rebinding 会对 routes 马上解析,因为上面已经使用 instances 绑定过 routes 了。返回 routes 解析后的对象。同时会把 rebinding 回调函数存入以备后用。

$app->rebinding('routes', function ($app, $routes) {
    $app['url']->setRoutes($routes);
});

最后返回这个 url 对象。

前面提及,extend 有替换的作用,前提是这个绑定已经被解析过。在这里,singleton 绑定 url 和 extend 绑定 url 之间没有解析的过程,所以就没有替换的情况,他会依次执行,先解析 singleton 然后触发 extend 的回调,相当于给 url 对象做了一个辅助扩展。

5.3 $this->registerRedirector(); 注册一个重定向对象。

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

1. 就是绑定 redirect 和其回调函数。
2. 判断 $app['session.store'] 是否存在,存在把其存入 redirect 对象中。

session.store 是 Session 驱动器,是 Illuminate\Session\Store 的实例,Store 类实现了 Illuminate\Contracts\Session\Session 向开发者提供了统一的接口来访问 Session 数据,驱动器通过不同的 SessionHandler 来访问 database、redis、memcache 等不同的存储介质里的 session 数据。

5.4 registerPsrRequest 和 registerPsrResponse 放一起。绑定满足 psr-7 标准的 request 和 response。但是不知道具体如何使用。代码中无迹可寻。请大牛指点。

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

5.5 注册响应的工厂对象,主要用来生成返回给用户的数据。

protected function registerResponseFactory()
{
    $this->app->singleton(ResponseFactoryContract::class, function ($app) {
        return new ResponseFactory($app[ViewFactoryContract::class], $app['redirect']);
    });
}

5.6 这个是注册控制器的分发对象。ControllerDispatcher 这个对象主要用来触发 request 对应的 controller 逻辑。

protected function registerControllerDispatcher()
{
    $this->app->singleton(ControllerDispatcherContract::class, function ($app) {
        return new ControllerDispatcher($app);
    });
}

5.5 5.6 没有详细解释因为不在本章范围,后面遇到操作再补充。

这就是整个 RoutingServiceProvider 加载过程。

总结:
1.RoutingServiceProvider 的加载不复杂,主要是要明确他是在容器初始化的时候加载的,比其他一些 serviceprovider 要早。
2. 连续使用 singleton 和 extend 可以避免 extend 绑定的替换特性。
3.RoutingServiceProvider 的加载的作用主要还是绑定各个回调函数,还并没有真正触发这些回调。在给后面的操作打下基础。

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。