はじめに
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')
を呼び出した時の、流れを見てみましょう。
順を追うと以下のようになります。
-
Log
のスーパークラスであるFacadeの__callStatic()
が呼び出される。この際、第1引数にメソッド名、第2引数に配列で引数が渡されます。 -
getFacadeRoot()
が呼び出され、サービスコンテナにインスタンスを取りに行きます。このメソッド内では、以下のように処理されるます。
2-1.getFacadeAccessor()
が呼び出される。このメソッドは、Log
によってオーバーライドされており、サービスプロバイダに設定されている結合名が返却されます。
2-2.resolveFacadeInstance()
が呼び出され、取得した結合名でサービスコンテナからサービスのインスタンスを取得します。(この例では、LogManager
が取得できます) - 取得したサービスに対して、引数で渡ってきたメソッド名と引数を、可変関数として呼び出し、結果をそのまま返却します。
Facadeの作成
Facadeを作成する方法を説明します。今回は、図書館システムの貸し出し機能を想定したサンプルコードです。「本を借りる」機能をFacadeを使用して呼び出せるようにします。
サンプルコードに必要な実装
以下のマイグレーションと実装をベースに、ServiceとFacadeを作成します。
マイグレーション:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | <?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'); } } |
モデル:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <?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/
ディレクトリの中に作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <?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
を使用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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
ディレクトリに作成してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <?php namespace App\Facades; use Illuminate\Support\Facades\Facade; class Library extends Facade { protected static function getFacadeAccessor() { return 'library'; } } |
getFacadeAccessor()
メソッドで、先ほどのサービスプロバイダで設定した結合名を指定します。
Facadeの呼び出し
コントローラーから呼び出してみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <?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も合わせて検討したいと思いました。