はじめに
Laravelを使い始めて1年ぐらいが経ちました。とはいえ仕事の50%はWordPress案件で、Laravelは5%ぐらいしか使えていません。
堂々と「Laravelで開発やってるよ!」と言えないレベルで、Laravelの大きなポイントの一つであるファサードの事を全然理解していなかったので色々調べました。
これからLaravelを学ぶ人たちのためになれば幸いです。
ファサードがわからない
Laravelを始めて触ってから1年以上経っていますが、今までファサードのこと分かっていませんでした。
知り合いなどから「Laravel使ってんの?やっぱファサード便利だよね」とか言われても利便性などをわかっていませんでした。
そんなファサードのことを知らない素人だったんですが、なんとか小さいシステムは組めていましたし、なんとかなってきました。
ですがが最近、ちょっと大きめの案件でユニットテストを書こうとした時に壁に当たりました。
ユニットテストを通す際にはモックを行いまずが、その際のモックの通し方がわからず困りました。
検索に検索を重ねてようやくファサードのことがわかり、ユニットテストを書くことができました。
意味
「ファサード」(英: facade)
あまり聞きなれない言葉ですがフランス語、イタリア語からの借用語で、「建物の表面」、とりわけ「正面」という意味で使われます。
語源はラテン語のfacies(英: face)(直面する、顔)が転じたものです。
facade - Wiktionary
ファサードは元々は建築用語だったそうです。
ファサード保存
歴史的建築物の保存方法として、ファサード保存が選択されることがある。これは、建築物の正面部分(時には正面の一部)だけを保存する方法で、都市における再開発と歴史的建造物の保存を両立させるための苦肉の策である。
この「建築物の正面部分だけを保存する方法で、都市における再開発と歴史的建造物の保存」というのはプログラミングで言うところの「リファクタリングと過去のコードの保存」に繋がる意味だと思います。
Laravelのファサードとは、建築用語での「建築物 の 正面部分」という意味が、プログラミングでの「ライブラリ(建築物)の呼び出し部分(正面部分)」という意味になるわけですね。
実際に使い方を見ていきましょう。
使われているファサード
実際にファサードを見てみます。
use Illuminate\Support\Facades\Cache;
Route::get('/cache', function () {
return Cache::get('key');
});
上記のコードではCache::get('key')
の部分がファサードになります。
名前から見るにCache::get('key');
はkey
という名前でキャッシュされた値を取得する、とわかります。
ですが実際にこのCache::get('key')
というのがどういった機構でキャッシュを提供しているかというのは明らかにしません。
もしかするとmemcachedかもしれませんし、Redisかもしれません。はたまたAPCを使っているかもしれません。
そういったバックエンド側をプログラマに明らかにしないままに機能を提供する、それが「ファサード」です。
バックエンド側を明らかにしたコードはどうなるでしょうか?
例としてmemcacheとAPCでのキャッシュ取得のコードを見てみます。
$memcache_obj = new Memcache;
$memcache_obj->connect('memcache_host', 11211);
$var = $memcache_obj->get('some_key');
$bar = 'BAR';
apc_store('foo', $bar);
var_dump(apc_fetch('foo'));
memcacheではオブジェクト指向のコードが、一方のAPCでは関数のコードになっています。
memcacheではget
、APCではfetch
という名前が付いています。それぞれの関数名を覚えていないと大変ですね。
またもしプロジェクトの途中で「memcacheからAPCへ変更」といったことが起こった場合には置換だけでは動きません。
そういった違いを吸収してくれるのがファサードです。
デザインパターンとファサード
元々「ファサード」という言葉はGoFのデザインパターンで紹介されていました。
Facadeクラスはあくまでサブシステム内部に仕事を投げるだけで複雑な実装は持たない。
多様な機能の塊であるサブシステムから、サブシステムを利用するユーザーの用途に合わせた窓口(インターフェース)を提供するだけである。
Facadeパターン - Wikipedia
サブシステム内に存在する複数のインタフェースに1つの統一インタフェースを与える。
Facade - Strategic Choice
用意されたファサード
Laravelでは標準で37種類のファサードが提供されています。
その中から使う機会のありそうなファサードについて列挙します。
Facade | 使い方 |
---|---|
Cache | キャッシュを提供する。config/cache.php で設定可能。 |
Config | config/app.phpの値を取得したりする。 |
DB | クエリビルダなどを提供。ファサードじゃない書き方もよくあるようです。 |
File | ファイル操作。 |
Log | ログの書き出しなど。storage/logsなどに書き出せるのでデバッグなどで重宝します。syslogも可能。 |
Request | $_REQUEST周りの処理。Laravel5からはInputファサードの代わりにこちらを使った方が良いそうです。 |
Response | ブラウザへの返答の制御。ヘッダー関連の処理全般、CORSなどが制御できます。 |
Route | ルーティングをするならコレ。Laravelで最初に目にするファサードかも。 |
Storage | ファイルの保存など。 |
Laravelを使ってまず最初に使うのはRoute
ファサードでしょうか。
Laravelを始めた頃は「こうやって書くのがお作法なんだな」ぐらいにしか思っていなくて、ファサードということを意識してなかったですね
テスト、モック、ファサード
ここまでファサードの利点は「バックエンド側を意識せずコードを書くことができる」と述べてきました。
例えばCache
ファサードであればmemcacheかAPCか意識せず、それぞれのメソッド名を知らなくとも使うことができるし、切り替えも容易ということでした。
この「バックエンドを意識しない」ファサードが(ユニット)テストで非常に強力な存在になります。
DI
コードのテストを書くことは素晴らしいですが、大変なこともあります。
その一つが依存性注入です。
テスト側から見たコードの深いところ、ての及ばないライブラリでの動作をモックする場合には引数にモックしたい値を渡す依存性注入が必要になります。
A()
=> B()
=> C()
と順に関数を呼ぶプログラムで具体的に見てみます。
Class Hoge{
public static function A() { return self::B(); }
public static function B() { return self::C(); }
public static function C() { $fuga = new Fuga('hoge'); return $fuga->toString() ; }
}
echo Hoge::A(); // 'hoge'
このHoge::A()
メソッドをテストする場合にFuga
クラスに依存しています。
この依存しているFuga
クラスのインスタンス$fuga
をモックしたい場合、依存性を注入する必要があります。
引数での依存性を行います。
Class Hoge {
public static function A(Fuga $fuga) { return self::B($fuga); }
public static function B(Fuga $fuga) { return self::C($fuga); }
public static function C(Fuga $fuga) { return $fuga->toString() ; }
}
$fuga = new Fuga('injection!');
echo Hoge::A($fuga); // injection!
これでHoge::C();
は外側で定義されたFugaのインスタンスで実行することができるようになりました。
ですが大変です。
これを簡単にするのがファサードを使ったモックです。
Class Hoge {
public static function A() { return self::B(); }
public static function B() { return self::C(); }
public static function C() { Fuga::toString('hoge'); }
}
Fuga::shouldReceive('toString')->andReturn('injection!');
echo Hoge::A(); // injection!
上記の例では Fuga
クラスの静的メソッド、shouldReceive()
を呼び出し、その後でandReturn()
を呼んでいます。
こうすることで Fuga::toString('hoge');
の戻り値がandReturn()
の引数であるinjection!
に置き換わっています。
こうすることで、呼び出しの外側から依存された内部の動作を変更することができます。
テストでは非常に便利ですね!
実際のモック
上記の例では仮想のFugaというクラスを使ってLaravelとは無関係の操作を行っていました。
Laravelで実際にファサードをモックさせて動かしてみます。
通常の作成ではtest/*
などに置くコードですが、動作テストコードとして認識しやすいようweb.phpファイルだけで完結させています。
まずはモックをせずCache
を使うコードです。
Route::get('/cache_test1', function() {
Cache::put('key', 'cached value', 300);
echo Cache::get('key'); // cached value と表示される
});
echo文のところでcached value
と表示されるコードです。
このキャッシュをモックを使って呼び出します。
Route::get('/cache_test2', function() {
Cache::put('key', 'cached value', 300);
Cache::shouldReceive('get')
->andReturn('mocked value');
echo Cache::get('key'); // mocked value と表示される
});
これでCache::get('key')
がモックされました。
Cache::shouldReceive('get')->andReturn('mocked value');
と書くことでget
メソッドの戻り値がmocked value
となります。
このモックはどこからでも定義することができるので、ユニットテスト内部を簡単にモックすることができます。便利ですね!
ファサードのオプション
これでファサードをモックして呼び出すことができました。
LaravelのファサードのモックはMockeryというライブラリを使っています。
サンプルコードの例をコメントで追ってみます。
Route::get('/cache_test3', function() {
Cache::shouldReceive('get') // getメソッドをモックする
->with('key') // 引数がkeyのとき
->once() // 一度だけ呼び出されます
->andReturn('value') // 戻り値です
echo Cache::get('key'); // ここで value と表示される。
});
このように、Mockeryでは期待値条件(Expectation Declarations)と呼ばれるメソッドをメソッドチェインで列挙します。
この期待値条件を定義することでモックの動作を決めていきます。
LaravelのMockeryでは決まった多いですが、Mockeryの期待値条件の書き方について抜粋します。
最初に書かれる期待値条件はShouldReceiveです。呼び出し方にいくつかの種類があります。
ShouldReceive | 使い方 |
---|---|
shouldReceive(method_name) | メソッド名を文字列で与えます |
shouldReceive(method1, method2, ...) | 複数のメソッド名に合致させます。 |
shouldReceive(array('method1'=>1, 'method2'=>2, ...)) | メソッド名と戻り値までを配列で定義します。 |
withで引数を定義します。
with | 使い方 |
---|---|
with(arg1, arg2, ...) | 引数を提示します。可変長引数です。 |
withArgs(array(arg1, arg2, ...)) | 可変長引数ではなく配列で与えることもできます。 |
withNoArgs() | 引数なしのみが合致します。 |
withAnyArgs() | デフォルトの動作で、どんな引数でも合致します。 |
回数の制御です。指定した回数分だけモックします。
回数 | 使い方 |
---|---|
once() | 一度だけです。 |
twice() | 二回までです。 |
times(n) | n回までです。 |
atLeast() | times(n)と組み合わせて、最低何回は呼ばれる、と定義します。 |
between(min, max) | 回数制限の範囲です。 |
戻り値の制御です。
return | 使い方 |
---|---|
andReturn(value) | 戻り値。オブジェクトもいけます。 |
andReturn(value1, value2, ...) | 複数回数呼ばれる場合の定義 |
andReturnValues(array) | こちらも複数回呼ばれる場合に定義します |
andReturnNull() | NULLを返します |
andThrow(Exception) | 例外を返します。 |
andThrow(exception_name, message) | 例外を返す場合の別の書き方です。 |
andSet(name, value1) | 値を返す代わりにモックオブジェクトのpublic変数にセットを行います |
Expectation Declarations
Mockeryのドキュメントを翻訳された方
Mockeryだけでも色々な使い方ができそうですね。
shouldReceive()->with()->andReturn()
という書き方が多いかなと思います。
配列を使った複数の戻り値を順に定義する使い方も便利ですので、覚えておくと良いかなと思います。
Route::get('/cache_test4', function() {
$returns = [
'1st mocked value',
'2nd mocked value',
'3rd mocked value',
];
Cache::shouldReceive('get')
->times(count($returns))
->andReturnValues($returns);
echo Cache::get('key'); // 1st mocked value と表示される
echo Cache::get('key'); // 2nd mocked value と表示される
echo Cache::get('key'); // 3rd mocked value と表示される
// echo Cache::get('key'); // ここはエラー
});
OSI基本参照モデルとDI、ファサード
ちょっと他の話を。
現在僕はプログラマとしてコーディングしていますが、元々プログラミングはあまり得意ではなく(今でも!)、ネットワークエンジニアになりたいと思っていました。
ネットワークエンジニアになるための勉強をしていた時期があり、ネットワーク分野に似ている印象を受けました。
それはネットワーク分野の「OSI基本参照モデル」です。
[OSI参照モデル](https://ja.wikipedia.org/wiki/OSI%E5%8F%82%E7%85%A7%E3%83%A2%E3%83%87%E3%83%AB)
「OSI基本参照モデル」とはネットワーク通信のプロトコルスタックを定めたものです。
それぞれのレイヤ(層)でどんな通信を行うかという取り決め(プロトコル)を定めています。
現在のインターネット通信ではTCP/IPプロトコルスイートが(ほぼ)標準になっていますが、本来はそれぞれのレイヤで具体的なプロトコルを定めて良いものです。
この抽象化された「OSI基本参照モデル」のレイヤ定義は、Laravelのファサードに似ているなぁ、という印象を受けました。
とは言え、抽象化と具現化というのはプログラミングではよく出てくる概念なので、ファサードとOSI基本参照モデルに限った話ではないかもしれないですね。
自作ファサード
ここまで使ってきたファサード(サービスプロバイダ)は自作することもできます。
ファサードの作り方は以下の通りになります。
- クラスを作成
- サービスプロバイダーを作成
- サービスプロバイダーの設定
- ファサードクラスを作成
- ファサードを使ってみる
- ファサードエリアスの設定
コード自体は単純なのですが、ファサードの自作を行うには手順が多く大変そうです。
またファサードは非常に便利で融通がきくため、設計を誤ると後の実装に響きそうです。
僕もHTTP通信のライブラリでファサードを使いたい機会がありました。
自作も検討しましたが検索したところファサード(サービスプロバイダ)が公開されていました。そういった公開されたファサードを使うというのが便利かもしれません。
ファサードに慣れてきたら自作を検討したいですね。
まとめ
以上、ファサードについて自分なりにまとめてみました。
当初は聞き慣れない言葉だったので使い方も概念もわからなかったですが、単語を調べることでかなり理解が進んだ気がします。
また実際にモックできるようになるとテストを書きやすくなり、テストが楽しくなりました。モックがキマると気分爽快ですね :)
お礼
今回の記事を書くにあたって、@naname さんにファサードのことを教えてもらい、記事の監査してもらいました。ありがとうございました😃
参考サイト
Laravel コードで見るファサードクラスの仕組み
Laravel 5.4 ファサード
Laravel ファサードを利用しないメリット
LaravelのDIの挙動を勘違いしていてドハマリした件
laravel5.2 自作Facadeを作る