イントロダクション

ファサード(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のテストヘルパーへアクセスする必要はありません。

ファサード Vs. Dependency Injection

依存注入の最大の利便性は、注入するクラスの実装を入れ替えられるという機能です。モックやスタブを注入し、そうした代替オブジェクトの様々なメソッドのアサートが行えるため、テスト中に便利です。

本当の静的クラスメソッドをモックしたり、スタブにすることは、通常は不可能です。しかしファサードは、サービスコンテナが依存解決したオブジェクトの代替メソッドを呼び出すために、動的メソッドが使えますので、注入したクラスインスタンスをテストするのと同様に、ファサードを実際にテスト可能です。

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

ファサード Vs. Helper Functions

ファサードに加え、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 Cache;
use App\Http\Controllers\Controller;

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)そのオブジェクトに対し実行します。

ファサードクラス一覧

以下は全ファサードと実際のクラスの一覧です。これは特定のファサードを元にし、APIドキュメントを素早く探したい場合に便利な道具になります。対応するサービスコンテナ結合キーも記載しています。

ファサード クラス サービスコンテナ結合
App Illuminate\Foundation\Application app
Artisan Illuminate\Contracts\Console\Kernel artisan
Auth Illuminate\Auth\AuthManager auth
Blade Illuminate\View\Compilers\BladeCompiler blade.compiler
Bus Illuminate\Contracts\Bus\Dispatcher
Cache Illuminate\Cache\Repository cache
Config Illuminate\Config\Repository config
Cookie Illuminate\Cookie\CookieJar cookie
Crypt Illuminate\Encryption\Encrypter encrypter
DB Illuminate\Database\DatabaseManager db
DB (Instance) Illuminate\Database\Connection
Event Illuminate\Events\Dispatcher events
File Illuminate\Filesystem\Filesystem files
Gate Illuminate\Contracts\Auth\Access\Gate
Hash Illuminate\Contracts\Hashing\Hasher hash
Lang Illuminate\Translation\Translator translator
Log Illuminate\Log\Writer log
Mail Illuminate\Mail\Mailer mailer
Notification Illuminate\Notifications\ChannelManager
Password Illuminate\Auth\Passwords\PasswordBrokerManager auth.password
Queue Illuminate\Queue\QueueManager queue
Queue (Instance) Illuminate\Contracts\Queue\Queue queue
Queue (Base Class) Illuminate\Queue\Queue
Redirect Illuminate\Routing\Redirector redirect
Redis Illuminate\Redis\Database redis
Request Illuminate\Http\Request request
Response Illuminate\Contracts\Routing\ResponseFactory
Route Illuminate\Routing\Router router
Schema Illuminate\Database\Schema\Blueprint
Session Illuminate\Session\SessionManager session
Session (Instance) Illuminate\Session\Store
Storage Illuminate\Contracts\Filesystem\Factory filesystem
URL Illuminate\Routing\UrlGenerator url
Validator Illuminate\Validation\Factory validator
Validator (Instance) Illuminate\Validation\Validator
View Illuminate\View\Factory view
View (Instance) Illuminate\View\View