イントロダクションIntroduction
Laravelのサービスコンテナは、クラス間の依存を管理する強力な管理ツールです。依存注入というおかしな言葉は主に「コンストラクターか、ある場合にはセッターメソッドを利用し、あるクラスをそれらに依存しているクラスへ外部から注入する」という意味で使われます。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:
<?php
namespace App\Jobs;
use App\User;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Contracts\Bus\SelfHandling;
class PurchasePodcast implements SelfHandling
{
/**
* メーラーの実装
*/
protected $mailer;
/**
* 新しいインスタンスの生成
*
* @param Mailer $mailer
* @return void
*/
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
/**
* ポッドキャストの購入
*
* @return void
*/
public function handle()
{
//
}
}
この例でPurchasePodcast
コマンドハンドラは、ポッドキャスト購入時にメールを送信する必要があります。そのためメールを送信できるサービスを注入しています。サービスが外部から注入されているため、簡単に他の実装と交換できます。さらに「モック」したり、アプリケーションのテストを行うためにメーラーのダミー実装を作成したりするのも簡単に実現できます。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.
Laravelのサービスコンテナを深く理解することは、パワフルで大きなアプリケーションを構築するのと同時に、Laravelコア自身に貢献するために重要なことです。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.
結合Binding
サービスコンテナへの登録は、ほぼ全てサービスプロバイダの中で行うことになります。そのため以降のサンプルコードは、コンテナをプロバイダ内で結合するデモンストレーションになっています。しかし、インターフェイスに依存していなければコンテナにクラスを結合する必要はありません。コンテナはそのような「具象」オブジェクトはPHPのリフレクションサービスを使用し、自動的に依存を解決しますので、オブジェクトの生成方法をコンテナに指示する必要はありません。Almost all of your service container bindings will be registered within service providers[/docs/{{version}}/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 on how to build these objects, since it can automatically resolve such "concrete" objects using PHP's reflection services.
サービスプロバイダの中からは、いつでも$this->app
インスタンス変数によりコンテナにアクセスできます。bind
メソッドへ登録したいクラス名かインターフェイス名と、クラスのインスタンスを返す「クロージャ」を引数として渡せば、結合を登録できます。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:
$this->app->bind('HelpSpot\API', function ($app) {
return new HelpSpot\API($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.
シングルトン結合Binding A Singleton
singleton
メソッドは、クラスやインターフェイスが一度だけ依存解決されるようにコンテナに登録します。以降コンテナから呼び出されると同じインスタンスが返されます。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:
$this->app->singleton('FooBar', function ($app) {
return new FooBar($app['SomethingElse']);
});
インスタンス結合Binding Instances
既に存在するオブジェクトのインスタンスをinstance
メソッドを用いて、コンテナに結合することもできます。指定されたインスタンスが、以降のコンテナで呼び出されるたびに返されます。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:
$fooBar = new FooBar(new SomethingElse);
$this->app->instance('FooBar', $fooBar);
インターフェイスと実装の結合Binding Interfaces To Implementations
サービスコンテナーのとても強力な機能は、インターフェイスを指定された実装に結合できることです。たとえばEventPusher
インターフェイスとRedisEventPusher
実装があるとしましょう。このインターフェイスのRedisEventPusher
を実装し終えたら、サービスコンテナに登録できます。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 a RedisEventPusher
implementation. Once we have coded our RedisEventPusher
implementation of this interface, we can register it with the service container like so:
$this->app->bind('App\Contracts\EventPusher', 'App\Services\RedisEventPusher');
これはつまり、EventPusher
の実装クラスが必要な時、コンテナはRedisEventPusher
を注入するということです。ではコンストラクタか、もしくはサービスコンテナが依存を注入できる場所で、EventPusher
インターフェイスをタイプヒントしてみましょう。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:
use App\Contracts\EventPusher;
/**
* 新しいインスタンスの生成
*
* @param EventPusher $pusher
* @return void
*/
public function __construct(EventPusher $pusher)
{
$this->pusher = $pusher;
}
コンテキストによる結合Contextual Binding
時には、同じインターフェイスを使用した2つのクラスがあり、クラスごとに異なった実装を注入しなくてはならない場合もあるでしょう。たとえばシステムが新しい注文(order)を受けた時は、Pusherの代わりに、PubNubを利用してイベントを送る必要がある場合です。Laravelはこのような振る舞いを定義できる、シンプルで読みやすいインターフェイスを提供しています。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[http://www.pubnub.com/] rather than Pusher. Laravel provides a simple, fluent interface for defining this behavior:
$this->app->when('App\Handlers\Commands\CreateOrderHandler')
->needs('App\Contracts\EventPusher')
->give('App\Services\PubNubEventPusher');
give
メソッドにはクロージャを渡すことも可能です。You may even pass a Closure to the give
method:
$this->app->when('App\Handlers\Commands\CreateOrderHandler')
->needs('App\Contracts\EventPusher')
->give(function () {
// 依存の解決…
});
プリミティブ結合Binding Primitives
クラスにより依存注入されたインスタンスを受け取ることもあるでしょうし、さらに整数のようなプリミティブな値を挿入する必要も起きるでしょう。必要なクラスのコンテキストによる結合を使用すれば簡単です。Sometimes you may have a class that receives some injected classes, but also needs an injected primitive value such as an integer. You may easily use contextual binding to inject any value your class may need:
$this->app->when('App\Handlers\Commands\CreateOrderHandler')
->needs('$maxOrderCount')
->give(10);
タグ付けTagging
ある明確な「カテゴリー」の結合を全部解決する必要がある場合も存在すると思います。例えば、Report
インターフェイスの実装である、異なった多くの配列を受け取る、レポート収集プログラム(aggregator)を構築しているとしましょう。Report
の実装を登録後、それらをtag
メソッでタグ付けすることができます。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 different Report
interface implementations. After registering the Report
implementations, you can assign them a tag using the tag
method:
$this->app->bind('SpeedReport', function () {
//
});
$this->app->bind('MemoryReport', function () {
//
});
$this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');
サービスにタグを付けてしまえば、tagged
メソッドで簡単に全部解決できます。Once the services have been tagged, you may easily resolve them all via the tagged
method:
$this->app->bind('ReportAggregator', function ($app) {
return new ReportAggregator($app->tagged('reports'));
});
依存解決Resolving
コンテナから何かを依存解決する方法は、数多くあります。最初に解決したいクラスかインターフェイスの名前をmake
メソッドに指定することができます。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:
$fooBar = $this->app->make('FooBar');
次に、コンテナはPHPのArrayAccess
インターフェイスを実装していますので、配列のようにアクセスできます。Secondly, you may access the container like an array, since it implements PHP's ArrayAccess
interface:
$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[/docs/{{version}}/controllers], event listeners[/docs/{{version}}/events], queue jobs[/docs/{{version}}/queues], middleware[/docs/{{version}}/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:
<?php
namespace App\Http\Controllers;
use App\Users\Repository as UserRepository;
class UserController extends Controller
{
/**
* Userリポジトリインスタンス
*/
protected $users;
/**
* 新しいインスタンスの生成
*
* @param UserRepository $users
* @return void
*/
public function __construct(UserRepository $users)
{
$this->users = $users;
}
/**
* 指定されたIDのユーザ表示
*
* @param int $id
* @return Response
*/
public function show($id)
{
//
}
}
コンテナイベントContainer Events
コンテナはオブジェクトの依存解決時に毎回イベントを発行します。このイベントは、resolving
を使用して購読できます。The service container fires an event each time it resolves an object. You may listen to this event using the resolving
method:
$this->app->resolving(function ($object, $app) {
// どんなタイプのオブジェクトをコンテナが解決した場合でも呼び出される
});
$this->app->resolving(FooBar::class, function (FooBar $fooBar, $app) {
// "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.