Laravel的服务提供者的作用是什么?
1). 当前使用的 Laravel 版本?
Laravel8.83.9
根据网上的教程,我跟着写了一遍服务提供者相关代码,我先给大家看看吧,模拟发短信功能的。
第一步:新建一个接口:
namespace App\Services;
interface SmsService
{
public function send($phone, $content);
}
第二步:新建短信具体功能类,实现SmsService接口:
namespace App\Services;
class AliSms implements SmsService
{
public function send($phone, $content)
{
return "阿里发短信平台手机号:" . $phone . " 内容:" . $content;
}
}
这里假设是阿里的发短信平台。
第三步:新建服务提供者:
php artisan make:provider SmsServiceProvider
会在app\Providers 下面生成SmsServiceProvider:
namespace App\Providers;
use App\Services\AliSms;
use Illuminate\Support\ServiceProvider;
class SmsServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
$this->app->singleton('sms', function () {
return new AliSms();
});
}
}
第四步:在config\app.php providers数组中加入 App\Providers\SmsServiceProvider::class,
第五步:新建控制器:
namespace App\Http\Controllers;
use Illuminate\Support\Facades\App;
use App\Services\SmsService;
class BlackController extends Controller
{
public function index()
{
echo app('sms')->send("18700000000","你好呀");
}
}
运行之后成功打印:阿里发短信平台手机号:18700000000 内容:你好呀
代码是成功运行的,可就是不明白为什么需要这么复杂的过程,有些人会说以后如果换一个短信平台,只要改服务提供者就可以了,业务代码不需要改。但是,我觉得如果真是这样,我也可以写一个单独的短信发送公共类,在刚刚的控制器里面调:SendClass::send(“18700000000”,”你好呀”);以后要改其他平台发送短信,我也就改send方法而已,调用方也无需改。
所以不清楚服务提供者真正的用途是什么?有没有知道的举个通俗一点的场景说一说。
USB知道吗?可以插鼠标,可以插手机,可以插键盘
优。。。雅。。。 :joy:
翻译:Laravel 中的服务提供者:Service Providers 是什么以及如何使用
今天怎么都是来问 ServiceProvider 的。
ServiceProvider 是在请求周期最早的时候进行加载的,其目的就是注册可以全局调用的服务(功能),在代码运行时可以直接对其调用,不必去临时加载。
举个例子:
前提:
当你走进 7-11 的时候直接走过去拿就行了,你也不用管它是怎么做出来的,这些在你进入 7-11 的时候就已经准备好了。
那为什么 7-11 会准备 咖啡机、饮料机、冰淇淋机 呢?它怎么不准备 自动洗鞋机 呢?
因为 咖啡机、饮料机、冰淇淋机 是顾客最频繁使用的功能,每天将耗费店员很多时间来做这些事情。所以干脆把它们以「服务」的形式来提供,把职责从店员中抽离了出来,可以省掉他们很多时间。
但还有一种情况:
假设你就非要在 7-11 洗鞋,但是购买这个服务的人比较少,而且比较麻烦,店员不能亲自去做。所以干脆配置了一台自动洗鞋机,但由于平时用的人比较少,都是处于关闭状态,当有顾客需要洗鞋子时,店员需要打开自动洗鞋机把鞋子放进去。
这就是 Defer 延迟加载,因为「洗鞋」功能并不常用,但它又比较麻烦,所以也为它配置了一个「服务」,但是它不是必须和请求进入应用时和其他 Provider 加载,它是按需加载的。
明白了没?
官方文档其实都有写啦,服务容器 IoC,DI 可以也看看
服务提供者并不仅限于容器绑定,它还可以做很多事情,详见文档中 Package Development (扩展包开发)章节,这里面提到的所有的都是要在服务提供者中完成的。
正如 @MArtian 前面提到的那样,Service Provider 的执行时机较早,所以你可以在这里做一些框架启动阶段所要做的事情,比如注册事件监听、注册/替换配置、容器绑定、Babel 指令等。
容器绑定仅仅是可以在 Service Provider 中可以做的一部分,也是被常用于举例的。
现在再来细说容器绑定。
静态方法调用最大的好处就是不用 new ,直接就可以用,但是这样带来了最大的问题,就是不可测性,很难对静态方法进行替换测试,如果有配置有 CI/CD ,你总不希望,每次测试的时候都真实发送一条短信吧。
比如现在我们要用 AliSMS 这个类来发送短信,如果你不想每次都
new AliSMS($token)
然后再调用 send 的话,你肯定需要再封装一个 factory 方法,在这个方法里面去new AliSMS($token)
。使用的时候直接AliSMS::factory()
(耦合)。这样就能拿到一个新实例,那假如,现在这个 AliSMS 是一个第三包里面的, 你不能修改它,那你就需要单独创建一个Sender
类或者函数来做这件事情了。也就是说为了实现发短信的基本功能,你需要修改原类,或者再创建一个 Sender 类,来愉快的使用,从而避免每次都
new AliSMS($token)
。如果使用服务提供者的容器绑定,你只需要像下面这样。
现在,你就可以在 Laravel 所有支持类型提示注入的时候声明一个 AliSMS 的类型即可,就像控制器方法常用的 Request 一样,从某种意义上来说,你还应该定义一个契约(接口)这才是好的实践。
当然,前面的需求,除了 bind ,还可以用 extend 和 resolving 都可以做这个事情。
其次就是相较于静态方法,这种方式的可被测试性也更高。
:smile: 结合容器即可解锁服务提供能者新姿势,先引用上面部分内容和评论看下。
在服务提供者中为容器绑定单例都是基操了,前面评论也有提到。
那假如部分业务使用阿里短信,部分业务腾讯短信,或者部分业务需要本地存储,部分业务需要云存储,
实际情况花里胡哨的可能太多了
咱们看下文档中的上下文绑定, 控制器中完全一样的用法就行
另外,服务提供者为第三方服务带来了很大便利,比如何时注册路由,何时刷新缓存中的数据。
来看下
RouteServiceProvider
千言万语难汇于一句话,这样的帖子子评论太难了,但是看到大家写这么多,我也忍不住。。
这实际就是个数组缓存吧。当调用字符串 key ‘sms’的时候,会调用闭包返回一个sms的实例。容器是个单例,这么做的原因我想是因为让程序不用每次请求的时候都生成一个sms实例。从而直接去容器里取这个服务就行了
:joy:学习了很多, 但是我一直找不到实战中的一些使用 除了简单的单例绑定以外, 我觉得如果单纯写业务代码用的真的很少, 但是如果是要搭项目基本架构的话,比如上传服务,或许可以用到容器,因为会有很多不同的第三方上传
我觉得这么理解可能更好理解一点,如果你定义一个生成订单的服务,比如:createOrder,恰好生成支付宝和微信的订单规则是不一样的,对于调用的人只需要调用createOrder这个服务就可以了,你只需要在createOrder中把两种可能处理好就可以
个人拙见:有一个作用可能是方便单测
拿楼主 BlackController 的 index 方法举例,如果你用个公共类 SendClass ,导致 index 方法的测试要依赖 SendClass,而如果通过容器的方式注入,可以在单测时模拟一个模拟实例,只是简单的模拟输出,而不会真正的去发送短信。
当然,你说我单测也可以更改 SendClass 的行为,但需要更改你的业务逻辑代码。而容器注册也不一定非得用 ServiceProvider 的方式。只是 Laravel 提供给你的一种组织代码的方式而已,用不用在于自己。
基本只用
服务容器
, 根本原因:new
回到
服务提供者
,假设没有这个功能, 扩展包还怎么注册。核心方法就是
register
: 绑定服务容器
boot
: 给服务初始化用的因为用得多,也的确是核心,就产生个名词呗。
至于上面的代码创建个服务提供者,应该是太闲了。
有没有一种可能,我是说可能啊
你发送短信需要依赖一个用户服务,用户服务需要依赖一个隐私服务,而你的短信需要依赖一个网关服务,而你的网关服务有需要依赖一个调度服务,然而 这些服务都是一一唯一对应,如果你要换一个短信,就要同时换掉,用户服务,隐私服务,网关服务,调度服务,假如你用
ServiceProvider
你只需要更换一下各个服务真实使用的类即可。而不是 你一个一个的去换,递归一样的去更换new
的对象。多用用才能明白他是做什么的。问出来的都是别人的结论,不一定百分百适合你。你能用20次
ServiceProvider
你也会有你的理解的。用过三方组件包嘛,以
intervention/image
(压缩图片) 为例,它会在文档里说在
config/app.php
的$providers
数组中添加Intervention\Image\ImageServiceProvider::class
在
$aliases
数组中添加'Image' => Intervention\Image\Facades\Image::class
然后
use Image;
就可以使用了。再看
ImageServiceProvider::class
的register()
不使用服务提供者也行,
use Intervention\Image\ImageManager;
也可以使用,但是不能使用门面facade
。实质就是容器初始化时向其中提供服务,至于为什么要这样做?那就需要了解为什么要使用容器了。
题主是觉得例举的5步太麻烦了?直接一个静态类发完短信就行了,扩展性不差,代码量也少。
我的理解是这样的:
send
函数就OK。对比下,比服务模式也就多了个配置(第4步),和一种不太习惯的
app()
调用方式刚开始看这东西,不理解,自己模仿了下也觉得不得劲。写多了后发现:
自己写麻烦,但系统自带的
auth
和route
挺好用的例子:
我觉得,服务及服务提供者,让协作更容易了
希望能解答你的疑惑
而且你文中的写法其实也不推荐,建议这样:
然后在调用的时候,直接通过容器注入,而不是手动去容器中取:
现在,如果要接入新的短信服务,那么实现 SmsService 接口即可,甚至可以直接把 Provider 中改成配置文件,就完全符合开闭原则了;而你如果用工具类来做,是不是还要改代码?改代码是不是就意味着有可能出bug?