イントロダクション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\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Repositories\UserRepository;
use App\User;
class UserController extends Controller
{
/**
* ユーザーリポジトリの実装
*
* @var UserRepository
*/
protected $users;
/**
* 新しいコントローラインスタンスの生成
*
* @param UserRepository $users
* @return void
*/
public function __construct(UserRepository $users)
{
$this->users = $users;
}
/**
* 指定ユーザーのプロフィール表示
*
* @param int $id
* @return Response
*/
public function show($id)
{
$user = $this->users->find($id);
return view('user.profile', ['user' => $user]);
}
}
この例中で、UserController
はデータソースからユーザーを取得する必要があります。そのため、ユーザーを取得できるサービスを注入しています。このようなコンテキストでは、データベースからユーザー情報を取得するために、ほとんどの場合でEloquentが使われるでしょう。しかしながら、リポジトリは外部から注入されているため、他の実装へ簡単に交換できます。さらに、「モック」することも簡単ですし、アプリケーションのテストでUserRepository
のダミー実装を作成することもできます。In this example, the UserController
needs to retrieve users from a data source. So, we will inject a service that is able to retrieve users. In this context, our UserRepository
most likely uses Eloquent[/docs/{{version}}/eloquent] to retrieve user information from the database. However, since the repository 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 UserRepository
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
結合の基礎Binding Basics
ほとんどのサービスコンテナの結合は、サービスプロバイダの中で行われています。そのため、ほとんどの例はコンテナ中で使用される状況をデモンストレートしています。Almost all of your service container bindings will be registered within service providers[/docs/{{version}}/providers], so most of these examples will demonstrate using the container in that context.
{tip} 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 these objects using reflection.
">Tip!! インターフェイスに依存していないのであれば、コンテナでクラスを結合する必要はありません。コンテナにオブジェクトを結合する方法を指示する必要はなく、リフレクションを使用してそのオブジェクトを自動的に依存解決します。
シンプルな結合Simple Bindings
サービスプロバイダの中からは、いつでも$this->app
プロパティにより、コンテナへアクセスできます。bind
メソッドへ登録したいクラス名かインターフェイス名と、クラスのインスタンスを返す「クロージャ」を引数として渡せば、結合を登録できます。Within a service provider, you always have access to the container via the $this->app
property. 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->make('HttpClient'));
});
コンテナ自身がリゾルバの引数として渡されることに注目してください。これで構築中の依存を解決するためにも、コンテナを利用できます。Note 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. Once a singleton binding is resolved, the same object instance will be returned on subsequent calls into the container:
$this->app->singleton('HelpSpot\API', function ($app) {
return new \HelpSpot\API($app->make('HttpClient'));
});
インスタンス結合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:
$api = new \HelpSpot\API(new HttpClient);
$this->app->instance('HelpSpot\API', $api);
プリミティブ結合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\Http\Controllers\UserController')
->needs('$variableName')
->give($value);
インターフェイスと実装の結合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 statement 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つのクラスがあり、クラスごとに異なった実装を注入しなくてはならない場合もあるでしょう。たとえば、2つのコントローラが異なったIlluminate\Contracts\Filesystem\Filesystem
契約の実装に依存している場合です。Laravelでは、このような振る舞いの定義をシンプルで、読み書きしやすくしています。Sometimes you may have two classes that utilize the same interface, but you wish to inject different implementations into each class. For example, two controllers may depend on different implementations of the Illuminate\Contracts\Filesystem\Filesystem
contract[/docs/{{version}}/contracts]. Laravel provides a simple, fluent interface for defining this behavior:
use App\Http\Controllers\PhotoController;
use App\Http\Controllers\UploadController;
use App\Http\Controllers\VideoController;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Support\Facades\Storage;
$this->app->when(PhotoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('local');
});
$this->app->when([VideoController::class, UploadController::class])
->needs(Filesystem::class)
->give(function () {
return Storage::disk('s3');
});
タグ付け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'));
});
結合の拡張Extending Bindings
extend
メソッドでサービスの解決結果を修正できます。たとえば、あるサービスが解決されたときに、そのサービスをデコレート、もしくは設定するために追加のコードを実行できます。extend
メソッドは唯一引数としてクロージャを受け取り、修正したサービスを返します。クロージャは依存解決されたサービスとコンテナインスタンスを受け取ります。The extend
method allows the modification of resolved services. For example, when a service is resolved, you may run additional code to decorate or configure the service. The extend
method accepts a Closure, which should return the modified service, as its only argument. The Closure receives the service being resolved and the container instance:
$this->app->extend(Service::class, function ($service, $app) {
return new DecoratedService($service);
});
依存解決Resolving
make
メソッドThe make
Method
コンテナによりクラスインスタンスを依存解決する場合は、make
メソッドを使います。make
メソッドには、依存解決したいクラスかインターフェイスの名前を引数として渡します。You may use the make
method to resolve a class instance out of the container. The make
method accepts the name of the class or interface you wish to resolve:
$api = $this->app->make('HelpSpot\API');
もし、$app
変数へアクセスできない場所で依存解決したい場合は、グローバルなresolve
ヘルパが使えます。If you are in a location of your code that does not have access to the $app
variable, you may use the global resolve
helper:
$api = resolve('HelpSpot\API');
依存しているクラスが、コンテナにより解決できない場合は、makeWith
メソッドへ連想配列を渡すことにより注入できます。If some of your class' dependencies are not resolvable via the container, you may inject them by passing them as an associative array into the makeWith
method:
$api = $this->app->makeWith('HelpSpot\API', ['id' => 1]);
自動注入Automatic Injection
一番重要な依存解決方法は、コンテナによる依存解決が行われるクラスのコンストラクタで、シンプルに依存を「タイプヒント」で記述するやりかたです。これはコントローラ、イベントリスナ、ミドルウェアなどで利用できます。さらに、キュージョブのhandle
メソッドでも、依存をタイプヒントで指定できます。実践において、コンテナによりオブジェクトの解決が行われるのは、これが一番多くなります。Alternatively, and importantly, you may "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], middleware[/docs/{{version}}/middleware], and more. Additionally, you may type-hint dependencies in the handle
method of queued jobs[/docs/{{version}}/queues]. In practice, this is how most of your objects should be resolved by the container.
たとえば、コントローラのコンストラクタで、アプリケーションにより定義されたリポジトリをタイプヒントしてみましょう。このリポジトリは、自動的に依存解決され、クラスへ注入されます。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(\HelpSpot\API::class, function ($api, $app) {
// "HelpSpot\API"クラスのオブジェクトをコンテナが解決した場合に呼び出される
});
ご覧の通り、依存解決対象のオブジェクトがコールバックに渡され、最終的に取得元へ渡される前に追加でオブジェクトのプロパティをセットできます。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.
PSR-11PSR-11
Laravelのサービスコンテナは、PSR-11インターフェイスを持っています。これにより、Laravelコンテナのインスタンスを取得するために、PSR-11コンテナインターフェイスをタイプヒントで指定できます。Laravel's service container implements the PSR-11[https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-11-container.md] interface. Therefore, you may type-hint the PSR-11 container interface to obtain an instance of the Laravel container:
use Psr\Container\ContainerInterface;
Route::get('/', function (ContainerInterface $container) {
$service = $container->get('Service');
//
});
指定された識別子を解決できない場合は例外が投げられます。識別子に何も結合されていない場合は、Psr\Container\NotFoundExceptionInterface
のインスタンスが例外として投げられます。識別子が結合されているが、依存解決できない場合は、Psr\Container\ContainerExceptionInterface
のインスタンスが投げられます。An exception is thrown if the given identifier can't be resolved. The exception will be an instance of Psr\Container\NotFoundExceptionInterface
if the identifier was never bound. If the identifier was bound but was unable to be resolved, an instance of Psr\Container\ContainerExceptionInterface
will be thrown.