end Framework 2 使用ServiceManager(簡稱SM)來實現控制反轉(IoC)。有很多資料介紹了service managers的背景,我推薦大家看看this blog post from Evan和 this post from Reese Wilson,但是仍然有很多開發者不能夠很好地使用ServiceManager去解決他們的需求。這篇文章我將解釋為什么ZF2框架需要使用多個服務管理器以及怎樣使用它們。主要包含以下幾個方面:
這些不同的服務管理器是什么?不同的服務管理器用來干什么?服務管理器與服務定位器是什么關系?如何使用這些服務管理器定義服務?如何在一個服務管理器中通過另一個服務管理器調用服務?服務管理器使用在ZF2的許多地方,其中最重要的四個地方是:
應用全局服務管理(根服務管理器或者說是主要服務管理器)控制器控制器插件視圖助手
每一組功能都有一個服務管理器,這樣做的好處是,可以使用同一個服務Key值指向不同的服務。假如有一個名為url的試圖助手,也有一個名為url的控制器插件,如果只有一個服務管理器的話很難使用一個url Key值達到這個目的,而使用多個服務管理器可以輕松做到。
還有一個原因是出于安全考慮。假設有一個route向controller傳遞一個參數,通過此參數,服務管理器可以實例化相應的服務,如果你沒有考慮安全問題,那么可以通過給一個服務管理器提供各種各樣的參數從而實例化所有服務。
很多人問ServiceLocator和ServiceManager有什么不同。ServiceLocator(簡稱SL)是一個接口:
ServiceManager是ServiceLocator的一個具體實現。在zf2中SL的默認實現是SM。在整個框架中,有時會看到 getServiceLocator()方法,而有時會看到getServiceManager()方法。getServiceLocator()獲得的 SL接口,而getServiceManager獲得的是具體的SM實現。
兩者之間并沒有很大的區別,因為他們通常返回的是同一個對象。但是有時候一個SL可以有多個不同SM實現,許多zf2組件需要明確指定一個實現。
配置服務管理器兩種方法可以配置服務管理器:1.module類本身可以return SM配置; 2.模塊配置文件(通常是config/module.config.php)可以return SM配置。兩種方法功能是一樣的,只是看你自己喜歡放置到哪兒。
你可以使用下面任意一種方法添加服務:
我們看到,兩種不同的方法中返回的數組都是一樣的,四種類型的服務管理器都是這樣的。在module類中,你只需要實現getServiceConfig方法,配置就會被加載,使用的是duck type模式(不一定要繼承,只要他們方法一樣,就認為他們是一回事。例如:有一只鳥,如果它像鴨子一樣叫,像鴨子一樣游泳,像鴨子一樣走路,就認為它就是一只鴨子)。如果你想嚴格規范這個方法,也可以添加一個接口。例如:
四種服務管理器,你都可以添加一個Key到模塊配置文件或者添加一個方法到模塊類。對于后者,你可以duck type一些方法也可以添加一個新的接口在Zend/MoudleManager/Feature/*interface。下面的列表反映了他們之間的聯 系。“manager”代表管理什么,還提供了管理器類名、模塊配置數組中的Key、模塊的方法和接口。對于controller、controller plugin、view helper管理器,在全局管理器service manger中注冊服務時指定了service name(服務名稱)。
Manager: Application services
Manager class: Zend/ServiceManager/ServiceManagerConfig key: service_managerModule method: getServiceConfig()Module interface: ServiceProviderInterfaceManager: Controllers
Manager class: Zend/Mvc/Controller/ControllerManagerConfig key: controllersModule method: getControllerConfig()Module interface: ControllerProviderInterfaceService name: ControllerLoaderManager: Controller plugins
Manager class: Zend/Mvc/Controller/PluginManagerConfig key: controller_pluginsModule method: getControllerPluginConfig()Module interface: ControllerPluginProviderInterfaceService name: ControllerPluginManagerManager: View helpers
Manager class: Zend/View/HelperPluginManagerConfig key: view_helpersModule method: getViewHelperConfig()Service name: ViewHelperManager需要注意的是
有一關鍵點我們需要注意,正如Evan解釋,對于一個工廠類有兩個選項,要么是一個閉包,要么是一個字符串指向的類。這個類必須實現Zend/ServiceManager/FactoryInterface接口,或者它必須有__invoke方法。這個工廠將被放置到模塊配置文件中,或者模塊類中。
如果模塊配置文件中使用閉包,就會有問題,因為所有的模塊配置文件都將緩存到一個大的合并后的配置文件中,然而PHP中的閉包不能被序列化,不能被合并后緩存。所以你要么在模塊配置文件中使用工廠類,要么使用getServiceConfig()方法。
根服務管理器與其他管理器的比較根(root)通常在討論IRC時使用,好像它是基礎代碼一樣,但是實際上它與zf2的基礎代碼不是毫無關聯。“根服務管理器”這個名字的也許來自于:Zend/ServiceManager/ServiceManager控制著所有主要的服務,而其他的服務管理器只專注于一種服務。“根”這個名字好像暗示著它與其他一些managers有著某種關系。猜一猜是不是這樣呢?確實,有一種聯系存在。
假設你有一個controller,需要注入一個cache(緩存實例)進去。controller在controller service manager中具有緩存實例的工廠factory,緩存是root service manager的一個service。在controller服務管理器的工廠中如何獲得緩存服務?這就是root service manager(根服務管理器)與其他服務管理器的關聯之處。controller、controller plugin、view helper的service manager都是AbstractPluginMangaer抽象類的實現(Implementation),這個類有一個方法getServiceLocator()能夠返回root service manager,這使得各種不同的服務管理器能夠來回調用:
這里cache服務通過root service locator(根服務定位器)獲得,通過$sm->getServiceLocator()可以獲得任何根服務管理器下的服務。
如果你知道controller plugin manager和view helper manager 是注冊在root service locator的話,這將變得非常有趣。你可以輕松的在一個服務中注入一個運行時對象到view helper中。例如,在url view helper(服務)中注入router(對象),這個對象對于使用route名字來組裝url是必須的。
你可以通過“ControllerPluginManager”這個Key從根服務管理器(root SM)中獲得controller plugin manager,view helper manager對應的Key是“ViewHelperManager”,你可以像這樣獲得一個插件:
點對點service manager的概念很簡單,就是說controller plugin和view helper service manager從root SM調用其他服務時不適用$sm->getServiveLocator()。點對點(peering)的主要意思是,controller plugin SM 加載自己的服務失敗后再從root SM中加載服務。
因此,看上面的例子,在某種場合下,你可以跳過$sm->getServiceLocator(),直接獲取服務。這只適用于 controller plugins和view helpers,對于controller SM是不適用的。原因很顯然,controller SM有一個安全問題:你有可能由于請求了一個特俗的URL而意外地實例化了一個對象。如果你允許controller SM點對點獲取服務的話,你將導致安全漏洞。盡管這樣但是對于controller plugin和view helper,點對點仍然是有價值的。
這么做的好處就是對于controller plugin和view helper,你可以忽略getServiceLocator(),這使得你的代碼更加易讀。在字里行間你可能讀到了我的擔憂:點對點并不是很容易掌握。 在上面的例子中,$sm并沒有“my-cache”這個服務,但是你嘗試去獲取這個服務,你將得到cache。(這個地方不是很明白)。最好對這個工廠做 好文檔,否則以后將會遇到麻煩。
個人喜好我更加喜歡在Module中使用嚴格的接口。我總是使用Zend/ModuleManager/Feature interfaces,我總是把所有的service的配置放到一個config文件中,使用閉包作為工廠,這使得我可以清楚看到一個module中所有 的service key,而不是混雜著route config(從module config文件)或者 autoload config 或者 bootstrap 邏輯(從Module類)。
通常在module.config.php同目錄旁邊放置一個servcie.config.php文件在config/目錄下面,然后include這個文件就像include module配置文件一樣。Module類通常像這樣:
module.config.php文件提供一些基礎配置,service.config.php把所有的服務整合到一起。通過EnsembleKernel這個例子可以了解這種配置方式,其中service.config.php看起來像這樣。當然,也有一些別的方法能夠處理的非常好,看你個人喜好了。
英文原文鏈接 Using Zend Framework service managers in your application
本文是作者的團隊博客ComingX上 Zend Framework 2中如何使用Service Manager 文章的一份拷貝,同為原創文章。
PHP編程鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯系我們修改或刪除,多謝。
新聞熱點
疑難解答