因为完全不理解Laravel的Facade,所以我自己做了很多调查

首先

我开始使用Laravel已经有大约一年的时间了。但是我的工作中大约50%是WordPress项目,而只有大约5%的时间是在使用Laravel。

由于对Laravel的重要特性之一的Facade几乎没有理解,以至于我甚至不能自豪地说“我正在使用Laravel开发!”。因此,我做了很多调研。

如果这对即将学习Laravel的人们有帮助,那将是幸福的。

不了解外观

我开始接触 Laravel 已经有一年多了,但直到最近才了解到门面模式(facade)的存在。即使朋友告诉我“你在用 Laravel 吧?门面模式真的很方便”,我也不明白它的实用性。虽然我以前对门面模式一无所知,但我还是能够完成小型系统的构建,并且逐渐上手了。然而最近,在一个稍微大一点的项目中,当我想编写单元测试的时候遇到了困难。在进行单元测试时,需要进行模拟(mock),但是我不知道如何正确使用模拟。我进行了多次搜索,最终才理解了门面模式,并且成功编写了单元测试。

意味着

「ファサード」(英:facade)是一个不太常见的词汇,源自法语和意大利语,用来表示“建筑物的表面”,尤其指的是“正面”。它的词源是拉丁语的facies(英:face)(面对,面孔)转变而来。

据说”ファサード”最初是建筑术语。

Screen Shot 2017-03-20 at 15.41.08.png

“ファサード- 维基百科”

我认为这句话中的「只保存建筑物正面部分,对城市再开发与历史建筑物保存的方法」在编程上对应于「重构与保存过去代码」的意义。

Laravel的Facade即为建筑术语中的“建筑物的正面部分”,在编程中表示调用库(建筑物)的接口部分(正面部分)。

让我们通过实际示例来看一下如何使用它。

使用中的外观

我实际去看看立面。

use Illuminate\Support\Facades\Cache;

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

请用中文用一种方式转述以下内容:

https://laravel.com/docs/5.4/facades

在上述的代码中,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');

请参考以下链接获取关于memcache的get方法的详细信息:
http://php.net/manual/zh/memcache.get.php

$bar = 'BAR';
apc_store('foo', $bar);
var_dump(apc_fetch('foo'));

请访问以下网址以获取有关apc_fetch函数的详细信息:http://php.net/manual/ja/function.apc-fetch.php

在Memcache中,代码是面向对象的,而在APC中,代码是函数的。
在Memcache中使用get函数,在APC中使用fetch函数。如果不记得每个函数的名称,会很麻烦。
而且,如果在项目进行中出现“从Memcache更改为APC”的情况,仅进行替换将不会起作用。

解读这种差异的是立面设计。

设计模式和外观

最初,“Facade”这个词是在GoF的设计模式中引入的。

Screen Shot 2017-03-20 at 15.45.32.png

预备好的外观

在Laravel中,标准提供了37种Facade(门面)。
以下列举了可能会用到的Facade。

Facade使い方Cacheキャッシュを提供する。config/cache.phpで設定可能。Configconfig/app.phpの値を取得したりする。DBクエリビルダなどを提供。ファサードじゃない書き方もよくあるようです。Fileファイル操作。Logログの書き出しなど。storage/logsなどに書き出せるのでデバッグなどで重宝します。syslogも可能。Request$_REQUEST周りの処理。Laravel5からはInputファサードの代わりにこちらを使った方が良いそうです。Responseブラウザへの返答の制御。ヘッダー関連の処理全般、CORSなどが制御できます。Routeルーティングをするならコレ。Laravelで最初に目にするファサードかも。Storageファイルの保存など。

外观类引用- Laravel

最初使用Laravel时,我们会首先使用Route Facade吗?

开始学习 Laravel 时,我只是觉得这就是编写代码的规范而已,并没有意识到门面模式的存在。

考试、模拟、门面

在前面我们已经提到过门面模式的优点是“能够编写代码而无需考虑后端”,例如,使用Cache门面模式,无论是使用内存缓存还是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!

使用外部定义的Fuga实例,可以在外部调用Hoge::C()。但这很麻烦。
使用Facade模式进行模拟会使这更简单。

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ファイルだけで完結させています。

首先是使用缓存而不进行模拟的代码。

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’。

这个模拟可以从任何地方定义,所以可以轻松地在单元测试中进行模拟。非常方便!

外观选项

我能够使用这个来模拟并调用Facade。
Laravel的Facade模拟使用了一个叫做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変数にセットを行います

期待宣言
对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基本参照モデル」です。

Screen Shot 2017-03-20 at 15.55.06.png

「OSI基本参照モデル」とはネットワーク通信のプロトコルスタックを定めたものです。
それぞれのレイヤ(層)でどんな通信を行うかという取り決め(プロトコル)を定めています。
現在のインターネット通信ではTCP/IPプロトコルスイートが(ほぼ)標準になっていますが、本来はそれぞれのレイヤで具体的なプロトコルを定めて良いものです。
この抽象化された「OSI基本参照モデル」のレイヤ定義は、Laravelのファサードに似ているなぁ、という印象を受けました。
とは言え、抽象化と具現化というのはプログラミングではよく出てくる概念なので、ファサードとOSI基本参照モデルに限った話ではないかもしれないですね。

自行制造的表面

到目前为止使用的外观(服务提供商)可以自己制作。

建造外立面的方法如下所示。

创建一个类
创建一个服务提供者
配置服务提供者
创建一个门面类
尝试使用门面
配置门面别名

创建一个名为Laravel的门面

虽然代码本身很简单,但自己制作外观模式需要很多步骤,看起来很繁琐。而且外观模式非常方便和灵活,如果设计出错将会对后续的实现产生影响。

我也有机会想要在 HTTP 通信库中使用外观模式。我也考虑过自己编写,但在搜索时发现已经有公开的外观模式(服务提供商)。使用这样公开的外观模式可能会很方便。

Laravel-Guzzle提供者

当你对外观有了一定的熟悉后,可以考虑自己制作。

总结

我已经总结了关于以上事项和外立面的个人意见。
最初我对这个词不熟悉,对它的用法和概念一无所知,但通过查阅相关单词,我觉得自己对它有了相当的理解。
而且,一旦我能够在模拟环境中进行模拟,编写测试也变得更加容易,测试也变得更有趣了。当模拟达到预期效果时,心情真是畅快啊 🙂

谢谢

在写这篇文章之前,我向@naname了解了一些有关”façade”的内容,并请他帮助审查了这篇文章。非常感谢!?

参考网站

用 Laravel 代码了解门面类的机制
Laravel 5.4 门面类
不使用 Laravel 门面类的好处
对 Laravel 的依赖注入机制误解并陷入困境的案例
创建自定义门面类在 Laravel 5.2 中

广告
将在 10 秒后关闭
bannerAds