end Framework 2 使用ServiceManager(简称SM)来实现控制反转(IoC)。有很多资料介绍了service managers的背景,我推荐大家看看this blog post from Evanthis post from Reese Wilson,但是仍然有很多开发者不能够很好地使用ServiceManager去解决他们的需求。这篇文章我将解释为什么ZF2框架需要使用多个服务管理器以及怎样使用它们。主要包含以下几个方面:

  1. 这些不同的服务管理器是什么?
  2. 不同的服务管理器用来干什么?
  3. 服务管理器与服务定位器是什么关系?
  4. 如何使用这些服务管理器定义服务?
  5. 如何在一个服务管理器中通过另一个服务管理器调用服务?

服务管理器使用在ZF2的许多地方,其中最重要的四个地方是:

  1. 应用全局服务管理(根服务管理器或者说是主要服务管理器)
  2. 控制器
  3. 控制器插件
  4. 视图助手

每一组功能都有一个服务管理器,这样做的好处是,可以使用同一个服务Key值指向不同的服务。假如有一个名为url的试图助手,也有一个名为url的控制器插件,如果只有一个服务管理器的话很难使用一个url Key值达到这个目的,而使用多个服务管理器可以轻松做到。
还有一个原因是出于安全考虑。假设有一个route向controller传递一个参数,通过此参数,服务管理器可以实例化相应的服务,如果你没有考虑安全问题,那么可以通过给一个服务管理器提供各种各样的参数从而实例化所有服务。

服务管理器与服务定位器的不同

很多人问ServiceLocator和ServiceManager有什么不同。ServiceLocator(简称SL)是一个接口:

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

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 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 services

  • 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 文章的一份拷贝,同为原创文章。

Zend Framework 2中如何使用Service Manager的更多相关文章

  1. zend framework安装中出现的问题与总结

    1.按照官方的教程来做http://framework.zend.com/manual/current/en/user-guide/skeleton-application.html 但其中有些步骤没 ...

  2. 关于Zend Framework 2中 Zend\Session的使用

    一直迷惑于zend\Session的使用,这个是Zend\Session的官方教程的中文版,http://zend-framework-2.yangfan.co/blog/556. 其中最重要的是关于 ...

  3. 在zend framework框架中try{}catch(Exception e){}的跳转问题

    请勿盗版,转载请加上出处http://blog.csdn.net/yanlintao1 首先我先说明我遇到的问题 try{ //导入学生信息 $ModelStudent->insert($dat ...

  4. 【原创】Zend Framework 2框架之MVC

    ZendFramework 2框架之MVC 作者:sys(360电商技术组) 1.前言 Zend Framework 2是zend官方推出的php开源框架,基于php5.3.他全然採用面向对象的代码实 ...

  5. 浅谈Android系统进程间通信(IPC)机制Binder中的Server和Client获得Service Manager接口之路

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6627260 在前面一篇文章浅谈Service ...

  6. Zend Framework 1 - Quick Start

    创建 Zend 项目 要创建 Zend 项目,首先要下载并解压 Zend Framework. 安装 Zend Framework 下载最新的 Zend Framework 1.12.20 源码包,( ...

  7. Ubuntu14.0下安装Zend Framework 2

    Ubuntu14.0下安装Zend Framework 2为了安装这个东西,忙活了快一天了,参考中文博客一直没有安装成功,有些博客的时间也是已经很早了,后来google看英文版的才安装成功,这里记录一 ...

  8. 部署Service Manager 2012遇到的2个问题

    上周装了个Service Manager 2012学习,以便完善System Center整个解决方案,在部署期间遇到2个问题,花了我不少时间解决.一.安装时提示“执行自定义操作时失败”每当到了安装的 ...

  9. 搭建PHP官方框架zend framework 2(LINUX)

    在五花八门的语言里,PHP作为我第一个觉得欣赏的理由,就是它的简单和快捷,因为它封装了许多的常用函数.PHP作为网站中一种算作比较流行的语言,也产生各种优秀的框架.我所接触过的有zend framew ...

随机推荐

  1. 获取url的参数值

    var url=location.search; //获取url中从?开始的所有字符 var  theRequest=new Object();//定义一个对象来存放url中的参数 if( url.i ...

  2. ORACLE中能否找到未提交事务的SQL语句

      在Oracle数据库中,我们能否找到未提交事务(uncommit transactin)的SQL语句或其他相关信息呢?  关于这个问题,我们先来看看实验测试吧.实践出真知. 首先,我们在会话1(S ...

  3. $'\r': command not found 或者 syntax error: unexpected end of file 或者 syntax error near unexpected token `$'\r''

    执行shell脚本如果报如下错误: syntax error near unexpected token `$'\r'' syntax error: unexpected end of file $' ...

  4. Html style="visibility:hidden"与style="display:none"的区别

    style="visibility:hidden": 使对象在网页上隐藏,但该对象在网页上所占的空间没有改变. style="display:none": 使对 ...

  5. python爬虫之路——无头浏览器初识及简单例子

    from selenium import webdriver url='https://www.jianshu.com/p/a64529b4ccf3' def get_info(url): inclu ...

  6. 在linux命令行下如何访问网址

    1. wget Ubuntu系统自带,会将访问的首页下载到本地 admin@iZj6c9c6vaqj1i0a9j7h78Z:~$ wget www.baidu.com --2019-04-20 17: ...

  7. 《毛毛虫团队》第九次团队作业:BETA冲刺与团队项目验收

    一:实验名称:Beta冲刺与验收准备 二:实验目的与要求 (1)掌握软件黑盒测试技术: (2)学会编制软件项目总结PPT.项目验收报告: (3)掌握软件项目验收内容,验收流程. 三.实验内容与步骤 任 ...

  8. webpack4 + vue多页面项目精细构建思路

    #构建思路 虽然当前前端项目多以单页面为主,但多页面也并非一无是处,在一些情况下也是有用武之地的,比如: 项目庞大,各个业务模块需要解耦 SEO更容易优化 没有复杂的状态管理问题 可以实现页面单独上线 ...

  9. Python语言编写脚本时,对日期控件的处理方式

    对日期控件,日期控件的输入控一般是不能手动输入的:把readonly属性去掉就好 其实很简单,我们不去搞时间日期空间,我们把它当成一个普通的input框处理就好了! 但是,很多此类型input框都是禁 ...

  10. LINQ中AsEnumerable与AsQueryable的区别

    AsEnumerable将一个序列向上转换为一个IEnumerable, 强制将Enumerable类下面的查询操作符绑定到后续的子查询当中:AsQueryable将一个序列向下转换为一个IQuery ...