Skip to content

参考网址: 服务提供者

服务提供者

简介

服务提供者是所有 Laravel 应用程序的引导中心。你的应用程序,以及通过服务器引导的 Laravel 核心服务都是通过服务提供器引导。

源码调用流程

首先,请求到达index.php文件,加载bootstrap/app.php文件,实例化容器类。

php
// 从容器中解析出Kernel类,在文件bootstrap/app.php中已绑定到容器类中
$kernel = $app->make(Kernel::class);
// Request::capture()获取请求对象
$response = $kernel->handle(
    $request = Request::capture()
)->send();

查看handle()方法

这里调用的handle()方法,是Illuminate\Foundation\Http\Kernel类的handle()方法,查看该类

php
// handle
public function handle($request)
{
    try {
        $request->enableHttpMethodParameterOverride();
        // 重点看这行
        $response = $this->sendRequestThroughRouter($request);
    } catch (Throwable $e) {
        $this->reportException($e);

        $response = $this->renderException($request, $e);
    }

    $this->app['events']->dispatch(
        new RequestHandled($request, $response)
    );

    return $response;
}
// sendRequestThroughRouter方法详细
protected function sendRequestThroughRouter($request)
{
    $this->app->instance('request', $request);

    Facade::clearResolvedInstance('request');
    // 重点在这行
    $this->bootstrap();

    return (new Pipeline($this->app))
                ->send($request)
                ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                ->then($this->dispatchToRouter());
}
// bootstrap方法详细
public function bootstrap()
{
    // 初始化时$this->app->hasBeenBootstrapped()为false
    if (! $this->app->hasBeenBootstrapped()) {
        /**
         * 看这行
         * 参数$this->bootstrappers()为数组。
         * protected function bootstrappers()
         * {
         *      return $this->bootstrappers;
         * }
         * 
         * $this->bootstrappers为类Kernel的属性,值如下
         * protected $bootstrappers = [
         *       \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
         *       \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
         *       \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
         *       \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
         *       \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
         *       \Illuminate\Foundation\Bootstrap\BootProviders::class,
         *   ];
         * $this->app实际是Illuminate\Foundation\Application类的实例,调用该类的bootstrapWith()方法
         */ 
        $this->app->bootstrapWith($this->bootstrappers());
    }
}

查看bootstrapWith()方法

php
public function bootstrapWith(array $bootstrappers)
{
    $this->hasBeenBootstrapped = true;
    /**
     * $bootstrappers = [
     *       \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
     *       \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
     *       \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
     *       \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
     *       \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
     *       \Illuminate\Foundation\Bootstrap\BootProviders::class,
     * ];
     */
    foreach ($bootstrappers as $bootstrapper) {
        $this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]);
        // 从服务容器中解析出对应实例,并调用bootstrap方法
        $this->make($bootstrapper)->bootstrap($this);

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

提示

该方法循环数组$bootstrappers的每一个元素,并调用其bootstrap()方法。重点看\Illuminate\Foundation\Bootstrap\RegisterProviders::class类的bootstrap()方法

RegisterProviders::class类

查看该类的bootstrap()方法

警告

这是服务提供者加载的核心方法(重要!)

php
public function bootstrap(Application $app)
{
    // 这里实际调用的是Application类的registerConfiguredProviders()方法
    $app->registerConfiguredProviders();
}
// 该方法从配置文件config/app.php中读取providers配置,调用集合的partittion方法,根据条件分组
public function registerConfiguredProviders()
{
    // 从容器中解析出别名为config的实例,实际是illuminate/config/Repository类
    $providers = Collection::make($this->make('config')->get('app.providers'))
                    ->partition(function ($provider) {
                        return strpos($provider, 'Illuminate\\') === 0;
                    });

    $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);
    /**
     * 
     * 查看bootstrap/cache目录下是否存在services.php文件,如果存在,则require该文件
     * $this->getCachedServicesPath()返回缓存文件位置
     * public function getCachedServicesPath()
     * {
     *     return $this->normalizeCachePath('APP_SERVICES_CACHE', 'cache/services.php');
     * }
     * 
     * protected function normalizeCachePath($key, $default)
     * {
     *    如果.env文件未设置APP_SERVICES_CACHE的值,则调用bootstrapPath方法
     *    if (is_null($env = Env::get($key))) {
     *        return $this->bootstrapPath($default);
     *    }
     *
     *    return Str::startsWith($env, $this->absoluteCachePathPrefixes)
     *            ? $env
     *            : $this->basePath($env);
     * }
     * public function bootstrapPath($path = '')
     * {   $this->basePath为当前项目根目录 DIRECTORY_SEPARATOR为 / ,$path的值为cache/services.php
     *     return $this->basePath.DIRECTORY_SEPARATOR.'bootstrap'.($path ? DIRECTORY_SEPARATOR.$path : $path);
     * }
     */
    (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
                ->load($providers->collapse()->toArray());
}
ProviderRepository类

new时,调用构造函数,将容器对象和文件系统对象传入,并将缓存服务提供者的路径传入。

php
// 该类的构造函数
public function __construct(ApplicationContract $app, Filesystem $files, $manifestPath)
{
    $this->app = $app;
    $this->files = $files;
    $this->manifestPath = $manifestPath;
}

之后调用load方法,

php
public function load(array $providers)
{
    /**
     * 该方法判断缓存文件是否存在,存在则require文件
       public function loadManifest()
       {
          if ($this->files->exists($this->manifestPath)) {
              $manifest = $this->files->getRequire($this->manifestPath);

              if ($manifest) {
                  return array_merge(['when' => []], $manifest);
              }
          }
       }
     */
    $manifest = $this->loadManifest();

    /**
     * shouldRecompile()方法,如果缓存文件不存在,或者缓存文件跟传入的服务提供者数组不一致,则返回true,否则返回false
     * return is_null($manifest) || $manifest['providers'] != $providers;
     */
    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);
    }

    // 循环providers数组,调用app->register()方法
    foreach ($manifest['eager'] as $provider) {
        // 调用Application类的register()方法
        $this->app->register($provider);
    }
    // 添加到延迟服务提供者
    $this->app->addDeferredServices($manifest['deferred']);
}
compileManifest()方法
php
/**
 * compileManifest方法
 */
protected function compileManifest($providers)
{
    // 刷新$manifest的数组结构
    // $manifest = ['providers' => $providers, 'eager' => [], 'deferred' => []];
    $manifest = $this->freshManifest($providers);
    // 然后循环所有服务提供者
    foreach ($providers as $provider) {
        // 从容器中解析出对应提供者实例
        // return new $provider($this->app);
        $instance = $this->createProvider($provider);

        /**
         *  然后调用对应服务提供者的isDeferred()方法,该方法是所有服务提供者父类ServiceProvider的方法
         * 这里是延迟服务加载实现的方式,通过实现DeferrableProvider接口,然后判断,如果实现了该接口,则判断为是延迟服务提供者
         * public function isDeferred()
         *  {
         *     return $this instanceof DeferrableProvider;
         *  }
         */
        if ($instance->isDeferred()) {
            // 调用服务提供者的provides方法,将定义的键绑定到该服务提供者
            foreach ($instance->provides() as $service) {
                $manifest['deferred'][$service] = $provider;
            }

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

        else {
            // else就是作为普通服务提供者,在框架初始化时加载
            $manifest['eager'][] = $provider;
        }
    }
    // 写入到config/cache/services.php文件中
    return $this->writeManifest($manifest);
}
php
public function register($provider, $force = false)
{
    // 如果已经注册过,则直接返回
    if (($registered = $this->getProvider($provider)) && ! $force) {
        return $registered;
    }

    // 如果传入的是字符串,则调用resolveProvider()方法,将字符串转换为Provider实例
    if (is_string($provider)) {
        $provider = $this->resolveProvider($provider);
    }
    // 这里会调用Provider类的register()方法
    $provider->register();

    // 如果Provider类有bindings属性,则调用bind()方法
    if (property_exists($provider, 'bindings')) {
        foreach ($provider->bindings as $key => $value) {
            $this->bind($key, $value);
        }
    }

    // 如果Provider类有singletons属性,则调用singleton()方法
    if (property_exists($provider, 'singletons')) {
        foreach ($provider->singletons as $key => $value) {
            $this->singleton($key, $value);
        }
    }
    // 标记为已注册
    $this->markAsRegistered($provider);

    // If the application has already booted, we will call this boot method on
    // the provider class so it has an opportunity to do its boot logic and
    // will be ready for any usage by this developer's application logic.
    if ($this->isBooted()) {
        $this->bootProvider($provider);
    }

    return $provider;
}

总结

上面我们看到,registerConfiguredProviders()方法会从配置文件config/app.php中读取providers配置,并调用load()方法,将providers分为eagerdeferred两组,然后分别调用register()方法注册eager服务提供者,并将deferred服务提供者添加到延迟服务提供者列表中。

创建服务提供者

所有服务提供者都会继承Illuminate\Support\ServiceProvider类,并且需要包含一个register()方法和一个boot()方法,该方法将会在服务容器中注册服务。在register()方法中,只需要将服务绑定到服务容器上即可。 使用Artisan命令即可创建服务提供者,也可以使用框架提供的AppServiceProvider类,来绑定一些服务到服务容器中。

bash
php artisan make:provider ExampleServiceProvider

使用

修改ExampleServiceProvider

不要尝试在 register 方法中注册任何监听器,路由,或者其他任何功能。否则,你可能会意外地使用到尚未加载的服务提供者提供的服务。

php
namespace App\Providers;

use App\Services\ExampleService;
use Illuminate\Support\ServiceProvider;

class ExampleServiceProvider extends ServiceProvider
{
    public function register()
    {
        // 将服务绑定到服务容器上
        $this->app->bind('example', function () {
            return new ExampleService();
        });
    }

    public function boot()
    {
        // 这里可以写一些服务启动的逻辑
    }
}

提示

分析源码可知,ExampleServiceProvider继承了Illuminate\Support\ServiceProvider类,当new类时,调用了ServiceProvider的构造函数,将服务容器绑定到$this->app中,并将example服务绑定到ExampleService实例上。

新建服务类

新建位于app/Services目录下的ExampleService.php类,写一个测试方法

php
namespace App\Http\Service;

class ExampleService
{
    public function test()
    {
        echo 'test';
    }
}

注册服务提供者

然后在config/app.php中添加服务提供者的配置

php
'providers' => [
    //省略...
    App\Providers\ExampleServiceProvider::class,
],

修改路由

修改路由web.php配置,

php
Route::get('/', function () {
    var_dump(app('example'));
    app('example')->test();
});

访问首页,可以看到输出test以及ExampleService对象实例。

$bindings 和 $singletons属性

php
// 如果Provider类有bindings属性,则调用bind()方法
if (property_exists($provider, 'bindings')) {
    foreach ($provider->bindings as $key => $value) {
        $this->bind($key, $value);
    }
}

// 如果Provider类有singletons属性,则调用singleton()方法
if (property_exists($provider, 'singletons')) {
    foreach ($provider->singletons as $key => $value) {
        $this->singleton($key, $value);
    }
}

通过上面的源码分析可知,当服务提供者有bindings属性时,会调用bind()方法,将服务提供者的bindings属性中的服务绑定到容器中。 同样,当服务提供者有singletons属性时,会调用singleton()方法,将服务提供者的singletons属性中的服务绑定到容器中,并且在每次容器实例化时,都会返回同一个实例。

设置变量$bindings
php
public $bindings = [
        'example' => \App\Http\Service\ExampleService::class
    ];

注释掉register方法中的绑定,访问首页依旧可以正常解析

设置变量$singletons
php
public $singletons = [
        'example' => \App\Http\Service\ExampleService::class
    ];

注释掉register方法中的绑定,访问首页依旧可以正常解析

提示

如果有很多简单的绑定,可以使用这两个属性来简化操作

延迟加载提供者

如果只注册,可以选择延迟加载该服务。延迟加载可以提高应用性能。

要延迟加载提供者,需要实现 \Illuminate\Contracts\Support\DeferrableProvider 接口并置一个 provides 方法。这个 provides 方法返回该提供者注册的服务容器绑定

php
namespace App\Providers;

use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;
// 如果使用延迟加载该服务,需要实现DeferrableProvider接口
class ExampleServiceProvider extends ServiceProvider implements DeferrableProvider
{
    public $bindings = [
        'ex' => \App\Http\Service\ExampleService::class,
        'ex2' => \App\Http\Service\ExampleService::class
    ];
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
    //    $this->app->bind('example', function () {
    //        return new ExampleService();
    //    });
    }

    public function provides()
    {
        return [
            'ex','ex2'
        ];
    }
}
// 路由配置
Route::get('/', function () {
    var_dump(app('ex2'));
    app('ex2')->test();
});

这时访问首页也可以解析出ExampleService实例,并调用其中的test方法。