カテゴリー: BackEnd

LaravelのFacade(ファサード)とは? 何気なく使用していた裏側の仕組みを解説!

はじめに

Laravelで開発を行なっていると、use文で Illuminate\Support\Facades\ の中を指定する場合があります。しかし、実態は振る舞いを持たないクラスがほとんどです。これが、実際はどのような動きをしているのか、説明したいと思います。

Facadeを使用しているクラス

Laravel単体で使用頻度の高いものとしては、以下が挙げられます。

  • Auth
  • DB
  • Config
  • Log
  • Storage
  • Route

どれも、staticメソッドで利用できるクラスばかりだと思います。この中の Logクラスを例に、どのような仕組みになっているか、次節で説明します。
また、Laravelだけでも、この他にFacadeを使用しているクラスがあります。ドキュメントにまとまっているので、こちらを参照してください。

Facadeの仕組み

Facadeは、サービスコンテナに結合されたサービスのインタンスにアクセスする方法の一つです。他の方法は、こちらの記事で紹介しているような、 make()メソッドを使用する方法や、コンストラクタインジェクション、メソッドインジェクションを使用した方法です。
他の方法と比較し、Facadeはどこでも気軽にサービスコンテナのインスタンスにアクセスし、機能を呼び出せることが特徴です。
例えば、Logクラスも、Facadeです。 LogServiceProviderによって LogManagerクラスがシングルトンでバインドされています。
実際に、 Log::info('Hello')を呼び出した時の、流れを見てみましょう。
順を追うと以下のようになります。

  1. LogのスーパークラスであるFacadeの__callStatic()が呼び出される。この際、第1引数にメソッド名、第2引数に配列で引数が渡されます。
  2. getFacadeRoot()が呼び出され、サービスコンテナにインスタンスを取りに行きます。このメソッド内では、以下のように処理されるます。
    2-1. getFacadeAccessor()が呼び出される。このメソッドは、Logによってオーバーライドされており、サービスプロバイダに設定されている結合名が返却されます。
    2-2. resolveFacadeInstance()が呼び出され、取得した結合名でサービスコンテナからサービスのインスタンスを取得します。(この例では、 LogManagerが取得できます)
  3. 取得したサービスに対して、引数で渡ってきたメソッド名と引数を、可変関数として呼び出し、結果をそのまま返却します。

Facadeの作成

Facadeを作成する方法を説明します。今回は、図書館システムの貸し出し機能を想定したサンプルコードです。「本を借りる」機能をFacadeを使用して呼び出せるようにします。

サンプルコードに必要な実装

以下のマイグレーションと実装をベースに、ServiceとFacadeを作成します。

マイグレーション:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateBooksTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */    public function up()
    {
        Schema::create('books', function (Blueprint $table) {
            $table->id();
            $table->string('author');
            $table->string('title');
            $table->string('description');
            $table->integer('status')->default(\App\Book::Available);
            $table->integer('rent_count')->default(0);
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */    public function down()
    {
        Schema::dropIfExists('books');
    }
}

モデル:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Book extends Model
{
    public const Available = 1;
    public const LoanedOut = 2;

    /**
     * 本を借りる
     */    public function checkOut() {
        $this->increment('rent_count', 1);
        $this->status = self::LoanedOut;
        $this->save();
    }
}

Serviceの作成

FacadeがアクセスするServiceを、 app/Services/ディレクトリの中に作成します。

<?php
namespace App\Services;

use App\Book;

class Library
{
    /**
     * 本を借りる
     * @param int $bookId 本ID
     */    public function checkOut(int $bookId)
    {
        $book = Book::find($bookId);
        $book->checkOut();
    }
}

次に、サービスプロバイダにバインドします。今回は、 AppServiceProvidorを使用します。

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */    public function register()
    {
       // Libraryサービスをバインドする。
        $this->app->bind('library', 'App\Services\Library');
    }

// ・・・以下略・・・

bind()メソッドの第1引数で、サービスコンテナの結合名を指定しています。後ほど、Facadeの実装時に、この結合名を使用します。

サービスコンテナ・サービスプロバイダについては、以前の記事も参考にしてみてください。

Facadeクラスの作成

先ほど作成した Services\Libraryクラス用のFacadeを作成します。 app/Facadesディレクトリに作成してください。

<?php


namespace App\Facades;


use Illuminate\Support\Facades\Facade;

class Library extends Facade
{
    protected static function getFacadeAccessor()
    {
        return 'library';
    }
}

getFacadeAccessor()メソッドで、先ほどのサービスプロバイダで設定した結合名を指定します。

Facadeの呼び出し

コントローラーから呼び出してみます。

<?php

namespace App\Http\Controllers;

// use文ではFacadeを指定する
use App\Facades\Library;

class BookController extends Controller
{
    public function checkOut(int $id)
    {
        Library::checkOut($id);
    }
}

staticメソッドの呼び出し方で、Serviceに定義したメソッド checkOut()を呼び出しています。また、use文では、Facadeを指定する点がポイントです。

さいごに

サービスを使用する方法の1つとして、Facadeは便利な機能です。コンストラクタインジェクションやメソッドインジェクションで済む場合も多いと思いますが、複数のレイヤーから共通の機能を使用したい場合に、活用できると思います。
サービスへのアクセスは、コンストラクタインジェクションやメソッドインジェクションなどもありますが、実際に開発する機能に応じて、Facadeも合わせて検討したいと思いました。

おすすめ書籍

カイザー

シェア
執筆者:
カイザー
タグ: laravelphp

最近の投稿

フロントエンドで動画デコレーション&レンダリング

はじめに 今回は、以下のように…

3週間 前

Goのクエリビルダー goqu を使ってみる

はじめに 最近携わっているとあ…

1か月 前

【Xcode15】プライバシーマニフェスト対応に備えて

はじめに こんにちは、suzu…

2か月 前

FSMを使った状態管理をGoで実装する

はじめに 一般的なアプリケーシ…

3か月 前