イントロダクション
Laravelのサービスコンテナは、クラス間の依存を管理する強力な管理ツールです。依存注入というおかしな言葉は主に「コンストラクターか、ある場合にはセッターメソッドを利用し、あるクラスをそれらに依存しているクラスへ外部から注入する」という意味で使われます。
シンプルな例を見てみましょう。
<?php namespace App\Handlers\Commands;
use App\User;
use App\Commands\PurchasePodcastCommand;
use Illuminate\Contracts\Mail\Mailer;
class PurchasePodcastHandler {
/**
* メイラーの実装
*/
protected $mailer;
/**
* 新しいインスタンスの生成
*
* @param Mailer $mailer
* @return void
*/
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
/**
* ポッドキャストの購入
*
* @param PurchasePodcastCommand $command
* @return void
*/
public function handle(PurchasePodcastCommand $command)
{
//
}
}
この例でPurchasePodcast
コマンドハンドラーは、ポッドキャスト購入時にメールを送信する必要があります。そのため、メールを送信できるサービスを注入しています。サービスが外部から注入されているため、簡単に他の実装と交換できます。さらに「モック」したり、アプリケーションのテストを行うためにメーラーのダミー実装を作成したりするのも簡単に実現できます。
Laravelのサービスコンテナを深く理解するのは、パワフルで大きなアプリケーションを構築することと同時に、Laravelコア自身に貢献するために重要なことです。
基本的な使用法
結合
サービスコンテナへの登録は、ほぼ全てサービスプロバイダーの中で行われるでしょう。そのため以降のサンプルコードは、コンテナをプロバイダー内で使用するデモンストレーションになっています。しかし、例えばファクトリーのような、アプリケーションのどこか他の場所で、コンテナのインスタンスが必要になったら、Illuminate\Contracts\Container\Container
契約をタイプヒントで指定すれば、コンテナのインスタンスが注入されます。もしくは、App
ファサードでコンテナにアクセスすることもできます。
基本的なリゾルバーの登録
サービスプロバイダーの中でコンテナへアクセスするには、$this->app
インスタンス変数を使用する必要があります。
サービスコンテナへ依存を登録するには、多くの方法があります。この中には、クロージャーのコールバックやインターフェイスと実装の結合も含まれます。最初に、クロージャーのコールバックを説明しましょう。クロージャーを利用したリゾルバー(resolver:依存の解決ロジック、通常はインスンタンスの生成)は、キー(典型的な使用法ではクラス名)と値(インスタンス)を返すクロージャーをコンテナの中で登録します。
$this->app->bind('FooBar', function($app)
{
return new FooBar($app['SomethingElse']);
});
シングルトンとして登録
場合により、コンテナに結合した何かを一度だけ依存解決したいことがあると思います。コンテナが何度呼び出されても、同じインスタンスが返されます。
$this->app->singleton('FooBar', function($app)
{
return new FooBar($app['SomethingElse']);
});
存在するインスタンスをコンテナへ結合
既に存在するオブジェクトのインスタンスをinstance
メソッドを用いて、コンテナに結合することもできます。指定されたインスタンスが、以降のコンテナで呼び出されるたびに返されます。
$fooBar = new FooBar(new SomethingElse);
$this->app->instance('FooBar', $fooBar);
依存解決
コンテナから何かの依存を解決して取り出すには、様々な方法が取れます。最初に、make
メソッドを使用してみましょう。
$fooBar = $this->app->make('FooBar');
コンテナはPHPのArrayAccess
を実装していますので、次にコンテナへの「配列アクセス」を使ってみましょう。
$fooBar = $this->app['FooBar'];
最後に一番重要な、コンテナにより依存解決されるコントローラーや、イベントリスナー/キュージョブ/フィルターなどのクラスのコンストラクターで、「タイプヒント」を指定するだけで依存を指定する方法です。コンテナが自動的に依存を注入します。
<?php namespace App\Http\Controllers;
use Illuminate\Routing\Controller;
use App\Users\Repository as UserRepository;
class UserController extends Controller {
/**
* ユーザーリポジトリーインスタンス
*/
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)
{
//
}
}
インターフェイスと実装の結合
具象依存クラスの注入
サービスコンテナーのとても強力な機能は、インターフェイスを指定された実装に結合することです。例えば、アプリケーションが、リアルタイムイベントを送受信するサービスであるPusher Webサービスと統合されているとしましょう。PusherのPHP DSKを使用していれば、クラスへPusherクライアントのインスタンスを注入することができます。
<?php namespace App\Handlers\Commands;
use App\Commands\CreateOrder;
use Pusher\Client as PusherClient;
class CreateOrderHandler {
/**
* Pusher SDKクライアントインスタンス
*/
protected $pusher;
/**
* 新しい注文処理インスタンスの生成
*
* @param PusherClient $pusher
* @return void
*/
public function __construct(PusherClient $pusher)
{
$this->pusher = $pusher;
}
/**
* 指定されたコマンドの実行
*
* @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する
*
* @param string $event
* @param array $data
* @return void
*/
public function push($event, array $data);
}
このインターフェイスに対して、PusherEventPusher
実装をコードし終えたら、次のようにサービスコンテナで登録します。
$this->app->bind('App\Contracts\EventPusher', 'App\Services\PusherEventPusher');
これはコンテナに対し、EventPusher
の実装が必要になったら、PusherEventPusher
を注入するように指示しています。
/**
* 新しい注文処理インスタンスの生成
*
* @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');
タグ付け
ある明確な「カテゴリー」の結合を全部解決する必要がある場合も存在すると思います。例えば、Report
インターフェイスの実装である、異なった多くの配列を受け取る、レポート収集プログラム(aggregator)を構築しているとしましょう。それらを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'));
});
アプリケーションでの実践
Laravelではアプリケーションの柔軟性とテスタビリティーを上げるため、サービスコンテナを利用できる箇所がたくさんあります。重要な一例は、コントローラーの依存解決時です。コントローラーは全てサービスコンテナを通じて解決されます。つまり、コントローラーのコンストラクターの中で、依存をタイプヒントで指定でき、それらは自動的に注入されます。
<?php namespace App\Http\Controllers;
use Illuminate\Routing\Controller;
use App\Repositories\OrderRepository;
class OrdersController extends Controller {
/**
* 注文リポジトリーインスタンス
*/
protected $orders;
/**
* コントローラーインスタンスの生成
*
* @param OrderRepository $orders
* @return void
*/
public function __construct(OrderRepository $orders)
{
$this->orders = $orders;
}
/**
* 全注文の表示
*
* @return Response
*/
public function index()
{
$orders = $this->orders->all();
return view('orders', ['orders' => $orders]);
}
}
この例では、OrderRepository
クラスは自動的にコントローラーへ注入されます。つまりユニットテストを行う場合には、コンテナに「モック」のOrderRepository
を結合して、苦労なくデータベース層のやりとりをスタブ化できるのです。
その他のコンテナ使用例
もちろん、前記の通り、Laravelでサービスコンテナにより依存解決されるクラスは、コントローラーだけではありません。ルートクロージャーやフィルター、キュージョブ、イベントリスナーなどでも依存をタイプヒントで指定できます。そうしたコンテキストでのコンテナ使用例は、各ドキュメントを参照してください。
コンテナイベント
リゾルバーリスナーの登録
コンテナは、オブジェクトを依存解決した時に毎回イベントを発行します。このイベントは、resolving
を使用して、リッスンできます。
$this->app->resolving(function($object, $app)
{
// どんなタイプのものでもコンテナが依存解決した時に呼び出される…
});
$this->app->resolving(function(FooBar $fooBar, $app)
{
// "FooBar"タイプのオブジェクトがコンテナにより依存解決された時に呼び出される…
});
依存解決されたオブジェクトが、コールバックに渡されます。