Laravel 核心--Facades 門面

介紹

Facades 為應用的 IoC 服務容器 的類提供了一個靜態的接口。Laravel 里面自帶了一些 Facades,如Cache等。Laravel 的門面作為服務容器中底層類的“靜態代理”,相比于傳統靜態方法,在維護時能夠提供更加易于測試、更加靈活、簡明優雅的語法。

解釋

在 Laravel 應用這個上下文里面,一個 Facade 就是一個類,使用這個類可以訪問到來自容器里的一個對象,這個功能就是在 Facade 類里面定義的。Laravel 的 Facades 還有任何你自己定義的 Facades,都會去繼承 Facade 這個類。

你的 Facade 類只需要實施一個的方法:getFacadeAccessor。要在容器里 resolve 什么出來,都是在這個方法里去做的。Facade 這個基類里面使用了__callStatic() 魔術方法,可以延遲到 resolved 對象上的,來自 Facade 的調用。

所以,當你使用 Facade 調用的時候,比如像這樣:Cache:get,laravel 會從 Ioc 服務容器 里面 resolves 緩存管理類,然后再去調用這個類上面的 get 方法。Laravel 的 Facades 可以去定位服務,它是一種使用 Laravel 的 Ioc 服務容器 的更方便的語法。

優點

Facade 有諸多優點,其提供了簡單、易記的語法,讓我們無需記住長長的類名即可使用 Laravel 提供的功能特性,此外,由于他們對 PHP 動態方法的獨到用法,使得它們很容易測試。

實際使用

下面的例子,去調用了一下 Laravel 的緩存系統。先看一下下面這行代碼,你可能會覺得,這是直接去調用 Cache 這個類上面的一個叫 get 的靜態的方法。

$value = Cache::get('key');

不過,如果你查看 Illuminate\Support\Facades\Cache 這個類,你會發現這里根本就沒有 get 這個靜態方法:

class Cache extends Facade {

    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor() { return 'cache'; }

}

Cache 這個類繼承了 Facade 這個基類,它里面定義了一個叫 getFacadeAccessor() 的方法。注意,這個方法的干的事就是去返回一個 Ioc 綁定的名字,這里就是 cache。

當用戶在引用任何在 Cache 這個 Facade 上的靜態方法的時候,Laravel 就會從 Ioc 服務容器 里面去 resolves cache 這個綁定,并且會去執行在對象上的這個所請求的方法(這里就是 get 這個方法)。

所以,我們在調用 Cache::get 的時候,它的真正的意思是這樣的:

$value = $app->make('cache')->get('key');

導入 Facades

注意,在使用 facade 的時候,如果控制器里面用到了命名空間,你需要把 Facade 類導入到這個命名空間里。所有的 Facades 都是在全局命名空間下:

<?php namespace App\Http\Controllers;

use Cache;

class PhotosController extends Controller {

    /**
     * Get all of the application photos.
     *
     * @return Response
     */
    public function index()
    {
        $photos = Cache::get('photos');

        //
    }

}

創建 Facades

創建 Facade 只需要三個東西:

  • 一個 IoC 綁定。
  • 一個 Facade 類。
  • 一個 Facade 別名的配置。

在下面我們定義了一個類:PaymentGateway\Payment

namespace PaymentGateway;

class Payment {

    public function process()
    {
        //
    }

}

我們需要能在 Ioc 服務容器 里面去 resolve 這個類。所以,先要去添加一個 Service Provider 綁定:

App::bind('payment', function()
{
    return new \PaymentGateway\Payment;
});

去注冊這個綁定最好的方法就是去創建一個新的 Service Provider ,把它命名為 PaymentServiceProvider ,然后把它綁定到 register 方法上。再去配置 laravel 在 config/app.php 這個配置文件里加載你的 Service Provider

下一步就是去創建自己的 Facade 類:

use Illuminate\Support\Facades\Facade;

class Payment extends Facade {

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

}

最后,如果你愿意,可以去給 Facade 添加一個別名,放到 config/app.php 配置文件里的 aliases 數組里。

可以去調用 Payment 類的一個實例上的 process 這個方法了。像這樣:

Payment::process();

何時使用 Facade

注意

在使用 Facade 也有需要注意的地方,一個最主要的危險就是類范圍蠕變。由于Facade 如此好用并且不需要注入,在單個類中使用過多Facade,會讓類很容易變得越來越大。使用依賴注入則會讓此類問題緩解,因為一個巨大的構造函數會讓我們很容易判斷出類在變大。因此,使用Facade的時候要尤其注意類的大小,以便控制其有限職責。

注:構建與 Laravel 交互的第三方擴展包時,最好注入 Laravel 契約而不是使用門面,因為擴展包在 Laravel 之外構建,你將不能訪問 Laravel 的門面測試輔助函數。

Facade vs. 依賴注入

依賴注入的最大優點是可以替換注入類的實現,這在測試時很有用,因為你可以注入一個模擬或存根并且在存根上斷言不同的方法。

但是在靜態類方法上進行模擬或存根卻行不通,不過,由于Facade 使用了動態方法對服務容器中解析出來的對象方法調用進行了代理,我們也可以像測試注入類實例那樣測試門面。例如,給定以下路由:

use Illuminate\Support\Facades\Cache;

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

我們可以這樣編寫測試來驗證 Cache::get 方法以我們期望的方式被調用:

use Illuminate\Support\Facades\Cache;

/**
 * A basic functional test example.
 *
 * @return void
 */
public function testBasicExample()
{
    Cache::shouldReceive('get')
        ->with('key')
        ->andReturn('value');

    $this->visit('/cache')
        ->see('value');
}

Facade vs. 輔助函數

除了Facade之外,Laravel 還內置了許多輔助函數用于執行通用任務,比如生成視圖、觸發事件、分配任務,以及發送 HTTP 響應等。很多輔助函數提供了和相應 Facade 一樣的功能,例如,下面這個Facade調用和輔助函數調用是等價的:

return View::make('profile');
return view('profile');

Facade和輔助函數之間并不存在實質性差別,使用輔助函數的時候,可以像測試相應門面那樣測試它們。例如,給定以下路由:

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

在調用底層, cache 方法會去調用 Cache Facade上的 get方法,因此,盡管我們使用這個輔助函數,我們還是可以編寫如下測試來驗證這個方法以我們期望的方式和參數被調用:

use Illuminate\Support\Facades\Cache;

/**
 * A basic functional test example.
 *
 * @return void
 */
public function testBasicExample()
{
    Cache::shouldReceive('get')
        ->with('key')
        ->andReturn('value');

    $this->visit('/cache')
        ->see('value');
}

Facade 工作原理

在 Laravel 應用中,Facade就是一個為容器中對象提供訪問方式的類。該機制原理由 Facade 類實現。Laravel 自帶的 Facade,以及我們創建的自定義門面,都會繼承自 Illuminate\Support\Facades\Facade 基類??梢詤⒖? Facade 實現原理

Facade 類只需要實現一個方法:getFacadeAccessor。正是 getFacadeAccessor方法定義了從容器中解析什么,然后 Facade 基類使用魔術方法 __callStatic() 從你的門面中調用解析對象。

下面的例子中,我們將會調用 Laravel 的緩存系統,瀏覽代碼后,也許你會覺得我們調用了 Cache 的靜態方法 get

<?php

namespace App\Http\Controllers;

use 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。該門面作為代理訪問底層 Illuminate\Contracts\Cache\Factory 接口的實現。我們對門面的所有調用都會被傳遞給 Laravel 緩存服務的底層實例。

如果我們查看 Illuminate\Support\Facades\Cache 類的源碼,將會發現其中并沒有靜態方法 get

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

Cache Facade 繼承 Facade 基類并定了 getFacadeAccessor方法,該方法的工作就是返回服務容器綁定類的別名,當用戶引用 Cache
類的任何靜態方法時,Laravel 從服務容器中解析 cache
綁定,然后在解析出的對象上調用所有請求方法(本例中是 get

門面類列表

下面列出了每個門面及其對應的底層類,這對深入給定根門面的 API 文檔而言是個很有用的工具。服務容器綁定鍵也被包含進來:

門面 Facade class 服務容器綁定
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
Mail 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
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容