32
29

More than 3 years have passed since last update.

たった3行 世界一カンタンにLaravelのファサードを作る方法(とサービスコンテナ的観点から掘るファサードの本質)

Last updated at Posted at 2019-07-26

世界一カンタンなファサードの作り方(当社比)

Facadeをつくる手順は下記の通り。カッコは省略可能です。

  • ファサード化したいクラスを作る
  • ファサードクラスを作る
  • (サービスプロバイダを作る)
  • (サービスコンテナにインスタンス化する方法を伝える)
  • (エイリアスを書く)

カッコは省略可能ですが、エイリアスは作っておきましょう。
というわけで今回の手順はこれだけ。

  • クラスを作る
  • ファサードクラスを作る
  • (エイリアスを書く)

そして、これを実現するのに、書き換えるコードは3行
あとはコピペです。

クラスを作る

まずは、ファサード化したいクラスを作ります。
クラスを置く場所や中身は何でもいいのですが、
ファサードとして提供したいメソッドはすべて「インスタンスメソッド」で書くのがポイントです。

ひとまず、 Services というネームスペースを用意して MyLoggerService を作ってみます。

app\Services\MyLoggerService.php
namespace App\Services;

class MyLoggerService
{
    public function write( string $message )
    {
        // ...
    }
}

当然ですが、このままではスタティック呼び出しはできません。

use App\Services\MyLoggerService;

MyLoggerService::write('なにかログを記録するよ'); 
// Deprecated: Non-static method MyLoggerService::write() should not be called statically

#改めてやってみたら Deprecated と警告されるだけで、できるにはできるのね……。PHPは優しいなぁ。

ファサードクラスを作る

以下をコピペしてください。

app/Facades/MyLogger.php
<?php
namespace App\Facades;

use Illuminate\Support\Facades\Facade;

class MyLogger extends Facade // 【1行目】ここのクラス名と
{
    protected static function getFacadeAccessor()
    {
        // Facade化したいクラスのクラス名を書く
        return \App\Services\MyLoggerService::class; // 【2行目】ここを書き換える
    }
}

ファサードは artisan コマンドでも作れますが、書換がたった2行なのでコピペしたほうが速いです。

作る場所はどこでもいいのですが、大きく2通りの考え方があって、
1つは app/Facades といった共通のディレクトリにまとめること。
もう1つが、ファサード化対象のクラスの近くに作ること。

(Laravel標準のFacadeは、ほぼ前者。
Illuminate\Support\Facades にまとまっています。)

サービスクラスの数や種類が少なければ前者がいいですが、
サービスクラスの種類が多かったりモジュールが別れていたりする場合は、
無理に1箇所に集めるより、対象クラスの近くに置くほうが管理が楽です。
その場合でも、どんなファサードがあるかは後述する「エイリアス」のところで一覧管理できますし。

でも、今回はカンタンにしたいので、前者。

さて、こうすると、そのファサードクラスを使って、
さきほど作ったインスタンスメソッドを、
いきなりスタティックメソッド的に使うことができるようになります。

use App\Facades\MyLogger;

MyLogger::write('なにかログを記録するよ'); 
// 動く!

これで ファサードの完成 です。
早い!

※ここで登場した唯一のメソッド getFacadeAccessor は、解説コーナーで詳しく取り上げています。

エイリアスを作る

ちょっとちょっと!
Laravel標準のFacadeって、単語1つの短い名前でも使えるでしょ。
それ欲しい!

ごもっともです。

ただ、それを実現しているのは Facade ではなく「エイリアス」という機能です。
#あるクラスが別のクラス名で定義されているかのように使える機能。別にFacadeじゃなくてもエイリアスは作れます。

エイリアスの追加自体は超簡単で、はじめから用意されている config/app.php を開いて

config/app.php

    'aliases' => [
        'App' => Illuminate\Support\Facades\App::class,
        'Artisan' => Illuminate\Support\Facades\Artisan::class,
        // ... たくさんある
        'View' => Illuminate\Support\Facades\View::class,

        // ここに追加 エイリアスクラス名 => 実態クラス名 のペア
        'MyLogger' => App\Facades\MyLogger::class, // これが最後の【3行目】
    ],

とするだけ。こうすると、

use App\Facades\MyLogger;
MyLogger::write('なにかログを記録するよ'); 

これが

use MyLogger;
MyLogger::write('なにかログを記録するよ'); 

// それか\をつけて use せずにいきなり使う。
\MyLogger::write('なにかログを記録するよ'); 

このように使えるようになります。

僕自身、Laravel始めた頃に

use Illuminate\Support\Facades\DB;
use DB;

と2つのクラスがあって、どっちを使ったらいいか迷ったりしていましたが、
どちらも 全く同じもの です。

Facade使ってるぜ!ということを明確にしたいなら前者のロング版(IDEのコードヒントがうまく動く)。
Facadeの「あとから差し替え可能だぜ!」というメリットを最大限活かしたいなら後者のショート版。
個人的には、VSCodeの自動補完が効くので、ロング版で書くことが多いです。

解説

image.png

#「ファサード」というと建築用語で、建物の正面外観のデザインを指します。
ヨーロッパの街並みはレンガ造りの長屋が多く、
建物の正面デザインだけで個性を競う文化が発達したので専門用語があるんですね。
(昔は建築家といえばファサードデザイナーのことを指していたとか)

Facadeとは何か?

それはさておき、LaravelのFacadeとは一体なんなのか?

Laravel Facadeの機能

  • クラスのインスタンスメソッドがスタティックメソッドのように使える
  • カンタンにモック化できる(…という機能もあるけどこの記事では触れません)

以上。

Facadeに対するよくある誤解

  • えーと……要するに、スタティックメソッドを書くところですよね?
    → あざす!それが今回のテーマ。
  • DB というネームスペースを使わない短い名前のクラスが作れるからアクセスがカンタン
    → それは「エイリアス」
  • Laravelに用意されている DB とか Cookie とか Redis といったクラスのことだ!
    → ファサードには違いないけど、今回は標準ファサードではなく、独自ファサードの作り方(本質)でちょっと話題が違う。
  • それだけのために、サービスプロバイダ書いて、コンテナに登録して……手順が多くてめんどくさい!!
    → 3行でできましたよー。

[一般的な話] サービス(クラス)とは?

ファサードの話をする前に、それと同時によく耳にする「サービス」とか「サービスクラス」とは何かという話を少しはさみます。

とは言っても、この「サービス」に明確な定義はありません。
とても抽象的。
ぶっちゃけ「何でもいい」くらいに言われることがほとんどです。
ただ、強いてアプリケーション開発における「サービス」というと、おぼろげながら下記のようなイメージで語られているようです。

  • アプリケーションのとある機能についてまとめられたクラスやモジュール
  • かなり具体的な機能のメソッドやインターフェースをいくつか持つ(多くのケースで、そのアプリのためだけの機能を提供し、他のアプリへの使い回しを考えない)
  • アプリケーションのどこからでもササッと呼び出される(ある機能を使うのに下準備がそれほど必要ない)

たとえば、「注文」というサービスに、「注文を作成する」「注文を確定する」「支払いする(支払い情報を紐付ける)」「発送する(発送情報を紐付ける)」といったメソッドがある……イメージ。
「ログ」というサービスには「ログを記録」「ログを一覧で取得する」といったメソッドがあるイメージ……。

それで「どこからでもササッと」なら………。
スタティックメソッドで実装したらいい感じですよね!

class Logger {

    public static function write( string $message )
    {
        // ...
    }

    // ...
}

Logger::write('エラーが起きました');

そうそう、まさにそんなイメージです。

[一般的な話] Facadeパターンとは?

LaravelのFacadeと同じ名前なのでややこしいですが、
それとは別の、デザインパターンの1つ「Facadeパターン」とはなにかをお話します。

ファサードパターンは、
さきほど出てきた「サービスクラス」的なものを作るときの、
作り方の1つ(設計方法)で、

  • サービスクラスが提供する機能にどんなものがあるか?だけ厳密に決めておく(メソッド名や引数、返り値など)
  • 具体的な実装は隠して、あとから差し替えが効くようにする

という方法論のことです。

この「提供する機能だけ決まっている」というのが、
お店の外観はビシッとカッコよく決まっているけど、
中身はまだ改装中……みたいな
ファサードしか無い というのがネーミングの由来と思われます。

image.png
https://commons.wikimedia.org/wiki/File:Scenic_Design,_The_Family_Series,_by_Glenn_Davis.JPG
#なのでより正しいイメージとしては、建築のファサードというよりも、舞台セットのような ハリボテの立て看板 に近いです。

これだけ聞くと、PHPではインターフェースがぴったり来るイメージですね。

interface LoggerInterface {

    public static function write( string $message );

    // ...
}

class Logger implements LoggerInterface {

    public static function write( string $message )
    {
        // 中身はまだ構築中...
    }

    // ...
}

さっきのスタティックメソッドはダメなんですか?

そう。ダメです。
スタティックメソッドは「あとから差し替え」が非常に困難です。
(しかもそれ自体がガッツリ実装を持っています。)
サービス的なものを作ることはできても、それは「Facadeパターンではない」ということになります。

[Laravelの話] Facadeとは?

さて、サービスクラスとファサードパターンの2つの話が出てきましたが、
このうち、Laravel の Facadeは、
後者の「ファサードパターン」をPHPでカンタンに実現するための機能です。

前者じゃないよ(スタティックメソッドを作るための機能じゃないよ)。ということ。
これ、自分も身に覚えのある「誤解」です。

スタティックメソッドじゃだめな理由

Laravel標準のヘルパ関数をオーバーライドする話でも実践しましたが
グローバル関数や、スタティックメソッドは、
それをあとから置き換えることがめちゃくちゃ困難です。

1回作っちゃうと、コンクリの基礎でガッチリ固められちゃっているように、動かせない。
これがアンチパターンとされている最大の理由です。

でも、先程「サービスクラス」でも触れましたが、
サービス的なクラスを作ろうとすると スタティックメソッドを作りたくなってくる

そこで、アンチパターンを避けつつ、スタティックメソッドを作るための仕組みとして、
Laravelは、Facadeパターンをうまく取り入れた、
Facadeという機能を用意してくれているんです。

Laravelがそれを解決する方法論

image.png

Facadeの動作原理はカンタンで、

  • スタティックメソッド的な呼び出し方をされる
  • (未定義なので)マジックメソッド __callstatic が呼ばれる
  • getFacadeAccessor でコンテナのキーを得る
  • サービスコンテナからインスタンスをもらう
  • 呼ぶ

という感じ。実際のコードも下記のとおり、とてもシンプルです。

vendor/lavael/framework/src/Illuminate/Support/Facades.php
    public static function __callStatic($method, $args)
    {
        $instance = static::getFacadeRoot();

        if (! $instance) {
            throw new RuntimeException('A facade root has not been set.');
        }

        return $instance->$method(...$args);
    }

getFacadeAccessorの返り値

というわけで、 getFacadeAccessor の返り値を振り返ります。

標準のファサードを見ると 'app' とか 'db' といった短い文字列なのに、
なぜ今回、クラス名を使用したのでしょうか?

先程ちらっと触れましたが、
実はここは「サービスコンテナにインスタンスをもらうためのキー名」を与えています。
「第3回 サービスコンテナ 結合編」で言うところの abstract 抽象 です。
つまり、

$instance = app('app');
$instance = app('db');
$instance = app(\App\Services\MyLoggerService::class); // MyLoggerServiceのインスタンスが返る

としたときに、期待したインスタンスが返ってくるかどうか、ということ。

ここを、

$instance = app('mylogger'); // MyLoggerServiceのインスタンスが返ってきてほしい

としたければ、

app()->bind( 'mylogger', \App\Services\MyLoggerService::class );

と結合を定義すればオッケー!というのは第3回の復習で、こうしておけば

    protected static function getFacadeAccessor()
    {
        return 'mylogger';
    }

こうできます。
でも、クラス名だったら、bindしなくても最初から使えるよね、
だからキーはクラス名でいいよね、というのが「第1回 サービスコンテナ new編」 の復習。

#ちなみに、getFacadeRoot はインスタンスをキャッシュする機能が備わっていたりするので、Facadeが使うインスタンスはすべて自動的にシングルトンになる、といった付加機能もあります(今気づきました笑)。つい下記のようにシングルトン結合を書きたくなってきますが、要らん!ということですね。

app()->singleton( \App\Services\MyLoggerService::class );

まとめ

ファサードとはなにか?

LaravelのFacadeの本質をまとめるとこうです。

スタティックメソッドを書きたい!という欲求に、
スタティックメソッドを書かせずに応えるための仕組み。

最終的に出来上がるのはスタティックメソッドですが、
スタティックメソッドを1つも書かない!というのが最大の特徴です。

その機能の実現だったら3行でサクッとできますので、
スタティックメソッドが書きたい!と思ったら、
代わりにこのFacadeを使ってみてください。

こんな記事も書いています

Laravelのちょっとマニアックな視点から、誰も書かない記事を書いています(笑
合わせてご覧いただけると幸いです(^^)

今回と似た内容の「ヘルパ関数編」

サービスコンテナ講座

32
29
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
29