Laravel 有幾個(gè) Manager
類,用來管理創(chuàng)建基于驅(qū)動(dòng)的組件。這些類包括緩存、session 、認(rèn)證和隊(duì)列組件。管理者類負(fù)責(zé)基于應(yīng)用程序的配置建立一個(gè)特定的驅(qū)動(dòng)實(shí)現(xiàn)。例如,CacheManager
類可以建立 APC 、 Memcached 、文件和各種其他的緩存驅(qū)動(dòng)實(shí)現(xiàn)。
這些管理者都擁有 extend
方法,可以簡(jiǎn)單地用它來注入新的驅(qū)動(dòng)解析功能到管理者。我們將會(huì)在下面的例子,隨著講解如何為它們注入自定義驅(qū)動(dòng)支持,涵蓋這些管理者的內(nèi)容。
注意: 建議花點(diǎn)時(shí)間來探索 Laravel 附帶的各種
Manager
類,例如:CacheManager
和SessionManager
??催^這些類將會(huì)讓你更徹底了解 Laravel 表面下是如何運(yùn)作。所有的管理者類繼承Illuminate\Support\Manager
基礎(chǔ)類,它提供一些有用、常見的功能給每一個(gè)管理者。
為了擴(kuò)展 Laravel 緩存功能,我們將會(huì)使用 CacheManager
的 extend
方法,這方法可以用來綁定一個(gè)自定義驅(qū)動(dòng)解析器到管理者,并且是全部的管理者類通用的。例如,注冊(cè)一個(gè)新的緩存驅(qū)動(dòng)名為「mongo」,我們將執(zhí)行以下操作:
Cache::extend('mongo', function($app){ return Cache::repository(new MongoStore);});
傳遞到 extend
方法的第一個(gè)參數(shù)是驅(qū)動(dòng)的名稱。這將會(huì)對(duì)應(yīng)到你的 config/cache.php
配置文件里的 driver
選項(xiàng)。第二個(gè)參數(shù)是個(gè)應(yīng)該返回 Illuminate\Cache\Repository
實(shí)例的閉包。 $app
將會(huì)被傳遞到閉包,它是 Illuminate\Foundation\Application
和服務(wù)容器的實(shí)例。
Cache::extend
的調(diào)用可以在新的 Laravel 應(yīng)用程序默認(rèn)附帶的 App\Providers\AppServiceProvider
的 boot
方法中完成,或者你可以建立自己的服務(wù)提供者來放置這個(gè)擴(kuò)展 - 記得不要忘記在 config/app.php
的提供者數(shù)組注冊(cè)提供者。
要建立自定義緩存驅(qū)動(dòng),首先需要實(shí)現(xiàn) Illuminate\Contracts\Cache\Store
contract 。所以,我們的 MongoDB 緩存實(shí)現(xiàn)將會(huì)看起來像這樣:
class MongoStore implements Illuminate\Contracts\Cache\Store { 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 連接來實(shí)現(xiàn)這些方法。當(dāng)實(shí)現(xiàn)完成,就可以完成自定義驅(qū)動(dòng)注冊(cè):
Cache::extend('mongo', function($app){ return Cache::repository(new MongoStore);});
如果你正在考慮要把自定義緩存驅(qū)動(dòng)代碼放在哪里,請(qǐng)考慮把它放上 Packagist !或者,你可以在 app
的目錄中建立 Extensions
命名空間。記得 Laravel 沒有嚴(yán)格的應(yīng)用程序架構(gòu),你可以依照喜好自由的組織應(yīng)用程序。
自定義 session 驅(qū)動(dòng)來擴(kuò)展 Laravel 和擴(kuò)展緩存系統(tǒng)一樣簡(jiǎn)單。我們將會(huì)再一次使用 extend
方法來注冊(cè)自定義代碼:
Session::extend('mongo', function($app){ // Return implementation of SessionHandlerInterface});
你應(yīng)該把 session 擴(kuò)展代碼放置在 AppServiceProvider
的 boot
方法里。
要注意我們的自定義緩存驅(qū)動(dòng)應(yīng)該要實(shí)現(xiàn) SessionHandlerInterface
。這個(gè)接口只包含少數(shù)需要實(shí)現(xiàn)的簡(jiǎn)單方法。一個(gè)基本的 MongoDB 實(shí)現(xiàn)會(huì)看起來像這樣:
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) {}}
因?yàn)檫@些方法不像緩存的 StoreInterface
一樣容易理解,讓我們快速地看過這些方法做些什么:
open
方法通常會(huì)被用在基于文件的 session 保存系統(tǒng)。因?yàn)?Laravel 附帶一個(gè) file
session 驅(qū)動(dòng),幾乎不需要在這個(gè)方法放任何東西。你可以讓它留空。PHP 要求我們?nèi)?shí)現(xiàn)這個(gè)方法,事實(shí)上明顯是個(gè)差勁的接口設(shè)計(jì) (我們將會(huì)晚點(diǎn)討論它)。
close
方法,就像 open
方法,通常也可以忽略。對(duì)大部份的驅(qū)動(dòng)來說,并不需要它。
read
方法應(yīng)該返回與給定 $sessionId
關(guān)聯(lián)的 session 數(shù)據(jù)的字串形態(tài)。當(dāng)你的驅(qū)動(dòng)取回或保存 session 數(shù)據(jù)時(shí)不需要做任何序列化或進(jìn)行其他編碼,因?yàn)?Laravel 將會(huì)為你進(jìn)行序列化
write
方法應(yīng)該寫入給定 $data
字串與 $sessionId
的關(guān)聯(lián)到一些永久存儲(chǔ)系統(tǒng),例如:MongoDB、 Dynamo、等等。
destroy
方法應(yīng)該從永久存儲(chǔ)移除與 $sessionId
關(guān)聯(lián)的數(shù)據(jù)。
gc
方法應(yīng)該銷毀所有比給定 $lifetime
UNIX 時(shí)間戳記還舊的 session 數(shù)據(jù)。對(duì)于會(huì)自己過期的系統(tǒng)如 Memcached 和 Redis,這個(gè)方法可以留空。
當(dāng) SessionHandlerInterface
實(shí)現(xiàn)完成,我們準(zhǔn)備好要用 Session 管理者注冊(cè)它:
Session::extend('mongo', function($app){ return new MongoHandler;});
當(dāng) session 驅(qū)動(dòng)已經(jīng)被注冊(cè),我們可以在 config/session.php
配置文件使用 mongo
驅(qū)動(dòng)。
注意: 記住,如果你寫了個(gè)自定義 session 處理器,請(qǐng)?jiān)?Packagist 分享它!
認(rèn)證可以用與緩存和 session 功能相同的方法擴(kuò)展。再一次的,使用我們已經(jīng)熟悉的 extend
方法:
Auth::extend('riak', function($app){ // 返回 Illuminate\Contracts\Auth\UserProvider 的實(shí)現(xiàn)});
UserProvider
實(shí)現(xiàn)只負(fù)責(zé)從永久存儲(chǔ)系統(tǒng)抓取 Illuminate\Contracts\Auth\Authenticatable
實(shí)現(xiàn),存儲(chǔ)系統(tǒng)例如: MySQL 、 Riak ,等等。這兩個(gè)接口讓 Laravel 認(rèn)證機(jī)制無論用戶數(shù)據(jù)如何保存或用什么種類的類來代表它都能繼續(xù)運(yùn)作。
讓我們來看一下 UserProvider
contract :
interface UserProvider { public function retrieveById($identifier); public function retrieveByToken($identifier, $token); public function updateRememberToken(Authenticatable $user, $token); public function retrieveByCredentials(array $credentials); public function validateCredentials(Authenticatable $user, array $credentials);}
retrieveById
函數(shù)通常接收一個(gè)代表用戶的數(shù)字鍵,例如:MySQL 數(shù)據(jù)庫(kù)的自動(dòng)遞增 ID。這方法應(yīng)該取得符合 ID 的 Authenticatable
實(shí)現(xiàn)并返回。
retrieveByToken
函數(shù)用用戶唯一的 $identifier
和保存在 remember_token
字段的「記住我」 $token
來取得用戶。跟前面的方法一樣,應(yīng)該返回 Authenticatable
的實(shí)現(xiàn)。
updateRememberToken
方法用新的 $token
更新 $user
的 remember_token
字段。新 token 可以是在「記住我」成功地登錄時(shí),傳入一個(gè)新的 token,或當(dāng)用戶注銷時(shí)傳入一個(gè) null。
retrieveByCredentials
方法接收當(dāng)嘗試登錄應(yīng)用程序時(shí),傳遞到 Auth::attempt
方法的憑證數(shù)組。這個(gè)方法應(yīng)該接著「查找」底層使用的永久存儲(chǔ),找到符合憑證的用戶。這個(gè)方法通常會(huì)對(duì) $credentials['username']
用「 where 」條件查找。 并且應(yīng)該返回一個(gè) UserInterface
接口的實(shí)現(xiàn)。這個(gè)方法不應(yīng)該嘗試做任何密碼驗(yàn)證或認(rèn)證。
validateCredentials
方法應(yīng)該通過比較給定的 $user
與 $credentials
來驗(yàn)證用戶。舉例來說,這個(gè)方法可以比較 $user->getAuthPassword()
字串跟 Hash::make
后的 $credentials['password']
。這個(gè)方法應(yīng)該只驗(yàn)證用戶的憑證數(shù)組并且返回布爾值。
現(xiàn)在我們已經(jīng)看過 UserProvider
的每個(gè)方法,接著來看一下 Authenticatable
。記住,提供者應(yīng)該從 retrieveById
和 retrieveByCredentials
方法返回這個(gè)接口的實(shí)現(xiàn):
interface Authenticatable { public function getAuthIdentifier(); public function getAuthPassword(); public function getRememberToken(); public function setRememberToken($value); public function getRememberTokenName();}
這個(gè)接口很簡(jiǎn)單。 The getAuthIdentifier
方法應(yīng)該返回用戶的「主鍵」。在 MySQL 后臺(tái),同樣,這將會(huì)是個(gè)自動(dòng)遞增的主鍵。getAuthPassword
應(yīng)該返回用戶哈希過的密碼。這個(gè)接口讓認(rèn)證系統(tǒng)可以與任何用戶類一起運(yùn)作,無論你使用什么 ORM 或保存抽象層。默認(rèn),Laravel 包含一個(gè)實(shí)現(xiàn)這個(gè)接口的 User
類在 app
文件夾里,所以你可以參考這個(gè)類當(dāng)作實(shí)現(xiàn)的例子。
最后,當(dāng)我們已經(jīng)實(shí)現(xiàn)了 UserProvider
,我們準(zhǔn)備好用 Auth
facade 來注冊(cè)擴(kuò)展:
Auth::extend('riak', function($app){ return new RiakUserProvider($app['riak.connection']);});
用 extend
方法注冊(cè)驅(qū)動(dòng)之后,在你的 config/auth.php
配置文件切換到新驅(qū)動(dòng)。
幾乎每個(gè) Laravel 框架引入的服務(wù)提供者都會(huì)綁定對(duì)象到服務(wù)容器中。你可以在 config/app.php
配置文件中找到應(yīng)用程序的服務(wù)提供者清單。如果你有時(shí)間,你應(yīng)該瀏覽過這里面每一個(gè)提供者的源代碼。通過這樣做,你將會(huì)更了解每一個(gè)提供者添加什么到框架,以及用什么鍵值來綁定各種服務(wù)到服務(wù)容器。
例如, HashServiceProvider
綁定 hash
做為鍵值到服務(wù)容器,它將解析成 Illuminate\Hashing\BcryptHasher
實(shí)例。你可以在應(yīng)用程序中覆寫這個(gè) IoC 綁定,輕松地?cái)U(kuò)展并覆寫這個(gè)類。例如:
<?php namespace App\Providers;class SnappyHashProvider extends \Illuminate\Hashing\HashServiceProvider { public function boot() { parent::boot(); $this->app->bindShared('hash', function() { return new \Snappy\Hashing\ScryptHasher; }); }}
要注意的是這個(gè)類擴(kuò)展 HashServiceProvider
,不是默認(rèn)的 ServiceProvider
基礎(chǔ)類。當(dāng)你擴(kuò)展了服務(wù)提供者,在 config/app.php
配置文件把 HashServiceProvider
換成你擴(kuò)展的提供者名稱。
這是被綁定在容器的所有核心類的一般擴(kuò)展方法。實(shí)際上,每個(gè)以這種方式綁定在容器的核心類都可以被覆寫。再次強(qiáng)調(diào),看過每個(gè)框架引入的服務(wù)提供者將會(huì)使你熟悉:每個(gè)類被綁在容器的哪里、它們是用什么鍵值綁定。這是個(gè)好方法可以了解更多關(guān)于 Laravel 如何結(jié)合它們。
更多建議: