ZF2服务定位器

3

我是一个对zf2不太熟悉的新手,正在进行实验。我有一个视图助手并需要它访问表对象。在我的控制器中,我可以运行:

$this->getServiceLocator();

但理想情况下,我希望在我的视图助手中运行它。不幸的是,我似乎无法从我的视图助手中访问它。我尝试通过构造函数传递它,配置module.config.php中的工厂方法,但是当我尝试这样做时,Zend将不再向从模块的Module.php文件中的服务工厂方法创建的一个模型对象传递tablegateway对象。这似乎是因为它不再调用工厂方法,而选择在没有任何参数的情况下运行实例化。

我不确定为什么视图工厂方法会影响具有不同名称的不同集合的工厂方法。

有人可以告诉我我做错了什么吗?我可以提供更多细节,但此时我不清楚哪些细节实际上很重要,而没有提供整个代码库。

谢谢。


可能是重复问题 https://dev59.com/Imcs5IYBdhLWcg3w0HIa - Developer
4个回答

7
Crisp提供了一个有效的答案,但我建议再进一步。服务定位器的注入使您的视图助手与框架和服务定位器模式紧密耦合,并且容易受到攻击,因为应用程序中的每个代码片段都可以修改服务定位器中的所有服务。
直接注入依赖项的原因,这样您只依赖于您的依赖项,不再实现这种反模式。假设您的视图助手依赖于MyModule\Model\MyTable,那么您的视图助手的构造函数将如下所示:
namespace MyModule;

use MyModule\Model\MyTable;
use Zend\View\Helper\AbstractHelper;

class MyViewHelper extends AbstractHelper
{
  protected $table;

  public function __construct(MyTable $table)
  {
    $this->table = $table;
  }
}

正如您指出的那样,您现在只是注入了您的MyTable

namespace MyModule;

class Module
{
  public function getViewHelperConfig()
  {
    return array(
      'factories' => array(
        'MyViewHelper' => function($sm) {
          $sm = $sm->getServiceLocator(); // $sm was the view helper's locator
          $table = $sm->get('MyModule_MyTable');

          $helper = new MyModule\View\Helper\MyHelper($table);
          return $helper;
        }
      )
    );
  }
}

请注意,在视图助手工厂内,您的服务管理器是“视图助手”的服务管理器,而不是注册表格的“主”服务管理器(还请参阅我之前写的博客文章)。$sm->getServiceLocator()会为您解决此问题。

我不确定为什么视图工厂方法会影响具有不同名称的一组不同工厂方法。

这并不是这样的,所以您的代码中可能存在错误。如果上述方法不起作用,请提供有关您的服务管理器配置的更多详细信息,以便我更新我的答案。
以上方法的一个巨大优点是,您可以轻松地为视图助手进行单元测试。您可以模拟表网关并专注于视图助手的完整行为。
use MyModule\View\Helper\MyHelper;

public function testHelperusesTable
{
  $mock   = $this->getMock('MyModule\Model\MyTable');
  $helper = new MyHelper($mock);

  // Test your $helper now
}

问题的根源在于我使用了服务管理器,而应该使用服务定位器。由于我的常规方法(print_r、debug_backtrace等)会导致内存严重不足,因此我没有检查对象类型。此外,它们似乎都有一个“get”方法,可以理解到工厂方法的路径,但只有一个似乎实际运行该方法。 - Shoreline

2
你可以在Module.php中的视图助手配置中将服务定位器注入到视图助手中。
// Application/Module.php

public function getViewHelperConfig()
{
    return array(
        'factories' => array(
            'myViewHelper' => function ($serviceManager) {
                // Get the service locator 
                $serviceLocator = $serviceManager->getServiceLocator();
                // pass it to your helper 
                return new \Application\View\Helper\MyViewHelper($serviceLocator);
            }
        )
    );
}

在您的视图助手中
<?php
namespace Application\View\Helper;

use Zend\View\Helper\AbstractHelper,
    Zend\ServiceManager\ServiceLocatorInterface as ServiceLocator;

class MyViewHelper extends AbstractHelper
{
    protected $serviceLocator;

    public function __construct(ServiceLocator $serviceLocator)
    {
        $this->serviceLocator = $serviceLocator;
    }
}

这似乎会导致同样的问题,即不再使用服务工厂方法来创建所需的表对象。也许它只是不喜欢同时存在服务工厂方法和视图助手方法,尽管我不知道为什么会这样。在我更好地理解这个问题之前,我将把服务管理器或其他对象传递到视图中,虽然对我来说这样做看起来同样混乱。 - Shoreline
似乎有些奇怪,当使用插件、助手或服务在工厂方法中时,我还没有遇到任何问题,无论是作为具体类还是像上面的示例一样作为闭包。也许你可以详细说明一下关于表对象的问题,我假设它只是使用 dbadapter 和表名实例化了一个 TableGateway,但也许你在那里有更多复杂的操作? - Crisp
要创建表对象,我基本上按照zf2教程(http://framework.zend.com/manual/2.0/en/user-guide/database-and-models.html)的步骤进行操作。工厂会创建一个对象,并传入一个TableGateway,就像你所说的那样。问题是,事实证明我使用的是ServiceManager而不是ServiceLocator。它们似乎都有一个'get'函数,在每种情况下都执行类似的操作,但并不完全相同。然后,根据猜测,ServiceManager::get将通过一个没有参数的构造函数运行get路径中已知的类。感谢你的帮助 :) - Shoreline

0
在使用Zend Framework时,我们经常需要自定义帮助程序来简化工作。在zf1中,从helper访问数据库模型很容易,但我卡在了如何在Custom View Helper中访问任何表的数据库模型上。因为我需要它,所以我通过在视图中创建一个新的db adapter对象来解决问题,这并不是一个好方法。但最近我通过一种非常有趣的方式知道了如何在视图助手中访问数据库适配器,并且可以在任何表上执行任何查询。这可能不是Zend F2的方式,但是解决问题的方法非常简单和快捷。

以下是我的模型示例...

<?php

namespace Application\Model;

use Zend\Db\TableGateway\TableGateway;

class SlideImageSubTable {

    protected $tableGateway;
    public $adapter;

    public function __construct(TableGateway $tableGateway) {
        $this->tableGateway = $tableGateway;
        $this->adapter = $this->tableGateway->getAdapter();
    }

    public function fetchAll() {
        $resultSet = $this->tableGateway->select();
        return $resultSet;
    }

    public function getSlideImageSub($id) {
        $id = (int) $id;
        $rowset = $this->tableGateway->select(array('id' => $id));
        $row = $rowset->current();
        if (!$row) {
            throw new \Exception("Could not find row $id");
        }
        return $row;
    }

    public function getImageMenu($id) {
        $id = (int) $id;
        $rowset = $this->tableGateway->select(array('slide_image_id' => $id));
        $rows = array_values(iterator_to_array($rowset));
        if (!$rows) {
            throw new \Exception("Could not find row $id");
        }
        return $rows;
    }

    public function saveSlideImageSub(SlideImageSub $slideImageSub) {
        $data = array(
            'slide_image_id' => $slideImageSub->slide_image_id,
            'title' => $slideImageSub->title,
            'description' => $slideImageSub->description
        );

        $id = (int) $slideImageSub->id;
        if ($id == 0) {
            $this->tableGateway->insert($data);
        } else {
            if ($this->getSlideImageSub($id)) {
                $this->tableGateway->update($data, array('id' => $id));
            } else {
                throw new \Exception('Form id does not exist');
            }
        }
    }

    public function deleteSlideImageSub($id) {
        $this->tableGateway->delete(array('id' => $id));
    }

}

只需查看“public $adapter”公共变量。在构造函数中,我将通过调用$this->tableGateway->getAdapter()方法来初始化它,getAdapter()可通过网关对象使用。

然后在我的控制器操作视图中,我必须将其分配给任何变量并将该变量传递到视图页面。像这样...

public function equitiesAction() {
        $image_id = $this->params('id');
        $result = $this->getTable('SlideImageSub')->getImageMenu($image_id);
        $adapter = $this->table->adapter;
        $view = new ViewModel(array(
                    'result' => $result,
                    'adapter' => $adapter,
                ));
        return $view;
    }

在视图中,我将“adapter”对象传递给自定义视图,如下所示...
<?php echo $this->GetMenuProducts( $this->adapter); ?>

现在在自定义视图中,我可以使用这个数据库适配器对象并在任何表上创建选择查询。

希望这能帮助到某些人,我寻找了如何在自定义视图助手中使用数据库访问,但提供的配置方法对我来说不起作用。

谢谢


-1
$this->getView()->getHelperPluginManager()->getServiceLocator();

这个答案是一年后添加的,显然没有增加任何价值。请编辑您的答案以澄清是否我错了。 - 700 Software

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接