イントロダクション
Laravelのサービスコンテナは、クラス間の依存を管理する強力な管理ツールです。依存注入というおかしな言葉は主に「コンストラクターか、ある場合にはセッターメソッドを利用し、あるクラスをそれらに依存しているクラスへ外部から注入する」という意味で使われます。
シンプルな例を見てみましょう。
<?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
コマンドハンドラは、ポッドキャスト購入時にメールを送信する必要があります。そのためメールを送信できるサービスを注入しています。サービスが外部から注入されているため、簡単に他の実装と交換できます。さらに「モック」したり、アプリケーションのテストを行うためにメーラーのダミー実装を作成したりするのも簡単に実現できます。
Laravelのサービスコンテナを深く理解することは、パワフルで大きなアプリケーションを構築するのと同時に、Laravelコア自身に貢献するために重要なことです。
結合
サービスコンテナへの登録は、ほぼ全てサービスプロバイダの中で行うことになります。そのため以降のサンプルコードは、コンテナをプロバイダ内で結合するデモンストレーションになっています。しかし、インターフェイスに依存していなければコンテナにクラスを結合する必要はありません。コンテナはそのような「具象」オブジェクトはPHPのリフレクションサービスを使用し、自動的に依存を解決しますので、オブジェクトの生成方法をコンテナに指示する必要はありません。
サービスプロバイダの中からは、いつでも$this->app
インスタンス変数によりコンテナにアクセスできます。bind
メソッドへ登録したいクラス名かインターフェイス名と、クラスのインスタンスを返す「クロージャ」を引数として渡せば、結合を登録できます。
$this->app->bind('HelpSpot\API', function ($app) {
return new HelpSpot\API($app['HttpClient']);
});
コンテナ自身をリゾルバ―の引数として受け取っていることに注目してください。これで構築しているオブジェクトの依存を解決するためにも、コンテナを利用できます。
シングルトン結合
singleton
メソッドは、クラスやインターフェイスが一度だけ依存解決されるようにコンテナに登録します。以降コンテナから呼び出されると同じインスタンスが返されます。
$this->app->singleton('FooBar', function ($app) {
return new FooBar($app['SomethingElse']);
});
インスタンス結合
既に存在するオブジェクトのインスタンスをinstance
メソッドを用いて、コンテナに結合することもできます。指定されたインスタンスが、以降のコンテナで呼び出されるたびに返されます。
$fooBar = new FooBar(new SomethingElse);
$this->app->instance('FooBar', $fooBar);
インターフェイスと実装の結合
サービスコンテナーのとても強力な機能は、インターフェイスを指定された実装に結合できることです。たとえばEventPusher
インターフェイスとRedisEventPusher
実装があるとしましょう。このインターフェイスのRedisEventPusher
を実装し終えたら、サービスコンテナに登録できます。
$this->app->bind('App\Contracts\EventPusher', 'App\Services\RedisEventPusher');
これはつまり、EventPusher
の実装クラスが必要な時、コンテナはRedisEventPusher
を注入するということです。ではコンストラクタか、もしくはサービスコンテナが依存を注入できる場所で、EventPusher
インターフェイスをタイプヒントしてみましょう。
use App\Contracts\EventPusher;
/**
* 新しいインスタンスの生成
*
* @param EventPusher $pusher
* @return void
*/
public function __construct(EventPusher $pusher)
{
$this->pusher = $pusher;
}
コンテキストによる結合
時には、同じインターフェイスを使用した2つのクラスがあり、クラスごとに異なった実装を注入しなくてはならない場合もあるでしょう。たとえばシステムが新しい注文(order)を受けた時は、Pusherの代わりに、PubNubを利用してイベントを送る必要がある場合です。Laravelはこのような振る舞いを定義できる、シンプルで読みやすいインターフェイスを提供しています。
$this->app->when('App\Handlers\Commands\CreateOrderHandler')
->needs('App\Contracts\EventPusher')
->give('App\Services\PubNubEventPusher');
give
メソッドにはクロージャを渡すことも可能です。
$this->app->when('App\Handlers\Commands\CreateOrderHandler')
->needs('App\Contracts\EventPusher')
->give(function () {
// 依存の解決…
});
プリミティブ結合
クラスにより依存注入されたインスタンスを受け取ることもあるでしょうし、さらに整数のようなプリミティブな値を挿入する必要も起きるでしょう。必要なクラスのコンテキストによる結合を使用すれば簡単です。
$this->app->when('App\Handlers\Commands\CreateOrderHandler')
->needs('$maxOrderCount')
->give(10);
タグ付け
ある明確な「カテゴリー」の結合を全部解決する必要がある場合も存在すると思います。例えば、Report
インターフェイスの実装である、異なった多くの配列を受け取る、レポート収集プログラム(aggregator)を構築しているとしましょう。Report
の実装を登録後、それらをtag
メソッでタグ付けすることができます。
$this->app->bind('SpeedReport', function () {
//
});
$this->app->bind('MemoryReport', function () {
//
});
$this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');
サービスにタグを付けてしまえば、tagged
メソッドで簡単に全部解決できます。
$this->app->bind('ReportAggregator', function ($app) {
return new ReportAggregator($app->tagged('reports'));
});
依存解決
コンテナから何かを依存解決する方法は、数多くあります。最初に解決したいクラスかインターフェイスの名前をmake
メソッドに指定することができます。
$fooBar = $this->app->make('FooBar');
次に、コンテナはPHPのArrayAccess
インターフェイスを実装していますので、配列のようにアクセスできます。
$fooBar = $this->app['FooBar'];
最後に一番重要な方法ですが、クラスのコンストラクターで「タイプヒント」を行うだけでコンテナが解決します。これには、コントローラー、イベントリスナ、キュージョブ、ミドルウェアなどが含まれます。実際の開発でコンテナによりオブジェクトの依存を解決する場合には、一番多用されるでしょう。
コンテナは解決しようとしているクラスの依存も自動的に注入します。たとえばコントローラーのコンストラクターでアプリケーションにより定義されているリポジトリーをタイプヒントしたとしましょう。そのリポジトリーは自動的に解決され、クラスへ依存注入されます。
<?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)
{
//
}
}
コンテナイベント
コンテナはオブジェクトの依存解決時に毎回イベントを発行します。このイベントは、resolving
を使用して購読できます。
$this->app->resolving(function ($object, $app) {
// どんなタイプのオブジェクトをコンテナが解決した場合でも呼び出される
});
$this->app->resolving(FooBar::class, function (FooBar $fooBar, $app) {
// "FooBar"タイプのオブジェクトをコンテナが解決した場合に呼び出される
});
ご覧の通り、依存解決対象のオブジェクトがコールバックに渡され、最終的に取得元へ渡される前に追加でオブジェクトのプロパティをセットすることができます。