0

0

Laravel服务提供器(ServiceProvider)的解读

不言

不言

发布时间:2018-07-06 15:03:12

|

2742人浏览过

|

来源于php中文网

原创

这篇文章主要介绍了关于laravel服务提供器(serviceprovider)的解读,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下

服务提供器是所有 Laravel 应用程序引导中心。你的应用程序自定义的服务、第三方资源包提供的服务以及 Laravel 的所有核心服务都是通过服务提供器进行注册(register)和引导(boot)的。

拿一个Laravel框架自带的服务提供器来举例子

class BroadcastServiceProvider extends ServiceProvider
{
    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();
        });
        //将BroadcastingFactory::class设置为BroadcastManager::class的别名
        $this->app->alias(
            BroadcastManager::class, BroadcastingFactory::class
        );
    }
    public function provides()
    {
        return [
            BroadcastManager::class,
            BroadcastingFactory::class,
            BroadcasterContract::class,
        ];
    }
}

在服务提供器BroadcastServiceProviderregister中, 为BroadcastingFactory的类名绑定了类实现BroadcastManager,这样就能通过服务容器来make出通过BroadcastingFactory::class绑定的服务BroadcastManger对象供应用程序使用了。

本文主要时来梳理一下laravel是如何注册、和初始化这些服务的,关于如何编写自己的服务提供器,可以参考官方文档

BootStrap

首先laravel注册和引导应用需要的服务是发生在寻找路由处理客户端请求之前的Bootstrap阶段的,在框架的入口文件里我们可以看到,框架在实例化了Application对象后从服务容器中解析出了HTTP Kernel对象

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

在Kernel处理请求时会先让请求通过中间件然后在发送请求给路由对应的控制器方法, 在这之前有一个BootStrap阶段来引导启动Laravel应用程序,如下面代码所示。

public function handle($request)
{
    ......
    $response = $this->sendRequestThroughRouter($request);
    ......
            
    return $response;
}
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());
}
    
//引导启动Laravel应用程序
public function bootstrap()
{
    if (! $this->app->hasBeenBootstrapped()) {
        /**依次执行$bootstrappers中每一个bootstrapper的bootstrap()函数
         $bootstrappers = [
             'Illuminate\Foundation\Bootstrap\DetectEnvironment',
             'Illuminate\Foundation\Bootstrap\LoadConfiguration',
             'Illuminate\Foundation\Bootstrap\ConfigureLogging',
             'Illuminate\Foundation\Bootstrap\HandleExceptions',
             'Illuminate\Foundation\Bootstrap\RegisterFacades',
             'Illuminate\Foundation\Bootstrap\RegisterProviders',
             'Illuminate\Foundation\Bootstrap\BootProviders',
            ];*/
            $this->app->bootstrapWith($this->bootstrappers());
        }
    }

上面bootstrap中会分别执行每一个bootstrapper的bootstrap方法来引导启动应用程序的各个部分

    1. DetectEnvironment  检查环境
    2. LoadConfiguration  加载应用配置
    3. ConfigureLogging   配置日至
    4. HandleException    注册异常处理的Handler
    5. RegisterFacades    注册Facades 
    6. RegisterProviders  注册Providers 
    7. BootProviders      启动Providers

启动应用程序的最后两部就是注册服务提供这和启动提供者,如果对前面几个阶段具体时怎么实现的可以参考这篇文章。在这里我们主要关注服务提供器的注册和启动。

先来看注册服务提供器,服务提供器的注册由类 \Illuminate\Foundation\Bootstrap\RegisterProviders::class 负责,该类用于加载所有服务提供器的 register 函数,并保存延迟加载的服务的信息,以便实现延迟加载。

class RegisterProviders
{
    public function bootstrap(Application $app)
    {
        //调用了Application的registerConfiguredProviders()
        $app->registerConfiguredProviders();
    }
}
    
class Application extends Container implements ApplicationContract, HttpKernelInterface
{
    public function registerConfiguredProviders()
    {
        (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
                    ->load($this->config['app.providers']);
    }
    
    public function getCachedServicesPath()
    {
        return $this->bootstrapPath().'/cache/services.php';
    }
}

可以看出,所有服务提供器都在配置文件 app.php 文件的 providers 数组中。类 ProviderRepository 负责所有的服务加载功能:

class ProviderRepository
{
    public function load(array $providers)
    {
        $manifest = $this->loadManifest();
        if ($this->shouldRecompile($manifest, $providers)) {
            $manifest = $this->compileManifest($providers);
        }
        foreach ($manifest['when'] as $provider => $events) {
            $this->registerLoadEvents($provider, $events);
        }
        foreach ($manifest['eager'] as $provider) {
            $this->app->register($provider);
        }
        $this->app->addDeferredServices($manifest['deferred']);
    }
}

loadManifest()会加载服务提供器缓存文件services.php,如果框架是第一次启动时没有这个文件的,或者是缓存文件中的providers数组项与config/app.php里的providers数组项不一致都会编译生成services.php。

//判断是否需要编译生成services文件
public function shouldRecompile($manifest, $providers)
{
    return is_null($manifest) || $manifest['providers'] != $providers;
}

//编译生成文件的具体过程
protected function compileManifest($providers)
{
    $manifest = $this->freshManifest($providers);
    foreach ($providers as $provider) {
        $instance = $this->createProvider($provider);
        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);
}


protected function freshManifest(array $providers)
{
    return ['providers' => $providers, 'eager' => [], 'deferred' => []];
}
  • 缓存文件中 providers 放入了所有自定义和框架核心的服务。

  • 如果服务提供器是需要立即注册的,那么将会放入缓存文件中 eager 数组中。

  • 如果服务提供器是延迟加载的,那么其函数 provides() 通常会提供服务别名,这个服务别名通常是向服务容器中注册的别名,别名将会放入缓存文件的 deferred 数组中,与真正要注册的服务提供器组成一个键值对。

  • 延迟加载若由 event 事件激活,那么可以在 when 函数中写入事件类,并写入缓存文件的 when 数组中。

生成的缓存文件内容如下:

array (
    'providers' => 
    array (
      0 => 'Illuminate\\Auth\\AuthServiceProvider',
      1 => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
      ...
    ),

    'eager' => 
    array (
      0 => 'Illuminate\\Auth\\AuthServiceProvider',
      1 => 'Illuminate\\Cookie\\CookieServiceProvider',
      ...
    ),

    'deferred' => 
    array (
      'Illuminate\\Broadcasting\\BroadcastManager' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
      'Illuminate\\Contracts\\Broadcasting\\Factory' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
      ...
    ),

    'when' => 
    array (
      'Illuminate\\Broadcasting\\BroadcastServiceProvider' => 
      array (
      ),
      ...
)

事件触发时注册延迟服务提供器

延迟服务提供器除了利用 IOC 容器解析服务方式激活,还可以利用 Event 事件来激活:

protected function registerLoadEvents($provider, array $events)
{
    if (count($events) < 1) {
        return;
    }
    $this->app->make('events')->listen($events, function () use ($provider) {
        $this->app->register($provider);
    });
}

即时注册服务提供器

需要即时注册的服务提供器的register方法由Application的register方法里来调用:

class Application extends Container implements ApplicationContract, HttpKernelInterface
{
    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);
        if ($this->booted) {
            $this->bootProvider($provider);
        }
        return $provider;
    }

    public function getProvider($provider)
    {
        $name = is_string($provider) ? $provider : get_class($provider);
        return Arr::first($this->serviceProviders, function ($value) use ($name) {
            return $value instanceof $name;
        });
    }

       public function resolveProvider($provider)
    {
        return new $provider($this);
    }

    protected function markAsRegistered($provider)
    {
        //这个属性在稍后booting服务时会用到
        $this->serviceProviders[] = $provider;
        $this->loadedProviders[get_class($provider)] = true;
       }

    protected function bootProvider(ServiceProvider $provider)
    {
        if (method_exists($provider, 'boot')) {
            return $this->call([$provider, 'boot']);
        }
    }
}

可以看出,服务提供器的注册过程:

kimi.ai
kimi.ai

Kimi.ai 是月之暗面(Moonshot AI)公司推出的AI智能聊天机器人,能进行智能闲聊、解答问题,提供生活AI助手服务等。

下载
  • 判断当前服务提供器是否被注册过,如注册过直接返回对象

  • 解析服务提供器

  • 调用服务提供器的 register 函数

  • 标记当前服务提供器已经注册完毕

  • 若框架已经加载注册完毕所有的服务容器,那么就启动服务提供器的 boot 函数,该函数由于是 call 调用,所以支持依赖注入。

服务解析时注册延迟服务提供器

延迟服务提供器首先需要添加到 Application 中

public function addDeferredServices(array $services)
{
    $this->deferredServices = array_merge($this->deferredServices, $services);
}

我们之前说过,延迟服务提供器的激活注册有两种方法:事件与服务解析。

当特定的事件被激发后,就会调用 Application 的 register 函数,进而调用服务提供器的 register 函数,实现服务的注册。

当利用 Ioc 容器解析服务名时,例如解析服务名 BroadcastingFactory:

class BroadcastServiceProvider extends ServiceProvider
{
    protected $defer = true;
    
    public function provides()
    {
        return [
            BroadcastManager::class,
            BroadcastingFactory::class,
            BroadcasterContract::class,
        ];
    }
}

在Application的make方法里会通过别名BroadcastingFactory查找是否有对应的延迟注册的服务提供器,如果有的话那么
就先通过registerDeferredProvider方法注册服务提供器。

class Application extends Container implements ApplicationContract, HttpKernelInterface
{
    public function make($abstract)
    {
        $abstract = $this->getAlias($abstract);
        if (isset($this->deferredServices[$abstract])) {
            $this->loadDeferredProvider($abstract);
        }
        return parent::make($abstract);
    }

    public function loadDeferredProvider($service)
    {
        if (! isset($this->deferredServices[$service])) {
            return;
        }
        $provider = $this->deferredServices[$service];
        if (! isset($this->loadedProviders[$provider])) {
            $this->registerDeferredProvider($provider, $service);
        }
    }
}

由 deferredServices 数组可以得知,BroadcastingFactory 为延迟服务,接着程序会利用函数 loadDeferredProvider 来加载延迟服务提供器,调用服务提供器的 register 函数,若当前的框架还未注册完全部服务。那么将会放入服务启动的回调函数中,以待服务启动时调用:

public function registerDeferredProvider($provider, $service = null)
{
    if ($service) {
        unset($this->deferredServices[$service]);
    }
    $this->register($instance = new $provider($this));
    if (! $this->booted) {
        $this->booting(function () use ($instance) {
            $this->bootProvider($instance);
        });
    }
}

还是拿服务提供器BroadcastServiceProvider来举例:

class BroadcastServiceProvider extends ServiceProvider
{
    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();
        });
        //将BroadcastingFactory::class设置为BroadcastManager::class的别名
        $this->app->alias(
            BroadcastManager::class, BroadcastingFactory::class
        );
    }
    public function provides()
    {
        return [
            BroadcastManager::class,
            BroadcastingFactory::class,
            BroadcasterContract::class,
        ];
    }
}

函数 register 为类 BroadcastingFactory 向 服务容器绑定了特定的实现类 BroadcastManagerApplication中的 make 函数里执行parent::make($abstract) 通过服务容器的make就会正确的解析出服务 BroadcastingFactory

因此函数 provides() 返回的元素一定都是 register() 向 服务容器中绑定的类名或者别名。这样当我们利用App::make() 解析这些类名的时候,服务容器才会根据服务提供器的 register() 函数中绑定的实现类,正确解析出服务功能。

启动Application

Application的启动由类 \Illuminate\Foundation\Bootstrap\BootProviders 负责:

class BootProviders
{
    public function bootstrap(Application $app)
    {
        $app->boot();
    }
}
class Application extends Container implements ApplicationContract, HttpKernelInterface
{
    public function boot()
    {
        if ($this->booted) {
            return;
        }
        $this->fireAppCallbacks($this->bootingCallbacks);
        array_walk($this->serviceProviders, function ($p) {
            $this->bootProvider($p);
        });
        $this->booted = true;
        $this->fireAppCallbacks($this->bootedCallbacks);
    }
    
    protected function bootProvider(ServiceProvider $provider)
    {
        if (method_exists($provider, 'boot')) {
            return $this->call([$provider, 'boot']);
        }
    }
}

引导应用Application的serviceProviders属性中记录的所有服务提供器,就是依次调用这些服务提供器的boot方法,引导完成后$this->booted = true 就代表应用Application正式启动了,可以开始处理请求了。这里额外说一句,之所以等到所有服务提供器都注册完后再来进行引导是因为有可能在一个服务提供器的boot方法里调用了其他服务提供器注册的服务,所以需要等到所有即时注册的服务提供器都register完成后再来boot。

以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注PHP中文网!

相关推荐:

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
Python 自然语言处理(NLP)基础与实战
Python 自然语言处理(NLP)基础与实战

本专题系统讲解 Python 在自然语言处理(NLP)领域的基础方法与实战应用,涵盖文本预处理(分词、去停用词)、词性标注、命名实体识别、关键词提取、情感分析,以及常用 NLP 库(NLTK、spaCy)的核心用法。通过真实文本案例,帮助学习者掌握 使用 Python 进行文本分析与语言数据处理的完整流程,适用于内容分析、舆情监测与智能文本应用场景。

9

2026.01.27

拼多多赚钱的5种方法 拼多多赚钱的5种方法
拼多多赚钱的5种方法 拼多多赚钱的5种方法

在拼多多上赚钱主要可以通过无货源模式一件代发、精细化运营特色店铺、参与官方高流量活动、利用拼团机制社交裂变,以及成为多多进宝推广员这5种方法实现。核心策略在于通过低成本、高效率的供应链管理与营销,利用平台社交电商红利实现盈利。

105

2026.01.26

edge浏览器怎样设置主页 edge浏览器自定义设置教程
edge浏览器怎样设置主页 edge浏览器自定义设置教程

在Edge浏览器中设置主页,请依次点击右上角“...”图标 > 设置 > 开始、主页和新建标签页。在“Microsoft Edge 启动时”选择“打开以下页面”,点击“添加新页面”并输入网址。若要使用主页按钮,需在“外观”设置中开启“显示主页按钮”并设定网址。

13

2026.01.26

苹果官方查询网站 苹果手机正品激活查询入口
苹果官方查询网站 苹果手机正品激活查询入口

苹果官方查询网站主要通过 checkcoverage.apple.com/cn/zh/ 进行,可用于查询序列号(SN)对应的保修状态、激活日期及技术支持服务。此外,查找丢失设备请使用 iCloud.com/find,购买信息与物流可访问 Apple (中国大陆) 订单状态页面。

111

2026.01.26

npd人格什么意思 npd人格有什么特征
npd人格什么意思 npd人格有什么特征

NPD(Narcissistic Personality Disorder)即自恋型人格障碍,是一种心理健康问题,特点是极度夸大自我重要性、需要过度赞美与关注,同时极度缺乏共情能力,背后常掩藏着低自尊和不安全感,影响人际关系、工作和生活,通常在青少年时期开始显现,需由专业人士诊断。

5

2026.01.26

windows安全中心怎么关闭 windows安全中心怎么执行操作
windows安全中心怎么关闭 windows安全中心怎么执行操作

关闭Windows安全中心(Windows Defender)可通过系统设置暂时关闭,或使用组策略/注册表永久关闭。最简单的方法是:进入设置 > 隐私和安全性 > Windows安全中心 > 病毒和威胁防护 > 管理设置,将实时保护等选项关闭。

6

2026.01.26

2026年春运抢票攻略大全 春运抢票攻略教你三招手【技巧】
2026年春运抢票攻略大全 春运抢票攻略教你三招手【技巧】

铁路12306提供起售时间查询、起售提醒、购票预填、候补购票及误购限时免费退票五项服务,并强调官方渠道唯一性与信息安全。

111

2026.01.26

个人所得税税率表2026 个人所得税率最新税率表
个人所得税税率表2026 个人所得税率最新税率表

以工资薪金所得为例,应纳税额 = 应纳税所得额 × 税率 - 速算扣除数。应纳税所得额 = 月度收入 - 5000 元 - 专项扣除 - 专项附加扣除 - 依法确定的其他扣除。假设某员工月工资 10000 元,专项扣除 1000 元,专项附加扣除 2000 元,当月应纳税所得额为 10000 - 5000 - 1000 - 2000 = 2000 元,对应税率为 3%,速算扣除数为 0,则当月应纳税额为 2000×3% = 60 元。

31

2026.01.26

oppo云服务官网登录入口 oppo云服务登录手机版
oppo云服务官网登录入口 oppo云服务登录手机版

oppo云服务https://cloud.oppo.com/可以在云端安全存储您的照片、视频、联系人、便签等重要数据。当您的手机数据意外丢失或者需要更换手机时,可以随时将这些存储在云端的数据快速恢复到手机中。

91

2026.01.26

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Node.js 教程
Node.js 教程

共57课时 | 9.5万人学习

CSS3 教程
CSS3 教程

共18课时 | 4.9万人学习

Rust 教程
Rust 教程

共28课时 | 4.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号