Laravel 5.0 サービスコンテナ

イントロダクション

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"タイプのオブジェクトがコンテナにより依存解決された時に呼び出される…
});

依存解決されたオブジェクトが、コールバックに渡されます。

ドキュメント章別ページ

Artisan CLI

ヘッダー項目移動

注目:アイコン:ページ内リンク設置(リンクがないヘッダーへの移動では、リンクがある以前のヘッダーのハッシュをURLへ付加します。

移動

クリックで即時移動します。

設定

適用ボタンクリック後に、全項目まとめて適用されます。

カラーテーマ
和文指定 Pagination
和文指定 Scaffold
Largeスクリーン表示幅
インデント
本文フォント
コードフォント
フォント適用確認

フォントの指定フィールドから、フォーカスが外れると、当ブロックの内容に反映されます。EnglishのDisplayもPreviewしてください。

フォント設定時、表示に不具合が出た場合、当サイトのクッキーを削除してください。

バックスラッシュを含むインライン\Code\Blockの例です。

以下はコードブロックの例です。

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    /**
     * ユーザに関連する電話レコードを取得
     */
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}

設定を保存する前に、表示が乱れないか必ず確認してください。CSSによるフォントファミリー指定の知識がない場合は、フォントを変更しないほうが良いでしょう。

キーボード・ショートカット

オープン操作

PDC

ページ(章)移動の左オフキャンバスオープン

HA

ヘッダー移動モーダルオープン

MS

移動/設定の右オフキャンバスオープン

ヘッダー移動

T

最初のヘッダーへ移動

E

最後のヘッダーへ移動

NJ

次ヘッダー(H2〜H4)へ移動

BK

前ヘッダー(H2〜H4)へ移動

その他

?

このヘルプページ表示
閉じる