深入剖析 Laravel 服务提供者实现原理

本文首发于 深入剖析 Laravel 服务提供者实现原理,转载请注明出处。

今天我们将学习 Laravel 框架另外一个核心内容「服务提供者(Service Provider)」。服务提供者的功能是完成 Laravel 应用的引导启动,或者说是将 Laravel 中的各种服务「注册」到「Laravel 服务容器」,这样才能在后续处理 HTTP 请求时使用这些服务。

目录

  • 服务提供者基本概念
  • 服务提供者入门
    • 创建自定义服务提供者
    • register 方法
      • 简单注册服务
    • boot 方法
    • 配置服务提供者
    • 延迟绑定服务提供者
    • 小结
  • 服务提供者启动原理
    • 引导程序的启动流程
      • Laravel 执行服务提供者注册(register)处理
        • RegisterProviders 引导注册
        • 由服务容器执行配置文件中的所有服务提供者服务完成注册。
        • 最后由服务提供者仓库(ProviderRepository)执行服务提供者的注册处理。
      • Laravel 执行服务提供者启动(boot)处理
        • BootProviders 引导启动
        • 由服务容器执行配置文件中的所有服务提供者服务完成启动。
      • Laravel 如何完成延迟加载类型的服务提供者
  • 总结

服务提供者基本概念

我们知道 「服务提供者」是配置应用的中心,它的主要工作是使用「服务容器」实现服务容器绑定、事件监听器、中间件,甚至是路由的注册。

除核心服务外,几乎所有的服务提供者都定义在配置文件 config/app.php 文件中的 providers 节点中。

服务提供者的典型处理流程是,当接 Laravel 应用接收到 HTTP 请求时会去执行「服务提供者的 register(注册)」方法,将各个服务「绑定」到容器内;之后,到了实际处理请求阶段,依据使用情况按需加载所需服务。这样的优势很明显能够提升应用的性能。

细心的朋友可能发现这里用了一个词「几乎」,没错还有一些属于核心服务提供者,这些并没有定义在 providers 配置节点中而是直接由 Illuminate\Foundation\Application 服务容器直接在实例化阶段就完成了注册服务。

<?php
...

class Application extends Container implements ApplicationContract, HttpKernelInterface
{
    public function __construct($basePath = null)
    {
        ...
        $this->registerBaseServiceProviders();
        ...
    }

    /**
     * Register all of the base service providers. 注册应用基础服务提供者
     *
     * @return void
     */
    protected function registerBaseServiceProviders()
    {
        $this->register(new EventServiceProvider($this));

        $this->register(new LogServiceProvider($this));

        $this->register(new RoutingServiceProvider($this));
    }

对服务容器不是很熟悉的老铁可以阅读 深入剖析 Laravel 服务容器,并且在文中「注册基础服务提供者」一节也有详细分析服务容器是如何注册服务提供者的。

另外一个,我们还需要了解的是所有的服务提供者都继承自 Illuminate\Support\ServiceProvider 类。不过对于我们来说目前还无需研究基类,所以我们将焦点放到如何实现一个自定义的服务提供者,然后还有两个需要掌握方法。

服务提供者入门

创建自定义服务提供者

要创建自定义的「服务提供者」,可以直接使用 Laravel 内置的 artisan 命令完成。

php artisan make:provider RiskServiceProvider

这个命令会在 app/Providers 目录下创建 RiskServiceProvider.php 文件,打开文件内容如下:

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class RiskServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.引导启动应用服务。
     *
     * @return void
     */
    public function boot()
    {
        //
    }

    /**
     * Register the application services. 注册应用服务。
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

register 方法

register 方法中,我们无需处理业务逻辑,在这个方法中你只需去处理「绑定」服务到服务容器中即可。

从文档中我们知道:

register 方法中,你只需要将类绑定到 服务容器 中。而不需要尝试在 register 方法中注册任何事件监听器、路由或者任何其他功能。否则,你可能会意外使用到尚未加载的服务提供器提供的服务。

如何理解这句话的含义呢?

如果你有了解过服务容器运行原理,就会知道在「绑定」操作仅仅是建立起接口和实现的对应关系,此时并不会创建具体的实例,即不会存在真实的依赖关系。直到某个服务真的被用到时才会从「服务容器」中解析出来,而解析的过程发生在所有服务「注册」完成之后。

一旦我们尝试在 register 注册阶段使用某些未被加载的服务依赖,即这个服务目前还没有被注册所以不可用。

这样就需要在「注册」绑定时,同时需要关注服务的注册顺序,但这一点 Laravel 并不作出任何保证。

理解了这个道理,我们就可以随便进入一个「服务提供者」来看看其中的 register 方法的逻辑,现在我们挑选的是 Illuminate\Cache\CacheServiceProvider 服务作为讲解:

<?php

namespace Illuminate\Cache;

use Illuminate\Support\ServiceProvider;

class CacheServiceProvider extends ServiceProvider
{
    /**
     * Indicates if loading of the provider is deferred.
     *
     * @var bool
     */
    protected $defer = true;

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton('cache', function ($app) {
            return new CacheManager($app);
        });

        $this->app->singleton('cache.store', function ($app) {
            return $app['cache']->driver();
        });

        $this->app->singleton('memcached.connector', function () {
            return new MemcachedConnector;
        });
    }

    /**
     * Get the services provided by the provider.
     *
     * @return array
     */
    public function provides()
    {
        return [
            'cache', 'cache.store', 'memcached.connector',
        ];
    }
}

没错,如你所预料的一样,它的 register 方法执行了三个单例绑定操作,仅此而已。

简单注册服务

对于处理复杂绑定逻辑,可以自定义「服务提供者」。但是如果是比较简单的注册服务,有没有比较方便的绑定方法呢?毕竟,并不是每个服务都会有复杂的依赖处理。

我们可以从 文档 中得到解答:

如果你的服务提供商注册许多简单的绑定,你可能想使用 bindingssingletons 属性而不是手动注册每个容器绑定。

<?php
class AppServiceProvider extends ServiceProvider
{
    /**
     * 设定容器绑定的对应关系
     *
     * @var array
     */
    public $bindings = [
        ServerProvider::class => DigitalOceanServerProvider::class,
    ];

    /**
     * 设定单例模式的容器绑定对应关系
     *
     * @var array
     */
    public $singletons = [
        DowntimeNotifier::class => PingdomDowntimeNotifier::class,
    ];
}

此时,通过 bingdingssingletons 成员变量来设置简单的绑定,就可以避免大量的「服务提供者」类的生成了。

boot 方法

聊完了 register 方法,接下来进入另一个主题,来研究一下服务提供者的 boot 方法。

通过前面的学习,我们知道在 register 方法中 Laravel 并不能保证所有其他服务已被加载。所以当需要处理具有依赖关系的业务逻辑时,应该将这些逻辑处理放置到 boot 方法内。在 boot 方法中我们可以去完成:注册事件监听器、引入路由文件、注册过滤器等任何你可以想象得到的业务处理。

config/app.php 配置中我们可以看到如下几个服务提供者:

        /*
         * Application Service Providers...
         */
        App\Providers\AppServiceProvider::class,
        App\Providers\AuthServiceProvider::class,
        // App\Providers\BroadcastServiceProvider::class,
        App\Providers\EventServiceProvider::class,
        App\Providers\RouteServiceProvider::class,

选择其中的 App\Providers\RouteServiceProvider::class 服务提供者它继承自 Illuminate\Foundation\Support\Providers\RouteServiceProvider 基类来看下:

// 实现类
class RouteServiceProvider extends ServiceProvider
{
    /**
     * This namespace is applied to your controller routes. In addition, it is set as the URL generator's root namespace.
     */
    protected $namespace = 'App\Http\Controllers';

    /**
     * Define your route model bindings, pattern filters, etc.
     */
    public function boot()
    {
        parent::boot();
    }

    /**
     * Define the routes for the application. 定义应用路由
     */
    public function map()
    {
        $this->mapApiRoutes();
        $this->mapWebRoutes();
    }

    /**
     * Define the "web" routes for the application. These routes all receive session state, CSRF protection, etc.
     * 定义 web 路由。web 路由支持会话状态和 CSRF 防御中间件等。
     */
    protected function mapWebRoutes()
    {
        Route::middleware('web')
             ->namespace($this->namespace)
             ->group(base_path('routes/web.php'));
    }

    /**
     * Define the "api" routes for the application. These routes are typically stateless.
     * 定义 api 路由。api 接口路由支持典型的  HTTP 无状态协议。
     */
    protected function mapApiRoutes()
    {
        Route::prefix('api')
             ->middleware('api')
             ->namespace($this->namespace)
             ->group(base_path('routes/api.php'));
    }
}

基类 Illuminate\Foundation\Support\Providers\RouteServiceProvider

//  基类
namespace Illuminate\Foundation\Support\Providers;

/**
 * @mixin \Illuminate\Routing\Router
 */
class RouteServiceProvider extends ServiceProvider
{
    /**
     * The controller namespace for the application.
     */
    protected $namespace;

    /**
     * Bootstrap any application services. 引导启动服务
     */
    public function boot()
    {
        $this->setRootControllerNamespace();

        // 如果已缓存路由,从缓存文件中载入路由
        if ($this->app->routesAreCached()) {
            $this->loadCachedRoutes();
        } else {
            //还没有路由缓存,加载路由
            $this->loadRoutes();

            $this->app->booted(function () {
                $this->app['router']->getRoutes()->refreshNameLookups();
                $this->app['router']->getRoutes()->refreshActionLookups();
            });
        }
    }

    /**
     * Load the application routes. 加载应用路由,调用实例的 map 方法,该方法定义在 App\Providers\RouteServiceProvider::class 中。
     */
    protected function loadRoutes()
    {
        if (method_exists($this, 'map')) {
            $this->app->call([$this, 'map']);
        }
    }
}

对于 RouteServiceProvider 来讲,它的 boot 方法在处理一个路由载入的问题:

  • 判断是否已有路由缓存;
  • 有路由缓存,则直接载入路由缓存;
  • 无路由缓存,执行 map 方法载入路由。

感兴趣的朋友可以自行了解下 Application Service Providers 配置节点的相关服务提供者,这边不再赘述。

配置服务提供者

了解完「服务提供者」两个重要方法后,我们还需要知道 Laravel 是如何查找到所有的服务提供者的。这个超找的过程就是去读取 config/app.php 文件中的 providers 节点内所有的「服务提供器」。

具体的读取过程我们也会在「服务提供者启动原理」一节中讲解。

延迟绑定服务提供者

对于一个项目来说,除了要让它跑起来,往往我们还需要关注它的性能问题。

当我们打开 config/app.php 配置文件时,你会发现有配置很多服务提供者,难道所有的都需要去执行它的 registerboot 方法么?

对于不会每次使用的服务提供者很明显,无需每次注册和启动,直到需要用到它的时候。

为了解决这个问题 Laravel 内置支持 延迟服务提供者 功能,启用时延迟功能后,当它真正需要注册绑定时才会执行 register 方法,这样就可以提升我们服务的性能了。

启用「延迟服务提供者」功能,需要完成两个操作配置:

  1. 在对应服务提供者中将 defer 属性设置为 true
  2. 并定义 provides 方法,方法返回在提供者 register 方法内需要注册的服务接口名称。

我们拿 config/app.php 配置中的 BroadcastServiceProvider 作为演示说明:

<?php

class BroadcastServiceProvider extends ServiceProvider
{
    /**
     * Indicates if loading of the provider is deferred. 标识提供者是否为延迟加载类型。
     *
     * @var bool
     */
    protected $defer = true;

    public function register()
    {
        $this->app->singleton(BroadcastManager::class, function ($app) {
            return new BroadcastManager($app);
        });

        $this->app->singleton(BroadcasterContract::class, function ($app) {
            return $app->make(BroadcastManager::class)->connection();
        });

        $this->app->alias(
            BroadcastManager::class, BroadcastingFactory::class
        );
    }

    /**
     * Get the services provided by the provider. 获取提供者所提供的服务接口名称。
     */
    public function provides()
    {
        return [
            BroadcastManager::class,
            BroadcastingFactory::class,
            BroadcasterContract::class,
        ];
    }
}

小结

在「服务提供者入门」这个小节我们学习了服务提供者的基本使用和性能优化相关知识,包括:

  • 如何创建自定义的服务提供者;
  • 创建 register 方法注册服务到 Laravel 服务容器;
  • 创建 boot 方法启动服务提供者的引导程序;
  • 配置我们的服务提供者到 config/app.php 文件,这样才能在容器中加载相应服务;
  • 通过延迟绑定技术,提升 Laravel 服务性能。

下一小节,我们将焦点转移到「服务提供者」的实现原理中,深入到 Laravel 内核中去探索「服务提供者」如何被注册和启动,又是如何能够通过延迟技术提升 Laravel 应用的性能的。

服务提供者启动原理

之前我们有学习 深度挖掘 Laravel 生命周期深入剖析 Laravel 服务容器,今天我们将学习「服务提供者」。

Laravel 的所有核心服务都是通过服务提供者进行引导启动的,所以想深入了解 Laravel 那么研究「服务提供者」的原理是个绕不开的话题。

引导程序的启动流程

服务提供者 注册引导启动 直到处理 HTTP 请求阶段才开始。所以我们直接进入到 App\Console\Kernel::class 类,同时这个类继承于 Illuminate\Foundation\Http\Kernel 类。

Illuminate\Foundation\Http\Kernel 类中我们可以看到如下内容:

class Kernel implements KernelContract
{
    ...

    /**
     * The bootstrap classes for the application. 应用引导类
     */
    protected $bootstrappers = [
        ...
        \Illuminate\Foundation\Bootstrap\RegisterProviders::class, // 用于注册(register)「服务提供者」的引导类
        \Illuminate\Foundation\Bootstrap\BootProviders::class, // 用于启动(boot)「服务提供者」的引导类
    ];

    /**
     * Handle an incoming HTTP request. 处理 HTTP 请求
     */
    public function handle($request)
    {
        try {
            $request->enableHttpMethodParameterOverride();

            $response = $this->sendRequestThroughRouter($request);
        } catch (Exception $e) {
            ...
        } catch (Throwable $e) {
            ...
        }

        ...
    }

    /**
     * Send the given request through the middleware / router. 对 HTTP 请求执行中间件处理后再发送到指定路由。
     */
    protected function sendRequestThroughRouter($request)
    {
        ...

        // 1. 引导类引导启动。
        $this->bootstrap();

        // 2. 中间件及请求处理,生成响应并返回响应。
        return (new Pipeline($this->app))
                    ->send($request)
                    ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                    ->then($this->dispatchToRouter());
    }

    /**
     * Bootstrap the application for HTTP requests. 接收 HTTP 请求时启动应用引导程序。
     */
    public function bootstrap()
    {
        // 引导类启动由 Application 容器引导启动。
        if (! $this->app->hasBeenBootstrapped()) {
            $this->app->bootstrapWith($this->bootstrappers());
        }
    }
}

Illuminate\Foundation\Http\Kernel 我们的内核处理 HTTP 请求时会经过一下两个主要步骤:

  1. 启动引导程序通过 $this->bootstrap() 方法完成,其中包括所有服务提供者的注册和引导处理;
  2. 处理 HTTP 请求(这个问题涉及到中间件、路由及相应处理,本文将不做深入探讨)。

进入 Illuminate\Foundation\Application 容器中的 bootstrapWith() 方法,来看看容器是如何将引导类引导启动的:

    /**
     * Run the given array of bootstrap classes. 执行给定引导程序
     */
    public function bootstrapWith(array $bootstrappers)
    {
        $this->hasBeenBootstrapped = true;

        foreach ($bootstrappers as $bootstrapper) {
            $this['events']->fire('bootstrapping: '.$bootstrapper, [$this]);

            // 从容器中解析出实例,然后调用实例的 bootstrap() 方法引导启动。 
            $this->make($bootstrapper)->bootstrap($this);

            $this['events']->fire('bootstrapped: '.$bootstrapper, [$this]);
        }
    }

通过服务容器的 bootstrap() 方法引导启动时,将定义的在 Illuminate\Foundation\Http\Kerne 类中的应用引导类($bootstrappers)交由 Application 服务容器引导启动。其中与「服务提供者」有关的引导类为:

Illuminate\Foundation\Http\Kerne HTTP 内核通过 bootstrap() 方法引导启动时,实际由服务容器(Application)去完成引导启动的工作,并依据定义在 HTTP 内核中的引导类属性配置顺序依次引导启动,最终「服务提供者」的启动顺序是:

  • 执行「服务提供者」register 方法的引导类:\Illuminate\Foundation\Bootstrap\RegisterProviders::class,将完成所有定义在 config/app.php 配置中的服务提供者的注册(register)处理;
  • 执行「服务提供者」boot 方法的引导类:\Illuminate\Foundation\Bootstrap\BootProviders::class,将完成所有定义在 config/app.php 配置中的服务提供者的启动(boot)处理。

Laravel 执行服务提供者注册(register)处理

前面说过「服务提供者」的注册由 \Illuminate\Foundation\Bootstrap\RegisterProviders::class 引导类启动方法(botstrap())完成。

1. RegisterProviders 引导注册
<?php
class RegisterProviders
{
    /**
     * Bootstrap the given application.
     */
    public function bootstrap(Application $app)
    {
        $app->registerConfiguredProviders();
    }
}

在其通过调用服务容器的 registerConfiguredProviders() 方法完成引导启动,所以我们需要到容器中一探究竟。

2. 由服务容器执行配置文件中的所有服务提供者服务完成注册。
    /**
     * Register all of the configured providers. 执行所有配置服务提供者完成注册处理。
     * 
     * @see https://github.com/laravel/framework/blob/5.6/src/Illuminate/Foundation/Application.php
     */
    public function registerConfiguredProviders()
    {
        $providers = Collection::make($this->config['app.providers'])
                        ->partition(function ($provider) {
                            return Str::startsWith($provider, 'Illuminate\\');
                        });

        $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);

        // 通过服务提供者仓库(ProviderRepository)加载所有的提供者。
        (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
                    ->load($providers->collapse()->toArray());
    }
3. 最后由服务提供者仓库(ProviderRepository)执行服务提供者的注册处理。
<?php

namespace Illuminate\Foundation;

use Exception;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Contracts\Foundation\Application as ApplicationContract;

class ProviderRepository
{
    ...

    /**
     * Register the application service providers. 注册应用的服务提供者。
     *
     * @see https://github.com/laravel/framework/blob/5.6/src/Illuminate/Foundation/ProviderRepository.php
     */
    public function load(array $providers)
    {
        $manifest = $this->loadManifest();

        // 首先从服务提供者的缓存清单文件中载入服务提供者集合。其中包含「延迟加载」的服务提供者。
        if ($this->shouldRecompile($manifest, $providers)) {
            $manifest = $this->compileManifest($providers);
        }

        // Next, we will register events to load the providers for each of the events
        // that it has requested. This allows the service provider to defer itself
        // while still getting automatically loaded when a certain event occurs.
        foreach ($manifest['when'] as $provider => $events) {
            $this->registerLoadEvents($provider, $events);
        }

        // 到这里,先执行应用必要(贪婪)的服务提供者完成服务注册。
        foreach ($manifest['eager'] as $provider) {
            $this->app->register($provider);
        }

        // 最后将所有「延迟加载服务提供者」加入到容器中。
        $this->app->addDeferredServices($manifest['deferred']);
    }

    /**
     * Compile the application service manifest file. 将服务提供者编译到清单文件中缓存起来。
     */
    protected function compileManifest($providers)
    {
        // The service manifest should contain a list of all of the providers for
        // the application so we can compare it on each request to the service
        // and determine if the manifest should be recompiled or is current.
        $manifest = $this->freshManifest($providers);

        foreach ($providers as $provider) {
            // 解析出 $provider 对应的实例
            $instance = $this->createProvider($provider);

            // 判断当前服务提供者是否为「延迟加载」类行的,是则将其加入到缓存文件的「延迟加载(deferred)」集合中。
            if ($instance->isDeferred()) {
                foreach ($instance->provides() as $service) {
                    $manifest['deferred'][$service] = $provider;
                }

                $manifest['when'][$provider] = $instance->when();
            }

            // 如果不是「延迟加载」类型的服务提供者,则为贪婪加载必须立即去执行注册方法。
            else {
                $manifest['eager'][] = $provider;
            }
        }

        // 将归类后的服务提供者写入清单文件。
        return $this->writeManifest($manifest);
    }

服务提供者仓库(ProviderRepository) 处理程序中依次执行如下处理:

    1. 如果存在服务提供者缓存清单,则直接读取「服务提供者」集合;
    1. 否则,将从 config/app.php 配置中的服务提供者编译到缓存清单中;
      • 2.1. 这个处理由 compileManifest() 方法完成;
      • 2.2. 编译缓存清单时将处理贪婪加载(eager)和延迟加载(deferred)的服务提供者;
    1. 对于贪婪加载的提供者直接执行服务容器的 register 方法完成服务注册;
    1. 将延迟加载提供者加入到服务容器中,按需注册和引导启动。

最后通过 Illuminate\Foundation\Application 容器完成注册处理:

    /**
     * Register a service provider with the application. 在应用服务容器中注册一个服务提供者。
     */
    public function register($provider, $options = [], $force = false)
    {
        if (($registered = $this->getProvider($provider)) && ! $force) {
            return $registered;
        }

        // 如果给定的服务提供者是接口名称,解析出它的实例。
        if (is_string($provider)) {
            $provider = $this->resolveProvider($provider);
        }

        // 服务提供者提供注册方法时,执行注册服务处理
        if (method_exists($provider, 'register')) {
            $provider->register();
        }

        $this->markAsRegistered($provider);

        // 判断 Laravel 应用是否已启动。已启动的话需要去执行启动处理。
        if ($this->booted) {
            $this->bootProvider($provider);
        }

        return $provider;
    }

为什么需要判断是否已经启动过呢?

因为对于延迟加载的服务提供者只有在使用时才会被调用,所以这里需要这样判断,然后再去启动它。

以上,便是

Laravel 执行服务提供者启动(boot)处理

「服务提供者」的启动流程和注册流程大致相同,有兴趣的朋友可以深入源码了解一下。

1. BootProviders 引导启动
<?php

namespace Illuminate\Foundation\Bootstrap;

use Illuminate\Contracts\Foundation\Application;

class BootProviders
{
    /**
     * Bootstrap the given application.
     *
     * @see https://github.com/laravel/framework/blob/5.6/src/Illuminate/Foundation/Bootstrap/BootProviders.php
     */
    public function bootstrap(Application $app)
    {
        $app->boot();
    }
}
2. 由服务容器执行配置文件中的所有服务提供者服务完成启动。
    /**
     * Boot the application's service providers. 引导启动应用所有服务提供者
     *
     * @see https://github.com/laravel/framework/blob/5.6/src/Illuminate/Foundation/Application.php
     */
    public function boot()
    {
        if ($this->booted) {
            return;
        }

        // Once the application has booted we will also fire some "booted" callbacks
        // for any listeners that need to do work after this initial booting gets
        // finished. This is useful when ordering the boot-up processes we run.
        $this->fireAppCallbacks($this->bootingCallbacks);

        // 遍历并执行服务提供者的 boot 方法。
        array_walk($this->serviceProviders, function ($p) {
            $this->bootProvider($p);
        });

        $this->booted = true;

        $this->fireAppCallbacks($this->bootedCallbacks);
    }

    /**
     * Boot the given service provider. 启动给定服务提供者
     */
    protected function bootProvider(ServiceProvider $provider)
    {
        if (method_exists($provider, 'boot')) {
            return $this->call([$provider, 'boot']);
        }
    }

以上便是服务提供者执行 注册绑定服务引导启动 的相关实现。

但是稍等一下,我们是不是忘记了还有「延迟加载」类型的服务提供者,它们还没有被注册和引导启动呢!

Laravel 如何完成延迟加载类型的服务提供者

对于延迟加载类型的服务提供者,我们要到使用时才会去执行它们内部的 registerboot 方法。这里我们所说的使用即使需要 解析 它,我们知道解析处理由服务容器完成。

所以我们需要进入到 Illuminate\Foundation\Application 容器中探索 make 解析的一些细节。

    /**
     * Resolve the given type from the container. 从容器中解析出给定服务
     * 
     * @see https://github.com/laravel/framework/blob/5.6/src/Illuminate/Foundation/Application.php
     */
    public function make($abstract, array $parameters = [])
    {
        $abstract = $this->getAlias($abstract);

        // 判断这个接口是否为延迟类型的并且没有被解析过,是则去将它加载到容器中。
        if (isset($this->deferredServices[$abstract]) && ! isset($this->instances[$abstract])) {
            $this->loadDeferredProvider($abstract);
        }

        return parent::make($abstract, $parameters);
    }

    /**
     * Load the provider for a deferred service. 加载给定延迟加载服务提供者
     */
    public function loadDeferredProvider($service)
    {
        if (! isset($this->deferredServices[$service])) {
            return;
        }

        $provider = $this->deferredServices[$service];

        // 如果服务为注册则去注册并从延迟服务提供者集合中删除它。
        if (! isset($this->loadedProviders[$provider])) {
            $this->registerDeferredProvider($provider, $service);
        }
    }

    /**
     * Register a deferred provider and service. 去执行服务提供者的注册方法。
     */
    public function registerDeferredProvider($provider, $service = null)
    {
        // Once the provider that provides the deferred service has been registered we
        // will remove it from our local list of the deferred services with related
        // providers so that this container does not try to resolve it out again.
        if ($service) {
            unset($this->deferredServices[$service]);
        }

        // 执行服务提供者注册服务。
        $this->register($instance = new $provider($this));

        // 执行服务提供者启动服务。
        if (! $this->booted) {
            $this->booting(function () use ($instance) {
                $this->bootProvider($instance);
            });
        }
    }

总结

今天我们深入研究了 Laravel 服务提供者的注册和启动的实现原理,希望对大家有所帮助。

如果对如何自定义服务提供者不甚了解的朋友可以去阅读 Laravel 服务提供者指南 这篇文章。

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

1024,感谢分享! :stuck_out_tongue_winking_eye:

5年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!