laravel里所谓的provider服务提供者,其实是对某一类功能进行整合,与做一些使用前的初始化引导工作。laravel里的服务提供者也分为,系统核心服务提供者、与一般系统服务提供者。例如上一篇博文里介绍的,最早在application中进行注册的event、log、routing这些就是系统的核心服务,laravel的初始化需要他们。那么现在就先来看一下provider的运行流程。

     protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this)); $this->register(new LogServiceProvider($this)); $this->register(new RoutingServiceProvider($this));
}

其他的serviceProvider则是指config/app.php中providers数组所配置的provider了,基本都是些laravel系统提供的工具型provider

     'providers' => [

         /*
* Laravel Framework Service Providers...
*/
Illuminate\Auth\AuthServiceProvider::class,
Illuminate\Broadcasting\BroadcastServiceProvider::class,
Illuminate\Bus\BusServiceProvider::class,
Illuminate\Cache\CacheServiceProvider::class,
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
Illuminate\Cookie\CookieServiceProvider::class,
Illuminate\Database\DatabaseServiceProvider::class,
Illuminate\Encryption\EncryptionServiceProvider::class,
Illuminate\Filesystem\FilesystemServiceProvider::class,
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
Illuminate\Hashing\HashServiceProvider::class,
Illuminate\Mail\MailServiceProvider::class,
Illuminate\Notifications\NotificationServiceProvider::class,
Illuminate\Pagination\PaginationServiceProvider::class,
Illuminate\Pipeline\PipelineServiceProvider::class,
Illuminate\Queue\QueueServiceProvider::class,
Illuminate\Redis\RedisServiceProvider::class,
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
Illuminate\Session\SessionServiceProvider::class,
Illuminate\Translation\TranslationServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class,
//Maatwebsite\Excel\ExcelServiceProvider::class, 这个是我自己测试的时候加的 /*
* Package Service Providers...
*/ /*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class, ],

那么这些配置中的provider会在什么时候加载呢?上一篇博文中介绍的当$kernel对象通过handle方法传入request时,会执行sendRequestThroughRouter方法,这个方法中的bootstrap方法会加载laravel系统初始化所需的对象并运行,其中RegisterProviders类便是用来注册刚刚config文件内所记录的provider的

     public function bootstrap()
{
if (! $this->app->hasBeenBootstrapped()) {
$this->app->bootstrapWith($this->bootstrappers());
}
} protected $bootstrappers = [
\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
\Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
\Illuminate\Foundation\Bootstrap\HandleExceptions::class,
//注册facade门面类
\Illuminate\Foundation\Bootstrap\RegisterFacades::class,
//注册provider
\Illuminate\Foundation\Bootstrap\RegisterProviders::class,
//引导provider执行其中boot方法内的代码
\Illuminate\Foundation\Bootstrap\BootProviders::class,
];

这几个文件的内容都很简单,并且都是调用了application中的方法

     public function bootstrapWith(array $bootstrappers)
{
$this->hasBeenBootstrapped = true; foreach ($bootstrappers as $bootstrapper) {
$this['events']->fire('bootstrapping: '.$bootstrapper, [$this]);
//make了刚刚传入的$bootstrappers数组,并执行了其中的bootstrap方法,暂且只看provider
$this->make($bootstrapper)->bootstrap($this); $this['events']->fire('bootstrapped: '.$bootstrapper, [$this]);
}
} //Illuminate\Foundation\Bootstrap\RegisterProviders.php
public function bootstrap(Application $app)
{
$app->registerConfiguredProviders();
} //Illuminate\Foundation\Bootstrap\BootProviders.php
public function bootstrap(Application $app)
{
$app->boot();
}

这里绕了一大圈,最终还是回到了application文件中,还记得上一篇博文中介绍的registerConfiguredProviders方法吗?

application的registerConfiguredProviders()方法对服务提供者进行了注册,通过框架的文件系统收集了配置文件中的各种provicers并转化成数组,在G:\wamp64\www\test\laravel55\vendor\laravel\framework\src\Illuminate\Foundation\ProviderRepository.php类的load方法中进行加载,但最终还是会在application类中的register()方法中通过字符串的方式new出对象,在执行provider中自带的register()方法

     public function registerConfiguredProviders()
{
//laravel的集合类,将之前初始化时存入的config中的数组取出
$providers = Collection::make($this->config['app.providers'])
->partition(function ($provider) {
//并过滤出系统providers
return Str::startsWith($provider, 'Illuminate\\');
});
//之前在registerBaseBindings方法中绑定在PackageManifest类中的providers数组拼接,通过load方法加载它们
$providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);
//new了provider库,传入服务容器、文件系统操作对象、与之前缓存的服务提供者路径
(new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
->load($providers->collapse()->toArray());
}
  //Illuminate\Foundation\ProviderRepository.php

    public function load(array $providers)
{
// 查看bootstrap/cache/services.php有没有这个缓存文件
// 第一次启动时是没有的
$manifest = $this->loadManifest();
// 开始没有这个缓存文件,那就把$providers[ ]里的值
if ($this->shouldRecompile($manifest, $providers)) {
// 然后根据$providers[ ]编译出services.php这个缓存文件
$manifest = $this->compileManifest($providers);
} foreach ($manifest['when'] as $provider => $events) {
// 注册包含有事件监听的service provider
// 包含有事件监听的service provider都要有when()函数返回
$this->registerLoadEvents($provider, $events);
} foreach ($manifest['eager'] as $provider) {
// 把'eager'字段中service provider注册进容器中,
// 即遍历每一个service provider,调用其中的register()方法
// 向容器中注册具体的服务
$this->app->register($this->createProvider($provider));
} // 注册延迟的service provider,
// deferred的service provider, 一是要设置$defer = true,二是要提供provides()方法返回绑定到容器中服务的名称
$this->app->addDeferredServices($manifest['deferred']);
}

而boot操作就更简单了

     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);
//使每个provider运行bootProvider,$p为provider
array_walk($this->serviceProviders, function ($p) {
$this->bootProvider($p);
});
//改变引导状态
$this->booted = true;
//调用引导方法的钩子函数
$this->fireAppCallbacks($this->bootedCallbacks);
} protected function bootProvider(ServiceProvider $provider)
{
//判断传入的provier,运行它们的boot方法完成引导
if (method_exists($provider, 'boot')) {
return $this->call([$provider, 'boot']);
}
}

到这里,provider通过register注册在了服务容器内,provider的初始化工作也由boot函数完成,这个provider所提供的对象便可以直接拿来使用了。

还记得学习laravel框架使用方式的时候,文档建议我们把所有在应用初始化时需要完成的事情,都写在AppServiceProvider的boot方法里吗?看到这里我们能明白作为系统核心prvider的app是最早被加载的,因此也充当了一个钩子函数的角色。

在了解了provider的注册流程之后,就可以自己来自定义一个provider了。我们上一篇博客里还有一个契约的概念没有说明,这里简单举一个小例子来说明。

1、新建一个接口。

 namespace App\Contracts;

 interface Test
{
public function doing();
}

2、新建两个接口的实现

 namespace App\Services;

 use App\Contracts\Test;

 class TestService implements Test
{
public function doing()
{
echo 'this is TestService';
}
} namespace App\Services; use App\Contracts\Test; class SecondTestService implements Test
{
public function doing()
{
echo 'this is SecondTestService';
}
}

3、新建一个provider,可使用artisan 命令行   php artisan make:provider TestServiceProvider 创建一个provider,契约上下文就在这个地方进行绑定。上一篇博文里讲到make方法的时候,容器在解析类的时候,有一个获取上下文的步骤,所要获取的concrete就是在provider中通过when方法绑定的类了,不过可惜这个绑定只能具体到类,不能具体到方法。

 namespace App\Providers;

 use Illuminate\Support\ServiceProvider;

 class TestServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
//
} public function register()
{
$this->app->bind('App\Contracts\Test', 'App\services\TestService');
//重点在于when方法确定运行环境,也就是执行上下文,needs为make所需的abstract类名或别名,give所传入的参数则是实际调用的实现类了
$this->app->when('App\Http\Controllers\IndexController')
->needs('App\Contracts\Test')
->give('App\Services\SecondTestService');
}
}

4、在config/app.php文件的providers数组中添加刚刚生成的provider

     'providers' => [

         /*
* Laravel Framework Service Providers...
*/
Illuminate\Auth\AuthServiceProvider::class,
Illuminate\Broadcasting\BroadcastServiceProvider::class,
Illuminate\Bus\BusServiceProvider::class,
Illuminate\Cache\CacheServiceProvider::class,
Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
Illuminate\Cookie\CookieServiceProvider::class,
Illuminate\Database\DatabaseServiceProvider::class,
Illuminate\Encryption\EncryptionServiceProvider::class,
Illuminate\Filesystem\FilesystemServiceProvider::class,
Illuminate\Foundation\Providers\FoundationServiceProvider::class,
Illuminate\Hashing\HashServiceProvider::class,
Illuminate\Mail\MailServiceProvider::class,
Illuminate\Notifications\NotificationServiceProvider::class,
Illuminate\Pagination\PaginationServiceProvider::class,
Illuminate\Pipeline\PipelineServiceProvider::class,
Illuminate\Queue\QueueServiceProvider::class,
Illuminate\Redis\RedisServiceProvider::class,
Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
Illuminate\Session\SessionServiceProvider::class,
Illuminate\Translation\TranslationServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
Illuminate\View\ViewServiceProvider::class, /*
* Package Service Providers...
*/ /*
* Application Service Providers...
*/
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,
//添加刚刚生成的provider
App\Providers\TestServiceProvider::class,
],

5、在IndexController文件中添加执行代码

 namespace App\Http\Controllers;

 use App\Contracts\Test;

 class IndexController extends Controller
{ public function __construct(Test $test)
{
$this->test = $test;
} public function index(Test $test)
{
app()->make('App\Contracts\Test')->doing(); echo '<br>';
//只有通过构造方法进行自动加载依赖的方式才能触发契约的when绑定
$this->test->doing();
    
echo '<br>';
    //因为laravel中的上下文绑定只能具体到类,所以这里的$test实例依然为普通绑定
$test->doing(); }
}

运行后,会发现只有通过构造函数实例化的对象,才能触发额外的分支绑定。通过这个小例子,我们可以很清楚的理解契约了,就是在不同情况下的一个对接口的动态调用,算是java中多态和策略模式的另一实现方式。使用了这种实现方式,可以使我们在开发过程中的代码更加灵活,在改变实现方式的时候,只需改变provider中的实现绑定,即可快速实现需求变更。

可能有人会发现我们的demo在执行时需要显示的使用make方法,一点也不优雅,这和laravel所宣扬的思想还是有差距。那是因为还有一个facade门面功能还没有用上,后面我们会来探寻一下facade到底是个什么东西。

laravel5.5源码笔记(二、服务提供者provider)的更多相关文章

  1. jQuery源码笔记(二):定义了一些变量和函数 jQuery = function(){}

    笔记(二)也分为三部分: 一. 介绍: 注释说明:v2.0.3版本.Sizzle选择器.MIT软件许可注释中的#的信息索引.查询地址(英文版)匿名函数自执行:window参数及undefined参数意 ...

  2. Tomcat8源码笔记(二)Bootstrap启动

    TOMCAT源码调试入口是Bootstrap类的main方法,我的启动参数VM: -Dcatalina.home=E:/Tomcat_Source_Code/apache-tomcat-8.0.53- ...

  3. laravel5.5源码笔记(七、数据库初始化)

    laravel中的数据库也是以服务提供者进行初始化的名为DatabaseServiceProvider,在config文件的providers数组中有写.路径为vendor\laravel\frame ...

  4. laravel5.5源码笔记(五、Pipeline管道模式)

    Pipeline管道模式,也有人叫它装饰模式.应该说管道是装饰模式的一个变种,虽然思想都是一样的,但这个是闭包的版本,实现方式与传统装饰模式也不太一样.在laravel的源码中算是一个比较核心的设计模 ...

  5. laravel5.5源码笔记(四、路由)

    今天这篇博文来探索一下laravel的路由.在第一篇讲laravel入口文件的博文里,我们就提到过laravel的路由是在application对象的初始化阶段,通过provider来加载的.这个路由 ...

  6. laravel5.5源码笔记(一、入口应用的初始化)

    laravel的项目入口文件index.php如下 define('LARAVEL_START', microtime(true)); require __DIR__.'/../vendor/auto ...

  7. laravel5.5源码笔记(八、Eloquent ORM)

    上一篇写到Eloquent ORM的基类Builder类,这次就来看一下这些方便的ORM方法是如何转换成sql语句运行的. 首先还是进入\vendor\laravel\framework\src\Il ...

  8. laravel5.5源码笔记(三、门面类facade)

    上次说了provider,那么这次来说说facade 首先是启动的源头,从laravel的kernel类中的$bootstrappers 数组,我们可以看到它的一些系统引导方法,其中的Register ...

  9. jQuery源码笔记——二

    jQuery选择这样返回对象 var jQuery = function( selector, context ) { return new jQuery.fn.init( selector, con ...

随机推荐

  1. NodeJS的特点

    一. NodeJS的特点 我们先来看看NodeJS官网上的介绍: Node.js is a platform built on Chrome’s JavaScript runtime for easi ...

  2. Python 爬虫 ajax爬取马云爸爸微博内容

    ajax爬取情况 有时候我们在用 Requests 抓取页面的时候,得到的结果可能和在浏览器中看到的是不一样的,在浏览器中可以看到正常显示的页面数据,但是使用 Requests 得到的结果并没有,这其 ...

  3. dADas

    Linux(Centos)之安装Nginx及注意事项 - Kencery - 博客园   怪才(Kencery) 菜鸟的里程中只有奋斗,别无其他 博客园 首页 新随笔 联系 订阅 管理   Linux ...

  4. Python初学者第十八天 函数(2)

    18day 函数 1.函数的返回值:return a.函数外的代码想要获取函数的返回结果时,即可使用return语句 b.函数中如遇到return后,会停止执行,并返回结果.所以若函数未使用retur ...

  5. 原生ajax和jsonp

    封装方法: function ajax(options) { options = options || {}; options.type = (options.type || "GET&qu ...

  6. 2、Node.js 第一个应用

    内容:三种变量申明方式,Node.js应用组成,第一个应用创建+代码 ################################################################# ...

  7. 021.10 IO流 打印流

    内容:PrintStream:字节流    PrintWriter:字符流 PrintStream public static void main(String[] args) throws IOEx ...

  8. 理解活在Iphone中的那些App (三)

    App的生存环境之宏观环境 从用户需求变成一个产品形体的App,是一个曲折的过程.主要的过程大概如此,分析用户需求,从用户需求中提炼出比较重要的部分,然后结合自己的创意,将其转化成产品.投放市场,接受 ...

  9. JavaScript浏览器检测之客户端检测

    客户端检测一共分为三种,分别为:能力检测.怪癖检测和用户代理检测,通过这三种检测方案,我们可以充分的了解当前浏览器所处系统.所支持的语法.所具有的特殊性能. 一.能力检测: 能力检测又称作为特性检测, ...

  10. ubuntu卸载virtualbox

    本想在ubuntu下virtualbox,可惜出错了,需要卸载后再安装,只能百度拼凑后再安装: 1.首先是执行删除命令:sudo apt-get remove virtualbox*( 这样就不用去查 ...