4

由于zend-mvc的2.7.0 版本已ServiceLocatorAwareInterface被弃用,因此$this->serviceLocator->get()控制器内部的调用也是如此。

这就是为什么几天前我对我的所有模块进行了巨大的重构,以通过使用工厂的构造函数注入所需的服务/对象。

当然,我理解为什么这是更好/更清洁的做事方式,因为依赖关系现在更加明显。但另一方面:

这会导致大量开销和更多从未使用过的类实例,不是吗?

让我们看一个例子:

因为我所有的控制器都有依赖关系,所以我为它们创建了工厂。

客户控制器工厂.php

namespace Admin\Factory\Controller;
class CustomerControllerFactory implements FactoryInterface {
    public function createService(ServiceLocatorInterface $controllerManager) {
        $serviceLocator = $controllerManager->getServiceLocator();
        $customerService = $serviceLocator->get('Admin\Service\CustomerService');
        $restSyncService = $serviceLocator->get('Admin\Service\SyncRestClientService');

        return new \Admin\Controller\CustomerController($customerService, $restSyncService);
    }
}

客户控制器.php

namespace Admin\Controller;

class CustomerController extends AbstractRestfulController {
    public function __construct($customerService, $restSyncService) {
        $this->customerService = $customerService;
        $this->restSyncService = $restSyncService;
    }
}

模块.config.php

'controllers' => [
  'factories' => [
    'Admin\Controller\CustomerController' => 'Admin\Factory\Controller\CustomerControllerFactory',
  ]
],
'service_manager' => [
  'factories' => [
    'Admin\Service\SyncRestClientService' => 'Admin\Factory\SyncRestClientServiceFactory',
  ]
]

SyncRestClientServiceFactory.php

namespace Admin\Factory;
class SyncRestClientServiceFactory implements FactoryInterface {
    public function createService(ServiceLocatorInterface $serviceLocator) {
        $entityManager = $serviceLocator->get('doctrine.entitymanager.orm_default');
        $x1 = $serviceLocator->get(...);
        $x2 = $serviceLocator->get(...);
        $x3 = $serviceLocator->get(...);
        // ...

        return new \Admin\Service\SyncRestClientService($entityManager, $x1, $x2, $x3, ...);
    }
}

SyncRestService 是一个复杂的服务类,它查询我们系统的一些内部服务器。它有很多依赖关系,并且总是在有请求到达 CustomerController 时创建。但是这个同步服务syncAction()在 CustomerController内部使用!在我只是$this->serviceLocator->get('Admin\Service\SyncRestClientService')在内部使用之前syncAction(),只有它被实例化了。

一般来说,看起来很多实例都是在每次请求时通过工厂创建的,但大多数依赖项都没有使用。这是因为我的设计问题还是“通过构造函数进行依赖注入”的正常副作用行为?

4

4 回答 4

8

在我看来,这是通过构造函数进行依赖注入的正常效果。

我认为您现在有两种选择(不相互排斥)来改进您的应用程序的工作方式:

  1. 拆分您的控制器,以便仅在需要时实例化依赖项。这肯定会产生更多的类、更多的工厂等等,但是您的代码将更多地实现单一责任原则

  2. 您可以使用Lazy Services,这样,即使某些服务是整个控制器的依赖项,它们实际上只会在第一次被调用时被实例化(所以永远不要用于未调用它们的操作!)

于 2016-04-22T14:28:59.223 回答
1

如果你只使用你的SyncRestClientService内部控制器,你应该考虑将它从一个服务更改为一个控制器插件(或者在你注入的地方制作一个控制器插件SyncRestClientService)。
像这样你仍然可以在你的控制器syncAction方法中得到它,就像你以前做的那样。这正是 ZF2 控制器插件的目的。

首先,您需要创建控制器插件类(扩展Zend\Mvc\Controller\Plugin\AbstractPlugin):

<?php
namespace Application\Controller\Plugin;

use Zend\Mvc\Controller\Plugin\AbstractPlugin;

class SyncPlugin extends AbstractPlugin{

    protected $syncRestClientService;

    public function __constuct(SyncRestClientService $syncRestClientService){
        $this->syncRestClientService = $syncRestClientService
    }

    public function sync(){
        // do your syncing using the service that was injected
    }
}

然后一个工厂在课堂上注入你的服务:

<?php
namespace Application\Controller\Plugin\Factory;

use Application\Controller\Plugin\SyncPlugin;

class SyncPluginFactory implements FactoryInterface
{
    /**
     * @param  ServiceLocatorInterface $serviceController
     * @return SyncPlugin
     */
    public function createService(ServiceLocatorInterface $serviceController)
    {
        $serviceManager = $serviceController->getServiceLocator();
        $syncRestClientService = $serviceManager>get('Admin\Service\SyncRestClientService');
        return new SyncPlugin($syncRestClientService);
    }
}

然后你需要在你的插件中注册你的module.config.php

<?php
return array(
    //...
    'controller_plugins' => array(
        'factories' => array(
            'SyncPlugin' => 'Application\Controller\Plugin\Factory\SyncPluginFactory',
        )
    ),
    // ...
);

现在您可以在控制器操作中使用它,如下所示:

protected function syncAction(){
    $plugin = $this->plugin('SyncPlugin');
    //now you can call your sync logic using the plugin
    $plugin->sync();
}

在文档中阅读更多关于控制器插件的信息

于 2016-04-22T16:35:00.333 回答
0

也许您只需要将一个依赖项注入控制器构造函数(ServiceManager 实例)。我没看到附近有警察...

namespace Admin\Factory\Controller;

class CustomerControllerFactory implements FactoryInterface {

    public function createService(ServiceLocatorInterface $controllerManager) 
    {
        $serviceLocator = $controllerManager->getServiceLocator();

        return new \Admin\Controller\CustomerController($serviceLocator);
    }

}
于 2016-04-22T20:34:58.667 回答
0

就个人而言,我在控制器工厂中获取操作名称,以基于每个操作注入服务。

看看我的站点控制器。

namespace Admin\Controller\Service;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Admin\Controller\SitesController;
use Admin\Model\Sites as Models;

class SitesControllerFactory implements FactoryInterface
{

    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $actionName = $serviceLocator->getServiceLocator()->get('Application')->getMvcEvent()->getRouteMatch()->getParam('action');

        $controller = new SitesController();

        switch ($actionName) {
            case 'list':
                $controller->setModel($serviceLocator->getServiceLocator()->get(Models\ListSitesModel::class));
                break;
            case 'view':
                $controller->setModel($serviceLocator->getServiceLocator()->get(Models\ViewSiteModel::class));
                break;
            case 'add':
                $controller->setModel($serviceLocator->getServiceLocator()->get(Models\AddSiteModel::class));
                break;
            case 'edit':
                $controller->setModel($serviceLocator->getServiceLocator()->get(Models\EditSiteModel::class));
                break;
        }

        return $controller;
    }

}

如您所见,我使用$serviceLocator->getServiceLocator()->get('Application')->getMvcEvent()->getRouteMatch()->getParam('action');来获取操作名称并在需要时使用 switch 语句注入依赖项。我不知道这是否是最好的解决方案,但它对我有用。

希望这可以帮助。

于 2016-04-23T08:54:43.000 回答