Laravel 5.5 底層原理:門面(Facades)
簡介
Facades 為應用服務容器中的繫結類提供了一個“靜態”介面。
Laravel 內建了很多 Facades ,可以訪問絕大部分 Laravel 的功能。
Laravel 的門面作為服務容器中底層類的“靜態代理”,相比於傳統靜態方法,在維護時能夠提供更加易於測試、更加靈活、簡明優雅的語法。
Laravel 的所有門面都定義在 Illuminate\Support\Facades 名稱空間下。
我們可以輕鬆訪問到門面:
use Illuminate\Support\Facades\Cache; Route::get('/cache', function () { return Cache::get('key'); });
在整個 Laravel 文件中,很多例子使用了門面來演示框架的各種功能特性。
何時使用 Facades
門面有諸多優點,其提供了簡單、易記的語法,讓我們無需記住長長的類名即可使用 Laravel 提供的功能特性,此外,由於他們對 PHP 動態方法的獨到用法,使得它們很容易測試。
在使用 Facades 時,有些地方還需要特別注意。
使用 Facades 最主要的風險就是會引起類作用範圍的膨脹。
因為 Facades 使用起來非常簡單而且不需要注入,就會使得我們在不經意間在單個類中使用許多 Facades,從而導致類變的越來越大。
而使用依賴注入的時候,使用的類越多,構造方法就會越長,在視覺上就會引起注意,提醒你這個類有點龐大了。因此在使用 Facades 的時候,要特別注意控制好類的大小,讓類的作用範圍保持短小。
在開發與 Laravel 進行互動的第三方擴充套件包時,建議最好選擇注入 Laravel 契約 ,而不是使用 Facades 的方式來使用類。因為擴充套件包是在 Laravel 本身之外構建,所以你無法使用 Laravel Facades 測試輔助函式。
Facades Vs. 依賴注入
依賴注入的主要優點之一是切換注入類的實現的能力。這在測試的時候很有用,因為你可以注入一個 mock 或者 stub ,並斷言在 stub 上呼叫的各種方法。
通常,真正的靜態方法是不可能被 mock 或者 stub。但是,因為 Facades 使用動態方法來代理從服務容器解析的物件的方法呼叫,我們可以像測試注入的類例項一樣來測試 Facades。例如,像下面的路由:
use Illuminate\Support\Facades\Cache;
Route::get('/cache', function () {
return Cache::get('key');
});
我們可以這樣編寫測試來驗證 Cache::get 方法以我們期望的方式被呼叫:
use Illuminate\Support\Facades\Cache;
/**
* 一個基礎功能的測試用例。
*
* @return void
*/
public function testBasicExample()
{
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$this->visit('/cache')
->see('value');
}
Facades Vs. 輔助函式
除了 Facades, Laravel 還包含各種「輔助函式」來實現一些常用的功能,比如生成檢視、觸發事件、排程任務或者傳送 HTTP 響應。
許多輔助函式的功能都有與之對應的 Facade。例如,下面這個 Facade 的呼叫和輔助函式的作用是一樣的:
return View::make('profile');
return view('profile');
這裡的 Facades 和輔助函式之間沒有實際的區別。當你使用輔助函式時,你可以使用對應的 Facade 進行測試。例如,下面的路由:
Route::get('/cache', function () {
return cache('key');
});
在底層,輔助函式 cache 實際上是呼叫了 Cache facade 中的 get 方法。
因此,儘管我們使用的是輔助函式,我們依然可以編寫以下測試來驗證該方法是否使用我們預期的引數來呼叫:
use Illuminate\Support\Facades\Cache;
/**
* 一個基礎功能的測試用例。
*
* @return void
*/
public function testBasicExample()
{
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$this->visit('/cache')
->see('value');
}
Facades 工作原理
在 Laravel 應用中,門面就是一個為容器中的物件提供訪問方式的類。該機制的原理由 Facade 類實現。
不管是 Laravel 自帶的 Facades,還是使用者自定義的 Facades ,都繼承自 Illuminate\Support\Facades\Facade 類。
門面類只需要實現一個方法:getFacadeAccessor。正是 getFacadeAccessor 方法定義了從容器中解析什麼,然後 Facade 基類使用魔術方法 __callStatic() 從你的門面中呼叫解析物件。
在下面的例子中,呼叫了 Laravel 的快取系統。通過瀏覽這段程式碼,可以假定在 Cache 類中呼叫了靜態方法 get:
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Cache;
use App\Http\Controllers\Controller;
class UserController extends Controller
{
/**
* 顯示給定使用者的資訊。
*
* @param int $id
* @return Response
*/
public function showProfile($id)
{
$user = Cache::get('user:'.$id);
return view('profile', ['user' => $user]);
}
}
在上面這段程式碼中,我們「匯入」了 Cache Facade 。這個 Facade 作為訪問 Illuminate\Contracts\Cache\Factory 介面底層實現的代理。我們使用 Facade 進行的任何呼叫都將傳遞給 Laravel 快取服務的底層例項。
如果我們看一下 Illuminate\Support\Facades\Cache 這個類,你會發現類中根本沒有 get 這個靜態方法:
class Cache extends Facade
{
/**
* 獲取元件的註冊名稱。
*
* @return string
*/
protected static function getFacadeAccessor() { return 'cache'; }
}
Cache Facade 繼承了 Facade 的基類,並定義了 getFacadeAccessor() 方法。這個方法的作用是返回服務容器繫結的類的名稱。
當用戶呼叫 Cache Facade 中的任何靜態方法時, Laravel 會從 服務容器 中解析 cache 繫結,然後在解析出的物件上呼叫所有的請求方法(本例中是 get)。
實時門面
使用實時門面,可以將應用中的任意類當做門面來使用。
為了說明如何使用這個功能,我們先看一個替代方案。例如我們假設 Podcast 模型有一個 publish 方法,儘管如此,為了釋出部落格,我們需要注入 Publisher 例項:
<?php
namespace App;
use App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* Publish the podcast.
*
* @param Publisher $publisher
* @return void
*/
public function publish(Publisher $publisher)
{
$this->update(['publishing' => now()]);
$publisher->publish($this);
}
}
因為可以模擬注入的釋出服務,所以注入釋出例項到該方法後允許我們輕鬆在隔離狀態下測試該方法。不過,這要求我們每次呼叫 publish 方法時,都要傳遞一個釋出服務例項。
使用實時門面,我們可以在維持這種易於測試的前提下不必顯式傳遞 Publisher 例項。要生成一個實時門面,在匯入類前面加上 Facades 名稱空間字首即可:
<?php
namespace App;
use Facades\App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* Publish the podcast.
*
* @return void
*/
public function publish()
{
$this->update(['publishing' => now()]);
Publisher::publish($this);
}
}
使用實時門面後,釋出服務例項將會通過使用 Facades 字首後的介面或類名在服務容器中解析。
在測試的時候,我們可以使用 Laravel 自帶的門面測試輔助函式來模擬這個方法呼叫。
<?php
namespace Tests\Feature;
use App\Podcast;
use Tests\TestCase;
use Facades\App\Contracts\Publisher;
use Illuminate\Foundation\Testing\RefreshDatabase;
class PodcastTest extends TestCase
{
use RefreshDatabase;
/**
* A test example.
*
* @return void
*/
public function test_podcast_can_be_published()
{
$podcast = factory(Podcast::class)->create();
Publisher::shouldReceive('publish')->once()->with($podcast);
$podcast->publish();
}
}
Facade 類參考
下面列出了每個門面及其對應的底層類。
這是一個查詢給定 Facade 類 API 文件的工具。服務容器繫結的可用鍵值也包含在內。
Facade | 類 | 服務容器繫結 |
---|---|---|
App | Illuminate\Foundation\Application | app |
Artisan | Illuminate\Contracts\Console\Kernel | artisan |
Auth | Illuminate\Auth\AuthManager | auth |
Blade | Illuminate\View\Compilers\BladeCompiler | blade.compiler |
Bus | Illuminate\Contracts\Bus\Dispatcher | |
Cache | Illuminate\Cache\Repository | cache |
Config | Illuminate\Config\Repository | config |
Cookie | Illuminate\Cookie\CookieJar | cookie |
Crypt | Illuminate\Encryption\Encrypter | encrypter |
DB | Illuminate\Database\DatabaseManager | db |
DB (Instance) | Illuminate\Database\Connection | |
Event | Illuminate\Events\Dispatcher | events |
File | Illuminate\Filesystem\Filesystem | files |
Gate | Illuminate\Contracts\Auth\Access\Gate | |
Hash | Illuminate\Contracts\Hashing\Hasher | hash |
Lang | Illuminate\Translation\Translator | translator |
Log | Illuminate\Log\Writer | log |
Illuminate\Mail\Mailer | mailer | |
Notification | Illuminate\Notifications\ChannelManager | |
Password | Illuminate\Auth\Passwords\PasswordBrokerManager | auth.password |
Queue | Illuminate\Queue\QueueManager | queue |
Queue (Instance) | Illuminate\Contracts\Queue\Queue | queue |
Queue (Base Class) | Illuminate\Queue\Queue | |
Redirect | Illuminate\Routing\Redirector | redirect |
Redis | Illuminate\Redis\Database | redis |
Request | Illuminate\Http\Request | request |
Response | Illuminate\Contracts\Routing\ResponseFactory | |
Route | Illuminate\Routing\Router | router |
Schema | Illuminate\Database\Schema\Blueprint | |
Session | Illuminate\Session\SessionManager | session |
Session (Instance) | Illuminate\Session\Store | |
Storage | Illuminate\Contracts\Filesystem\Factory | filesystem |
URL | Illuminate\Routing\UrlGenerator | url |
Validator | Illuminate\Validation\Factory | validator |
Validator (Instance) | Illuminate\Validation\Validator | |
View | Illuminate\View\Factory | view |
View (Instance) | Illuminate\View\View |