介绍
Laravel Service Container 是管理类的依赖的强大的工具。依赖注入(Dependency injection)其实就是使用类的构造函数或者 setter 方法,把类所依赖的东西注入到类里面。
来看个简单的示例:
<?php namespace App\Handlers\Commands; use App\User; use App\Commands\PurchasePodcast; use Illuminate\Contracts\Mail\Mailer; class PurchasePodcastHandler { /** * The mailer implementation. */ protected $mailer; /** * Create a new instance. * * @param Mailer $mailer * @return void */ public function __construct(Mailer $mailer) { $this->mailer = $mailer; } /** * Purchase a podcast. * * @param PurchasePodcastCommand $command * @return void */ public function handle(PurchasePodcastCommand $command) { // } }
在上面例子里,PurchasePodcast 命令处理器需要在有人购买了 podcast 以后去发送 e-mail 。所以,我们要去注入一个可以发送邮件的服务。因为这个服务是注入进来的,所以我们就可以很容易把它换成其它的实施。 在 “mock” 的时候也会很容易,或者在测试应用的时候,去创建一个假的 mailer 实施。
要去创建大型的应用,或者为 Laravel 核心做贡献,都需要深入的理解 Laravel Service Container 。
基本的使用
Binding
基本上你所有的 Service Container 的绑定都是在 Service Providers 里面注册的,下面所有这些示例都是在这个上下文里面演示使用容器的。如果在你的应用的其它的地方需要一个容器的实例,比如 factory ,你需要 type-hint Illuminate\Contracts\Container\Container Contract,这样容器的实例就会为你注入进来,你可以使用 App facade 来访问这个容器。
注册一个基本的 Resolver
在 Service Provider 里面,你可以通过 $this->app 实例变量来访问到容器。
有几种方法可以注册 Service Container 的依赖,比如 Closure 回调,还有在实施上绑定界面。首先,我们看一下 Closure 回调。在容器里注册的 Closure Resolver,会带一个 key(一般就是类的名字) ,还有一个可以返回值的 Closure 。
$this->app->bind('FooBar', function($app) { return new FooBar($app['SomethingElse']); });
注册一个 Singleton
有时候,你可能想要去绑定只会 Resolve 一次的东西到容器上,在后面调用这个容器的时候会返回这个实例:
$this->app->singleton('FooBar', function($app) { return new FooBar($app['SomethingElse']); });
在容器上绑定已有的实例
使用 instance 方法,你可以在容器上绑定一个已有的对象实例。这个实例会一直在随后调用容器的时候返回:
$fooBar = new FooBar(new SomethingElse); $this->app->instance('FooBar', $fooBar);
Resolving
有几种方法可以从容器里 resolve 些东西出来。比如使用 make 方法:
$fooBar = $this->app->make('FooBar');
你也可以在容器上使用数组访问,因为它实施了 PHP 的 ArrayAccess 接口:
$fooBar = $this->app['FooBar'];
最后,也是最重要的, 你只需要在类的构造函数里边 type-hint 依赖,包括控制器,事件监听器(Event Listeners),队列工作(Queue jobs),过滤器等等。容器会自动注入依赖:
<?php namespace App\Http\Controllers; use Illuminate\Routing\Controller; use App\Users\Repository as UserRepository; class UserController extends Controller { /** * The user repository instance. */ protected $users; /** * Create a new controller instance. * * @param UserRepository $users * @return void */ public function __construct(UserRepository $users) { $this->users = $users; } /** * Show the user with the given ID. * * @param int $id * @return Response */ public function show($id) { // } }
绑定接口到实施
注入固定的依赖
Service Container 很强大的一个功能就是可以绑定接口到一个实施上。比如,你的应用可以要整合 Pusher 这个 web service,它可以发送跟接收实时的事件。如果你使用 Pusher 的 PHP SDK,我们可以注入一个 Pusher 客户端的实例到一个类里面:
<?php namespace App\Handlers\Commands; use App\Commands\CreateOrder; use Pusher\Client as PusherClient; class CreateOrderHandler { /** * The Pusher SDK client instance. */ protected $pusher; /** * Create a new order handler instance. * * @param PusherClient $pusher * @return void */ public function __construct(PusherClient $pusher) { $this->pusher = $pusher; } /** * Execute the given command. * * @param CreateOrder $command * @return void */ public function execute(CreateOrder $command) { // } }
在上面这个例子里,注入类的依赖是个不错的主意,不过,我们紧耦合到了 Pusher SDK。如果 Pusher SDK 方法更了,或者我们想使用其它的事件服务,我们需要修改 CreateOrderHandler 的代码。
编码到一个接口
为了可以让 CreateOrderHandler 跟事件推送绝缘,我们可以去定义一个 EventPusher 接口,还有一个 PusherEventPusher 的实施:
<?php namespace App\Contracts;
interface EventPusher {
/**
* Push a new event to all clients.
*
* @param string $event
* @param array $data
* @return void
*/
public function push($event, array $data);
}
编写了实施这个接口的 PusherEventPusher 以后,我们像这样把它跟 Service Container 注册一下:
$this->app->bind('App\Contracts\EventPusher', 'App\Services\PusherEventPusher');
上面告诉容器当类需要一个 EventPusher 实施的时候要注入 PusherEventPusher 。现在我们可以在构造函数里去 type-hint 一下 EventPusher :
/**
* Create a new order handler instance.
*
* @param EventPusher $pusher
* @return void
*/
public function __construct(EventPusher $pusher)
{
$this->pusher = $pusher;
}
上下文绑定
有时候你会有两个使用同一接口的类,不过你想注入不同的实施到每个类里面。比如,当我们的系统收到一个新的订单,我们可能想要通过 PubNub 发送一个事件,而不是使用 Pusher。Laravel 提供了一个方便流畅的接口去定义这样的行为:
$this->app->when('App\Handlers\Commands\CreateOrderHandler') ->needs('App\Contracts\EventPusher') ->give('App\Services\PubNubEventPusher');
打标签
有时候你需要去 resolve 一个特定类型的所有的绑定。比如,你可能要创建一个报告聚合工具,可以接收来自不同的 Report 接口实施的数组,你可以使用 tag 方法给它们打上一个标签:
$this->app->bind('SpeedReport', function() { // }); $this->app->bind('MemoryReport', function() { // }); $this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');
服务被打上标签以后,你就可以很容易使用 tagged 方法去 resolve 它们了:
$this->app->bind('ReportAggregator', function($app) { return new ReportAggregator($app->tagged('reports')); });
实际应用
Laravel 提供了一些使用 Service Container 去提高应用灵活性还有可测性的方法。一个主要的例子就是当去 resolving 控制器的时候。所有的控制器都会通过 Service Container 去 resolved ,意思就是,你可以在控制器构造函数里去 type-hint 依赖,它们会自动被注入进来。
<?php namespace App\Http\Controllers; use Illuminate\Routing\Controller; use App\Repositories\OrderRepository; class OrdersController extends Controller { /** * The order repository instance. */ protected $orders; /** * Create a controller instance. * * @param OrderRepository $orders * @return void */ public function __construct(OrderRepository $orders) { $this->orders = $orders; } /** * Show all of the orders. * * @return Response */ public function index() { $all = $this->orders->all(); return view('orders', ['all' => $all]); } }
在上面的例子里,OrderRepository 类会被自动注入到控制器里。这样当做单元测试的时候,一个 OrderRepository 的 mock 可能会绑定到容器上,这样与数据层进行交互会更容易一些。
其它的使用 Container 的示例
在上面提到过, Laravel 通过 Service Container 去 resolve 的不仅仅只有控制器的类。你也可以在 Closure 路由,队列工作,事件监听器这些东西上面去 type-hint 依赖。在这些上下文里面使用 Service Container 可以参考它们相关的文档。
容器事件
注册一个 Resolving 监听器
容器在每次 resolve 对象的时候都会触发一个事件。你可以使用 resolving 方法去监听这个事件:
$this->app->resolving(function($object, $app) { // Called when container resolves object of any type... }); $this->app->resolving(function(FooBar $fooBar, $app) { // Called when container resolves objects of type "FooBar"... });
被 resolved 的对象会被传递到一个回调里面。
Laravel Laravel5 中文手册 Service Container