【问题标题】:Zend 2: Get service locator in pluginZend 2:在插件中获取服务定位器
【发布时间】:2015-10-20 18:30:02
【问题描述】:

我想在我的自定义插件中获取服务定位器。我通过 Module.php 中的工厂创建它:

public function getControllerPluginConfig() {
    return [
        'factories' => [
            'Application\Plugin\AppPlugin' => function($serviceManager) {

                $serviceLocator = $serviceManager->getServiceLocator();
                $appPlugin = new \Application\Plugin\AppPlugin();
                $appPlugin->setLocator($serviceLocator);
                return $appPlugin;
            }  
        ]
    ];
}

...和我的插件:

<?php

namespace Application\Plugin;

use Zend\Mvc\Controller\Plugin\AbstractPlugin;

class AppPlugin extends AbstractPlugin {

    protected $locator;

    public function getAppInfo() {

        $config = $this->locator->get('Config');
    }

   /**
    * Set Service Locator
    * @param \Zend\ServiceManager\ServiceManager $locator
    */
    public function setLocator($locator) {
        $this->locator = $locator;
    }
}

然后我在控制器中调用 getAppInfo():

$appPlugin = new AppPlugin();
$appPlugin->getAppInfo();

...我得到了错误:

Fatal error: Call to a member function get() on a non-object in /vagrant/app/module/Application/src/Application/Plugin/AppPlugin.php on line 13
Call Stack
#   Time    Memory  Function    Location
1   0.0027  232104  {main}( )   ../index.php:0
2   0.6238  3166904 Zend\Mvc\Application->run( )    ../index.php:21
3   1.4410  5659112 Zend\EventManager\EventManager->trigger( )  ../Application.php:314
4   1.4410  5659112 Zend\EventManager\EventManager->triggerListeners( ) ../EventManager.php:205
5   1.4411  5660872 call_user_func ( )  ../EventManager.php:444
6   1.4411  5661440 Zend\Mvc\DispatchListener->onDispatch( )    ../EventManager.php:444
7   1.4505  5704600 Zend\Mvc\Controller\AbstractController->dispatch( ) ../DispatchListener.php:93
8   1.4505  5705080 Zend\EventManager\EventManager->trigger( )  ../AbstractController.php:118
9   1.4505  5705080 Zend\EventManager\EventManager->triggerListeners( ) ../EventManager.php:205
10  1.4507  5716656 call_user_func ( )  ../EventManager.php:444
11  1.4507  5716784 Zend\Mvc\Controller\AbstractActionController->onDispatch( ) ../EventManager.php:444
12  1.4508  5717504 Application\Controller\IndexController->indexAction( )  ../AbstractActionController.php:82
13  1.4641  5727768 Application\Plugin\AppPlugin->getAppInfo( ) ../IndexController.php:21

但如果我从控制器传递服务定位器,它工作正常:

$appPlugin = new AppPlugin();
$appPlugin->setLocator($this->getServiceLocator());
$appPlugin->getAppInfo(); 

如果有人能解释我做错了什么,我会很高兴。谢谢。

【问题讨论】:

    标签: php zend-framework2 factory service-locator


    【解决方案1】:

    如果有可能,您不应该尝试在工厂以外的任何类中访问 ServiceLocator。这样做的主要原因是,如果将 ServiceLocator 注入到您的类中,您现在不知道该类的依赖项是什么,因为它现在可能包含任何内容。

    关于依赖注入,您有两个基本选择:构造函数或设置器注入。根据经验,总是更喜欢构造函数注入。 Setter 注入应该只用于可选的依赖项,它也会让你的代码更加模糊,因为这个类现在是可变的。如果您使用纯粹的构造函数注入,您的依赖项是不可变的,并且您始终可以确定它们会存在。

    除了使用闭包之外,通常最好使用具体的工厂类,因为闭包不能被操作码缓存,而且如果包含闭包,您的配置数组也不能被缓存。

    请参阅 https://stackoverflow.com/a/18866169/1312094 以获得有关如何设置类以实现 FactoryInterface 的详细说明

    话虽如此,为了讨论的目的,让我们继续使用您的闭包工厂示例。我们不是注入 ServiceManager,而是注入 config 数组(这仍然有点过于依赖上帝;最好注入您需要的配置的特定键,或者更好的是实例化类)。

    public function getControllerPluginConfig() {
        return [
            'factories' => [
                \Application\Plugin\AppPlugin::class => function($serviceManager) {
                    $serviceLocator = $serviceManager->getServiceLocator();
                    $config = $serviceLocator->get('Config');
                    $appPlugin = new \Application\Plugin\AppPlugin($config);
                    return $appPlugin;
                }  
            ]
        ];
    }
    

    这里插件被修改为在构造函数中获取配置。我假设这个插件代码只是一个概念证明,除了返回配置之外,你实际上会对插件做一些有用的事情,但希望这确实显示了注入插件依赖项的模式。

    <?php
    
    namespace Application\Plugin;
    
    use Zend\Mvc\Controller\Plugin\AbstractPlugin;
    
    class AppPlugin extends AbstractPlugin {
    
        protected $config;
    
        public function __construct($config){
             $this->config = $config;
        }
    
        public function getAppInfo() {
            return $this->config;
        }
    
    }
    

    另外,一件小事,但假设 PHP 5.5+,考虑使用 \Application\Plugin\AppPlugin::class 就像我在上面的示例中所做的那样,而不是配置键的字符串文字。

    【讨论】:

    • 很好的答案。早期我读到闭包不好,可能是缓存错误。但我决定实现ServiceLocatorAwareInterface。将来我想在我的插件中使用其他服务,所以我在其中传递了 ServiceLocator 的实例。
    • 谢谢。真的希望你重新考虑注入定位器。这是一个主要的代码异味。它固有地破坏了封装。
    【解决方案2】:

    我通过 ServiceLocatorAwareInterface 创建它并将其设为可调用:

    namespace Application\Controller\Plugin;
    
    use Zend\Mvc\Controller\Plugin\AbstractPlugin;
    use Zend\ServiceManager\ServiceLocatorAwareInterface;
    use Zend\ServiceManager\ServiceLocatorInterface;
    
    class AppPlugin extends AbstractPlugin implements ServiceLocatorAwareInterface {
    
        /**
         * @var ServiceLocatorInterface
         */
        protected $serviceLocator;
    
        /**
         * Get system information
         * @return type
         */
        public function getAppInfo() {
    
            $config = $this->getServiceLocator()->get('Config');
            // to do something...
        }
    
        /**
         * Retrieve service manager instance
         *
         * @return ServiceLocator
         */
        public function getServiceLocator() {
            return $this->serviceLocator->getServiceLocator();
        }
    
        /**
         * Set service locator
         *
         * @param ServiceLocatorInterface $serviceLocator
         */
        public function setServiceLocator(ServiceLocatorInterface $serviceLocator) {
            $this->serviceLocator = $serviceLocator;
        }
    }
    

    然后在module.appplication.config中:

    'controller_plugins' => [
        'invokables' => [
            'AppPlugin'     => \Application\Controller\Plugin\AppPlugin::class
             ]
        ]
    

    ...现在我可以像这样在控制器中使用我的插件了:

    $app = $this->AppPlugin()->getAppInfo();
    

    【讨论】:

      【解决方案3】:

      您不能直接在控制器中实例化该类,您需要使用“Application\Plugin\AppPlugin”键将其从控制器插件管理器中取出

      $pluginManager = $this->getPluginManager();
      $appPlugin = $pluginManager->get('Application\Plugin\AppPlugin');
      $appPlugin->getAppInfo();
      

      或者,您可以将方法更改为:

      public function getAppInfo() 
      {
          $serviceManager = $this->controller->getServiceLocator();
          $config = $serviceManager->get('Config');
      }
      

      【讨论】:

      • 这是真的,他可以把它提升到一个新的水平,并通过控制器工厂将插件注入控制器,这会给控制器一个明确的依赖关系,而不是让控制器打破封装直接访问定位器。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-06-25
      • 2012-09-08
      • 1970-01-01
      • 2019-01-30
      • 2018-02-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多