宣告:本文並非博主原創,而是來自對《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
類庫,如CacheManager
、SessionManager
。通讀這些程式碼可以讓你加深理解。所有的管理器類都繼承自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
介面。記住,上面提供器中的retrieveById
和retrieveByCredentials
方法返回的就是本介面實現類的例項化物件:
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
中有詳細的列表。有時間的話,最好過一遍原始碼。這樣,你能瞭解框架都載入了哪些服務,也就是容器中繫結的各式的服務。
比如,PaginationServiceProvider
把paginator
繫結到容器中,他對應的是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
例項,這能使我們實現的定義請求類的例項可用,甚至在單元測試中也能使用。