Laravel 服务容器和依赖注入
依赖注入(DI)
当A类需要依赖于B类,也就是说需要在A类中实例化B类的对象来使用时候,如果B类中的功能发生改变,也会导致A类中使用B类的地方也要跟着修改,导致A类与B类高耦合。这个时候解决方式是,A类应该去依赖B类的接口,把具体的类的实例化交给外部。
就拿在业务需求中经常会用到的发送邮件举例,假如我们现在在Business
业务类中需要发送邮件,下面的代码可以实现。
<?php
class Email
{
public function send()
{
echo "send an email";
}
}
class Business
{
protected $mailer;
public function __construct()
{
$this->mailer = new Email();
}
public function run() {
$this->mailer->send();
}
}
$business = new Business();
$business->run(); // send an email
上面的代码中我们先定义了一个Email
类,并Business
类中的构造方法中实例化了Email
类的对象,然后在run
方法中调用了Email
实例的send
方法发送邮件。到这里都很正常,但是,在现实中,需求是会改的,突然产品经理对你说发email已经过时了,现在改成发短信。
聪明的程序员在这里会问产品,以后还会不会改了,产品说,可能会。然后你就蛋疼了,你发现一改需求你的代码要改动很多,你要添加一个SmsMail
类作为发短信的类,还要修改Business
类,因为现在实例化的对象不是Email
类了而是SmsMail
类,而且以后可能还要这样地改。
这个时候,就该依赖注入上场了,我们可以写一个通用的Mail
接口,让Email
类和SmsMail
类都实现这个接口,并在Business
类中注入。
<?php
interface Mail
{
public function send();
}
class Email implements Mail {
public function send() {
echo "send an email";
}
}
class SmsMail implements Mail {
public function send() {
echo "send a sms";
}
}
class Business
{
protected $mailer;
public function __construct(Mail $mailer)
{
$this->mailer = $mailer;
}
public function run() {
$this->mailer->send();
}
}
$sms = new SmsEmail();
$email = new Email();
$business1 = new Business($sms); // send a sms
$business2 = new Business($email); // send an email
看到没,现在我们的Business
类不用关心使用的$mailer
到底是谁的实例,它可以是其他任何的Mail
接口的实现。
服务容器
laravel的服务容器就是用来管理类依赖和依赖管理的一个工具。为了让A类与B类解耦,我们把B类放进一个容器里,我们只需要A类中声明它需要什么,然后由容器提供具体实例。
绑定
几乎所有服务容器的绑定都是在ServiceProviders
(服务提供者)中的register
方法中。基本的绑定使用$this->app
的bind
方法,通过传递注册类或接口的名称、及返回该实例的Closure
作为参数。
$this->app->singleton('Mail', function ($app) {
return new Email();
});
还有一个singleton
方法,和bind
写法差不多,它们的在于在再一次请求周期中bind方法的闭包会在多次获取绑定实例的情况下执行多次,而singleton
只执行一次。你也可以绑定一个已经存在的对象到容器中,$this->app->instance('request', $request);
。绑定之后,我们可以通过一下几种方式来获取绑定实例:
-
app('XblogConfig');
-
app()->make('XblogConfig');
-
app()['XblogConfig'];
-
resolve('XblogConfig');
singleton
绑定的好处在于如果在一次请求中我们多次使用某个类,那么只生成该类的一个实例将节省时间和空间。但是如果这个类需要在不同的环境具有“个性化”,即不同类需要不同的实例的情况下就不能使用singleton
。Laravel的应用程序初始化的时候就singleton
绑定Illuminate\Contracts\Http\Kernel
,Illuminate\Contracts\Console\Kernel
,Illuminate\Contracts\Debug\ExceptionHandler
接口的实现类,这些是实现类框架的默认自带的。
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
还有一种上下文绑定,就是相同的接口,在不同的类中可以自动获取不同的实现,例如:
$this->app->when(PhotoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('local');
});
$this->app->when(VideoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('s3');
});
上述表明,同样的接口Filesystem
,使用依赖注入时,在PhotoController
中获取的是local
存储而在VideoController
中获取的是s3
存储。