ファサード
イントロダクション
Laravelのドキュメント全体を通して、「ファサード」を介してLaravelの機能と対話するコードの例を目にするでしょう。ファサードは、アプリケーションのサービスコンテナで利用可能なクラスへの「静的(static)」なインターフェイスを提供します。Laravelには多くのファサードが付属しており、Laravelのほぼすべての機能にアクセスできます。
Laravelのファサードは、サービスコンテナ内の実クラスへの「静的プロキシ」として機能し、従来の静的メソッドよりも高いテスト性と柔軟性を維持しながら、簡潔で表現力豊かな構文の利点を提供します。ファサードがどのように機能するかを完全に理解していなくても、まったく問題ありません。流れに沿ってLaravelについて学び続けてください。
Laravelのファサードはすべて、Illuminate\Support\Facades名前空間で定義されています。そのため、次のように簡単にファサードにアクセスできます。
1use Illuminate\Support\Facades\Cache;2use Illuminate\Support\Facades\Route;3 4Route::get('/cache', function () {5 return Cache::get('key');6});
Laravelのドキュメント全体を通して、多くの例でファサードを使用してフレームワークのさまざまな機能をデモしています。
ヘルパ関数
ファサードを補完するために、Laravelはさまざまなグローバルな「ヘルパ関数」を提供しており、これによって一般的なLaravelの機能との対話がさらに容易になります。皆さんがよく使うであろう一般的なヘルパ関数には、view、response、url、configなどがあります。Laravelが提供する各ヘルパ関数は、対応する機能とともにドキュメント化されています。しかし、完全なリストは専用のヘルパのドキュメントにあります。
例えば、JSONレスポンスを生成するためにIlluminate\Support\Facades\Responseファサードを使用する代わりに、単純にresponse関数を使用できます。ヘルパ関数はグローバルに利用できるため、使用するためにクラスをインポートする必要はありません。
1use Illuminate\Support\Facades\Response; 2 3Route::get('/users', function () { 4 return Response::json([ 5 // ... 6 ]); 7}); 8 9Route::get('/users', function () {10 return response()->json([11 // ...12 ]);13});
ファサードの利用場面
ファサードには多くの利点があります。手動で注入したり設定したりする必要がある長いクラス名を覚えることなくLaravelの機能を使用できる、簡潔で覚えやすい構文を提供します。さらに、PHPの動的メソッドを独自に使用しているため、テストが簡単です。
しかし、ファサードを使用する際には注意が必要です。ファサードの主な危険性は、クラスの「スコープクリープ」です。ファサードは非常に使いやすく、注入を必要としないため、クラスが成長し続け、単一のクラスで多くのファサードを使用してしまいがちです。依存性の注入を使用すると、大きなコンストラクタがクラスが大きくなりすぎていることを視覚的にフィードバックするため、この可能性は軽減されます。したがって、ファサードを使用する場合は、クラスの責任範囲が狭くなるように、クラスのサイズに特に注意を払ってください。クラスが大きくなりすぎている場合は、複数のより小さなクラスに分割することを検討してください。
ファサード 対 依存注入
依存性の注入の主な利点の1つは、注入されたクラスの実装を交換できることです。これはテスト中に便利です。モックまたはスタブを注入し、スタブでさまざまなメソッドが呼び出されたことをアサートできるからです。
通常、真の静的クラスメソッドをモックまたはスタブ化することはできません。しかし、ファサードはサービスコンテナから解決されたオブジェクトへのメソッド呼び出しをプロキシするために動的メソッドを使用するため、注入されたクラスインスタンスをテストするのと同じように、ファサードをテストできます。たとえば、次のルートを考えてみましょう。
1use Illuminate\Support\Facades\Cache;2 3Route::get('/cache', function () {4 return Cache::get('key');5});
Laravelのファサードテストメソッドを使用して、Cache::getメソッドが期待した引数で呼び出されたことを確認する次のテストを作成できます。
1use Illuminate\Support\Facades\Cache; 2 3test('basic example', function () { 4 Cache::shouldReceive('get') 5 ->with('key') 6 ->andReturn('value'); 7 8 $response = $this->get('/cache'); 9 10 $response->assertSee('value');11});
1use Illuminate\Support\Facades\Cache; 2 3/** 4 * A basic functional test example. 5 */ 6public function test_basic_example(): void 7{ 8 Cache::shouldReceive('get') 9 ->with('key')10 ->andReturn('value');11 12 $response = $this->get('/cache');13 14 $response->assertSee('value');15}
ファサード 対 ヘルパ関数
ファサードに加えて、Laravelには、ビューの生成、イベントの発行、ジョブのディスパッチ、HTTPレスポンスの送信などの一般的なタスクを実行できる、さまざまな「ヘルパ」関数が含まれています。これらのヘルパ関数の多くは、対応するファサードと同じ機能を実行します。たとえば、このファサード呼び出しとヘルパ呼び出しは同等です。
1return Illuminate\Support\Facades\View::make('profile');2 3return view('profile');
ファサードとヘルパ関数の間に実用的な違いはまったくありません。ヘルパ関数を使用する場合でも、対応するファサードとまったく同じようにテストできます。たとえば、次のルートを考えてみましょう。
1Route::get('/cache', function () {2 return cache('key');3});
cacheヘルパは、Cacheファサードの基盤となるクラスのgetメソッドを呼び出します。したがって、ヘルパ関数を使用している場合でも、メソッドが期待した引数で呼び出されたことを確認するために、次のテストを作成できます。
1use Illuminate\Support\Facades\Cache; 2 3/** 4 * A basic functional test example. 5 */ 6public function test_basic_example(): void 7{ 8 Cache::shouldReceive('get') 9 ->with('key')10 ->andReturn('value');11 12 $response = $this->get('/cache');13 14 $response->assertSee('value');15}
ファサードの仕組み
Laravelアプリケーションでは、ファサードはコンテナからオブジェクトへのアクセスを提供するクラスです。これを機能させる仕組みはFacadeクラスにあります。Laravelのファサード、および作成するカスタムファサードは、基本のIlluminate\Support\Facades\Facadeクラスを拡張します。
Facade基本クラスは、__callStatic()マジックメソッドを利用して、ファサードからの呼び出しをコンテナから解決されたオブジェクトに委譲します。以下の例では、Laravelキャッシュシステムへの呼び出しが行われます。このコードを一目見ると、Cacheクラスで静的なgetメソッドが呼び出されていると想定するかもしれません。
1<?php 2 3namespace App\Http\Controllers; 4 5use App\Http\Controllers\Controller; 6use Illuminate\Support\Facades\Cache; 7use Illuminate\View\View; 8 9class UserController extends Controller10{11 /**12 * Show the profile for the given user.13 */14 public function showProfile(string $id): View15 {16 $user = Cache::get('user:'.$id);17 18 return view('profile', ['user' => $user]);19 }20}
ファイルの先頭近くでCacheファサードを「インポート」していることに注意してください。このファサードは、Illuminate\Contracts\Cache\Factoryインターフェイスの基盤となる実装にアクセスするためのプロキシとして機能します。ファサードを使用して行うすべての呼び出しは、Laravelのキャッシュサービスの基盤となるインスタンスに渡されます。
そのIlluminate\Support\Facades\Cacheクラスを見ると、静的メソッドgetがないことがわかります。
1class Cache extends Facade 2{ 3 /** 4 * Get the registered name of the component. 5 */ 6 protected static function getFacadeAccessor(): string 7 { 8 return 'cache'; 9 }10}
代わりに、Cacheファサードは基本のFacadeクラスを拡張し、getFacadeAccessor()メソッドを定義します。このメソッドの仕事は、サービスコンテナの結合名を返すことです。ユーザーがCacheファサードで静的メソッドを参照すると、Laravelはサービスコンテナからcache結合を解決し、要求されたメソッド(この場合はget)をそのオブジェクトに対して実行します。
リアルタイムファサード
リアルタイムファサードを使用すると、アプリケーション内のどのクラスでもファサードであるかのように扱うことができます。これがどのように使用できるかを説明するために、まずリアルタイムファサードを使用しないコードを調べてみましょう。たとえば、Podcastモデルにpublishメソッドがあるとします。ただし、ポッドキャストを公開するには、Publisherインスタンスを注入する必要があります。
1<?php 2 3namespace App\Models; 4 5use App\Contracts\Publisher; 6use Illuminate\Database\Eloquent\Model; 7 8class Podcast extends Model 9{10 /**11 * Publish the podcast.12 */13 public function publish(Publisher $publisher): void14 {15 $this->update(['publishing' => now()]);16 17 $publisher->publish($this);18 }19}
注入されたパブリッシャーをモックできるため、メソッドにパブリッシャー実装を注入することで、メソッドを分離して簡単にテストできます。ただし、publishメソッドを呼び出すたびにパブリッシャーインスタンスを渡す必要があります。リアルタイムファサードを使用すると、Publisherインスタンスを明示的に渡す必要なく、同じテスト容易性を維持できます。リアルタイムファサードを生成するには、インポートされたクラスの名前空間の前にFacadesを付けます。
1<?php 2 3namespace App\Models; 4 5use App\Contracts\Publisher; 6use Facades\App\Contracts\Publisher; 7use Illuminate\Database\Eloquent\Model; 8 9class Podcast extends Model10{11 /**12 * Publish the podcast.13 */14 public function publish(Publisher $publisher): void 15 public function publish(): void 16 {17 $this->update(['publishing' => now()]);18 19 $publisher->publish($this); 20 Publisher::publish($this); 21 }22}
リアルタイムファサードが使用されると、Facadesプレフィックスの後に表示されるインターフェイスまたはクラス名の部分を使用して、パブリッシャー実装がサービスコンテナから解決されます。テスト時には、Laravelの組み込みファサードテストヘルパを使用して、このメソッド呼び出しをモックできます。
1<?php 2 3use App\Models\Podcast; 4use Facades\App\Contracts\Publisher; 5use Illuminate\Foundation\Testing\RefreshDatabase; 6 7uses(RefreshDatabase::class); 8 9test('podcast can be published', function () {10 $podcast = Podcast::factory()->create();11 12 Publisher::shouldReceive('publish')->once()->with($podcast);13 14 $podcast->publish();15});
1<?php 2 3namespace Tests\Feature; 4 5use App\Models\Podcast; 6use Facades\App\Contracts\Publisher; 7use Illuminate\Foundation\Testing\RefreshDatabase; 8use Tests\TestCase; 9 10class PodcastTest extends TestCase11{12 use RefreshDatabase;13 14 /**15 * A test example.16 */17 public function test_podcast_can_be_published(): void18 {19 $podcast = Podcast::factory()->create();20 21 Publisher::shouldReceive('publish')->once()->with($podcast);22 23 $podcast->publish();24 }25}
ファサードクラスリファレンス
以下に、すべてのファサードとその基になるクラスを示します。これは、特定のファサードルートのAPIドキュメントをすばやく調べるための便利なツールです。該当する場合は、サービスコンテナ結合キーも含まれています。