Laravel深入學習7 – 框架的擴充套件

laravel發表於2019-02-16

宣告:本文並非博主原創,而是來自對《Laravel 4 From Apprentice to Artisan》閱讀的翻譯和理解,當然也不是原汁原味的翻譯,能保證90%的原汁性,另外因為是理解翻譯,肯定會有錯誤的地方,歡迎指正。

歡迎轉載,轉載請註明出處,謝謝!

框架的擴充套件

介紹

Laravel為我們提供了很多自定義系統元件的擴充套件點,你甚至可以完全的替換掉他們。比如,雜湊結構是由HasherInterface介面約定的,你可以根據你的應用來實現自己的需求。你也可以擴充套件自己的Request物件,並新增自己的“輔助”方法。甚至我們完全新增自己的認證、快取、SESSION驅動。

Laravel元件擴充套件通常有兩種方法:向IoC容器中繫結自己的介面實現;痛過使用“工廠模式”實現的Manager類註冊自己的擴充套件。本章將探索多種擴充套件框架的方法,以及必要的測試程式碼。

擴充套件方式

牢記Laravel元件擴充套件的兩種方式:IoC容器繫結和使用Manager類註冊。類庫管理類以工廠模式實現,負責諸如快取、session等驅動的例項化。

類庫管理類和工廠方法

Laravel有諸多Manager類庫來管理建立那些基於驅動的元件。它包括快取、session、認證、佇列元件。管理類的職責是根據應用的配置來建立特殊的驅動例項。例如,CacheManager可以建立APC、Memcached、Native、以及其他快取驅動系統的實現。

所有這些管理器都有個extend方法,可以輕易的將新的驅動功能注入到其中。對於上面所有的管理器,我們通過例項來演示如何注入我們自定義的那些服務驅動。

學習我們自己的管理器

請花一些事件來熟悉下Laravel提供的Manager類庫,如CacheManagerSessionManager。通讀這些程式碼可以讓你加深理解。所有的管理器類都繼承自IlluminateSupportManager基類,他為每個具體的管理器提供了很多常見使用的方法。

快取 Cache

我們通過在CacheManager類中的extend方法繫結自定義的驅動解析器來擴充套件Laravel的快取機制。比如,註冊一個“mongo”驅動到快取驅動中,程式碼如下:

Cache::extend(`mongo`, function($app)
{
    // Return IlluminateCacheRepository instance...
});

傳入method方法的第一個引數是驅動名稱。通常和app/config/app.php配置檔案中的driver選項匹配。第二個引數是返回IlluminateCacheRepository例項的閉包。閉包須要傳入繼承自IlluminateFoundationApplication和IoC容器的例項化物件$app

對於我們自定義的快取驅動,要實現IlluminateCacheStoreInterface介面的約定。因此,我們的MongoDB快取實現程式碼為這樣:

class MongoStore implements IlluminateCacheStoreInterface {

    public function get($key) {}
    public function put($key, $value, $minutes) {}
    public function increment($key, $value = 1) {}
    public function decrement($key, $value = 1) {}
    public function forever($key, $value) {}
    public function forget($key) {}
    public function flush() {}

}

我們只需要使用MongoDB連線來實現上述類中的各個方法即可。當完成上述實現,就可完成我們自定義的驅動註冊:

use IlluminateCacheRepository;

Cache::extend(`mongo`, function($app)
{
    return new Repository(new MongoStore);
}

如上可見,我們可使用IlluminateCacheRepository來直接建立我們自定義的快取驅動,而無需建立自己的(Repository)倉庫類。

如果不知道該把程式碼放在哪裡,可以考慮放到Packagist!或者在應用主目錄下建立一個Extensions名稱空間的目錄存放。例如你的應用起名叫個Snappy,可以把這個擴充套件放到app/Snappy/Extensions/MongoStore.php。Laravel建立的應用沒有死板的各種結構的要求,根據你的喜好自己來組織就好。

何處引入擴充套件

如果你還在考慮該在何處引入擴充套件,不妨繼續使用服務提供器。我們已經討論過這是組織我們程式碼的一個利器,要善用利器。

Session 會話

擴充套件Session機制其實像擴充套件快取機制一樣簡單。同樣,使用extend方法來註冊我們自己的驅動:

Session::extend(`mongo`, function($app)
{
    // Return implementation of SessionHandlerInterface
});

注意,我們自定義的驅動須要實現SessionHandlerInterface介面。此介面是被包含在PHP5.4+核心中的。如果你在使用PHP5.3,Laravel也是向前相容的,Laravel已經為您定義好此介面。介面中包含了一些我們須要實現的方法。一個基於MongoDB實現的驅動程式碼如下:

class MongoHandler implements SessionHandlerInterface {

    public function open($savePath, $sessionName) {}
    public function close() {}
    public function read($sessionId) {}
    public function write($sessionId, $data) {}
    public function destroy($sessionId) {}
    public function gc($lifetime) {}

}

這些方法不像StoreInterface介面那樣秒懂,讓我們來快速過一遍這些方法:

  • open方法一般是在基於檔案系統的實現中被用到。自從Laravel基於以PHP自身的本地儲存實現了native會話驅動方式,你基本上不需要在本方法中新增任何東西了。你可以把他留空。PHP這種介面設計很顯然是一種不好的設計方式(我們後續討論)。

  • close方法同open方法,通常不用理會。大多數驅動都不需要他。

  • read方法返回根據$sessionId變數關聯session資料的字串。這裡不需要進行序列化或者其他型別的轉義,Laravel已經幫你進行了處理。

  • write方法根據$sessionId關聯session資料將$data資料寫入持久化儲存系統中,如MongoDB、Dynamo等。

  • gc方法將根據$lifetime指定UNIX時間戳銷燬之前所有的session資料。對於可以自動刪除過期資料的系統比如Memcached或者Redis,該方法留空即可。

當實現SessionHandlerInterface之後,我們就可以向Session管理器中註冊他了:

Session::extend(`mongo`, function($app)
{
    return new MongoHandler;
});

當會話驅動註冊之後,在配置檔案app/config/session.php中指定mongo配置就能使用我們自定義的session驅動了。

分享你的成果

記住,如果你實現了自己的session驅動,可以在Packagist上分享給大家!

認證

類似快取和會話擴充套件,我們繼續從method方法開始:

Session::extend(`mongo`, function($app)
{
    return new MongoHandler;
});

介面UserProviderInterface的實現指責是從各種持久化儲存系統如MySQL、Riak等中獲取資料,並返回介面UserInterface實現的物件。這兩個介面可以讓Laravel專注於驗證本身,而無需關心使用者資料的儲存實現,以及使用者物件是那種類來表示的問題。

讓我們先看一下UserProviderInterface介面:

interface UserProviderInterface {

    public function retrieveById($identifier);
    public function retrieveByCredentials(array $credentials);
    public function validateCredentials(UserInterface $user, array $credentials);

}

retrieveById方法通常接收的引數是一個唯一識別符號,例如MySQL資料庫中的主鍵自增索引ID。該方法將獲取$identifier對應的資料,並返回介面UserInterface實現類的例項化物件。

當使用者試圖登入系統時,retrieveByCredentials方法接收引數是一個驗證陣列,同時他也是傳入Auth::attempt的引數。該方法會“查詢”給定的使用者驗證資料,通常,它會根據$credentials[`username`]執行一個“查詢”條件語句來匹配資料。請不要用本方法進行任何密碼的校驗或驗證。

方法validateCredentials會通過對比$user$credentials來驗證使用者。例如,方法中會對$user->getAuthPassword();得到的字串和$credentials[`password`]通過Hash::make加密後的結果進行對比。

上面我們探索了UserProviderInterface中的各個方法,接下來看一看UserInterface介面。記住,上面提供器中的retrieveByIdretrieveByCredentials方法返回的就是本介面實現類的例項化物件:

interface UserInterface {

    public function getAuthIdentifier();
    public function getAuthPassword();

}

介面很簡單。getAuthIdentifier返回使用者的“主鍵索引”。在MySQL後臺儲存系統中,這裡就指代使用者表的主鍵自增索引。getAuthPassword返回使用者密碼的雜湊值。這種介面的實現方式使認證系統和User類完全分離,並能在各種型別的實現下正常工作,而不用關心我們用的是ORM方式還是其他儲存層實現的方式。在app/models下,Laravel已經實現了該介面的User類,你可以參考下這個類。

實現介面UserProviderInterface後,我們就做好了將我們的擴充套件註冊進Auth門面的準備:

Auth::extend(`riak`, function($app)
{
    return new RiakUserProvider($app[`riak.connection`]);
});

method註冊之後,我們就能在app/config/auth.php中切換新的驗證驅動了。

基於IoC容器的擴充套件

幾乎所有Laravel框架包含的服務提供器都是作為物件繫結到IoC容器中的。在配置檔案app/config/app.php中有詳細的列表。有時間的話,最好過一遍原始碼。這樣,你能瞭解框架都載入了哪些服務,也就是容器中繫結的各式的服務。

比如,PaginationServiceProviderpaginator繫結到容器中,他對應的是IlluminatePaginationEnvironment的例項。你可以很容易的通過擴充套件重寫這些類並重新繫結到容器中。又如,我們來擴充套件下基礎的Environment類:

namespace SnappyExtensionsPagination;

class Environment extends IlluminatePaginationEnvironment {

    //

}

擴充套件完成之後,在建立一個新的SnappyPaginationProvider服務提供器,並在boot方法中替換掉原有的paginator:

class SnappyPaginationProvider extends PaginationServiceProvider {

    public function boot()
    {
        App::bind(`paginator`, function()
        {
            return new SnappyExtensionsPaginationEnvironment;
        }

        parent::boot();
    }

}

注意,本類繼承的PaginationServiceProvider,並不是預設的ServiceProvider。當完成擴充套件之後要記住app/config/app.php中替換成自己的副檔名稱。

這就是對容器中已經繫結的核心類庫進行擴充套件的方法。實際上,所有核心類庫都能用這種方式進行重寫。深入閱讀原始碼,能幫你熟練知曉Laravel是怎麼將各個部分組織在一起運轉的。

請求的擴充套件

因為在每個請求的生命週期中,請求是很早就會被例項化的一個最基礎的部分,所以Request的擴充套件會有稍許不同。

首先,還是像往常一樣實現一個子類:

namespace QuickBillExtensions;

class Request extends IlluminateHttpRequest {

    // Custom, helpful methods here...

}

然後,開啟bootstrap/start.php,它是請求到達應用時最早被包含的檔案。注意這裡被執行的第一個動作就是建立Laravel的例項化物件$app

$app = new IlluminateFoundationApplication;

當物件被建立,同時也會建立IlluminateHttpRequest例項,並以request鍵繫結到IoC容器中。這裡,我們應該另闢途徑實現一個類來作為“預設的”請求型別,對不對?還好,requestClass方法就能幫我們那實現它!所以我們需要在bootstrap/start.php檔案開頭加上這樣一行:

use IlluminateFoundationApplication;

Application::requestClass(`QuickBillExtensionsRequest`);

當新增玩自定義的請求類後,Laravel在任何地方都能用到我們的這個自定義的Request例項,這能使我們實現的定義請求類的例項可用,甚至在單元測試中也能使用。

相關文章