如何使用 Laravel Facades ?

Facade 布局是在面向?qū)ο缶幊讨薪?jīng)常使用的一種軟件設(shè)計(jì)布局方式。Facade 實(shí)際上是一種包括復(fù)雜函數(shù)庫(kù)的類,提供了更加簡(jiǎn)潔易讀的接口。Facade 布局還能為一組結(jié)構(gòu)復(fù)雜、設(shè)計(jì)簡(jiǎn)陋的 API 提供統(tǒng)一、設(shè)計(jì)周到的 API。

如何使用 Laravel Facades ?

Laravel 框架與該布局的特點(diǎn)相似,也稱為 Facades。在本教程中,我們會(huì)學(xué)習(xí)如何在其他框架應(yīng)用 Laravel 的 “Facades”。在繼續(xù)學(xué)習(xí)之前,讓我們簡(jiǎn)單了解一下 Ioc 容器

首先,我們了解 Laravel 的 facades 內(nèi)部工作結(jié)構(gòu)。之后再討論如何將之改造并用于其他環(huán)境。

Laravel 中的 Facades

Laravel facade 是一種為容器內(nèi)部服務(wù)提供類似靜態(tài)接口的類。據(jù)其文檔描述,F(xiàn)acades 是可觸及容器服務(wù)底層實(shí)現(xiàn)方式的代理。

不過,在 PHP 社區(qū),有關(guān)其名稱的爭(zhēng)論一直不斷。一些人堅(jiān)持修改此名稱以避免開發(fā)者的困惑,因?yàn)槠洳⑽赐耆珜?shí)現(xiàn) Facade 布局。如果你也受此名稱困擾,大可以為其取個(gè)別名。但是,請(qǐng)注意,下文將會(huì)用到的 Laravel 框架基類(base class)將會(huì)稱為 Facade。

How Facades Are implemented in Laravel

Facades 在 Laravel 中如何實(shí)現(xiàn)

你可能也知道,容器內(nèi)的每個(gè)服務(wù)都有個(gè)唯一名稱。在 laravel 應(yīng)用中,可使用 App::make() 方法或 app() 輔助函數(shù)從容器中直接獲取服務(wù)。

<?php

App::make('some_service')->methodName();

前面已經(jīng)提過,Laravel 使用 facade 類的好處是讓開發(fā)者使用服務(wù)時(shí)更加便捷。使用 facade 類之后,下面的代碼就能達(dá)到相同的效果:

// ...
someService::methodName();
// ...

在 Laravel 中,所有服務(wù)都包含一個(gè) facade 類。這些 facade 類繼承自 Illuminate/Support 包中的 Facade 基類。它們只需實(shí)現(xiàn) getFacadeAccessor 方法即可,后者會(huì)返回容器內(nèi)的服務(wù)名。

在上面的示例中,someService 代表 facade 類。methodName 其實(shí)是容器內(nèi)原服務(wù)的一個(gè)方法。如果跳出 Laravel 的語(yǔ)境查看上面的示例,則表示一個(gè)名為 someService 的類引出名為 methodName() 的靜態(tài)方法。但 Laravel 并不是這樣實(shí)現(xiàn)接口的。在下一節(jié),我們將介紹 Laravel 的 Facade 基類在幕后的運(yùn)作方式。

Base Facade

Facade 類包含一個(gè)名為 $app 的私有屬性,其值為服務(wù)容器的引用。如果要在 Laravel 之外使用 facades,必須使容器明確使用 setFacadeApplication() 方法。

在 facade 基類內(nèi)部,__callStatic 魔術(shù)方法用于處理實(shí)際并不存在的靜態(tài)方法的調(diào)用。如果調(diào)用 Laravel facade 類的靜態(tài)方法, __callStatic 方法便會(huì)激活,因?yàn)?facade 類并未實(shí)現(xiàn)該方法。因此,__callStatic 會(huì)從容器獲取各自的服務(wù),進(jìn)而調(diào)用之。

以下是 facade 基類中 __callStatic 方法的實(shí)現(xiàn)方式:

<?php
// ...
/**
     * Handle dynamic, static calls to the object.
     *
     * @param  string  $method
     * @param  array   $args
     * @return mixed
     */
    public static function __callStatic($method, $args)
    {
        $instance = static::getFacadeRoot();

        switch (count($args)) {
            case 0:
                return $instance->$method();

            case 1:
                return $instance->$method($args[0]);

            case 2:
                return $instance->$method($args[0], $args[1]);

            case 3:
                return $instance->$method($args[0], $args[1], $args[2]);

            case 4:
                return $instance->$method($args[0], $args[1], $args[2], $args[3]);

            default:
                return call_user_func_array([$instance, $method], $args);
        }
    }

在上面的方法中,getFacadeRoot() 會(huì)從容器獲取服務(wù)。

Facade 類解析

每個(gè) facade 類均繼承自基類。我們只需實(shí)現(xiàn) getFacadeAccessor() 方法,該方法用于返回容器中的服務(wù)名。

<?php namespace App\Facades;

use Illuminate\Support\Facades\Facade as BaseFacade;

class SomeServiceFacade extends BaseFacade {

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

}

別名

由于 Laravel facades 是 PHP 類,在使用之前我們得導(dǎo)入它們。PHP 支持命名空間與自動(dòng)導(dǎo)入,因此只要調(diào)用全限定名,即可自動(dòng)載入這些類。PHP 還支持使用 use 指令給類取別名:

use App\Facades\SomeServiceFacade

SomeServiceFacade:SomeMethod();

然而,在需要某個(gè)特定的 facade 類時(shí),我們必須在每個(gè)腳本文件都寫一遍上面的代碼。Laravel 在處理 facade 別名時(shí)有其獨(dú)特的方法——?jiǎng)e名載入器(alias loader)。

Laravel 如何給 Facades 加別名

所有的別名都保存在 app.php 配置文件的 aliases 數(shù)組中,該文件保存在 /config 目錄下。

查看該數(shù)組,會(huì)發(fā)現(xiàn)每個(gè)別名都與一個(gè)全限定類名對(duì)應(yīng)。這意味著我們可以給 facade 類選定任意的名字。

// ..
'aliases' => [
    // ...
    'FancyName' => 'App\Facades\SomeServiceFacade',
],

現(xiàn)在,讓我們看看 Laravel 如何使用該數(shù)組給 facade 類取別名。在引導(dǎo)階段,Laravel 會(huì)使用來自 Illuminate\Foundation 包的 AliasLoader 服務(wù)。AliasLoader 以該別名數(shù)組為參數(shù),遍歷其所有元素,使用 PHP 的 spl_autoload_register 創(chuàng)建一個(gè) __autoload 函數(shù)隊(duì)列。各個(gè) __autoload 函數(shù)會(huì)用 PHP 的 class_alias 函數(shù)為各個(gè) facade 類創(chuàng)建別名。

因此,我們無需像使用 use 指令時(shí)那樣在使用類前導(dǎo)入之并為其創(chuàng)建別名。當(dāng)我們?cè)噲D使用一個(gè)不存在的類時(shí),PHP 會(huì)檢查 __autoload 隊(duì)列以得到合適的 autoloader。這時(shí),AliasLoader 已經(jīng)記下所有的 __autoload 函數(shù)。各個(gè) autoloader 會(huì)選定一個(gè)類名并根據(jù)別名數(shù)組推導(dǎo)出對(duì)應(yīng)的初始類名。最后,它會(huì)為其創(chuàng)建別名。請(qǐng)參考下面的方法調(diào)用:

<?php

// FancyName is resolved to App\Facades\SomeServiceFacade according to the aliases array

FancyName::someMethod()

在幕后,FancyName 會(huì)對(duì)應(yīng)至 App\Facades\SomeServiceFacade

在其他框架使用 Facades

現(xiàn)在,我們已經(jīng)了解 Laravel 如何處理 facades 與別名,我們可以將 Laravel 的 facade 方法運(yùn)用到其他環(huán)境中。接下來,我們會(huì)在 Silex 框架使用 facades。然而,只要遵循同樣的理念,你也可以將之用在別的框架。

Silex 擁有繼承自 Pimple 的容器。使用 $app 對(duì)象即可調(diào)用容器內(nèi)的服務(wù):

<?php
$app['some.service']->someMethod()

有了 facade 類,我們可以為 Silex 服務(wù)提供一個(gè)類似靜態(tài)的接口。此外,我們也可以使用 AliasLoader 服務(wù)為這些 facades 創(chuàng)建有意義的別名。因此,我們可以重組上面的代碼:

<?php
SomeService::someMethod();

必備條件

為了使用 facade 基類,我們要使用 composer 指令安裝 Illuminate\Support 包:

composer require illuminate\support

此包還包含其他服務(wù)。但目前我們只需要 facade 基類。

創(chuàng)建 Facades

只需繼承 Facade 基類并實(shí)現(xiàn) getFacadeAccessor 方法,即可為服務(wù)創(chuàng)建 facade。

在本文中,所有 facades 都會(huì)保存在 src/Facades 路徑下。例如:名為 some.service 的服務(wù),其 facade 類如下:

<?php
namespace App\Facades

use Illuminate\Support\Facades\Facade;

class SomeServiceFacade extends Facade {

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

}

請(qǐng)注意,此類位于 app\facades 命名空間下。

現(xiàn)在只剩下設(shè)定 facade 類的應(yīng)用容器。如前所述,在靜態(tài)語(yǔ)境下調(diào)用 facade 類的方法,會(huì)觸發(fā) __callStatic 方法。該方法會(huì)用 getFacadeAccessor() 返回的數(shù)據(jù)識(shí)別容器內(nèi)的服務(wù),并試圖獲取之。在 Laravel 之外使用 facade 基類時(shí),容器對(duì)象并不是自動(dòng)設(shè)定的,需要手動(dòng)設(shè)定。

為此,使用 facade 基類的 setFacadeApplication 方法,可以設(shè)定 facade 類的應(yīng)用容器。

app.php 文件,添加以下代碼:

<?php
Illumiante\Support\Facade::setFacadeApplication($app);

這會(huì)給繼承自 facade 基類的所有 facades 設(shè)定容器。

現(xiàn)在,無需直接從容器獲取服務(wù),我們可以使用剛剛創(chuàng)建的 facade 類來獲取,該類還允許我們調(diào)用靜態(tài)語(yǔ)境下的所有方法。

實(shí)現(xiàn)別名

為了給 facade 類創(chuàng)建別名,我們將使用之前介紹過的 AliasLoaderAliasLoader 類由 illuminate\foundation 包提供,可以下載整個(gè)包,也可以拷貝部分代碼保持為文件。

如果你想拷貝源文件,建議將其保存在 src/Facades 目錄下。你可以根據(jù)項(xiàng)目的結(jié)構(gòu)為 AliasLoader 類創(chuàng)建命名空間。

在本例中,我們將拷貝代碼并將其保存在 app/facades 命名空間下。

創(chuàng)建別名數(shù)組

config 目錄下創(chuàng)建 aliases.php 文件,并填入 alias-facade 綁定:

<?php
return [
    'FancyName' => 'App\Facades\SomeService',
];

FancyName 是我們給 App\Facades\SomeService 建立的別名。

注冊(cè)別名

AliasLoader 是一種單例服務(wù)。要?jiǎng)?chuàng)建或得到別名載入器(alias loader)的實(shí)例,需調(diào)用 getInstance 方法并以別名數(shù)組為參數(shù)。最后,為了注冊(cè)這些別名,需調(diào)用其 register 方法。

再次打開 app.php 文件,加入以下代碼:

<?php

// ...

$aliases = require __DIR__ . '/../../config/aliases.php';
App\Facades\AliasLoader::getInstance($aliases)->register();

現(xiàn)在,大功告成了!我們可以這樣使用該服務(wù):

<?php
FancyName::methodName();

進(jìn)行包裝

一個(gè) Facade 類只需實(shí)現(xiàn) getFacadeAccessor 方法即可,后者會(huì)返回容器內(nèi)的服務(wù)名。若要在 Laravel 環(huán)境外使用 facade,必須使用 setFacadeApplication() 方法明確設(shè)定服務(wù)容器。

要引用 facade 類,我們可以使用全限定類名或使用 PHP 的 use 指令導(dǎo)入之。或者,遵循 Laravel 給 facades 創(chuàng)建別名的方法,使用 alias loader。

原文鏈接:http://www.sitepoint.com/how-laravel-facades-work-and-how-to-use-them-elsewhere/ (作者:Reza Lavaryan)本文系 OneAPM 工程師編譯整理。

OneAPM for PHP 能夠深入到所有 PHP 應(yīng)用內(nèi)部完成應(yīng)用性能管理 能夠深入到所有 PHP 應(yīng)用內(nèi)部完成應(yīng)用性能管理和監(jiān)控,包括代碼級(jí)別性能問題的可見性、性能瓶頸的快速識(shí)別與追溯、真實(shí)用戶體驗(yàn)監(jiān)控、服務(wù)器監(jiān)控和端到端的應(yīng)用性能管理。想閱讀更多技術(shù)文章,請(qǐng)?jiān)L問 OneAPM 官方技術(shù)博客

本文轉(zhuǎn)自 OneAPM 官方博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容