依存注入 - ララベルと共に
依存性注入はオブジェクト指向プログラミングでよく使われる設計パターンです。いくつかの事前に確立された慣習を通じて、依存関係の作成をより容易に管理できます。依存関係を宣言したり、置き換えたり、さらにはモックしたりしても、依存関係に依存するコードを変更する必要がなくなります。
例えば、認証ロジックがあるとしましょう
クラスAuthLogic
{
function authenticate(Request $request): bool
{
試してみて {
$payload = $request->all();
$token = $this->verifyAuthToken($payload);
$this->verifyClient($token);
$this->verifyPermissions($token);
真を返す;
} catch (例外 $e) {
Return false;
}
}
}
このロジックと相互作用するコードに対してテストを書きたいので、最初に多くのデータをシードする必要があることがわかります。認証されたコントローラーテストでは、クライアント、ユーザー、いくつかの権限をシードし、上記の認証トークンを生成する必要があります。依存性注入を使えば、これらの設定を避けて「authenticate」をモックして常に「true」か「false」を返すことができます。Laravelでそれを行う方法を見てみましょう!
依存注入のやり方
ほとんどのフレームワークと同様に、Laravelは依存性注入を使ってコードを整理することを可能にします。それでは、その方法を見てみましょう!以下が私たちが実行すべきステップです。
- 依存関係を定義する
- 依存関係を拘束する
- 依存関係を注入する
- (任意)依存関係をモック/置き換える
依存関係を定義する
まず、他のクラスが依存するオブジェクトのクラスを定義する必要があります。ここでは、コードの他の部分で使いたいシンプルな「HelperService」クラスを作成します。
クラスヘルパーサービス
{
関数 __construct() {
$this->カウンター=0;
}
function incrementAndGetCounter(): int
{
$this->カウンター+= 1;
$this->カウンターを返す;
}
}
依存を縛れ
次に依存関係を構築し、Laravelのサービスコンテナにバインドする必要があります。Laravelでは、通常サービスプロバイダーを作成し、それを登録することでこれが行われます。
PHP artisan make:provider HelperServiceProvider
生成されたファイルには、自分のバインディングと、そのバインディングが必要なときに実行すべき関数を登録します。ここでクラスを構築します。
Illuminate\Support\ServiceProviderを使いましょう;
class HelperServiceProviders extends ServiceProvider
{
関数レジスタ()
{
$this->app->singletonを使って一度だけインスタンス化します
$this->app->bind(HelperService::class, function() {
新しいHelperService();
});
}
}
最後に、「HelperServiceProvider」を「config/app.php」のLaravelサービスコンテナにバインドします
「提供者」=> [
# ... 既存の提供者
App\Providers\HelperServiceProvider::class,
]
依存を注入
依存関係バインディングがLaravelサービスコンテナに宣言されたことで、必要な場所にLaravelコードに注入できます。例えば、コントローラーやミドルウェアで自動注入を通じて使用できます。Laravelは、先に宣言したバインディングとマッチングできる「HelperService」クラスを検出します。
クラス HomeController 拡張 コントローラー
{
function __construct(HelperService $helperService)
{
$this->helperService = $helperService;
}
function home()
{
帰還 [
'counter' => $this->helperService->incrementAndGetCounter()
];
}
}
また、Laravelの「App」ファサードを使って、その依存関係を他のクラスや関数に直接手動で注入することもできます。
Illuminate\Support\Facades\Appを使いましょう。
クラスUserService
{
function trySomething(HelperService $helperService)
{
print($this->helperService->incrementAndGetCounter());
}
}
インスタンスを関数引数として自動注入します
$result = App::call([新しいUserService, 'trySomething']);
あるいは、インスタンスを解決して自分で渡すこともできます
$helperService = App::make(HelperService::class);
$result = (新しいUserService)->trySomething($helperService);
ご覧の通り、サービスプロバイダーで呼び出される「new HelperService()」を、明示的に構築しなくても、すべての後続コードで利用できるようになりました。クラスの構築はLaravelの起動プロセスによって制御されます。
依存を嘲笑/置き換える
現在は依存性注入を使って依存オブジェクトを管理しているので、実行時にモックして置き換える機能が可能になりました。これは特にテスト中(例:認証を常にtrueに戻すモック)に有用です。
オブジェクトをサービスコンテナにバインディングする方法についてはこちらで詳しく読むことができます: https://laravel.com/docs/9.x/container
Mockery\MockInterfaceを使い、
Illuminate\Foundation\Testing\RefreshDatabaseを使い、
Tests\TestCaseを使いましょう;
class HomeControllerTestはTestCaseを拡張します
{
RefreshDatabaseを使い、
公的機能テストHome(): void
{
$this->mock(HelperService::class, function (MockInterface $mock) {
$mock->shouldReceive('incrementAndGetCounter')->andReturn(5);
});
$response = $this->getJson('/', []);
$this->assertEquals($response[「カウンター」], 5);
}
}
今では、テストで実際の「HelperService」をインスタンス化する代わりに、そのオブジェクトのモックを使うことができます!これはコードの一部を分離してユニットテストをより簡単に実行するのに非常に役立ちます。
嘲笑についてはこちらで詳しく知ることができます: https://laravel.com/docs/9.x/mocking#mocking-objects
依存注入の使い方
依存注入はコードを整理するのに優れたツールですが、他のツールと同様に適切な場合にのみ使うべきです。個人的には、依存注入が最も有用だと感じるのは以下の2つの目的です。
リクエストライフサイクル全体で何らかの状態(例えば、ミドルウェア、コントローラー、サービスクラス全体で使われるシングルトン)を維持・修正する必要があります。これは、オブジェクトを複数のコンポーネントやサービス層に渡すのが扱いにくくなるときにReduxに何かを入れる理由と少し似ています。
抽象化して模造可能にしたいロジックや状態(例:httpクライアント、認証状態)を望む
良い例がauthenticate stateです。通常、認証リクエストを作成するまでに多くの手順を踏む必要があるかもしれません。これは複雑すぎて、テストが認証ロジックと結びつきすぎてしまう可能性があります。作成するすべてのAPIリクエストテストで認証トークンの生成方法を再度テストする必要があるのでしょうか?
依存注入を使えば、最低限のものをシードし、認証レイヤーをモックするだけで済みます。その結果得られるテストコードは以下のようになります。
クラスAuthMiddleware
{
function __construct(AuthLogic $authLogic)
{
$this->authLogic = $authLogic;
}
function handle(Request $request, Closure $next)
{
もし ($this->authLogic->authenticate($request)) {
返$next($request);
} そうでなければ {
中止(応答:HTTP_FORBIDDEN);
}
}
}
class HomeControllerTestはTestCaseを拡張します
{
function testHome()
{
$this->mock(AuthLogic::class, function (MockInterface $mock) {
$mock->shouldReceive('authenticate')->andReturn(true);
});
$response = $this->getJson('/', []);
$this->assertNotEquals($response->code, Response:HTTP_FORBIDDEN);
}
}
結論
これでなぜ、いつ、そしてLaravelでDependency Injectionを使うかがわかりました。この技術は他のフレームワークにも応用でき、JavaのSpring Boot、Ruby on Rails、Django、そしてほとんどのウェブフレームワークには実装方法があります。ただし、適切な時に使うことを忘れないでください!やりすぎてモック化しないでください!実際のものでテストすることは、いつかは必ず必要です!