【问题标题】:zf2: how to get service manager in mapperzf2:如何在映射器中获取服务管理器
【发布时间】:2012-12-24 13:11:03
【问题描述】:

在 Zend Framework 1 中,我有几个映射器,它们从父 Mapper 类继承了 setDbTable 和 getDbTable。

现在在 ZF2 中,我面临一个问题,即我需要模型中的服务管理器,但我不知道如何获得它:

    class Mapper
    {
        protected $tableGateway;
        protected $module = 'application';

        public function setTableGateway($table)
            {
                if (is_string($table)) {
                    $class = $this->module . '\Model\DbTable\\' . ucfirst($table);
                    $sm = $this->getServiceLocator(); <= Fatal error: Call to undefined method Mapper::getServiceLocator()
                    $tableGateway = (class_exists($class)) ? $sm->get($class) : $sm->get(new TableGateway($table));
                } else {
                    $tableGateway = $table;
                }

                if (!$tableGateway instanceof Zend\Db\TableGateway\AbstractTableGateway) {
                    throw new \Exception('Invalid table data gateway provided');
                }
                $this->tableGateway = $tableGateway;
                return $this;
            }

        // more code

行:

$sm = $this->getServiceLocator();

给出一个致命错误:

Call to undefined method Application\Model\Mapper\Doc::getServiceLocator()

如何在我的模型中获取服务管理器?还是我不是按照 ZF2 的方式做事?我知道如何在我的控制器中获取服务管理器并将 tableGateway 传递给映射器,但这对我来说似乎有很多重复的代码。

【问题讨论】:

  • 在模型中注入服务管理器通常是一个非常糟糕的主意。尝试在您的类中注入依赖项,而不是依赖要注入的服务容器。这是“依赖注入”与“服务容器”的讨论,阅读其中的一些内容并理解为什么他们将这种类型的注入称为反模式是一个好主意。
  • @Jurian Sluiman:对我来说很难讨论,因为我最近才了解依赖注入,但我想我明白了基本概念。下面安迪的回答怎么样(我接受了,因为它有效)?在您看来,这也是一种反模式吗?
  • 您可以好好阅读这篇文章:blog.ircmaxell.com/2012/08/…(对我来说,Anthony Ferrara 是一位非常受人尊敬的开发人员)。它并不是真正的黑/白,但在 SO 上就同一问题提出了类似的问题:stackoverflow.com/questions/9011787/…

标签: zend-framework2


【解决方案1】:

首先,我认为您的意思是要从映射器类而不是模型访问服务管理器。我会避免做后者。有关更多详细信息,请参阅我对 Raj 回答的评论。

其次,有很多方法可以解决这个问题。在这个答案中,我会给你一个方法的例子,而只是提到另一种。

查看service manager's documentation(向下滚动一点),它声明默认初始值设定项已添加为默认值。此初始化程序检查从服务管理器检索的对象是否实现Zend\ServiceManager\ServiceLocatorAwareInterface。如果是,则将服务管理器注入到对象中。因此,如果您只是在映射器类中实现接口,这可能会自动发生。您可以使用抽象基类来避免为每个映射器类重写它。也许像下面这样。

基础映射器类:

namespace User\Mapper;

use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class AbstractMapper implements ServiceLocatorAwareInterface {
    protected $service_manager;

    public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
    {
        $this->service_manager = $serviceLocator;
    }

    public function getServiceLocator()
    {
        return $this->service_manager;
    }
}

映射器类:

namespace User\Mapper;

use User\Mapper\AbstractMapper;

class UserMapper extends AbstractMapper {
    public function doSomething() {
        $sm = $this->getServiceLocator();
        $sm->get('something');
    }
}

由于在运行初始化程序时会注入服务管理器,因此应该从服务管理器中检索映射器。如果您不想/必须将任何东西注入您的映射器类,那么一个可调用的就足够了。它们可以在invokables 键下添加,该键嵌套在module_name/config/module.config.php 中的service_manager 键下。或者,可以在模块类的getServiceConfig方法中配置。但是,您的映射器类现在或将来很可能会有一些依赖项,因此您可能希望使用工厂来代替。这样,您可以让服务管理器将表网关注入映射器类。

// Remember to include the appropriate namespaces
return array(
    'factories' => array(
        'User\Mapper\UserMapper' => function($service_manager) {
            $table_gateway = $service_manager->get('User\Model\DbTable\UserGateway');
            return new UserMapper($table_gateway);
        },
    ),
);

以上内容可以添加到模块的Module.php 文件中的getServiceConfig 方法中 - 或在module_name/config/module.config.php 中的service_manager 键中添加factories 键。您仍然需要添加一个创建数据库网关的工厂;以上只是一个例子。

这就是我的做法。当然,可以简单地为映射器类中的服务管理器提供一个 getter 和 setter 方法,并从控制器访问它们(控制器有一个getServiceLocator 方法)并像这样注入它。不过,我自己不会采用这种方法。

【讨论】:

    【解决方案2】:

    注意:正如 Jurian Sluiman 和 andy124 在他们的 cmets 中指出的那样,永远不要注入服务管理器并依赖于您的域特定模型/对象中的服务管理器,这不是一个好的做法,因为这会使您的域特定对象刚性和对便携性的影响。下面的解决方案更具体地针对所提出的问题

    我按照以下步骤在我的班级、模型等中获取服务定位器(服务管理器)
    1.创建实现ServiceMangerAwareInterface的类
    2. 在你的 Module.php 中定义 serviceconfig 中的条目 像这样

    公共函数 getServiceConfig(){ 返回数组( '工厂' => 数组( /* 楷模 */ 'MyModule\MyModel' => 函数($sm){ 返回新模型\MyModel($sm); }, /* 实体 */ 'MyModule\MyEntity1' => 函数($sm){ 返回新实体\MyEntity1($sm); }, 'MyModule\MyEntity2' => 函数($sm){ 返回新实体\MyEntity2($sm); }, . . . ), ); `
    1. 使用如下服务管理器访问您的模型或类(例如在控制器下) $model = $this->getServiceLocator()->get('MyModule\MyModel');
    您还可以在 module.config.php 中进行服务管理器配置 'service_manager' => 数组( '工厂' => 数组( 'MyModel' => 'MyModule\Models\MyModel', 'MyEntity' => 'MyModule\Entity\MyEntity', ), ), 4. 使用如下服务管理器访问您的模型或类(例如在控制器下) $model = $this->getServiceLocator()->get('MyModel');

    【讨论】:

    • 您的回答中有一些错误: 1. 如果您已经有自己的工厂注入 SM,则无需实施ServiceManagerAwareInterface。 2. 一般而言,将 SM 注入域模型中的想法是一个非常非常糟糕的主意,并且 3. 在您的 config.php(您可能指的是 module.config.php)中,您将模型用作可调用服务,但您使用工厂服务。由于这些事情,我对你投了反对票,但你可以在你的答案中轻松调整这些事情,让我再次删除它。
    • @Jurian Sluiman 感谢您指出错误,我已经更新了我的答案,另一个您已经说过一般来说,在您的域模型中注入 SM 的想法是一个非常非常糟糕的想法你能强调为什么这是一个坏主意吗,你能指出任何与之相关的文章吗,我想了解更多,谢谢
    • @Raj 就个人而言,我尽量让我的模型不受任何框架特定行为的影响,这样它就不会与业务逻辑相结合。这样既保留了业务逻辑的可移植性,又实现了逻辑的隔离。我还没有太多使用 ZF2 的实际经验,但我想不出我的模型中需要服务管理器的场景。使用依赖注入,模型不需要关心如何解决依赖,也不需要解决它们。这可能应该在服务层内完成。
    • @andy124,我明白了,在你的两个 cmets 之后,我重新分解了我所有的模型和实体,以便没有服务管理器,并且我使用服务管理器工厂来注入所有依赖项模型和模型负责将依赖项注入底层对象(例如实体),非常感谢。
    • Raj 这就是 andy124 所说的。请参阅我在问题本身顶部的评论。特别是在您的领域层,我只会依赖领域对象,实际上仅此而已。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-28
    • 1970-01-01
    相关资源
    最近更新 更多