Laravel 源码分析---Application 对 ServiceProvider 的管理与使用

标签:laravel 源码分析 Application ServiceProvider


我们在上一篇文章中看了 ServiceProvider 的基本源码(见文章Laravel 源码分析---ServiceProvider)。但是 Laravel 框架中 ServiceProvider 的实例化、注册、启动、延迟加载等管理功能都是由框架的核心类 Application 来完成的。Application 是框架最核心的类,管理整个框架的启动、运行以及整个生命周期,并通过 ServiceProvider 将其他功能的??樵厝肟蚣?。Application 是 Container 类的子类,所以也管理者框架中其他类的实例化、存储等功能。今天我们来看一下 Application 类中和 ServiceProvider 管理相关的功能。

Application 管理功能概述

Application 对 ServiceProvider 的管理主要有以下方面,首先根据配置文件获得项目中用到的 ServiceProvider,解析分类,将相关数据存入 Application 的属性进行管理和使用,并缓存相关数据。根据 ServiceProvider 是否延迟加载,进行不同的操作。对于非延迟加载的 ServiceProvider,在 Application 解析 ServiceProvider 配置数据的时候就自动注册(运行 register 方法),在 Application 启动的时候启动 ServiceProvider(运行 boot 方法)。对于延迟加载的 ServiceProvider ,在解析 ServiceProvider 配置的时候,监听触发加载 ServiceProvider 的事件;并重载 Application 的 make 方法,如果 Application 要实例化的对象是延迟 ServiceProvider 提供的服务对象的话,先加载服务对象对应的 ServiceProvider。

Application 管理 ServiceProvider 主要结构

我们先来看一下 Application 管理 ServiceProvider 所使用的主要结构

namespace Illuminate\Foundation;

class Application extends Container{
    /**
     * Indicates if the application has "booted".
     * 标识 Application 是否启动
     * @var bool
     */
    protected $booted = false;

    /**
     * All of the registered service providers.
     * 所有已经注册过的 ServiceProvider 的对象实例, 数字索引数组, 值为 ServiceProvider 的实例
     * @var array
     */
    protected $serviceProviders = [];

    /**
     * The names of the loaded service providers.
     * 标记加载过的 ServiceProvider, 键为ServiceProvider 的类名,值是一个布尔值。加载指 ServiceProvider 已经实例化并且调用过 register 和 boot 方法
     * @var array
     */
    protected $loadedProviders = [];

    /**
     * The deferred services and their providers.
     * 延迟加载服务的类名与注册这个类的 ServiceProvider, 键为服务的类名,值为对应的 ServiceProvider
     * @var array
     */
    protected $deferredServices = [];
    
    
    /**
     * Register all of the configured providers.
     * 将所有的配置的 ServiceProvider 信息载入框架。注册非延迟加载的 ServiceProvider,配置延迟加载的 ServiceProvider 与其载入的??槎韵蟮挠成涔叵担ㄅ渲?$this->deferredServices 变量),配置触发加载延迟 ServiceProvider 的事件
     * @return void
     */
    public function registerConfiguredProviders(){}
    
    
    /**
     * Register a service provider with the application.
     * 注册 $provider 配置的??榈娇蚣?。实例化对应的 ServiceProvider 类,并运行 register 和 boot 方法
     * @param  \Illuminate\Support\ServiceProvider|string  $provider
     * @param  array  $options
     * @param  bool   $force(是否忽略是否注册过强制注册)
     * @return \Illuminate\Support\ServiceProvider
     */
    public function register($provider, $options = [], $force = false){}

    /**
     * Get the registered service provider instance if it exists.
     * 返回 $provider 变量对应的已经注册过的 ServiceProvider 实例
     * @param  \Illuminate\Support\ServiceProvider|string  $provider
     * @return \Illuminate\Support\ServiceProvider|null
     */
    public function getProvider($provider){}

    /**
     * Resolve a service provider instance from the class name.
     * 实例化 $provider 对应的 ServiceProvider 实例
     * @param  string  $provider
     * @return \Illuminate\Support\ServiceProvider
     */
    public function resolveProviderClass($provider){}
    
    /**
     * Mark the given provider as registered.
     * 标识 $provider 对应的 ServiceProvider 已经注册过
     * @param  \Illuminate\Support\ServiceProvider  $provider
     * @return void
     */
    protected function markAsRegistered($provider){}
    
    /**
     * Load and boot all of the remaining deferred providers.
     * 加载所有的延迟加载的 ServiceProvider 
     * @return void
     */
    public function loadDeferredProviders(){}
    
    /**
     * Load the provider for a deferred service.
     * 加载 $service 对应的延迟加载的 ServiceProvider
     * @param  string  $service
     * @return void
     */
    public function loadDeferredProvider($service){}

/**
     * Register a deferred provider and service.
     * 注册并启动一个延迟加载的 ServiceProvider
     * @param  string  $provider
     * @param  string  $service
     * @return void
     */
    public function registerDeferredProvider($provider, $service = null){}
    
    /**
     * Resolve the given type from the container.
     * 重载 Container 的 make 方法,使得 Application 可以根据要实例化服务的类名自动加载其对应的延迟加载的 ServiceProvider
     * (Overriding Container::make)
     *
     * @param  string  $abstract
     * @param  array   $parameters
     * @return mixed
     */
    public function make($abstract, array $parameters = []){}
    
     /**
     * Boot the application's service providers.
     * 启动这个 application 的 ServiceProvider
     * @return void
     */
    public function boot(){}
    
    /**
     * Boot the given service provider.
     * 启动 ServiceProvider。运行 ServiceProvider 对象的 boot 方法
     * @param  \Illuminate\Support\ServiceProvider  $provider
     * @return mixed
     */
    protected function bootProvider(ServiceProvider $provider){}
}

以上方法是 Application 管理 ServiceProvider 提供的具体属性和方法,接下来我们来看具体功能的实现

ServiceProvider 配置的加载

我们先来看 ServiceProvider 配置的加载

class Application extends Container{
    /**
     * Register all of the configured providers.
     * 将所有的配置的 ServiceProvider 信息载入框架
     * @return void
     */
    public function registerConfiguredProviders()
    {
        //返回缓存 manifest 数据的缓存文件路径
        $manifestPath = $this->getCachedServicesPath();
        
        //导入框架配置的 ServiceProvider 信息,并解析到 Application 对象中。由 load 方法实现
        (new ProviderRepository($this, new Filesystem, $manifestPath))
            ->load($this->config['app.providers']);
    }
    
        /**
     * Get the path to the cached services.php file.
     *
     * @return string
     */
    public function getCachedServicesPath()
    {
        return $this->bootstrapPath().'/cache/services.php';
    }
    
    /**
     * Get the path to the bootstrap directory.
     *
     * @return string
     */
    public function bootstrapPath()
    {
        return $this->basePath.DIRECTORY_SEPARATOR.'bootstrap';
    }
}

通过以上代码我们知道,框架会缓存 manifest 数据,其包含了 框架解析过的 ServiceProvider 数据。我们先来看一下缓存的数据格式(位于 /bootstrap/cache/services.php 文件)。

return array (
  'providers' => 
  array (
    0 => 'Illuminate\\Auth\\AuthServiceProvider',
    1 => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
    ...
    28 => 'App\\Providers\\ImageServiceProvider',
  ),
  'eager' => 
  array (
    0 => 'Illuminate\\Auth\\AuthServiceProvider',
    1 => 'Illuminate\\Cookie\\CookieServiceProvider',
    ...
    15 => 'App\\Providers\\ImageServiceProvider',
  ),
  'deferred' => 
  array (
    'Illuminate\\Broadcasting\\BroadcastManager' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
    'Illuminate\\Contracts\\Broadcasting\\Factory' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
    ...
    'command.ide-helper.models' => 'Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider',
  ),
  'when' => 
  array (
    'Illuminate\\Broadcasting\\BroadcastServiceProvider' => 
    array (
    ),
    'Illuminate\\Bus\\BusServiceProvider' => 
    array (
    ),
    ...
    'Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider' => 
    array (
    ),
  ),
);

在上面数据中 providers 表示所有的 ServiceProvider 数组;eager 表示非延迟加载的 ServiceProvider 数组;deferred 表示延迟加载的服务类与其 ServiceProvider 的映射关系;when 表示触发加载延迟加载 ServiceProvider 的事件数组。

接下来我们来看框架如何将 $providers 解析到 Application 里面,在 Illuminate\Foundation\ProviderRepository 类中的 load 方法实现

namespace Illuminate\Foundation;

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

class ProviderRepository
{
    /**
     * Register the application service providers.
     * 导入 $providers 的信息
     * @param  array  $providers
     * @return void
     */
    public function load(array $providers)
    {
        //载入 manifest 的信息,即缓存文件里面的数据。如果缓存文件存在的话,加载缓存文件并将数据返回
        $manifest = $this->loadManifest();

        // 判断是否需要重新编译 $manifest,如果需要,根据编译生成新的 $manifest 并缓存
        if ($this->shouldRecompile($manifest, $providers)) {
            $manifest = $this->compileManifest($providers);
        }
        
        //注册触发加载延迟加载的 $provider 的事件
        foreach ($manifest['when'] as $provider => $events) {
            $this->registerLoadEvents($provider, $events);
        }

        //注册非延迟加载的$provider
        foreach ($manifest['eager'] as $provider) {
            $this->app->register($this->createProvider($provider));
        }
        
        //将延迟加载的 ServiceProvider 与其服务的对应关系添加到 Application 对象
        $this->app->addDeferredServices($manifest['deferred']);
    }


    /**
     * Load the service provider manifest data.
     * 载入缓存文件里面的 ServiceProvider 的 manifest 数据
     * @return array|null
     */
    public function loadManifest()
    {
       
        if ($this->files->exists($this->manifestPath)) {
            $manifest = $this->files->getRequire($this->manifestPath);

            if ($manifest) {
                return array_merge(['when' => []], $manifest);
            }
        }
    }
    
     /**
     * Determine if the manifest should be compiled.
     * 根据缓存的 $manifest 数据和我们要加载的 $providers 判断是否应该重新编译 $manifest 数据
     * @param  array  $manifest
     * @param  array  $providers
     * @return bool
     */
    public function shouldRecompile($manifest, $providers)
    {
        return is_null($manifest) || $manifest['providers'] != $providers;
    }

    /**
     * Compile the application manifest file.
     * 重新编译 manifest 数据
     * @param  array  $providers
     * @return array
     */
    protected function compileManifest($providers)
    {
        //根据 $providers 创建新的 manifest 数据
        $manifest = $this->freshManifest($providers);

        foreach ($providers as $provider) {
            $instance = $this->createProvider($provider);
            //根据 ServiceProvider 是否延迟加载,在 $manifest 里面进行标记
            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);
    }

    /**
     * Create a fresh service manifest data structure.
     * 根据 $providers 创建新的 manifest 数据
     * @param  array  $providers
     * @return array
     */
    protected function freshManifest(array $providers)
    {
        return ['providers' => $providers, 'eager' => [], 'deferred' => []];
    }
}

通过上面的代码,我们将应用配置的 ServiceProvider (app.providers)解析载入进 Application。

非延迟加载的 ServiceProvider 的管理

分析完 ServiceProvider 配置的加载和解析,我们接下来看 Application 对非延迟加载 ServiceProvider 的管理

namespace Illuminate\Foundation;

class Application extends Container{
    
    /**
     * Register a service provider with the application.
     * 注册 ServiceProvider 配置的模块到框架。实例化对应的 ServiceProvider 类,并运行 register 和 boot 方法
     * @param  \Illuminate\Support\ServiceProvider|string  $provider
     * @param  array  $options
     * @param  bool   $force(是否忽略是否注册过强制注册)
     * @return \Illuminate\Support\ServiceProvider
     */
    public function register($provider, $options = [], $force = false)
    {
        //如果 ServiceProvider 已经注册过,并且不需要强制重新注册,直接返回已经注册过的实例
        if (($registered = $this->getProvider($provider)) && ! $force) {
            return $registered;
        }

        //如果 $provider 是字符串,根据字符串实例化对应的 ServiceProvider 对象
        if (is_string($provider)) {
            $provider = $this->resolveProviderClass($provider);
        }
        
        //调用 $provider 的 register 方法
        if (method_exists($provider, 'register')) {
            $provider->register();
        }

        ...
        
        //标识 $provider 已经注册过
        $this->markAsRegistered($provider);

        //如果 Application 已经启动,启动这个 $provider
        if ($this->booted) {
            $this->bootProvider($provider);
        }

        return $provider;
    }
    
     /**
     * Get the registered service provider instance if it exists.
     * 返回 $provider 变量对应的已经注册过的 ServiceProvider 实例
     * @param  \Illuminate\Support\ServiceProvider|string  $provider
     * @return \Illuminate\Support\ServiceProvider|null
     */
    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;
        });
    }
    
    /**
     * Resolve a service provider instance from the class name.
     * 实例化 $provider 对应的 ServiceProvider 实例
     * @param  string  $provider
     * @return \Illuminate\Support\ServiceProvider
     */
    public function resolveProviderClass($provider)
    {
        return new $provider($this);
    }
    
     /**
     * Mark the given provider as registered.
     * 标识 $provider 对应的 ServiceProvider 已经注册过
     * @param  \Illuminate\Support\ServiceProvider  $provider
     * @return void
     */
    protected function markAsRegistered($provider)
    {
        $this['events']->fire($class = get_class($provider), [$provider]);

        $this->serviceProviders[] = $provider;

        $this->loadedProviders[$class] = true;
    }

      /**
     * Boot the given service provider.
     * 启动 $provider
     * @param  \Illuminate\Support\ServiceProvider  $provider
     * @return mixed
     */
    protected function bootProvider(ServiceProvider $provider)
    {
        if (method_exists($provider, 'boot')) {
            return $this->call([$provider, 'boot']);
        }
    }
    
    /**
     * Boot the application's service providers.
     * 启动这个 application 的 ServiceProvider
     * @return void
     */
    public function boot()
    {
        if ($this->booted) {
            return;
        }

        //触发启动 Application 前的回调
        $this->fireAppCallbacks($this->bootingCallbacks);

        //array_walk函数对数组中的每个元素应用用户自定义函数。在自定义函数中,第一个参数是值,第二个参数是键。
        array_walk($this->serviceProviders, function ($p) {
            $this->bootProvider($p);
        });

        $this->booted = true;
        //触发启动 Application 后的回调
        $this->fireAppCallbacks($this->bootedCallbacks);
    }
}

延迟加载的 ServiceProvider 管理

知道了 Application 如何对费延迟的 ServiceProvider 进行管理,我们来看 Application 对延迟加载的 ServiceProvider 的管理

namespace Illuminate\Foundation;

class Application extends Container{

    /**
     * Load and boot all of the remaining deferred providers.
     * 加载所有的延迟加载的 ServiceProvider
     * @return void
     */
    public function loadDeferredProviders()
    {
        foreach ($this->deferredServices as $service => $provider) {
            $this->loadDeferredProvider($service);
        }

        $this->deferredServices = [];
    }
    
    /**
     * Load the provider for a deferred service.
     * 加载 $service 对应的 ServiceProvider
     * @param  string  $service
     * @return void
     */
    public function loadDeferredProvider($service)
    {
        if (! isset($this->deferredServices[$service])) {
            return;
        }
        
        //获取 $service 对应的延迟加载 $provider
        $provider = $this->deferredServices[$service];

        //如果 $provider 没有加载,延迟加载 $provider
        if (! isset($this->loadedProviders[$provider])) {
            $this->registerDeferredProvider($provider, $service);
        }
    }
    
    /**
     * Register a deferred provider and service.
     * 注册并启动一个延迟加载的 $provider
     * @param  string  $provider
     * @param  string  $service
     * @return void
     */
    public function registerDeferredProvider($provider, $service = null)
    {
        //从延迟加载的服务中删除 $service,因为其将要被加载注册
        if ($service) {
            unset($this->deferredServices[$service]);
        }
        
        //注册 $provider。如果 Application 已经启动,同时调用 $provider 的 boot 方法
        $this->register($instance = new $provider($this));
        
        //如果 Application 没有启动,则添加一个启动事件,调用 $provider 的 boot 方法
        if (! $this->booted) {
            $this->booting(function () use ($instance) {
                $this->bootProvider($instance);
            });
        }
    }
    
    /**
     * Resolve the given type from the container.
     * 重载 Container 的 make 方法,使得 Application 可以根据要实例化服务的类名自动加载延迟加载的ServiceProvider
     * (Overriding Container::make)
     *
     * @param  string  $abstract
     * @param  array   $parameters
     * @return mixed
     */
    public function make($abstract, array $parameters = [])
    {
        $abstract = $this->getAlias($abstract);
        
        //如果 $abstract 是延迟加载的 ServiceProvider 提供的对象,则载入 $abstract 对应的 ServiceProvider
        if (isset($this->deferredServices[$abstract])) {
            $this->loadDeferredProvider($abstract);
        }
        return parent::make($abstract, $parameters);
    }
}

总结

至此,我们已经分析完了 Application 管理 ServiceProvider 相关功能的源码。Application 对 ServiceProvider 的管理是 Application 的重要功能之一,阅读这部分的源码对于我们理解 ServiceProvider 的功能,了解框架的设计思想,以及掌握框架的启动过程都具有很大的帮助。

最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,100评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,308评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,718评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,275评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,376评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,454评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,464评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,248评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,686评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,974评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,150评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,817评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,484评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,140评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,374评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,012评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,041评论 2 351

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,644评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,796评论 6 342
  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,353评论 8 265
  • A:你没怎么变,还是那么漂亮, B:你变化真大,现在抱我不会顶到我了。
    RainyCai阅读 235评论 0 1
  • 成语故事是我国历史的一部分,成语是历史的积淀,每一个成语的背后都有一个含义深远的故事,是我国几千年以来人民智慧的结...
    阡陌望你阅读 288评论 0 1