欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

laravel5.5源码笔记(二、服务提供者provider)

程序员文章站 2022-06-30 12:15:39
laravel里所谓的provider服务提供者,其实是对某一类功能进行整合,与做一些使用前的初始化引导工作。laravel里的服务提供者也分为,系统核心服务提供者、与一般系统服务提供者。例如上一篇博文里介绍的,最早在application中进行注册的event、log、routing这些就是系统的 ......

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

1     protected function registerbaseserviceproviders()
2     {
3         $this->register(new eventserviceprovider($this));
4 
5         $this->register(new logserviceprovider($this));
6 
7         $this->register(new routingserviceprovider($this));
8     }

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

 1     'providers' => [
 2 
 3         /*
 4          * laravel framework service providers...
 5          */
 6         illuminate\auth\authserviceprovider::class,
 7         illuminate\broadcasting\broadcastserviceprovider::class,
 8         illuminate\bus\busserviceprovider::class,
 9         illuminate\cache\cacheserviceprovider::class,
10         illuminate\foundation\providers\consolesupportserviceprovider::class,
11         illuminate\cookie\cookieserviceprovider::class,
12         illuminate\database\databaseserviceprovider::class,
13         illuminate\encryption\encryptionserviceprovider::class,
14         illuminate\filesystem\filesystemserviceprovider::class,
15         illuminate\foundation\providers\foundationserviceprovider::class,
16         illuminate\hashing\hashserviceprovider::class,
17         illuminate\mail\mailserviceprovider::class,
18         illuminate\notifications\notificationserviceprovider::class,
19         illuminate\pagination\paginationserviceprovider::class,
20         illuminate\pipeline\pipelineserviceprovider::class,
21         illuminate\queue\queueserviceprovider::class,
22         illuminate\redis\redisserviceprovider::class,
23         illuminate\auth\passwords\passwordresetserviceprovider::class,
24         illuminate\session\sessionserviceprovider::class,
25         illuminate\translation\translationserviceprovider::class,
26         illuminate\validation\validationserviceprovider::class,
27         illuminate\view\viewserviceprovider::class,
28         //maatwebsite\excel\excelserviceprovider::class,  这个是我自己测试的时候加的
29 
30         /*
31          * package service providers...
32          */
33 
34         /*
35          * application service providers...
36          */
37         app\providers\appserviceprovider::class,
38         app\providers\authserviceprovider::class,
39         // app\providers\broadcastserviceprovider::class,
40         app\providers\eventserviceprovider::class,
41         app\providers\routeserviceprovider::class,
42 
43     ],

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

 1     public function bootstrap()
 2     {
 3         if (! $this->app->hasbeenbootstrapped()) {
 4             $this->app->bootstrapwith($this->bootstrappers());
 5         }
 6     }
 7 
 8     protected $bootstrappers = [
 9         \illuminate\foundation\bootstrap\loadenvironmentvariables::class,
10         \illuminate\foundation\bootstrap\loadconfiguration::class,
11         \illuminate\foundation\bootstrap\handleexceptions::class,
12         //注册facade门面类
13         \illuminate\foundation\bootstrap\registerfacades::class,
14         //注册provider
15         \illuminate\foundation\bootstrap\registerproviders::class,
16         //引导provider执行其中boot方法内的代码
17         \illuminate\foundation\bootstrap\bootproviders::class,
18     ];    

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

 1     public function bootstrapwith(array $bootstrappers)
 2     {
 3         $this->hasbeenbootstrapped = true;
 4 
 5         foreach ($bootstrappers as $bootstrapper) {
 6             $this['events']->fire('bootstrapping: '.$bootstrapper, [$this]);
 7             //make了刚刚传入的$bootstrappers数组,并执行了其中的bootstrap方法,暂且只看provider
 8             $this->make($bootstrapper)->bootstrap($this);
 9 
10             $this['events']->fire('bootstrapped: '.$bootstrapper, [$this]);
11         }
12     }
13     
14     //illuminate\foundation\bootstrap\registerproviders.php
15     public function bootstrap(application $app)
16     {
17         $app->registerconfiguredproviders();
18     }
19 
20     //illuminate\foundation\bootstrap\bootproviders.php
21     public function bootstrap(application $app)
22     {
23         $app->boot();
24     }

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

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

 1     public function registerconfiguredproviders()
 2     {
 3         //laravel的集合类,将之前初始化时存入的config中的数组取出
 4         $providers = collection::make($this->config['app.providers'])
 5                         ->partition(function ($provider) {
 6                             //并过滤出系统providers
 7                             return str::startswith($provider, 'illuminate\\');
 8                         });
 9         //之前在registerbasebindings方法中绑定在packagemanifest类中的providers数组拼接,通过load方法加载它们
10         $providers->splice(1, 0, [$this->make(packagemanifest::class)->providers()]);
11         //new了provider库,传入服务容器、文件系统操作对象、与之前缓存的服务提供者路径
12         (new providerrepository($this, new filesystem, $this->getcachedservicespath()))
13                     ->load($providers->collapse()->toarray());
14     }
 1  //illuminate\foundation\providerrepository.php
 2 
 3    public function load(array $providers)
 4     {
 5         // 查看bootstrap/cache/services.php有没有这个缓存文件
 6         // 第一次启动时是没有的
 7         $manifest = $this->loadmanifest();
 8         // 开始没有这个缓存文件,那就把$providers[ ]里的值
 9         if ($this->shouldrecompile($manifest, $providers)) {
10             // 然后根据$providers[ ]编译出services.php这个缓存文件
11             $manifest = $this->compilemanifest($providers);
12         }
13 
14         foreach ($manifest['when'] as $provider => $events) {
15             // 注册包含有事件监听的service provider
16             // 包含有事件监听的service provider都要有when()函数返回
17             $this->registerloadevents($provider, $events);
18         }
19 
20         foreach ($manifest['eager'] as $provider) {
21             // 把'eager'字段中service provider注册进容器中,
22             // 即遍历每一个service provider,调用其中的register()方法
23             // 向容器中注册具体的服务
24             $this->app->register($this->createprovider($provider));
25         }
26 
27         // 注册延迟的service provider,
28         // deferred的service provider, 一是要设置$defer = true,二是要提供provides()方法返回绑定到容器中服务的名称
29         $this->app->adddeferredservices($manifest['deferred']);
30     }

 

而boot操作就更简单了

 1     public function boot()
 2     {
 3         if ($this->booted) {
 4             return;
 5         }
 6 
 7         // once the application has booted we will also fire some "booted" callbacks
 8         // for any listeners that need to do work after this initial booting gets
 9         // finished. this is useful when ordering the boot-up processes we run.
10         //调用引导方法的钩子函数
11         $this->fireappcallbacks($this->bootingcallbacks);
12         //使每个provider运行bootprovider,$p为provider
13         array_walk($this->serviceproviders, function ($p) {
14             $this->bootprovider($p);
15         });
16         //改变引导状态
17         $this->booted = true;
18         //调用引导方法的钩子函数
19         $this->fireappcallbacks($this->bootedcallbacks);
20     }
21 
22     protected function bootprovider(serviceprovider $provider)
23     {
24         //判断传入的provier,运行它们的boot方法完成引导
25         if (method_exists($provider, 'boot')) {
26             return $this->call([$provider, 'boot']);
27         }
28     }

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

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

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

1、新建一个接口。

 

1 namespace app\contracts;
2 
3 interface test
4 {
5     public function doing();
6 }

2、新建两个接口的实现

 1 namespace app\services;
 2 
 3 use app\contracts\test;
 4 
 5 class testservice implements test
 6 {
 7     public function doing()
 8     {
 9         echo 'this is testservice';
10     }
11 }
12 
13 
14 namespace app\services;
15 
16 use app\contracts\test;
17 
18 class secondtestservice implements test
19 {
20     public function doing()
21     {
22         echo 'this is secondtestservice';
23     }
24 }

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

 1 namespace app\providers;
 2 
 3 use illuminate\support\serviceprovider;
 4 
 5 class testserviceprovider extends serviceprovider
 6 {
 7     /**
 8      * bootstrap any application services.
 9      *
10      * @return void
11      */
12     public function boot()
13     {
14         //
15     }
16 
17     public function register()
18     {
19         $this->app->bind('app\contracts\test', 'app\services\testservice');
20         //重点在于when方法确定运行环境,也就是执行上下文,needs为make所需的abstract类名或别名,give所传入的参数则是实际调用的实现类了
21         $this->app->when('app\http\controllers\indexcontroller')
22                 ->needs('app\contracts\test')
23                 ->give('app\services\secondtestservice');
24     }
25 }

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

 1     'providers' => [
 2 
 3         /*
 4          * laravel framework service providers...
 5          */
 6         illuminate\auth\authserviceprovider::class,
 7         illuminate\broadcasting\broadcastserviceprovider::class,
 8         illuminate\bus\busserviceprovider::class,
 9         illuminate\cache\cacheserviceprovider::class,
10         illuminate\foundation\providers\consolesupportserviceprovider::class,
11         illuminate\cookie\cookieserviceprovider::class,
12         illuminate\database\databaseserviceprovider::class,
13         illuminate\encryption\encryptionserviceprovider::class,
14         illuminate\filesystem\filesystemserviceprovider::class,
15         illuminate\foundation\providers\foundationserviceprovider::class,
16         illuminate\hashing\hashserviceprovider::class,
17         illuminate\mail\mailserviceprovider::class,
18         illuminate\notifications\notificationserviceprovider::class,
19         illuminate\pagination\paginationserviceprovider::class,
20         illuminate\pipeline\pipelineserviceprovider::class,
21         illuminate\queue\queueserviceprovider::class,
22         illuminate\redis\redisserviceprovider::class,
23         illuminate\auth\passwords\passwordresetserviceprovider::class,
24         illuminate\session\sessionserviceprovider::class,
25         illuminate\translation\translationserviceprovider::class,
26         illuminate\validation\validationserviceprovider::class,
27         illuminate\view\viewserviceprovider::class,
28 
29         /*
30          * package service providers...
31          */
32 
33         /*
34          * application service providers...
35          */
36         app\providers\appserviceprovider::class,
37         app\providers\authserviceprovider::class,
38         // app\providers\broadcastserviceprovider::class,
39         app\providers\eventserviceprovider::class,
40         app\providers\routeserviceprovider::class,
41         //添加刚刚生成的provider
42         app\providers\testserviceprovider::class,
43     ],

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

 1 namespace app\http\controllers;
 2 
 3 use app\contracts\test;
 4 
 5 class indexcontroller extends controller
 6 {
 7 
 8     public function __construct(test $test)
 9     {
10         $this->test = $test;
11     }
12 
13     public function index(test $test)
14     {
15         app()->make('app\contracts\test')->doing();
16 
17         echo '<br>';
18         //只有通过构造方法进行自动加载依赖的方式才能触发契约的when绑定
19         $this->test->doing();
20     
21         echo '<br>';
22     //因为laravel中的上下文绑定只能具体到类,所以这里的$test实例依然为普通绑定
23         $test->doing();
24 
25     }
26 }

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

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