[关闭]
@Chiang 2020-02-07T12:15:57.000000Z 字数 4916 阅读 1020

Laravel中容器(Container),提供者(Provider),门面(Facade),契约(Contracts)的关系

Laravel


简述

当你接触一段时间Laravel的Service Container, Service Provider,Contracts和Facade后,也许已经知道它们是什么了,但是对于如何使用,在什么时候使用,以及它们之间的关系是什么,还不是非常清楚。
现在我们就来一次性把它们搞定;

基本概念

在继续本教程之前,你需要先对以上概念有基本了解,知道它们是什么;

Service Container 和 Service Provider

Service Container,也就是IOC容器的使用并不依赖Service Provider,例如:

  1. $app->make('App\Models\Post');

这句话和 new App\Models\Post; 的结果完全一样;
另外你在控制器里使用构造函数,type-hint进行依赖注入,也完全和Service Provider没有半毛钱关系。

总之,你可以完全不使用Service Provider;

Service Provider 和 Contracts

如果说IOC容器的使用并不依赖Service Provider,那么为什么我们用composer下载扩展包的时候总是要在config/app.php里绑定一下Service Provider呢,有时候还需要绑定一下Facade;

理解的思路是这样的,Laravel核心类(Services)都是用接口(contracts)+实现来构成的, 如果不理解这个概念,仔细看文档接口那一章。而你在使用的时候,如果要拿到某个接口实现的实例的话,需要用到Service Container,而要用Service Container去解析一个接口,而不是直接解析一个类,这时就要用到Service Provider了,可以说,Service Provider的主要功能,就是来绑定接口的。

在讲接口绑定前,先了解一些基本的事实:

一些事实

  1. $app->make('App\Models\Post');

你可以这样写,

  1. $app->make('post');

也可以这样写,这里的post是一个别名,这个别名是造成混淆的主要地方;
这个时候你肯定在想,这样写有啥用,我去哪里关联这个别名到App\Models\Post呢?

Service Provider 的 bind 方法

对,就是在Service Provider里用bind方法来绑定别名:

  1. $this->app->bind('post', function ($app) {
  2. return $this->app->make('App\Models\Post');
  3. });

这样绑定后你就可以$app->make('post');这样写了;为什么要用别名?? 没关系,稍后会讲到,它和Facade有关系;我们先来解释文档不足的地方:

文档是这样写这个bind方法的:

  1. $this->app->bind('HelpSpot\API', function ($app) {
  2. return new HelpSpot\API($app['HttpClient']);
  3. });

哇擦,您的这第一个参数到底填的啥啊,事实上,第一个参数可以填类的全称,但是如果不是填简称,我这样绑定有任何意义么? 后面再返回一个一样的类实例? 咦?$app[‘HttpClient’]这个是什么?? 其实它是告诉你可以在解析类的时候可以再接着注入一个其他类的实例;文档大哥,拜托你解释一下好不好,能不能举个靠谱点的例子…

如果你到其他的扩展包中去看别人的bind的写法,你会发现千奇百怪的绑定写法,先不管他们,现在我们来看Service Provider对接口的使用方法,最最基本的原理是这样的:

  1. //给一个接口起个别名
  2. $this->app->bind('event_pusher', function ($app) {
  3. return $this->app->make('App\Contracts\EventPusher');
  4. });
  5. //指定这个接口应该解析的实例
  6. $this->app->bind('App\Contracts\EventPusher', 'App\Services\RedisEventPusher');

通过这两步,我们让这个接口有了别名,也有了解析时对应的实现;

这样,我们就可以:

  1. $app->make('event_pusher');

得到App\Services\RedisEventPusher;

Service Provider 和 Facades

我们来看Facade的写法,比如说Illuminate\Support\Facades\Cache:

  1. class Cache extends Facade
  2. {
  3. protected static function getFacadeAccessor() { return 'cache'; }
  4. }

这个cache就是上面提到过的别名;

下面我们来看Facade的对应关系图:

facade name facade class resolved class service provider binding alias
Cache Illuminate\Support\Facades\Cache Illuminate\Cache\Repository cache

所以你调用Cache::get(‘user_id’)的时候,实际上是调用了Illuminate\Support\Facades\Cache 这个类,get并不是这个类的静态方法,事实上,get这个方法在Facade这个类里根本不存在,这正是它设计的本意,当get这个方法不存在的时候,它就会调用Facade基类里的__callStatic魔术方法(需要提前了解这个魔术方法),这个方法中就会把Service Provider中绑定的类(或接口)解析并返回出来,本例中也就是Illuminate\Cache\Repository 这个类,所以get其实是Illuminate\Cache\Repository这个类的方法;

然后我们在再看文档,有的Facade怎么没有别名呢?比如:

Facade Name Facade Class Resolved Class Service Provider Binding Alias
Response Illuminate\Support\Facades\Response Illuminate\Contracts\Routing\ResponseFactory

是的,你可以直接写类的全称,而不是别名,如果你看这个Illuminate\Support\Facades\Response源码,它是这样写的:

  1. class Response extends Facade
  2. {
  3. protected static function getFacadeAccessor()
  4. {
  5. return 'Illuminate\Contracts\Routing\ResponseFactory';
  6. }
  7. }

可以直接返回该类;

Facade 的命名空间到底是什么

我们发现,在使用Cache::get(‘user_id’)的时候,你可以使用use Cache; 也可以使用use Illuminate\Support\Facades\Cache;

这是为什么呢?

别忘了,你在config/app.php里面Class Aliases 那里绑定过 Facade 别名,也就是:

  1. 'Cache' => Illuminate\Support\Facades\Cache::class,

这样绑定过,你就可以直接use Cache来使用Facade了;

实践

做项目时需要用到Log,但是monolog需要进行初始化什么的,感觉很多处使用很冗余,于是想封装成一个LogFacade,直接调用。但laravel的文档看得不明不白的,于是就在这列举一下创建一个Facade的步骤

定义接口TestContract

  1. path: ./laravel/app/Contract/TestContract.php
  2. <?php
  3. namespace App\Contract;
  4. interface TestContract
  5. {
  6. public function test($msg='');
  7. }
  8. ?>

实现接口TestContract

  1. path: ./laravel/app/Contract/Test.php
  2. <?php
  3. namespace App\Contract;
  4. class Test implements TestContract
  5. {
  6. public function test($msg=''){
  7. echo 'I am Test~ '.$msg;
  8. }
  9. }
  10. ?>

创建服务提供者,提供test服务

  1. path: ./laravel/app/Contract/Test.php
  2. <?php
  3. namespace App\Contract;
  4. use Illuminate\Support\ServiceProvider;
  5. use App\Contract\Test;
  6. class TestServiceProvider extends ServiceProvider
  7. {
  8. public function register(){
  9. // 绑定test与Test类的实例
  10. $this->app->singleton('test', function($app){
  11. return new Test();
  12. });
  13. }
  14. }
  15. ?>

创建TestFacade

  1. path: ./laravel/app/Contract/Test.php
  2. <?php
  3. namespace App\Contract;
  4. use Illuminate\Support\Facades\Facade;
  5. class TestFacade extends Facade
  6. {
  7. // 这里的test跟服务提供者TestServiceProvider里面注册的'test'一致
  8. protected static function getFacadeAccessor() { return 'test'; }
  9. }
  10. ?>

配置

在Config/app.php里面加入

服务提供者

  1. providers加入我们的TestServiceProvider
  2. 'providers' => [
  3. /*
  4. * my defined
  5. */
  6. App\Contract\TestServiceProvider::class,
  7. ],

在aliases添加别名以供调用

  1. 'aliases' => [
  2. 'Test' => App\Contract\TestFacade::class
  3. ],

使用

  1. <?php
  2. namespace App\Http\Controllers;
  3. use Illuminate\Routing\Controller;
  4. use Test;
  5. class TestController extends Controller {
  6. public function test(){
  7. Test::test('hello world');
  8. }
  9. }

参考资料:
Laravel实践-创建自定义Facade(Contract、ServiceProvider与Facade之间的关系)
解释清楚Laravel的Service Container, Service Provider,Contracts和Facade之间的关系
laravel中的Contracts, ServiceContainer, ServiceProvider, Facades关系
Laravel应用
Laravel 核心概念

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注