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