Laravel 5.3 サービスコンテナ

イントロダクション

Laravelのサービスコンテナは、クラス間の依存を管理する強力な管理ツールです。依存注入というおかしな言葉は主に「コンストラクターか、ある場合にはセッターメソッドを利用し、あるクラスをそれらに依存しているクラスへ外部から注入する」という意味で使われます。

シンプルな例を見てみましょう。

<?php

namespace App\Http\Controllers;

use App\User;
use App\Repositories\UserRepository;
use App\Http\Controllers\Controller;

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のダミー実装を作成することもできます。

Laravelのサービスコンテナを深く理解することは、パワフルで大きなアプリケーションを構築するのと同時に、Laravelコア自身に貢献するために重要なことです。

結合

結合の基礎

ほとんどのサービスコンテナの結合は、サービスプロバイダの中で行われています。そのため、ほとんどの例はコンテナ中で使用される状況をデモンストレートしています。

Tip!! インターフェイスに依存していないのであれば、コンテナでクラスを結合する必要はありません。コンテナにオブジェクトを結合する方法を指示する必要はなく、リフレクションを使用してそのオブジェクトを自動的に依存解決します。

シンプルな結合

サービスプロバイダの中からは、いつでも$this->appプロパティにより、コンテナへアクセスできます。bindメソッドへ登録したいクラス名かインターフェイス名と、クラスのインスタンスを返す「クロージャ」を引数として渡せば、結合を登録できます。

$this->app->bind('HelpSpot\API', function ($app) {
    return new HelpSpot\API($app->make('HttpClient'));
});

コンテナ自身をリゾルバ―の引数として受け取っていることに注目してください。これで構築しているオブジェクトの依存を解決するためにも、コンテナを利用できます。

シングルトン結合

singletonメソッドは、クラスやインターフェイスが一度だけ依存解決されるようにコンテナに登録します。一度シングルトン結合が解決されると、以降にコンテナへこの結合が呼び出されるたび、同じオブジェクトインスタンスが返されます。

$this->app->singleton('HelpSpot\API', function ($app) {
    return new HelpSpot\API($app->make('HttpClient'));
});

インスタンス結合

既に存在するオブジェクトのインスタンスをinstanceメソッドを用いて、コンテナに結合することもできます。指定されたインスタンスが、以降のコンテナで呼び出されるたびに返されます。

$api = new HelpSpot\API(new HttpClient);

$this->app->instance('HelpSpot\Api', $api);

プリミティブ結合

クラスにより依存注入されたインスタンスを受け取ることもあるでしょうし、さらに整数のようなプリミティブな値を挿入する必要も起きるでしょう。必要なクラスのコンテキストによる結合を使用すれば簡単です。

$this->app->when('App\Http\Controllers\UserController')
          ->needs('$variableName')
          ->give($value);

インターフェイスと実装の結合

サービスコンテナーのとても強力な機能は、インターフェイスを指定された実装に結合できることです。たとえば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つのクラスがあり、クラスごとに異なった実装を注入しなくてはならない場合もあるでしょう。たとえば、2つのコントローラが異なったIlluminate\Contracts\Filesystem\Filesystem契約の実装に依存している場合です。Laravelでは、このような場合の定義をシンプルで、読み書きしやすくなっています。

use Illuminate\Support\Facades\Storage;
use App\Http\Controllers\PhotoController;
use App\Http\Controllers\VideoController;
use Illuminate\Contracts\Filesystem\Filesystem;

$this->app->when(PhotoController::class)
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk('local');
          });

$this->app->when(VideoController::class)
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk('s3');
          });

タグ付け

ある明確な「カテゴリー」の結合を全部解決する必要がある場合も存在すると思います。例えば、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メソッド

コンテナによりクラスインスタンスを依存解決する場合は、makeメソッドを使います。makeメソッドには、依存解決したいクラスかインターフェイスの名前を引数として渡します。

$api = $this->app->make('HelpSpot\API');

もし、$app変数へアクセスできない場所で依存解決したい場合は、グローバルなresolveヘルパが使えます。

$api = resolve('HelpSpot\API');

自動注入

一番重要な依存解決方法は、コンテナによる依存解決が行われるクラスのコンストラクタで、シンプルに依存を「タイプヒント」で記述するやりかたです。これはコントローライベントリスナキュージョブミドルウェアなどで利用できます。実践でコンテナによりオブジェクトの解決が行われるのは、これが一番多くなります。

たとえば、コントローラのコンストラクタで、アプリケーションにより定義されたリポジトリをタイプ品としてみましょう。このリポジトリは、自動的に依存解決され、クラスへ注入されます。

<?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(HelpSpot\API::class, function ($api, $app) {
    // "HelpSpot\API"クラスのオブジェクトをコンテナが解決した場合に呼び出される
});

ご覧の通り、依存解決対象のオブジェクトがコールバックに渡され、最終的に取得元へ渡される前に追加でオブジェクトのプロパティをセットすることができます。

ドキュメント章別ページ

開発環境
ビューとテンプレート
Artisanコンソール
公式パッケージ

ヘッダー項目移動

注目:アイコン:ページ内リンク設置(リンクがないヘッダーへの移動では、リンクがある以前のヘッダーのハッシュを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)へ移動

その他

?

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