laravel5.5源码笔记(二、服务提供者provider)
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到底是个什么东西。