Skip to content

ファサード

はじめに

Laravelのドキュメントを読んでいると、"ファサード"を介して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はさまざまなグローバル"ヘルパー関数"を提供し、一般的なLaravel機能との対話をさらに容易にします。viewresponseurlconfigなど、よく対話するヘルパー関数がいくつかあります。Laravelが提供する各ヘルパー関数は、対応する機能とともに文書化されていますが、完全なリストは専用のヘルパードキュメントで利用できます。

例えば、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つのクラスで多くのファサードを使用することが容易になります。依存性注入を使用すると、大きなコンストラクタがクラスが大きくなりすぎていることを視覚的に通知します。したがって、ファサードを使用する場合は、クラスのサイズに特に注意を払い、その責任範囲が狭く保たれるようにしてください。クラスが大きくなりすぎる場合は、複数の小さなクラスに分割することを検討してください。

ファサード vs. 依存性注入

依存性注入の主な利点の1つは、注入されたクラスの実装を交換できることです。これはテスト中にモックやスタブを注入し、さまざまなメソッドがスタブで呼び出されたことをアサートするのに便利です。

通常、本当の静的クラスメソッドをモックまたはスタブすることはできません。しかし、ファサードは動的メソッドを使用してサービスコンテナから解決されたオブジェクトへメソッド呼び出しをプロキシするため、実際には注入されたクラスインスタンスと同様にファサードをテストできます。例えば、次のルートがあるとします。

use Illuminate\Support\Facades\Cache;

Route::get('/cache', function () {
    return Cache::get('key');
});

Laravelのファサードテストメソッドを使用して、Cache::getメソッドが期待する引数で呼び出されたことを確認する次のテストを記述できます。

use Illuminate\Support\Facades\Cache;

test('basic example', function () {
    Cache::shouldReceive('get')
         ->with('key')
         ->andReturn('value');

    $response = $this->get('/cache');

    $response->assertSee('value');
});
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');
}

ファサード vs. ヘルパー関数

ファサードに加えて、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 Facades\App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;

class Podcast extends Model
{
    /**
     * ポッドキャストを公開します。
     */
    public function publish(): void
    {
        $this->update(['publishing' => now()]);

        Publisher::publish($this);
    }
}
use Facades\App\Contracts\Publisher; // [tl! add]
use Illuminate\Database\Eloquent\Model;

class Podcast extends Model
{
    /**
     * ポッドキャストを公開する。
     */
    public function publish(): void // [tl! add]
    {
        $this->update(['publishing' => now()]);

        Publisher::publish($this); // [tl! add]
    }
}

リアルタイムファサードが使用されると、パブリッシャーの実装は、Facadesプレフィックスの後に現れるインターフェースまたはクラス名の部分を使用して、サービスコンテナから解決されます。テスト時には、Laravelの組み込みファサードテストヘルパーを使用して、このメソッド呼び出しをモックすることができます。

<?php

use App\Models\Podcast;
use Facades\App\Contracts\Publisher;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

test('ポッドキャストを公開できる', function () {
    $podcast = Podcast::factory()->create();

    Publisher::shouldReceive('publish')->once()->with($podcast);

    $podcast->publish();
});
<?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;

    /**
     * テスト例。
     */
    public function test_podcast_can_be_published(): void
    {
        $podcast = Podcast::factory()->create();

        Publisher::shouldReceive('publish')->once()->with($podcast);

        $podcast->publish();
    }
}

ファサードクラスリファレンス

以下に、すべてのファサードとその基礎となるクラスを示します。これは、特定のファサードルートのAPIドキュメントをすばやく掘り下げるのに便利なツールです。該当する場合は、サービスコンテナのバインディングキーも含まれています。

ファサード クラス サービスコンテナのバインディング
App Illuminate\Foundation\Application app
Artisan Illuminate\Contracts\Console\Kernel artisan
Auth (インスタンス) Illuminate\Contracts\Auth\Guard auth.driver
Auth Illuminate\Auth\AuthManager auth
Blade Illuminate\View\Compilers\BladeCompiler blade.compiler
Broadcast (インスタンス) Illuminate\Contracts\Broadcasting\Broadcaster  
Broadcast Illuminate\Contracts\Broadcasting\Factory  
Bus Illuminate\Contracts\Bus\Dispatcher  
Cache (インスタンス) Illuminate\Cache\Repository cache.store
Cache Illuminate\Cache\CacheManager cache
Config Illuminate\Config\Repository config
Context Illuminate\Log\Context\Repository  
Cookie Illuminate\Cookie\CookieJar cookie
Crypt Illuminate\Encryption\Encrypter encrypter
Date Illuminate\Support\DateFactory date
DB (インスタンス) Illuminate\Database\Connection db.connection
DB Illuminate\Database\DatabaseManager db
Event Illuminate\Events\Dispatcher events
Exceptions (インスタンス) Illuminate\Contracts\Debug\ExceptionHandler  
Exceptions Illuminate\Foundation\Exceptions\Handler  
File Illuminate\Filesystem\Filesystem files
Gate Illuminate\Contracts\Auth\Access\Gate  
Hash Illuminate\Contracts\Hashing\Hasher hash
Http Illuminate\Http\Client\Factory  
Lang Illuminate\Translation\Translator translator
Log Illuminate\Log\LogManager log
Mail Illuminate\Mail\Mailer mailer
Notification Illuminate\Notifications\ChannelManager  
Password (インスタンス) Illuminate\Auth\Passwords\PasswordBroker auth.password.broker
Password Illuminate\Auth\Passwords\PasswordBrokerManager auth.password
Pipeline (インスタンス) Illuminate\Pipeline\Pipeline  
Process Illuminate\Process\Factory  
Queue (基底クラス) Illuminate\Queue\Queue  
Queue (インスタンス) Illuminate\Contracts\Queue\Queue queue.connection
Queue Illuminate\Queue\QueueManager queue
RateLimiter Illuminate\Cache\RateLimiter  
Redirect Illuminate\Routing\Redirector redirect
Redis (インスタンス) Illuminate\Redis\Connections\Connection redis.connection
Redis Illuminate\Redis\RedisManager redis
Request Illuminate\Http\Request request
Response (インスタンス) Illuminate\Http\Response  
Response Illuminate\Contracts\Routing\ResponseFactory  
Route Illuminate\Routing\Router router
Schedule Illuminate\Console\Scheduling\Schedule  
Schema Illuminate\Database\Schema\Builder  
Session (インスタンス) Illuminate\Session\Store session.store
Session Illuminate\Session\SessionManager session
Storage (インスタンス) Illuminate\Contracts\Filesystem\Filesystem filesystem.disk
Storage Illuminate\Filesystem\FilesystemManager filesystem
URL Illuminate\Routing\UrlGenerator url
Validator (インスタンス) Illuminate\Validation\Validator  
Validator Illuminate\Validation\Factory validator
View (インスタンス) Illuminate\View\View  
View Illuminate\View\Factory view
Vite Illuminate\Foundation\Vite  

ユーザーノート