对Laravel服务提供者的认识

参考网址:
服务提供者
(如有哪里分析不对的地方,请各位大佬指正)

环境

# php artisan --version
bootLaravel Framework 8.83.27

# php -v 
PHP 7.4.27 (cli) (built: Dec 16 2021 22:42:18) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.27, Copyright (c), by Zend Technologies
    with Xdebug v2.9.2, Copyright (c) 2002-2020, by Derick Rethans

简介

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

源码调用流程

首先,请求到达index.php文件,加载bootstrap/app.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()方法,查看该类

// 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()方法

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()方法
这是服务提供者加载的核心方法(重要!)

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时,调用构造函数,将容器对象和文件系统对象传入,并将缓存服务提供者的路径传入。

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

之后调用load方法,

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()方法
/**
* 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);
}
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类,来绑定一些服务到服务容器中。

php  artisan  make:provider  ExampleServiceProvider

使用

修改ExampleServiceProvider

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

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类,写一个测试方法

namespace  App\Http\Service;
class  ExampleService
{
    public  function  test()
    {
        echo  'test';
    }
}

注册服务提供者

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

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

修改路由

修改路由web.php配置,

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

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

$bindings 和 $singletons属性

// 如果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
public  $bindings  = [
    'example' => \App\Http\Service\ExampleService::class
];

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

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

注释掉register方法中的绑定,访问首页依旧可以正常解析,如果有很多简单的绑定,可以使用这两个属性来简化操作

延迟加载提供者

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

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

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方法。

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

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