Laravel的服务容器是什么
laravel的服务容器是用于管理类的依赖和执行依赖注入的工具。依赖注入实质上是指:类的依赖项通过构造函数或者某些情况下通过setter方法注入到类中
setter就是类似魔术方法__set的方法,也可以自定义
服务容器的核心功能是IoC容器用以解决依赖注入,控制反转 IoC模式又称依赖注入模式
依赖注入是指组件的依赖通过外部以参数或其他形式注入
为什么使用服务容器
使用这种方式,一方面降低了代码之间的耦合,另一方面也提高了代码的可维护性与拓展性
服务容器的应用场景
类似发邮件服务的全局服务
我们把发送邮件的功能封装成一个类,需要使用的时候,实例化并调用发送方法
下面是php代码的简单实现
/**
*发送邮件服务类
*/
class EmailService{
public function send(){
//todo 发送邮件方法
}
}
//如果任何地方要发邮件我们就复制下面这两行代码
$emailService = new EmailService();
$emailService->send();
但是使用了laravel的服务容器之后
$this->app->bind('emailService', function ($app) {
return new EmailService();
});
//如果任何地方要发邮件我们就复制下面这两行代码
$emailService = app('emailService');
$emailService->send();
这就使我们的代码更加简洁了,由于有了中间层,灵活性提高了,那么无论测试或者优化我们的服务类都会变得更方便
//只需要改这一个地方
$this->app->bind('emailService', function ($app) {
return new SupperEmailService();
});
其他调用的地方我们完全不用动,如果我们没有这个绑定操作(中间层),那么我们不得不在每个使用服务的地方进行修改,这样就会很繁琐.
实现单例模式
还是使用上面的邮件服务例子,可能处于性能的考虑,需要这个服务实现单例模式,于是在不适用laravel服务容器的情况下,做出如下更改
class SupperEamilService{
//创建静态私有的变量保存该类对象
static private $instance;
//防止直接创建对象
private function __construct(){
}
//防止克隆对象
private function __clone(){
}
static public function getInstance(){
//判断$instance是否是Uni的对象
//没有则创建
if (!self::$instance instanceof self) {
self::$instance = new self();
}
return self::$instance;
}
//发送邮件方法
public function send(){
}
}
除此之外,由于服务类的构造函数为私有,无法通过new关键字来实例化对象,所以在每个实例化的地方调用都需要改成这样
$emailService=SupperEmailService::getInstance();
$emailService->send();
但是laravel的服务容器天生支持单例
//只需要把bind改成singleton
$this->app->singleton('emailService', function ($app) {
return new SupperEmailService();
});
我们只需要把原来的bind方法改成singleton,通过容器取出来的就是单例了
旅行者去旅行
这个例子就是讲旅行和旅行工具之间的服务解耦.
假设一个旅行者去西藏旅行,可以坐火车或者走路去
不使用laravel服务容器:
<?php
interface TrafficTool
{
public function go();
}
class Train implements TrafficTool
{
public function go()
{
echo "train....";
}
}
class Leg implements TrafficTool
{
public function go()
{
echo "leg..";
}
}
class Traveller
{
/**
* @var Leg|null|Train
* 旅行工具
*/
protected $_trafficTool;
public function __construct(TrafficTool $trafficTool)
{
$this->_trafficTool = $trafficTool;
}
public function visitTibet()
{
$this->_trafficTool->go();
}
}
当旅行者要坐或者去旅行,通常我们这样写
<?php
$train = new Train();
$tra = new Traveller($train);
$tra->visitTibet();
事实上这种写法非常不错了,因为对于旅行工具的依赖已经通过接口的方式转移到外部了。但是使用new来实例化的时候还是会产生依赖。也就是说Train和Traveller之间产生了耦合
那么laravel服务容器怎么实现的?
<?php
namespace App\Providers;
use Laravel\Lumen\Providers\EventServiceProvider as ServiceProvider;
class RepositoryServiceProvider extends ServiceProvider
{
public function register()
{
//在服务容器中绑定类
$this->app->bind( 'TrafficTool', 'Train');
$this->app->bind('Traveller', 'Traveller');
}
}
实例化对象
<?php
// 实例化对象
$tra = app()->make('Traveller');
$tra->visitTibet();
当我们使用服务容器获取旅行类的对象时,容器会自动注入对象所需要的参数,而在此之前我只需要绑定特定的类就可以了,这样才体现了真正的自动化,是的旅行和旅行工具完全解耦了,当我们需要更改旅行方式的时候,只需要更改绑定就好了
参考资料:
使用laravel服务容器的优势
应用场景的总结
1,应用于服务之间的解耦
2,方便的实现单例模式
服务容器原理
反射
laravel服务容器的一个强大的特性就是能通过反射自动解析依赖。反射具有检测类及其方法的能力,比如PHP中的ReflectionClass类允许你检测一些方法在给定的类中是否可用.
通过PHP的这种特性,laravel可以实现一些有趣的功能!
class UserController extends BaseController {
public function __construct(StripeBiller $biller)
{
$this->biller = $biller;
}
}
如上控制器初始化时需要传入StripeBiller类型的对象,我们可以通过反射进行类型检测。当laravel容器没有绑定相应的解析器,他就会通过反射尝试解析该类.流程大致如下
- 服务容器有无StripeBiiler解析器?(就是是否在服务容器绑定过)
- 没有解析器?映射类StripeBiller判断其依赖
- 递归的解析StripeBiller类的所有依赖
- 通过ReflectionClass->newInstanceArgs()实例化一个新的StripeBiller
可以看到服务容器为我们做了许多繁重的工作,使你能释放更多时间用于编码各种逻辑的代码类。掌握容器,先查看下容器的源码IlluminateContainerContainer这个类文件