Zend Framework 2中如何使用Service Manager

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

时间: 2024-10-07 01:06:13

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

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

请勿盗版,转载请加上出处http://blog.csdn.net/yanlintao1 首先我先说明我遇到的问题 try{ //导入学生信息 $ModelStudent->insert($data2); } catch (Exception $e) { unlink(DOCS_PATH.'/student.xls'); $this->view->str = '导入失败,请检查数据格是否正确!'; $this->_forward("error","glo

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

一直迷惑于zend\Session的使用,这个是Zend\Session的官方教程的中文版,http://zend-framework-2.yangfan.co/blog/556. 其中最重要的是关于Zend\Session\Container 类的使用,是最重要的.Container 是存储信息的容器. Container的实例化, use Zend\Session\Container; $container = new Container('namespace'); $container->

zend framework模板中判断脚本和循环脚本的写法

<?php if ($this->books): ?> <!-- 包含几本书信息的HTML表格. --> <table> <tr> <th>Author</th> <th>Title</th> </tr> <?php foreach ($this->books as $key => $val): ?> <tr> <td><?php echo

【原创】Zend Framework 2框架之MVC

ZendFramework 2框架之MVC 作者:sys(360电商技术组) 1.前言 Zend Framework 2是zend官方推出的php开源框架,基于php5.3.他全然採用面向对象的代码实现,并利用了php5.3的一些新特性,比方命名空间.闭包等.由于是官方的推荐的,今天我们就来学习学习Zend Framework 2,由于本人知识浅薄,也没有实际的用Zend Framework 2做项目开发的经验.有错误的地方还请大家指正,也希望在后面的开发中能用用该框架,此篇文章权当抛砖引玉.

使用LiveDocx和Zend Framework生成基于模板的文档

使用PHP生成打印良好的格式良好的PDF文档并非易事.传统上,使用PHP生成PDF有两种主要方法.如果有足够的时间和耐心,两者都可以完成工作,但仍然有很多不足之处: HTML-to-PDF:这种方法广泛用于主流应用程序.这里使用众多开源库之一以编程方式创建HTML文档并将其转换为PDF.但是,由于HTML不是面向页面的格式(如PDF),因此无法在HTML和PDF之间执行1对1映射.典型的文字处理文件格式功能,例如页眉和页脚,孤儿和寡妇甚至页码都不能用HTML表示. 程序化:此方法可以完全控制生成

Zend Framework 2 Service Manager 配置方法

我们通常会把Service Manager配置在两个地方 1.module.config.php 2.Module.php 不同的service manager 类型有不同的配置方法 Application services Manager Application services Manager class Zend\ServiceManager\ServiceManager Config key service_manager Module method getServiceConfig()

Zend Framework 2 获取 Service Manager

在学习zend framework的时候很多的问题只能看源代码,为了加深自己的理解,记录下来平时用到的方法: 1在控制器里 $serviceLocator = $this->getServiceLocator(); 2在Module.php里 namespace ModuleName; use Zend\Mvc\MvcEvent; class Module { public function onBootstrap(MvcEvent $e) { $serviceLocator = $e->ge

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

文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6627260 在前面一篇文章浅谈Service Manager成为Android进程间通信(IPC)机制Binder守护进程之路中,介绍了Service Manager是如何成为Binder机制的守护进程的.既然作为守护进程,Service Manager的职责当然就是为Server和Client服务了.那么,Server和Client如何获得S

zend framework获取数据库中枚举类enum的数据并将其转换成数组

在model中建立这样的模型,在其中写入获取枚举类的方法 class Student extends Zend_Db_Table{ protected $_name ='student'; protected $_primary ='id'; function getPartyEnum($enumname){ //适配器并获取数据 $db = $this->getAdapter(); $db->query("SET NAMES GBK"); $sql = $db->q