【Laravel官方導讀】Facades全攻略

【Laravel官方導讀】Facades全攻略

翻遍 Laravel 的官方文件,你將會看到很多範例碼會透過 "facades" 來使用 Laravel 的功能。

Facades 為應用的服務容器提供了一個 『靜態』 介面,對應很多好用的 Laravel 類別。Laravel 自帶了很多 Facades,可以取用其絕大部分功能。

Laravel Facades 實際是服務容器中底層類別的 『靜態代理』 ,相對於傳統靜態方法,在使用時能夠提供更加靈活、更加易於測試、更加優雅的語法。就算你一點都不了解 Facades 神秘面紗底下的面容,你還是可以在學習Laravel的過程中輕鬆地使用它

所有的 Laravel Facades 都定義在 Illuminate\Support\Facades 命名空間底下。所以,我們可以輕鬆的像這樣去使用 Facade :

//routes\web.php

use Illuminate\Support\Facades\Cache;

Route::get('/cache', function () {
    //從快取中抓取資料
    return Cache::get('key');
});

幫助函式

不想使用 Facades的話,Laravel 也提供了很多的全域幫助函式,讓你取用常用的 Laravel 功能變得更為簡單。有些常用的幫助函式讓你取用視圖. 回應. 網址. 設定以及更多。每一個 Laravel 所提供的幫助函式都被整理在該功能章節裡頭;不過有一個完整的列表能夠在幫助函式 Helpers 章節中找到

舉例來說,除了使用 Illuminate\Support\Facades\Response 這個 Facade 來生成JSON回應-也可以使用 response()

因為幫助函式是全域的關係,你不需要導入任何類別就能夠使用它們,下面程式碼的兩段程式目的相同

\\routes\web.php

use Illuminate\Support\Facades\Response;

Route::get('/users', function () {
    return Response::json([
        // ...
    ]);
});

Route::get('/users', function () {
    return response()->json([
        // ...
    ]);
});

何時該使用 Facades

Facades 有很多的優點。它提供了簡單又易記的語法,從而無需手動注入或設定長長的類別名。除此之外,由於它們對 PHP 靜態方法的獨特調用,使得測試起來也很簡單

然而,在使用 Facades 時,有些地方需要特別注意。使用 Facades 時最主要的危險就是會引起類別作用域的膨脹。由於 Facades 使用起來非常簡單並且不需要注入,就會使得我們不經意間在單個類別中使用許多 Facades ,從而導致類別變得越來越大。然而使用依賴注入的時候,使用的類別越多,建構子就會越長,在視覺上就會因為看起來很複雜而造成困擾。因此在使用 Facades 的時候,要特別注意控制類別的大小,如果類別過大的話建議將之分割為多個小類別

技巧:

在開發與 Laravel 進行交互的第三方套件時,最好選擇注入 Laravel Contract 而非 Facades 。因為套件是在 Laravel 之外架構的,所以你無法使用 Laravel Facades 來測試你的功能

Facades 相較於依賴注入

依賴注入的主要好處之一是能替換實作時所使用的注入類別。這在測試時非常有用,因為你可以注入一個 mock 或者 stub,並測試眾多於 stub 上被呼叫的方法

mock 和 stub 我用的還不多,理解不夠深,拿不出太多的例子做說明

通常,真正的靜態方法是不可能 mock 或 stub 的。但是 Facades 使用動態方法對服務容器中解析出來的對象方法的呼叫進行了代理,我們也可以像測試注入類別實例一樣測試 Facades。比如,像下面的路由:

//routes\web.php

use Illuminate\Support\Facades\Cache;

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

使用 Laravel Facade 的測試方法,我們可以帶上期望的參數來編寫下面的測試代碼來驗證 Cache::get()


use Illuminate\Support\Facades\Cache;

/**
 * 一個基本的測試範例.
 *
 * @return void
 */
public function testBasicExample()
{
    Cache::shouldReceive('get')
         ->with('key')
         ->andReturn('value');

    $response = $this->get('/cache');

    $response->assertSee('value');
}

Facades 相較於輔助函數

除了 Facades,Laravel 還包含各種 『幫助函數』 來實現一些常用功能,比如生成視圖、觸發事件、任務調度或者發送 HTTP 回應。許多幫助函數都有與之對應的 Facades 。例如,下面這個 Facades 和幫助函式的作用是一樣的:

return Illuminate\Support\Facades\View::make('profile');

return view('profile');

感想:

掌握幫助函式和對應的Facades用法,可以幫助你更好的去理解別的套件所撰寫的程式碼內容

Facade 和幫助函式之間沒有實際的區別。當你使用幫助函式時,你也可以像測試相應的 Facade 那樣進行測試。例如下面的路由程式:

//routes\web.php

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');

    $response = $this->get('/cache');

    $response->assertSee('value');
}

上面這段程式碼取自於Laravel官方文件,但並沒有使用幫助函式,我猜是文章撰寫錯誤,作參考就好

Facades 是如何運作的?

在 Laravel 應用中,Facade 就是一個可以從服務容器中取用對象的類別,其中核心的元件就是 Facade 類別。不管是 Laravel 自帶的 Facades,還是自定義的 Facades,都繼承自 Illuminate\Support\Facades\Facade 類別。

Facade 父類別使用了__callStatic() 魔術方法,直到對象從容器中被解析出來後,才會進行呼叫。在下面的例子中,呼叫了 Laravel 的快取系統。

通過瀏覽這段程式碼,可以看到在 Cache 類別中呼叫了靜態方法 get()

\\App\Http\Controllers\UserController.php

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Cache;

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 快取服務的底層實作。

說明

是否看的雲裡霧裡的,這就是一種抽象的境界,能夠把現狀精確地說明出來,卻不容易幫助理解。我就打個比方來說,A公司需要有人協助後台程式的開發,需要有具備Laravel知識的人協助。因此具備Laravel知識就是一個『介面』,為了方便稱呼這個能夠協助A公司的人,稱之為"Laraveler們",就是一個『代理』。最終A公司找到一個人具備了Laravel知識來協助他們,這個人就是『底層實作』

如果你去看一下 Illuminate\Support\Facades\Cache 這個類別,你會發現類別中根本沒有 get 這個靜態方法,莫非是靈異現象?

//Illuminate\Support\Facades\Cache.php

class Cache extends Facade
{
    /**
     * 獲取元件的註冊名稱.
     *
     * @return string
     */
    protected static function getFacadeAccessor() { return 'cache'; }
}

真相是,Cache Facade 繼承了 Facade 父類別,並且定義了 getFacadeAccessor() 方法

這個方法的作用是返回服務容器綁定的名稱。當呼叫 Cache Facade 中的任何靜態方法時,Laravel 就會從服務容器中去解析 cache 綁定以及該對象被請求的方法(在這個例子中就是 get 方法)

即時 Facades

使用即時 Facades,你可以將應用中的任何類別都視為 Facade。為了更好說明這能如何使用,我們來看看另一些沒有使用即時 Facades 的例子。例如假設我們的 Podcast 模型有一個 publish 方法,為了發布 Podcast,我們就需要注入一個 Publisher 實例:

//app\Models\Podcast.php

namespace App\Models;

use App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;

class Podcast extends Model
{
    /**
     * 出版 Podcast.
     *
     * @param  Publisher  $publisher
     * @return void
     */
    public function publish(Publisher $publisher)
    {
        $this->update(['publishing' => now()]);

        $publisher->publish($this);
    }
}

將一個發布者的實作注入到該方法中允許我們可以輕鬆地去測試這種方法,因為我們可以模擬各種注入的發布者。但是,它要求我們每次呼叫 publish 方法時都要傳遞一個發布者實例。使用即時的 Facades,我們可以保持同樣的可測試性,而不需要直接傳入 Publisher 實例

要生成即時 Facade,請在導入類別的名稱空間的最前面加上 Facades,如下例:

//app\Models\Podcast.php

namespace App\Models;

use Facades\App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;

class Podcast extends Model
{
    /**
     * 出版 Podcast.
     *
     * @return void
     */
    public function publish()
    {
        $this->update(['publishing' => now()]);

        Publisher::publish($this);
    }
}

當使用即時 Facade 時,發布者實作將通過使用 Facades 前綴之後出現的介面或類別名的部分來透過服務容器進行解析。在測試時,我們可以使用 Laravel 的內置 facade 測試幫助函式來模擬這種方法呼叫

\\Tests\Feature\PodcastTest.php

<?php

namespace Tests\Feature;

use App\Models\Podcast;
use Facades\App\Contracts\Publisher;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class PodcastTest extends TestCase
{
    use RefreshDatabase;

    /**
     * A test example.
     *
     * @return void
     */
    public function test_podcast_can_be_published()
    {
        $podcast = Podcast::factory()->create();

        Publisher::shouldReceive('publish')->once()->with($podcast);

        $podcast->publish();
    }
}

Facade 類別參考

在下面你可以找到每個 Facade 類別及其對應的底層類別。這是一個幫助你查找特定 Facade 類別 API 文件的工具

此外,服務容器綁定的鍵 key 也包含在內

Facade 類別 服務容器綁定
App Illuminate\Foundation\Application app
Artisan Illuminate\Contracts\Console\Kernel artisan
Auth Illuminate\Auth\AuthManager auth
Auth (Instance) Illuminate\Contracts\Auth\Guard auth.driver
Blade Illuminate\View\Compilers\BladeCompiler blade.compiler
Broadcast Illuminate\Contracts\Broadcasting\Factory
Broadcast (Instance) Illuminate\Contracts\Broadcasting\Broadcaster
Bus Illuminate\Contracts\Bus\Dispatcher
Cache Illuminate\Cache\CacheManager cache
Cache (Instance) Illuminate\Cache\Repository cache.store
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 db.connection
Event Illuminate\Events\Dispatcher events
File Illuminate\Filesystem\Filesystem files
Gate Illuminate\Contracts\Auth\Access\Gate
Hash Illuminate\Contracts\Hashing\Hasher hash
Http Illuminate\Http\Client\Factory
Lang Illuminate\Translation\Translator translator
Log Illuminate\Log\LogManager log
Mail Illuminate\Mail\Mailer mailer
Notification Illuminate\Notifications\ChannelManager
Password Illuminate\Auth\Passwords\PasswordBrokerManager auth.password
Password (Instance) Illuminate\Auth\Passwords\PasswordBroker auth.password.broker
Queue Illuminate\Queue\QueueManager queue
Queue (Instance) Illuminate\Contracts\Queue\Queue queue.connection
Queue (Base Class) Illuminate\Queue\Queue
Redirect Illuminate\Routing\Redirector redirect
Redis Illuminate\Redis\RedisManager redis
Redis (Instance) Illuminate\Redis\Connections\Connection redis.connection
Request Illuminate\Http\Request request
Response Illuminate\Contracts\Routing\ResponseFactory
Response (Instance) Illuminate\Http\Response
Route Illuminate\Routing\Router router
Schema Illuminate\Database\Schema\Builder
Session Illuminate\Session\SessionManager session
Session (Instance) Illuminate\Session\Store session.store
Storage Illuminate\Filesystem\FilesystemManager filesystem
Storage (Instance) Illuminate\Contracts\Filesystem\Filesystem filesystem.disk
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

分享這篇文章:

關聯文章:

訂閱電子報,索取 Laravel 學習手冊

價值超過 3000 元,包含常用 Laravel 語法與指令!

Laravel 百萬年薪特訓營

從最基礎的 PHP 語法開始,包含所有你該知道的網頁基礎知識,連同 Laravel 從零開始一直到實戰,最後還將告訴你如何找好工作,讓你及早擁有百萬年薪