Laravel5.5 LTS 日本語ドキュメント

このリポジトリは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ドキュメントを素早く探したい場合に便利な道具になります。対応するサービスコンテナ結合キーも記載しています。

ファサード クラス サービスコンテナ結合
App Illuminate\Foundation\Application app
Artisan Illuminate\Contracts\Console\Kernel artisan
Auth Illuminate\Auth\AuthManager auth
Auth (Instance) Illuminate\Contracts\Auth\Guard auth.driver
Blade Illuminate\View\Compilers\BladeCompiler blade.compiler
Broadcast Illuminate\Contracts\Broadcasting\Factory  
Broadcast (Instance) Illuminate\Contracts\Broadcasting\Broadcaster  
Bus Illuminate\Contracts\Bus\Dispatcher  
Cache Illuminate\Cache\CacheManager cache
Cache (Instance) Illuminate\Cache\Repository cache.store
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 db.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
Password (Instance) Illuminate\Auth\Passwords\PasswordBroker auth.password.broker
Queue Illuminate\Queue\QueueManager queue
Queue (Instance) Illuminate\Contracts\Queue\Queue queue.connection
Queue (Base Class) Illuminate\Queue\Queue  
Redirect Illuminate\Routing\Redirector redirect
Redis Illuminate\Redis\RedisManager redis
Redis (Instance) Illuminate\Redis\Connections\Connection redis.connection
Request Illuminate\Http\Request request
Response Illuminate\Contracts\Routing\ResponseFactory  
Response (Instance) Illuminate\Http\Response  
Route Illuminate\Routing\Router router
Schema Illuminate\Database\Schema\Builder  
Session Illuminate\Session\SessionManager session
Session (Instance) Illuminate\Session\Store session.store
Storage Illuminate\Filesystem\FilesystemManager filesystem
Storage (Instance) Illuminate\Contracts\Filesystem\Filesystem filesystem.disk
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