end Framework 2 使用ServiceManager(简称SM)来实现控制反转(IoC)。有不少资料介绍了service managers的背景,我推荐你们看看this blog post from Evan和 this post from Reese Wilson,可是仍然有不少开发者不可以很好地使用ServiceManager去解决他们的需求。这篇文章我将解释为何ZF2框架须要使用多个服务管理器以及怎样使用它们。主要包含如下几个方面:php
服务管理器使用在ZF2的许多地方,其中最重要的四个地方是:html
每一组功能都有一个服务管理器,这样作的好处是,可使用同一个服务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: Controllers
Manager: Controller plugins
Manager: View helpers
须要注意的是
有一关键点咱们须要注意,正如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; }, ), ), ); |
点对点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
本文是做者的团队博客ComingX上 Zend Framework 2中如何使用Service Manager 文章的一份拷贝,同为原创文章。