使用 Laravel Manager 類別 - 原理篇
今天來聊聊 Laravel 相關的主題,主要是 Manager 的使用方法。
下面將以 Laravel v9.37.0 的原始碼做說明
官方文件並沒有寫到這個類別,不過框架本身有在使用。
首先先來分析原始碼,是一段含註解不到 200 行的抽象類別,裡面依賴了 Container 實例,同時 Container 必須註冊 config
,裡面放的是 Config 的實例;而抽象類所要求要實作的方法是 getDefaultDriver()
,這裡的 Driver 指的可能是一群行為有相關的「實作」,而這個抽象方法是要取得預設的實作。
abstract class Manager |
這個抽象類別在框架裡面的實作有三個:
Illuminate\Hashing\HashManager
Illuminate\Notifications\ChannelManager
Illuminate\Session\SessionManager
(詳細分析上篇與下篇)
觀察上面官方的這三個實作可以發現,Manager 想要達成的其中一個目標是,要統一管理「建立實例的方法」,而實例的樣貌有三種類型如下:
- 相同的類別,但是不同的初始化方法。例如:
SessionManager
一律都是回傳Illuminate\Session\Store
,但它建立實體的方法會隨著定義不同而有所不同; - 相同的介面,但是不同的實作。例如:
HashManager
產生的都是實作Illuminate\Contracts\HashingHasher
介面的實體。 - 完全不相關的實作。例如:
ChannelManager
產生的實體並沒有被任何介面約束,雖然程式裡可以查得到產生出來的實體都有實作send()
方法,但實際上並沒有實作任何介面。
所以 Manager 是一個實作工廠模式(Factory Pattern)或建造者模式(Builder Pattern)的「管理者」。當不同的實作需要透過同一個實體管理時,Manager 會是一個很好的工具。
Manager 如何產生實體?
繼續看原始碼,首先是取得實體的入口 driver()
:
public function driver($driver = null) |
這裡可以看到有個 $this->drivers
的屬性,這是要存放已產生過的實體。相同的請求,在第二次進來的時候,會從記憶體直接取得並回傳,這是 Registry of Singleton Pattern 的實作。
再來看 createDriver()
實際建立實體的方法:
protected function createDriver($driver) |
一般使用 Manager 是為了要管理實體產生的方法,接著需要透過實作方法(指類別的 Method)來讓 Manager 在需要實體的實作來呼叫並產生對應的實體。
從上面這段程式碼裡面的 $method
變數可以知道,這個實作的方法名稱會跟 Driver 的名稱有關。實際的從 Driver 名稱轉換到方法名稱的範例如下:
cache => createCacheDriver
session => createSessionDriver
database => createDatabaseDriver
my_new-class => createMyNewClassDriver
擴充實體生成方法
在實作 Manager 的時候,會先預設好可能的實體生成方法,並先實作完成。例如 SessionManager 這個類別,已經實作好 File、Redis 與 Database 等實體的生成方法。但在兩個情境下,可能需要為實作的 Manager 擴充新的方法:
- 有新的實體生成方法,如 HashManager 有新的演算法。
- 原有的生成方法不滿意,因此想使用新的方法取代它。
Manager 擴充新的建立實體方法是透過呼叫 extend()
方法來達成:
public function extend($driver, Closure $callback) |
從行為上來看,這個方法可以透過註冊一個新的建立實體方法在 $this->customCreators
屬性裡。 而在前面呼叫 createDriver()
的時候會先確認 Driver 的名稱有沒有註冊,如果有的話會透過 callCustomCreator()
呼叫:
protected function callCustomCreator($driver) |
因此可以想像註冊的方法可能會是像這樣:
$manager->extend('new', function(Container $app) { |
代理模式
Manager 除了當管理實體角色外,它也可以做為 Proxy Pattern 使用,因為它實作了一層可以作為代理的魔術方法(Magic Method):
public function __call($method, $parameters) |
所有 Manager 不存在的方法,全都會轉去呼叫預設實作(因為 driver()
沒指定會拿到預設實作)的對應方法。
這個實作模式在 Laravel 很常見,像是 LogManager 可以把它當 PSR-3 來使用,實際上它代理了預設 Logger 的實作。
注意事項
前面有提到 Laravel 框架,剛好用在三個不同場景,因此這篇文章就不特別說明完整範例,只說明要注意的重點為何。
實務上在使用時,建議把它作為同一類型的實體產生器,如前面提到的第一個情境,相同類別,不同的初始化方法,或是第二個情境,相同介面不同實作。這就如同把 Manager 作為 Factory Pattern 來使用--回傳的實體具有一致的介面或行為。
但因為 PHP 沒有泛型,所以有辦法創造出第三種情境--呼叫 driver()
但回傳不同類型的實體。以我目前的經驗來說,是不建議這麼做的,因為在 Manager 回傳實作不明確時,在使用上就有可能造成非預期的結果。
2022/11/12:實作的部分額外寫一篇參考--使用 Laravel Manager 類別 - 實戰篇