Zend Framework 2中如何使用Service Manager

end Framework 2 使用ServiceManager(簡稱SM)來實現控制反轉(IoC)。有不少資料介紹了service managers的背景,我推薦你們看看this blog post from Evanthis post from Reese Wilson,可是仍然有不少開發者不可以很好地使用ServiceManager去解決他們的需求。這篇文章我將解釋爲何ZF2框架須要使用多個服務管理器以及怎樣使用它們。主要包含如下幾個方面:php

  1. 這些不一樣的服務管理器是什麼?
  2. 不一樣的服務管理器用來幹什麼?
  3. 服務管理器與服務定位器是什麼關係?
  4. 如何使用這些服務管理器定義服務?
  5. 如何在一個服務管理器中經過另外一個服務管理器調用服務?

服務管理器使用在ZF2的許多地方,其中最重要的四個地方是:html

  1. 應用全局服務管理(根服務管理器或者說是主要服務管理器)
  2. 控制器
  3. 控制器插件
  4. 視圖助手


每一組功能都有一個服務管理器,這樣作的好處是,可使用同一個服務Key值指向不一樣的服務。假若有一個名爲url的試圖助手,也有一個名爲url的控制器插件,若是隻有一個服務管理器的話很難使用一個url Key值達到這個目的,而使用多個服務管理器能夠輕鬆作到。
還有一個緣由是出於安全考慮。假設有一個route向controller傳遞一個參數,經過此參數,服務管理器能夠實例化相應的服務,若是你沒有考慮安全問題,那麼能夠經過給一個服務管理器提供各類各樣的參數從而實例化全部服務。
git

服務管理器與服務定位器的不一樣

不少人問ServiceLocator和ServiceManager有什麼不一樣。ServiceLocator(簡稱SL)是一個接口:github

namespace Zend\ServiceManager;   interface ServiceLocatorInterface { public function get($name); public function has($name); }

ServiceManager是ServiceLocator的一個具體實現。在zf2中SL的默認實現是SM。在整個框架中,有時會看到 getServiceLocator()方法,而有時會看到getServiceManager()方法。getServiceLocator()得到的 SL接口,而getServiceManager得到的是具體的SM實現。bootstrap

二者之間並無很大的區別,由於他們一般返回的是同一個對象。可是有時候一個SL能夠有多個不一樣SM實現,許多zf2組件須要明確指定一個實現。數組

配置服務管理器

兩種方法能夠配置服務管理器:1.module類自己能夠return SM配置; 2.模塊配置文件(一般是config/module.config.php)能夠return SM配置。兩種方法功能是同樣的,只是看你本身喜歡放置到哪兒。
你可使用下面任意一種方法添加服務:緩存

/**
 * 在module class類自己
 */
namespace MyModule;   class Module { public function getServiceConfig() { return array( 'invokables' => array( 'my-foo' => 'MyModule\Foo\Bar', ), ); } }
/**
 * 在module config中
 */
return array( 'service_manager' => array( 'invokables' => array( 'my-foo' => 'MyModule\Foo\Bar' ), ), );

咱們看到,兩種不一樣的方法中返回的數組都是同樣的,四種類型的服務管理器都是這樣的。在module類中,你只須要實現getServiceConfig方法,配置就會被加載,使用的是duck type模式(不必定要繼承,只要他們方法同樣,就認爲他們是一回事。例如:有一隻鳥,若是它像鴨子同樣叫,像鴨子同樣游泳,像鴨子同樣走路,就認爲它就是一隻鴨子)。若是你想嚴格規範這個方法,也能夠添加一個接口。例如:安全

namespace MyModule;   use Zend\ModuleManager\Feature\ServiceProviderInterface;   class Module implements ServiceProviderInterface { public function getServiceConfig() { return array( 'invokables' => array( 'my-foo' => 'MyModule\Foo\Bar', ), ); } }

四種服務管理器,你均可以添加一個Key到模塊配置文件或者添加一個方法到模塊類。對於後者,你能夠duck type一些方法也能夠添加一個新的接口在Zend\MoudleManager\Feature\*interface。下面的列表反映了他們之間的聯 系。「manager」表明管理什麼,還提供了管理器類名、模塊配置數組中的Key、模塊的方法和接口。對於controller、controller plugin、view helper管理器,在全局管理器service manger中註冊服務時指定了service name(服務名稱)。閉包

Manager: Application servicesapp

  • Manager class: Zend\ServiceManager\ServiceManager
  • Config key: service_manager
  • Module method: getServiceConfig()
  • Module interface: ServiceProviderInterface

Manager: Controllers

  • Manager class: Zend\Mvc\Controller\ControllerManager
  • Config key: controllers
  • Module method: getControllerConfig()
  • Module interface: ControllerProviderInterface
  • Service name: ControllerLoader

Manager: Controller plugins

  • Manager class: Zend\Mvc\Controller\PluginManager
  • Config key: controller_plugins
  • Module method: getControllerPluginConfig()
  • Module interface: ControllerPluginProviderInterface
  • Service name: ControllerPluginManager

Manager: View helpers

  • Manager class: Zend\View\HelperPluginManager
  • Config key: view_helpers
  • Module 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,這使得各類不一樣的服務管理器可以來回調用:

/*在Module.config.php中*/
use MyModule\Controller;   return array( 'controllers' => array( 'factories' => array( 'MyModule\Controller\Foo' => function($sm) { $controller = new Controller\FooController;   $cache = $sm->getServiceLocator()->get('my-cache'); $controller->setCache($cache);   return $controller; }, ), ), );

這裏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」,你能夠像這樣得到一個插件:

use MyModule\Service;   return array( 'service_manager' => array( 'factories' => array( 'MyModule\Service\Foo' => function($sm) { $service = new Service\Foo;   $plugins = $sm->get('ViewHelperManager'); $plugin = $plugins->plugin('my-plugin'); $service->setPlugin($plugin);   return $service; }, ), ), ); 

點對點的(peering) service manager

點對點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,點對點仍然是有價值的。

use MyModule\Controller\Plugin;   return array( 'controller_plugins' => array( 'factories' => array( 'MyModule\Controller\Plugin\Foo' => function($sm) { $plugin = new Plugin\Foo;   $cache = $sm->get('my-cache'); $plugin->setCache($cache);   return $plugin; }, ), ), );

這麼作的好處就是對於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類一般像這樣:

namespace MyModule;   use Zend\Loader; use Zend\ModuleManager\Feature; use Zend\EventManager\EventInterface;   class Module implements Feature\AutoloaderProviderInterface, Feature\ConfigProviderInterface, Feature\ServiceProviderInterface, Feature\BootstrapListenerInterface { public function getAutoloaderConfig() { return array( Loader\AutoloaderFactory::STANDARD_AUTOLOADER => array( Loader\StandardAutoloader::LOAD_NS => array( __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__, ), ), ); }   public function getConfig() { return include __DIR__ . '/config/module.config.php'; }   public function getServiceConfig() { return include __DIR__ . '/config/service.config.php'; }   public function onBootstrap(EventInterface $e) { // Some logic } }

module.config.php文件提供一些基礎配置,service.config.php把全部的服務整合到一塊兒。經過EnsembleKernel這個例子能夠了解這種配置方式,其中service.config.php看起來像這樣。固然,也有一些別的方法可以處理的很是好,看你我的喜愛了。

英文原文連接 Using Zend Framework service managers in your application

本文是做者的團隊博客ComingXZend Framework 2中如何使用Service Manager 文章的一份拷貝,同爲原創文章。

相關文章
相關標籤/搜索