Lang x Lang

Service Container

Table of Contents

Introduction

Laravel service container は、class の依存関係の管理や依存性の注入を実行するための強力なツールです。"依存性の注入"は、基本的にこのような意味を持つ高尚なフレーズです:class の依存関係は、コンストラクタまたは、場合によっては"setter"method を介して class に"注入"されます。

簡単な例を見てみましょう:

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Repositories\UserRepository;
use App\Models\User;
use Illuminate\View\View;

class UserController extends Controller
{
    /**
     * Create a new controller instance.
     */
    public function __construct(
        protected UserRepository $users,
    ) {}

    /**
     * Show the profile for the given user.
     */
    public function show(string $id): View
    {
        $user = $this->users->find($id);

        return view('user.profile', ['user' => $user]);
    }
}

この例では、UserControllerは users を data ソースから取得する必要があります。したがって、users を取得することが可能な service を注入します。この context では、おそらく私たちのUserRepositoryは users 情報を database から取得するためにEloquentを使用しています。しかし、リポジトリが注入されているため、別の実装に簡単にスワップできます。また、testing の際にUserRepositoryのダミー実装を作成する、つまりモックを簡単に作成することも可能です。

強力で大規模な application を開発するため、また Laravel のコア自体に貢献するためにも、 Laravel service container についての深い理解が不可欠です。

ゼロ設定解決

もし class が依存関係を持たない、または他の具象クラス(インターフェースではない)にのみ依存している場合、コンテナにその class の解決方法を指示する必要はありません。例えば、次のような code をあなたのroutes/web.phpファイルに配置することができます:

<?php

class Service
{
    // ...
}

Route::get('/', function (Service $service) {
    die($service::class);
});

この例では、application の / route にアクセスすると、自動的に Service class が解決され、route の handler に注入されます。これはゲームチェンジングです。つまり、あなたは自身の application を development し、dependency injection を活用することができるのです。それも、設定ファイルが肥大化することを心配することなく。

ありがたいことに、Laravel application を構築する際に書く多くのクラスは、controllersevent listenersmiddleware など、コンテナを通じて自動的に依存関係を受け取ります。さらに、queued jobshandle method で依存関係をタイプヒントすることもできます。自動かつゼロ構成の dependency injection の力を一度味わうと、それなしで開発することが不可能に感じられるでしょう。

コンテナを利用するタイミング

ゼロ構成解決のおかげで、コンテナを手動で操作することなく、 routes 、コントローラー、 event listeners などの依存関係をしばしばタイプヒントします。例えば、Illuminate\Http\Request object を route 定義にタイプヒントすることで、現在の request に容易にアクセスできるようになるでしょう。この code を書くためにコンテナとつながらなくても、依存関係の注入は裏側で管理されています:

use Illuminate\Http\Request;

Route::get('/', function (Request $request) {
    // ...
});

多くの場合、自動的な dependency injection やfacadesのおかげで、一度も手動で何もバインドしたりコンテナから解決したりせずに、 build Laravel アプリケーションを作成することができます。では、いつコンテナと手動で対話する必要があるのでしょうか? 2 つの状況を見てみましょう。

まず、 class を書き、そのインターフェースを route または class のコンストラクタに型ヒントしたい場合、そのインターフェースがどのように解決するかコンテナに指示する必要があります。次に、他の Laravel 開発者と共有する予定のLaravel パッケージを書く場合は、パッケージの services をコンテナにバインドする必要があるかもしれません。

Binding

バインディングの基本

シンプルなバインディング

ほとんどすべてのあなたの service container のバインディングはservice providersの中で登録されるでしょう、そのため、これらの例のほとんどはその context でコンテナを使うことを示しています。

service provider 内では、常に$this->app プロパティを通じてコンテナにアクセスすることが可能です。我々は bind method を使用して、登録したい class 名やインターフェース名と、その class のインスタンスを返すクロージャを一緒に登録するバインディングを登録することができます。

use App\Services\Transistor;
use App\Services\PodcastParser;
use Illuminate\Contracts\Foundation\Application;

$this->app->bind(Transistor::class, function (Application $app) {
    return new Transistor($app->make(PodcastParser::class));
});

リゾルバへの引数としてコンテナ自体を受け取ることに注意してください。その後、構築している object のサブ依存関係を解決するために、コンテナを使用することができます。

前述の通り、通常は service プロバイダ内でコンテナと対話することになりますが、もし service provider の外部でコンテナと対話したい場合は、App facadeを通じて行うことができます。

use App\Services\Transistor;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\App;

App::bind(Transistor::class, function (Application $app) {
    // ...
});

ある type に対してまだバインディングが登録されていない場合にのみ、コンテナバインディングを register するためにbindIf method を使用することができます:

$this->app->bindIf(Transistor::class, function (Application $app) {
    return new Transistor($app->make(PodcastParser::class));
});

NOTE

インターフェースに依存しないクラスをコンテナにバインドする必要はありません。これらのオブジェクトを build する方法についてコンテナに指示する必要はありません。なぜなら、それはリフレクションを使用してこれらのオブジェクトを自動的に解決することができるからです。

Singleton のバインディング

singleton method は、一度だけ解決されるべき class あるいはインターフェースをコンテナに結びつけます。一度 singleton バインディングが解決されると、その後のコンテナへの呼び出しで同じ object インスタンスが返されます:

use App\Services\Transistor;
use App\Services\PodcastParser;
use Illuminate\Contracts\Foundation\Application;

$this->app->singleton(Transistor::class, function (Application $app) {
    return new Transistor($app->make(PodcastParser::class));
});

singletonIfという method を使用して、指定した type のバインディングがまだ登録されていない場合にのみ、シングルトンコンテナバインディングを登録することができます:

$this->app->singletonIf(Transistor::class, function (Application $app) {
    return new Transistor($app->make(PodcastParser::class));
});

バインディングスコープ付きシングルトン

scoped method は、与えられた Laravelrequest / job のライフサイクル内で一度だけ解決されるべき class またはインターフェイスをコンテナにバインドします。この method は singleton method に似ていますが、scoped method を使用して登録されたインスタンスは、新しい "ライフサイクル"が始まるたびにフラッシュされます。例えば、Laravel Octane ワーカーが新しい request を処理した時や、Laravel の queue ワーカーが新しい job を処理するといった場合です。

use App\Services\Transistor;
use App\Services\PodcastParser;
use Illuminate\Contracts\Foundation\Application;

$this->app->scoped(Transistor::class, function (Application $app) {
    return new Transistor($app->make(PodcastParser::class));
});

インスタンスのバインディング

既存の object インスタンスをコンテナにバインドすることもできます。これはinstance method を使用します。与えられたインスタンスは、その後のコンテナへの呼び出しで常に返されます。

use App\Services\Transistor;
use App\Services\PodcastParser;

$service = new Transistor(new PodcastParser);

$this->app->instance(Transistor::class, $service);

インターフェースを実装にバインドする

service container の非常に強力な特徴の一つは、インターフェースを特定の実装にバインドする能力です。例えば、EventPusherインターフェースとRedisEventPusher実装があると仮定しましょう。このインターフェースのRedisEventPusher実装をコード化した後、登録して service container に以下のようにすることができます:

use App\Contracts\EventPusher;
use App\Services\RedisEventPusher;

$this->app->bind(EventPusher::class, RedisEventPusher::class);

この statement は、コンテナに対して、ある class がEventPusherの実装を必要とする場合にはRedisEventPusherを注入すべきであることを告げます。これで、コンテナによって解決される class のコンストラクタでEventPusherインターフェースに型ヒントを付けることができます。コントローラー、 event listeners 、 middleware 、そして Laravel アプリケーション内のさまざまな他のタイプのクラスは常にコンテナを使用して解決されることを覚えておいてください。

use App\Contracts\EventPusher;

/**
 * Create a new class instance.
 */
public function __construct(
    protected EventPusher $pusher
) {}

コンテキストバインディング

場合によっては、同じインターフェースを利用する 2 つのクラスがあるかもしれませんが、それぞれの class に異なる実装を注入したいと思うかもしれません。たとえば、2 つのコントローラはIlluminate\Contracts\Filesystem\Filesystem 契約の異なる実装に依存するかもしれません。 Laravel は、この動作を定義するためのシンプルで流暢なインターフェースを提供します:

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');
          });

プリミティブのバインディング

時々、いくつかの注入 integer を受け取る class を持つ場合がありますが、整数などの注入されたプリミティブ value も必要とする場合があります。context バインディングを使用することで、 class が必要とする任意の value を簡単に注入できます。

use App\Http\Controllers\UserController;

$this->app->when(UserController::class)
          ->needs('$variableName')
          ->give($value);

時々、 class はtaggedインスタンスの array に依存することがあります。giveTagged method を使用すると、そのタグを持つすべてのコンテナバインディングを簡単に注入できます:

$this->app->when(ReportAggregator::class)
    ->needs('$reports')
    ->giveTagged('reports');

アプリケーションの設定ファイルの一つから value を注入する必要がある場合、giveConfig method を使用することができます。

$this->app->when(ReportAggregator::class)
    ->needs('$timezone')
    ->giveConfig('app.timezone');

タイプ指定可変長引数のバインディング

たまに、可変長コンストラクタ引数を使用して型付きオブジェクトの array を受け取る class があるかもしれません:

<?php

use App\Models\Filter;
use App\Services\Logger;

class Firewall
{
    /**
     * The filter instances.
     *
     * @var array
     */
    protected $filters;

    /**
     * Create a new class instance.
     */
    public function __construct(
        protected Logger $logger,
        Filter ...$filters,
    ) {
        $this->filters = $filters;
    }
}

context バインディングを使用すると、この依存関係を解決するために、解決されたFilterインスタンスの array を返すクロージャーを method giveに提供することができます。

$this->app->when(Firewall::class)
          ->needs(Filter::class)
          ->give(function (Application $app) {
                return [
                    $app->make(NullFilter::class),
                    $app->make(ProfanityFilter::class),
                    $app->make(TooLongFilter::class),
                ];
          });

便宜上、FirewallFilterインスタンスを必要とするたびに、コンテナによって解決される class の名前の array を提供することもできます。

$this->app->when(Firewall::class)
          ->needs(Filter::class)
          ->give([
              NullFilter::class,
              ProfanityFilter::class,
              TooLongFilter::class,
          ]);

可変長タグ依存関係

時折、 class は特定の class (Report ...$reports)として types 指定されたバリアディックな依存性を持つことがあります。needsgiveTaggedの method を使うと、その依存性に対するtagを持つすべてのコンテナのバインドを簡単に注入できます:

$this->app->when(ReportAggregator::class)
    ->needs(Report::class)
    ->giveTagged('reports');

Tagging

たまに、特定の "category" のバインディング全てを解決する必要があるかもしれません。例えば、あなたが様々な Report インターフェースの実装を含む "array" を受け取るレポート分析エンジンを作成している場合があります。 Report の実装を登録した後、それらに tag method を使用して tag を割り当てることができます:

$this->app->bind(CpuReport::class, function () {
    // ...
});

$this->app->bind(MemoryReport::class, function () {
    // ...
});

$this->app->tag([CpuReport::class, MemoryReport::class], 'reports');

一旦 services がタグ付けされると、コンテナのtagged method を使って簡単にそれらすべてを解決できます:

$this->app->bind(ReportAnalyzer::class, function (Application $app) {
    return new ReportAnalyzer($app->tagged('reports'));
});

バインディングの拡張

extendmethod は、解決された service の変更を可能にします。たとえば、service が解決されると、追加のコードを実行して service を設定または装飾することができます。 extendmethod は、2 つの引数を受け取ります。拡張する serviceclass と、変更された service を返すべきクロージャです。クロージャは、解決される service とコンテナインスタンスを受け取ります。

$this->app->extend(Service::class, function (Service $service, Application $app) {
    return new DecoratedService($service);
});

Resolving

make Method

あなたは、コンテナからの make method を使用して、 class インスタンスを解決することができます。 make method は、解決したい class またはインターフェイスの名前を受け入れます:

use App\Services\Transistor;

$transistor = $this->app->make(Transistor::class);

あなたのクラスの依存関係の一部がコンテナ経由で解決できない場合、それらをアソシエーション array としてmakeWith method に渡すことで注入できます。例えば、Transistor service が required とする$idのコンストラクタ引数を手動で渡すことができます。

use App\Services\Transistor;

$transistor = $this->app->makeWith(Transistor::class, ['id' => 1]);

bound method は、 class やインターフェースがコンテナに明示的にバインドされているかどうかを判断するために使用することができます:

if ($this->app->bound(Transistor::class)) {
    // ...
}

$app 変数へのアクセスがない code の位置の service provider の外側にいる場合、App facade または app helper を使用してコンテナから class インスタンスを解決することができます。

use App\Services\Transistor;
use Illuminate\Support\Facades\App;

$transistor = App::make(Transistor::class);

$transistor = app(Transistor::class);

もしもコンテナにより解決される class の中に、 Laravel のコンテナインスタンス自体を注入したい場合は、自分のクラスのコンストラクタでIlluminate\Container\Container class に types ヒントを与えることができます:

use Illuminate\Container\Container;

/**
 * Create a new class instance.
 */
public function __construct(
    protected Container $container
) {}

自動注入

あるいは、重要なことに、コンテナによって解決される class のコンストラクターで依存関係を types ヒントすることができます。これには、コントローラーevent listenersmiddlewareなどが含まれます。さらに、handle method でqueue に入れられた jobsの依存関係を types ヒントすることもできます。実際には、これがコンテナによって解決されるべき objects のほとんどの方法です。

たとえば、コントローラのコンストラクタで application によって定義されたリポジトリを型ヒントとして指定することができます。リポジトリは自動的に解決され、 class に注入されます:

<?php

namespace App\Http\Controllers;

use App\Repositories\UserRepository;
use App\Models\User;

class UserController extends Controller
{
    /**
     * Create a new controller instance.
     */
    public function __construct(
        protected UserRepository $users,
    ) {}

    /**
     * Show the user with the given ID.
     */
    public function show(string $id): User
    {
        $user = $this->users->findOrFail($id);

        return $user;
    }
}

Method Invocation and Injection

時々、あなたはコンテナにそのメソッドの依存関係を自動的に注入させながら、 object インスタンス上の method を呼び出すことを望むかもしれません。例えば、以下のような class が与えられた場合:

<?php

namespace App;

use App\Repositories\UserRepository;

class UserReport
{
    /**
     * Generate a new user report.
     */
    public function generate(UserRepository $repository): array
    {
        return [
            // ...
        ];
    }
}

次のようにコンテナからgenerate method を呼び出すことができます:

use App\UserReport;
use Illuminate\Support\Facades\App;

$report = App::call([new UserReport, 'generate']);

call method は、任意の PHP コール可能を受け入れます。 コンテナのcall method は、依存関係を自動的に注入しながらクロージャを呼び出すためにも使用できます。

use App\Repositories\UserRepository;
use Illuminate\Support\Facades\App;

$result = App::call(function (UserRepository $repository) {
    // ...
});

Container Events

service container は、 object を解決するたびに event を発火します。この event は、resolving method を使用してリッスンすることができます:

use App\Services\Transistor;
use Illuminate\Contracts\Foundation\Application;

$this->app->resolving(Transistor::class, function (Transistor $transistor, Application $app) {
    // Called when container resolves objects of type "Transistor"...
});

$this->app->resolving(function (mixed $object, Application $app) {
    // Called when container resolves object of any type...
});

ご覧の通り、解決される object はコールバックに渡され、 object にインスタンス化する前に追加のプロパティを設定することができます。

PSR-11

Laravel の service container は、PSR-11 インターフェースを実装しています。したがって、PSR-11 コンテナインターフェースを types ヒントとして指定して、Laravel コンテナのインスタンスを取得することができます:

use App\Services\Transistor;
use Psr\Container\ContainerInterface;

Route::get('/', function (ContainerInterface $container) {
    $service = $container->get(Transistor::class);

    // ...
});

指定された識別子が解決できない場合、例外がスローされます。識別子が一度もバインドされていない場合、例外はPsr\Container\NotFoundExceptionInterfaceのインスタンスになります。識別子がバインドされていたが解決できなかった場合、Psr\Container\ContainerExceptionInterfaceのインスタンスがスローされます。

当社サイトでは、Cookie を使用しています。各規約をご確認の上ご利用ください:
Cookie Policy, Privacy Policy および Terms of Use