このリポジトリはPHP Webアプリフレームワークである、LaravelのLTSバージョンである5.5の公式英文ドキュメントを日本語へ翻訳しています。
This project is maintained by okinaka
ファサード(facade、「入り口」)はアプリケーションのサービスコンテナに登録したクラスへ、「静的」なインターフェイスを提供します。Laravelのほとんどの機能に対して、ファサードが用意されています。Laravelの「ファサード」は、サービスコンテナ下で動作しているクラスに対し、”static proxy”として動作しています。これにより伝統的な静的メソッドよりもテストの行いやすさと柔軟性を保ちながらも、簡潔で記述的であるという利点があります。
Laravelのファサードはすべて、Illuminate\Support\Facades
名前空間下で定義されています。ですから、簡単にファサードへアクセスできます。
use Illuminate\Support\Facades\Cache;
Route::get('/cache', function () {
return Cache::get('key');
});
フレームワークの様々な機能をデモンストレートするために、Laravelのドキュメント全般でたくさんの例がファサードを使用しています。
ファサードにはたくさんの利点があります。自分で取り込んだり、設定したりする必要があり、長くて覚えにくいクラス名を使わずに、Laravelの機能を簡素で覚えやすい文法で使ってもらえます。その上に、PHPの動的メソッドのユニークな使用方法のおかげで、簡単にテストができます。
しかしながら、ファサードの使用にはいくつか気をつけるべき点も存在します。ファサードの一番の危険性は、クラスの責任範囲の暴走です。ファサードはとても簡単に使用でき、依存注入も必要ないため、簡単にクラスが成長し続ける結果、一つのクラスで多くのファサードが使われます。依存注入を使用すれば、クラスが大きくなりすぎることに伴う、大きなコンストラクタの視覚的なフィードバックにより、この危険性は抑制されます。ですから、ファサードを使用するときは、クラスの責任範囲を小さくとどめるため、クラスサイズに特に注意をはらいましょう。
{tip} Laravelに関連した、サードパーティパッケージを構築する場合は、ファサードの代わりにLaravelの契約を使うほうが好ましいでしょう。Laravel自身の外でパッケージを構築するわけですから、Laravelのテストヘルパへアクセスする必要はありません。
依存注入の最大の利便性は、注入するクラスの実装を入れ替えられるという機能です。モックやスタブを注入し、そうした代替オブジェクトの様々なメソッドのアサートが行えるため、テスト中に便利です。
本当の静的クラスメソッドをモックしたり、スタブにすることは、通常は不可能です。しかしファサードは、サービスコンテナが依存解決したオブジェクトの代替メソッドを呼び出すために、動的メソッドが使えますので、注入したクラスインスタンスをテストするのと同様に、ファサードを実際にテスト可能です。
use Illuminate\Support\Facades\Cache;
Route::get('/cache', function () {
return Cache::get('key');
});
Cache::get
メソッドが、予想した引数で呼び出されることを確認するために、以下のようなテストを書けます。
use Illuminate\Support\Facades\Cache;
/**
* 基本的なテスト機能の例
*
* @return void
*/
public function testBasicExample()
{
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$this->visit('/cache')
->see('value');
}
ファサードに加え、Laravelは様々な「ヘルパ」関数を用意しており、ビューの生成、イベントの発行、ジョブの起動、HTTPレスポンスの送信など、一般的なタスクを実行できます。こうしたヘルパ関数の多くは、対応するファサードと同じ機能を実行します。たとえば、以下のファサードとヘルパの呼び出しは、同じ働きをします。
return View::make('profile');
return view('profile');
ここではファサードとヘルパ関数との間に、全く違いはありません。ヘルパ関数を使う場合も、対応するファサードと全く同様にテストできます。たとえば、以下のルートが存在するとしましょう。
Route::get('/cache', function () {
return cache('key');
});
内部でcache
ヘルパは、Cache
ファサードの裏で動作しているクラスのget
メソッドを呼び出します。ですから、ヘルパ関数を使用していても、期待する引数でメソッドが呼びだされていることを確認する、以下のテストを書けます。
use Illuminate\Support\Facades\Cache;
/**
* 基本的なテスト機能の例
*
* @return void
*/
public function testBasicExample()
{
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$this->visit('/cache')
->see('value');
}
Laravelアプリケーション中で、ファサードとは、コンテナを通じてオブジェクトにアクセス方法を提供するクラスのことです。Facade
クラス中の仕組みでこれを行なっています。Laravelのファサードと皆さんが作成するカスタムファサードは、Illuminate\Support\Facades\Facade
クラスを拡張します。
Facade
基本クラスは、ファサードへの関数呼び出しをコンテナにより依存解決されたオブジェクトへ送るため、__callStatic()
マジックメソッドを使用します。下の例では、Laravelのキャッシュシステムを呼び出しています。これを読むと一見、Cache
クラスのstaticなget
メソッドが呼び出されているのだと考えてしまうことでしょう。
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Cache;
class UserController extends Controller
{
/**
* 指定したユーザーのプロフィール表示
*
* @param int $id
* @return Response
*/
public function showProfile($id)
{
$user = Cache::get('user:'.$id);
return view('profile', ['user' => $user]);
}
}
ファイルの先頭で、Cache
ファサードを取り込んでいることに注目です。このファサードサービスは、Illuminate\Contracts\Cache\Factory
インターフェイスの裏にある実装へアクセスするプロキシとして動作します。ファサードを使ったメソッド呼び出しは、裏のLaravelのキャッシュサービスの実装へ渡されます。
そのため、Illuminate\Support\Facades\Cache
クラスを見てもらえば、staticのget
メソッドは存在していないことが分かります。
class Cache extends Facade
{
/**
* コンポーネントの登録名を取得
*
* @return string
*/
protected static function getFacadeAccessor() { return 'cache'; }
}
かわりにCache
ファサードは、Facade
ベースクラスを拡張し、getFacadeAccessor()
メソッドを定義しています。このメソッドの仕事は、サービスコンテナの結合名を返すことです。ユーザーがCache
ファサードのどのstaticメソッドを利用しようと、Laravelはサービスコンテナからcache
に結び付けられたインスタンスを依存解決し、要求されたメソッドを(この場合はget
)そのオブジェクトに対し実行します。
リアルタムファサードを使用すれば、アプリケーション中のどんなクラスでも、ファサードとして取り扱えます。活用法を示すために、新しいテストの手法を撮ってみましょう。例として、Podcast
モデルがpublish
メソッドを持っているとしましょう。しかしポッドキャストを公開(publish)するには、Publisher
インスタンスを注入する必要があるとします。
<?php
namespace App;
use App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* ポッドキャストの公開
*
* @param Publisher $publisher
* @return void
*/
public function publish(Publisher $publisher)
{
$this->update(['publishing' => now()]);
$publisher->publish($this);
}
}
メソッドへPublisherの実装を注入することにより、注入するpublisherをモックできるため、メソッドを簡単にメソッドを他と切り離してテストできます。しかし、publish
メソッドを呼び出すごとに、publisherインスタンスを常に渡す必要があります。リアルタイムファサードを使用すれば、同じてスタビリティを保ちながらも、明確にPublisher
インスタンスを渡す必要がなくなります。リアルタイムファサードを作成するには、インポートするクラスのプレフィックスとして、Facade
を付けるだけです。
<?php
namespace App;
use Facades\App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* ポッドキャストの公開
*
* @return void
*/
public function publish()
{
$this->update(['publishing' => now()]);
Publisher::publish($this);
}
}
リアルタイムファサードを使用しているため、インターフェイスやクラス名のFacade
プレフィックス後の部分を使い、サービスコンテナがpublisherの実装を依存注入解決します。テストのときは、このメソッドの呼び出しをモックするために、ファサードに組み込まれているLaravelのテストヘルパが使用できます。
<?php
namespace Tests\Feature;
use App\Podcast;
use Tests\TestCase;
use Facades\App\Contracts\Publisher;
use Illuminate\Foundation\Testing\RefreshDatabase;
class PodcastTest extends TestCase
{
use RefreshDatabase;
/**
* A test example.
*
* @return void
*/
public function test_podcast_can_be_published()
{
$podcast = factory(Podcast::class)->create();
Publisher::shouldReceive('publish')->once()->with($podcast);
$podcast->publish();
}
}
以下は全ファサードと実際のクラスの一覧です。これは特定のファサードを元にし、APIドキュメントを素早く探したい場合に便利な道具になります。対応するサービスコンテナ結合キーも記載しています。