Service Container
#介绍
The Laravel service container is a powerful tool for managing class dependencies and performing dependency injection. Dependency injection is a fancy phrase that essentially means this: class dependencies are "injected" into the class via the constructor or, in some cases, "setter" methods.
Let's look at a simple example:
Laravel服务容器是管理类依赖的强力工具, 依赖注入是比较专业的说法,真正意思是将类依赖透过构造器,或setter方法注入。 让我们看一个简单的例子。
<?php
namespace AppJobs;
use AppUser;
use IlluminateContractsMailMailer;
use IlluminateContractsBusSelfHandling;
class PurchasePodcast implements SelfHandling
{
/**
* 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.
*
* @return void
*/
public function handle()
{
//
}
}
In this example, the PurchasePodcast
job needs to send e-mails when a podcast is purchased. So, we will inject a service that is able to send e-mails. Since the service is injected, we are able to easily swap it out with another implementation. We are also able to easily "mock", or create a dummy implementation of the mailer when testing our application.
这个例子中, 当播客被购买时,PurchasePodcast 命令处理器需要发送一封电子邮件。 所以,我们将注入一个服务来提供这个能力。当这个服务被注入后,我们就可以轻易地切换到不同的实现。 当测试我们的应用程序时, 我们同样也可以轻易地“模拟”,或者创建一个虚拟的发信服务实现,来帮助我们进行测试。
A deep understanding of the Laravel service container is essential to building a powerful, large application, as well as for contributing to the Laravel core itself.
如果要创建一个强大且大型的应用,或者对Laravel的内核做贡献,首先必须对Laravel的服务容器进行深入了解。
#绑定
Almost all of your service container bindings will be registered within service providers, so all of these examples will demonstrate using the container in that context. However, there is no need to bind classes into the container if they do not depend on any interfaces. The container does not need to be instructed how to build these objects, since it can automatically resolve such "concrete" objects using PHP's reflection services.
Within a service provider, you always have access to the container via the $this->app
instance variable. We can register a binding using the bind
method, passing the class or interface name that we wish to register along with a Closure
that returns an instance of the class:
(这里5.0文档的翻译不准确,让人看得直挠头。 我这样翻译:)几乎所有的服务容器绑定,都发生在服务提供者里面, 所以,所有这些例子都是在上面语境下使用容器时所展现的。然而,没有必要在容器里绑定类,如果它们不依赖任何接口。 容器不需要被告知如何建立这些对象,因为它会使用PHP的反射服务自动解析这个“具体”对象。
$this->app->bind('HelpSpotAPI', function ($app) {
return new HelpSpotAPI($app['HttpClient']);
});
Notice that we receive the container itself as an argument to the resolver. We can then use the container to resolve sub-dependencies of the object we are building.
注意到我们的解析器接收一个容器对象作为引数, 我们然后就可以使用这个容器去解析底下的需要绑定的对象。
绑定一个单例
The singleton
method binds a class or interface into the container that should only be resolved one time, and then that same instance will be returned on subsequent calls into the container:
singleton方法绑定一个类或接口到容器里面,而且只会被解析一次, 之后相同的实例在以后的调用会被返回到容器里。
$this->app->singleton('FooBar', function ($app) {
return new FooBar($app['SomethingElse']);
});
绑定一个已存在的实例
You may also bind an existing object instance into the container using the instance
method. The given instance will always be returned on subsequent calls into the container:
你也可以使用 instance 方法,绑定一个已经存在的实例到容器, 接下来将总是返回该实例。
$fooBar = new FooBar(new SomethingElse);
$this->app->instance('FooBar', $fooBar);
#绑定实例到接口
A very powerful feature of the service container is its ability to bind an interface to a given implementation. For example, let's assume we have an EventPusher
interface and aRedisEventPusher
implementation. Once we have coded our RedisEventPusher
implementation of this interface, we can register it with the service container like so:
服务容器一个很强大的特性是他可以给接口绑定一个给定的实现,举例, 让我们假设我们有个EventPusher 接口, 和要给RedisEventPusher 实现, 一旦我们实现了RedisEventPusher, 我们就可以用服务容器来绑定它。
$this->app->bind('AppContractsEventPusher', 'AppServicesRedisEventPusher');
This tells the container that it should inject the RedisEventPusher
when a class needs an implementation of EventPusher
. Now we can type-hint the EventPusher
interface in a constructor, or any other location where dependencies are injected by the service container:
这告诉容器,当一个类需要一个EventPusher的实现的时候,它应该注入RedisEventPusher, 现在我们就可以 在类的构造器中类型提示(type-hint)EventPusher接口,或其他容器可以注入依赖的地方。
use AppContractsEventPusher;
/**
* Create a new class instance.
*
* @param EventPusher $pusher
* @return void
*/
public function __construct(EventPusher $pusher)
{
$this->pusher = $pusher;
}
#上下文绑定(context binding)
Sometimes you may have two classes that utilize the same interface, but you wish to inject different implementations into each class. For example, when our system receives a new Order, we may want to send an event via PubNub rather than Pusher. Laravel provides a simple, fluent interface for defining this behavior:
有时候,你有两个类需要用到同一个接口,但是你希望为每个类注入不同的接口实现。 例如当我们的系统收到一个新的订单时, 我们需要使用PubNub来代替Pusher发送消息。Laravel提供了一个简单便利的接口来定义以上行为
$this->app->when('AppHandlersCommandsCreateOrderHandler')
->needs('AppContractsEventPusher')
->give('AppServicesPubNubEventPusher');
You may even pass a Closure to the give method:
$this->app->when('AppHandlersCommandsCreateOrderHandler')
->needs('AppContractsEventPusher')
->give(function () {
// Resolve dependency...
});
#标签
Occasionally, you may need to resolve all of a certain "category" of binding. For example, perhaps you are building a report aggregator that receives an array of many differentReport
interface implementations. After registering the Report
implementations, you can assign them a tag using the tag
method:
偶尔你可能需要解析绑定中的某个类别, 例如你正在建设要给汇总报表,它需要接收实现了Report接口的不同实现的数组。 在注册了Report 的这些实现后,你可以用tag方法来给他们赋予一个标签:
$this->app->bind('SpeedReport', function () {
//
});
$this->app->bind('MemoryReport', function () {
//
});
$this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');
Once the services have been tagged, you may easily resolve them all via the tagged
method:
一旦这些服务已经被打标签,就可以用tagged方法轻易解析他们
$this->app->bind('ReportAggregator', function ($app) {
return new ReportAggregator($app->tagged('reports')); //这里ReportAggregator接收的是一个数组
});
#解析
There are several ways to resolve something out of the container. First, you may use the make
method, which accepts the name of the class or interface you wish to resolve:
有几种方法可从容器中解析出某些东西, 首先,你可以使用 make方法, 它接受一个你希望解析的类或接口的名字
$fooBar = $this->app->make('FooBar');
Secondly, you may access the container like an array, since it implements PHP's ArrayAccess
interface:
第二种,你可以像数组一般使用容器,因为它实现了PHP的ArrayAccess接口
$fooBar = $this->app['FooBar'];
Lastly, but most importantly, you may simply "type-hint" the dependency in the constructor of a class that is resolved by the container, including controllers, event listeners, queue jobs, middleware, and more. In practice, this is how most of your objects are resolved by the container.
The container will automatically inject dependencies for the classes it resolves. For example, you may type-hint a repository defined by your application in a controller's constructor. The repository will automatically be resolved and injected into the class:
最后也是最重要的,你可以在一个类的构造器里面只是去”类型提示”那个依赖,容器会自动注入解析后的类,比如, 你可以在应用的控制器里类型提示一个repository实体类, ,然后实体类会自动被解析然后注入。
<?php
namespace AppHttpControllers;
use IlluminateRoutingController;
use AppUsersRepository 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)
{
//
}
}
#容器事件
The service container fires an event each time it resolves an object. You may listen to this event using the resolving
method:
服务容器每次发出一个解析对象的事件, 你使用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"...
});
As you can see, the object being resolved will be passed to the callback, allowing you to set any additional properties on the object before it is given to its consumer.
被解析的对象传到闭包方法中,允许你设置其他属性。