使用 Laravel Manager 類別 - 原理篇
許久沒寫文章了,今天寫個 Laravel 相關的主題,主要是 Manager 的使用方法。
這個類別在官方文件並沒有出現,而框架本身有在使用。首先先來分析原始碼,它是一段含註解不到 200 行的類別:
abstract class Manager |
它是一個抽象類別,裡面依賴了 Container 實例,同時 Container 必須註冊 config
,裡面放的是 Config 的實例;而抽象類所要求要實作的方法是 getDefaultDriver()
,這裡的 Driver 看下文可以了解,它指的可能是一群行為有相關的「實作」,而這個抽象方法是要取得預設的實作。
這個抽象類別在框架裡面的實作有三個:
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 Singleton Pattern 的實作。
再來看 createDriver()
實際建立實體的方法:
protected function createDriver($driver) |
這段比較 Magic。一般使用 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 框架,剛好用在三個不同場景,因此這篇文章就不特別說明完整範例,只說明要注意的重點為何。
2022/11/12:實作的部分後來有額外寫一篇可以參考--使用 Laravel Manager 類別 - 實戰篇
實務上在使用時,建議把它作為同一類型的實體產生器,如前面提到的第一個情境,相同類別,不同的初始化方法,或是第二個情境,相同介面,不同實作。這就如同把 Manager 作為 Factory Pattern 來使用--回傳的實體具有一致的介面或行為。
但因為 PHP 沒有泛型,所以有辦法創造出第三種情境--呼叫 driver()
但回傳不同類型的實體。以我目前的經驗來說,是不建議這麼做的,因為在 Manager 回傳實作不明確時,在使用上就有可能造成非預期的結果。