#はじめに
Laravelのドキュメント全体で、「ファサード」を通じてLaravelの機能とやり取りするコード例をよく見かけます。ファサードはアプリケーションのサービスコンテナにあるクラスへの「静的」インターフェイスを提供します。Laravelにはほぼすべての機能にアクセスできる多くのファサードが用意されています。
Laravelのファサードはサービスコンテナ内の基盤クラスへの「静的プロキシ」として機能し、簡潔で表現力豊かな構文を提供しつつ、従来の静的メソッドよりもテストしやすく柔軟性があります。ファサードの仕組みを完全に理解していなくても問題ありません。流れに沿ってLaravelの学習を続けてください。
Laravelのすべてのファサードは Illuminate\Support\Facades 名前空間に定義されています。したがって、以下のように簡単にファサードにアクセスできます:
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Route;
Route::get('/cache', function () {
return Cache::get('key');
});
Laravelのドキュメントでは、多くの例でファサードを使ってフレームワークのさまざまな機能を示しています。
#ヘルパー関数
ファサードを補完する形で、Laravelは共通の機能にさらに簡単にアクセスできるグローバルな「ヘルパー関数」を多数提供しています。よく使われるヘルパー関数には view、response、url、config などがあります。各ヘルパー関数は対応する機能のドキュメントに記載されていますが、完全な一覧は専用のヘルパードキュメントにあります。
例えば、Illuminate\Support\Facades\Response ファサードを使ってJSONレスポンスを生成する代わりに、単に response 関数を使うことができます。ヘルパー関数はグローバルに利用可能なので、クラスをインポートする必要はありません:
use Illuminate\Support\Facades\Response;
Route::get('/users', function () {
return Response::json([
// ...
]);
});
Route::get('/users', function () {
return response()->json([
// ...
]);
});
#ファサードを使うタイミング
ファサードには多くの利点があります。簡潔で覚えやすい構文を提供し、長いクラス名を覚えたり手動で注入・設定したりする必要なくLaravelの機能を使えます。さらに、PHPの動的メソッドを独自に利用しているため、テストも簡単です。
ただし、ファサードを使う際には注意も必要です。ファサードの主なリスクはクラスの「スコープの肥大化」です。ファサードは使いやすく注入も不要なため、1つのクラスで多くのファサードを使い続けてクラスが肥大化しやすくなります。依存性注入では、大きなコンストラクタが視覚的にクラスの肥大化を示すため、この問題を軽減できます。ファサードを使う場合は、クラスの責務範囲が狭く保たれているか特に注意してください。クラスが大きくなりすぎたら、複数の小さなクラスに分割することを検討しましょう。
#ファサードと依存性注入の違い
依存性注入の主な利点の1つは、注入されたクラスの実装を差し替えられることです。これはテスト時にモックやスタブを注入して、特定のメソッドが呼ばれたか検証するのに便利です。
通常、真の静的クラスメソッドはモックやスタブにできません。しかし、ファサードは動的メソッドを使ってサービスコンテナから解決されたオブジェクトにメソッド呼び出しをプロキシするため、注入されたクラスインスタンスと同様にファサードもテストできます。例えば、以下のルートがあるとします:
use Illuminate\Support\Facades\Cache;
Route::get('/cache', function () {
return Cache::get('key');
});
Laravelのファサードテストメソッドを使うと、Cache::get メソッドが期待した引数で呼ばれたかを検証する以下のようなテストを書けます:
use Illuminate\Support\Facades\Cache;
/**
* 基本的な機能テストの例です。
*/
public function test_basic_example(): void
{
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$response = $this->get('/cache');
$response->assertSee('value');
}
#ファサードとヘルパー関数の違い
ファサードに加えて、Laravelはビュー生成、イベント発火、ジョブディスパッチ、HTTPレスポンス送信などの共通タスクを行う多様な「ヘルパー」関数も含みます。多くのヘルパー関数は対応するファサードと同じ機能を持ちます。例えば、以下のファサード呼び出しとヘルパー呼び出しは同等です:
return Illuminate\Support\Facades\View::make('profile');
return view('profile');
ファサードとヘルパー関数の間に実質的な違いはありません。ヘルパー関数を使う場合でも、対応するファサードと同様にテストできます。例えば、以下のルートがあるとします:
Route::get('/cache', function () {
return cache('key');
});
cache ヘルパーは Cache ファサードの基盤クラスの get メソッドを呼び出します。したがって、ヘルパー関数を使っていても、以下のように期待した引数でメソッドが呼ばれたかを検証するテストを書けます:
use Illuminate\Support\Facades\Cache;
/**
* 基本的な機能テストの例です。
*/
public function test_basic_example(): void
{
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$response = $this->get('/cache');
$response->assertSee('value');
}
#ファサードの仕組み
Laravelアプリケーションにおいて、ファサードはコンテナからオブジェクトにアクセスするためのクラスです。この仕組みは Facade クラスにあります。Laravelのファサードやカスタムファサードはすべて、基本の Illuminate\Support\Facades\Facade クラスを継承します。
Facade 基底クラスは __callStatic() マジックメソッドを使い、ファサードからサービスコンテナで解決されたオブジェクトへの呼び出しを遅延させます。以下の例ではLaravelのキャッシュシステムに呼び出しを行っています。このコードを見ると、Cache クラスの静的な get メソッドが呼ばれているように見えます:
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Cache;
use Illuminate\View\View;
class UserController extends Controller
{
/**
* 指定されたユーザーのプロフィールを表示します。
*/
public function showProfile(string $id): View
{
$user = Cache::get('user:'.$id);
return view('profile', ['user' => $user]);
}
}
ファイルの上部で Cache ファサードを「インポート」していることに注目してください。このファサードは Illuminate\Contracts\Cache\Factory インターフェイスの基盤実装へのプロキシとして機能します。ファサードを通じて行う呼び出しはすべて、Laravelのキャッシュサービスの基盤インスタンスに渡されます。
Illuminate\Support\Facades\Cache クラスを見ると、静的な get メソッドは定義されていません:
class Cache extends Facade
{
/**
* コンポーネントの登録名を取得します。
*/
protected static function getFacadeAccessor(): string
{
return 'cache';
}
}
代わりに、Cache ファサードは基底の Facade クラスを継承し、getFacadeAccessor() メソッドを定義しています。このメソッドはサービスコンテナのバインディング名を返します。ユーザーが Cache ファサードの任意の静的メソッドを参照すると、Laravelは サービスコンテナから cache バインディングを解決し、そのオブジェクトに対して要求されたメソッド(この場合は get)を実行します。
#リアルタイムファサード
リアルタイムファサードを使うと、アプリケーション内の任意のクラスをファサードのように扱えます。これを説明するために、まずリアルタイムファサードを使わないコードを見てみましょう。例えば、Podcast モデルに publish メソッドがあるとします。ただし、ポッドキャストを公開するには Publisher インスタンスを注入する必要があります:
<?php
namespace App\Models;
use App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* ポッドキャストを公開します。
*/
public function publish(Publisher $publisher): void
{
$this->update(['publishing' => now()]);
$publisher->publish($this);
}
}
メソッドにパブリッシャー実装を注入することで、注入されたパブリッシャーをモックしてメソッドを単体テストしやすくなります。ただし、publish メソッドを呼ぶたびに必ずパブリッシャーインスタンスを渡す必要があります。リアルタイムファサードを使うと、同じテスト可能性を保ちつつ、明示的に Publisher インスタンスを渡す必要がなくなります。リアルタイムファサードを生成するには、インポートするクラスの名前空間に Facades をプレフィックスとして付けます:
<?php
namespace App\Models;
use App\Contracts\Publisher;
use Facades\App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* ポッドキャストを公開します。
*/
public function publish(Publisher $publisher): void
public function publish(): void
{
$this->update(['publishing' => now()]);
$publisher->publish($this);
Publisher::publish($this);
}
}
リアルタイムファサードを使うと、Facades プレフィックスの後ろに続くインターフェイスやクラス名の部分を使ってサービスコンテナからパブリッシャー実装が解決されます。テスト時にはLaravelの組み込みファサードテストヘルパーを使ってこのメソッド呼び出しをモックできます:
<?php
namespace Tests\Feature;
use App\Models\Podcast;
use Facades\App\Contracts\Publisher;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class PodcastTest extends TestCase
{
use RefreshDatabase;
/**
* A test example.
*/
public function test_podcast_can_be_published(): void
{
$podcast = Podcast::factory()->create();
Publisher::shouldReceive('publish')->once()->with($podcast);
$podcast->publish();
}
}
#ファサードクラスリファレンス
以下にすべてのファサードとその基盤クラスを示します。特定のファサードのAPIドキュメントを素早く調べるのに便利です。該当する場合はサービスコンテナバインディングのキーも含まれています。