Php 如何在Zend框架中使用依赖项注入?

Php 如何在Zend框架中使用依赖项注入?,php,zend-framework,dependency-injection,Php,Zend Framework,Dependency Injection,目前我正在努力学习Zend框架,因此我买了《Zend框架在行动》一书 在第三章中,介绍了一个基本模型和控制器,并对它们进行了单元测试。基本控制器如下所示: class IndexController extends Zend_Controller_Action { public function indexAction() { $this->view->title = 'Welcome'; $placesFinder = new Pl

目前我正在努力学习Zend框架,因此我买了《Zend框架在行动》一书

在第三章中,介绍了一个基本模型和控制器,并对它们进行了单元测试。基本控制器如下所示:

class IndexController extends Zend_Controller_Action 
{
    public function indexAction()
    {
        $this->view->title = 'Welcome';
        $placesFinder = new Places();
        $this->view->places = $placesFinder->fetchLatest();
    }
}
class IndexController extends Zend_Controller_Action 
{
    private $placesFinder;

    // Here I can inject anything: mock, stub, the real instance
    public function setPlacesFinder($places)
    {
        $this->placesFinder = $places;
    }

    public function indexAction()
    {
        $this->view->title = 'Welcome';
        $this->view->places = $this->placesFinder->fetchLatest();
    }
}
Places
是从数据库中获取最新位置的模型类。什么让我感到困扰:我应该如何在隔离中测试
IndexController
?由于对
Places
类的引用是“硬编码”的,因此我无法在
IndexController
中插入任何存根或模拟

我想要的是这样的东西:

class IndexController extends Zend_Controller_Action 
{
    public function indexAction()
    {
        $this->view->title = 'Welcome';
        $placesFinder = new Places();
        $this->view->places = $placesFinder->fetchLatest();
    }
}
class IndexController extends Zend_Controller_Action 
{
    private $placesFinder;

    // Here I can inject anything: mock, stub, the real instance
    public function setPlacesFinder($places)
    {
        $this->placesFinder = $places;
    }

    public function indexAction()
    {
        $this->view->title = 'Welcome';
        $this->view->places = $this->placesFinder->fetchLatest();
    }
}
我发布的第一个代码示例显然不利于单元测试,因为
IndexController
不能单独测试。第二个更好。现在我只需要一些方法将模型实例注入控制器对象

我知道Zend框架本身没有用于依赖注入的组件。但是有一些很好的PHP框架,可以和Zend框架一起使用吗?或者在Zend框架中还有其他方法可以做到这一点吗?

模型逻辑 首先,值得一提的是,控制器应该只需要功能测试,尽管所有逻辑都属于模型

我的实现 下面是我的Action Controller实现的摘录,它解决了以下问题:

  • 允许向操作注入任何依赖项
  • 验证操作参数,例如,当需要整数时,您可能无法在
    $\u GET
    中传递数组
我的完整代码还允许基于或必需或已处理的操作参数生成规范URL(用于SEO或用于统计的唯一页面哈希)。为此,我使用这个抽象动作控制器和定制请求对象,但我们这里讨论的不是这种情况

显然,我使用反射来自动确定动作参数和依赖对象

这是一个巨大的优势,简化了代码,但对性能也有影响(对于我的应用程序和服务器来说,这是最小的,也不重要),但您可以实施一些缓存来加快速度。计算优点和缺点,然后做出决定

DocBlock注释正在成为一个非常著名的行业标准,并且出于评估目的对其进行解析变得越来越流行(例如,条令2)。我在很多应用程序中使用了这种技术,效果很好

写这门课是我的灵感来源

下面是代码:

<?php

/**
 * Enchanced action controller
 *
 * Map request parameters to action method
 *
 * Important:
 * When you declare optional arguments with default parameters, 
 * they may not be perceded by optional arguments,
 * e.g.
 * @example
 * indexAction($username = 'tom', $pageid); // wrong
 * indexAction($pageid, $username = 'tom'); // OK
 * 
 * Each argument must have @param DocBlock
 * Order of @param DocBlocks *is* important
 * 
 * Allows to inject object dependency on actions:
 * @example
 *   * @param int $pageid
 *   * @param Default_Form_Test $form
 *   public function indexAction($pageid, Default_Form_Test $form = null)
 *
 */
abstract class Your_Controller_Action extends Zend_Controller_Action
{  
    /**
     *
     * @var array
     */
    protected $_basicTypes = array(
        'int', 'integer', 'bool', 'boolean',
        'string', 'array', 'object',
        'double', 'float'
    );

    /**
     * Detect whether dispatched action exists
     * 
     * @param string $action
     * @return bool 
     */
    protected function _hasAction($action)
    {
        if ($this->getInvokeArg('useCaseSensitiveActions')) {
            trigger_error(
                    'Using case sensitive actions without word separators' .
                    'is deprecated; please do not rely on this "feature"'
            );

            return true;
        }

        if (method_exists($this, $action)) {

            return true;
        }

        return false;
    }

    /**
     *
     * @param string $action
     * @return array of Zend_Reflection_Parameter objects
     */
    protected function _actionReflectionParams($action)
    {
        $reflMethod = new Zend_Reflection_Method($this, $action);
        $parameters = $reflMethod->getParameters();

        return $parameters;
    }

    /**
     *
     * @param Zend_Reflection_Parameter $parameter
     * @return string
     * @throws Your_Controller_Action_Exception when required @param is missing
     */
    protected function _getParameterType(Zend_Reflection_Parameter $parameter)
    {
        // get parameter type
        $reflClass = $parameter->getClass();

        if ($reflClass instanceof Zend_Reflection_Class) {
            $type = $reflClass->getName();
        } else if ($parameter->isArray()) {
            $type = 'array';
        } else {
            $type = $parameter->getType();
        }

        if (null === $type) {
            throw new Your_Controller_Action_Exception(
                    sprintf(
                            "Required @param DocBlock not found for '%s'", $parameter->getName()
                    )
            );
        }

        return $type;
    }

    /**
     *
     * @param Zend_Reflection_Parameter $parameter 
     * @return mixed
     * @throws Your_Controller_Action_Exception when required argument is missing
     */
    protected function _getParameterValue(Zend_Reflection_Parameter $parameter)
    {
        $name = $parameter->getName();
        $requestValue = $this->getRequest()->getParam($name);

        if (null !== $requestValue) {
            $value = $requestValue;
        } else if ($parameter->isDefaultValueAvailable()) {
            $value = $parameter->getDefaultValue();
        } else {
            if (!$parameter->isOptional()) {
                throw new Your_Controller_Action_Exception(
                        sprintf("Missing required value for argument: '%s'", $name));
            }

            $value = null;
        }

        return $value;
    }

    /**
     *
     * @param mixed $value 
     */
    protected function _fixValueType($value, $type)
    {
        if (in_array($type, $this->_basicTypes)) {
            settype($value, $type);
        }

        return $value;
    }

    /**
     * Dispatch the requested action
     *
     * @param   string $action Method name of action
     * @return  void
     */
    public function dispatch($action)
    {
        $request = $this->getRequest();

        // Notify helpers of action preDispatch state
        $this->_helper->notifyPreDispatch();

        $this->preDispatch();
        if ($request->isDispatched()) {
            // preDispatch() didn't change the action, so we can continue
            if ($this->_hasAction($action)) {

                $requestArgs = array();
                $dependencyObjects = array();
                $requiredArgs = array();

                foreach ($this->_actionReflectionParams($action) as $parameter) {
                    $type = $this->_getParameterType($parameter);
                    $name = $parameter->getName();
                    $value = $this->_getParameterValue($parameter);

                    if (!in_array($type, $this->_basicTypes)) {
                        if (!is_object($value)) {
                            $value = new $type($value);
                        }
                        $dependencyObjects[$name] = $value;
                    } else {
                        $value = $this->_fixValueType($value, $type);
                        $requestArgs[$name] = $value;
                    }

                    if (!$parameter->isOptional()) {
                        $requiredArgs[$name] = $value;
                    }
                }

                // handle canonical URLs here

                $allArgs = array_merge($requestArgs, $dependencyObjects);
                // dispatch the action with arguments
                call_user_func_array(array($this, $action), $allArgs);
            } else {
                $this->__call($action, array());
            }
            $this->postDispatch();
        }

        $this->_helper->notifyPostDispatch();
    }

}
并像往常一样为操作提供注释(至少您已经应该这样做了)

e、 g


(DocBlock是必需的,但是我使用Netbeans IDE,因此DocBlock是根据操作参数自动生成的)

好的,我就是这样做的:

正如我使用的IoC框架一样(但我没有下载最新版本,我使用的是我以前在项目中使用过的旧版本……请记住!)。我在
/library/ioc/lib/
下添加了它的类

为了注册IoC框架的自动加载程序,我在我的
Bootstrap.php
中添加了这些init函数:

protected function _initIocFrameworkAutoloader()
{
    require_once(APPLICATION_PATH . '/../library/Ioc/lib/sfServiceContainerAutoloader.php');

    sfServiceContainerAutoloader::register();
}
接下来,我在
application.ini
中做了一些设置,设置了连接xml的路径,并允许禁用自动依赖项注入。G单元测试中:

ioc.controllers.wiringXml = APPLICATION_PATH "/objectconfiguration/controllers.xml"
ioc.controllers.enableIoc = 1
然后,我创建了一个自定义生成器类,它扩展了
sfServiceContainerBuilder
,并将其放在
/library/MyStuff/Ioc/builder.php
下。在这个测试项目中,我将所有的类都保存在
/library/MyStuff/

class MyStuff_Ioc_Builder extends sfServiceContainerBuilder
{
  public function initializeServiceInstance($service)
  {
      $serviceClass = get_class($service);
      $definition = $this->getServiceDefinition($serviceClass);


    foreach ($definition->getMethodCalls() as $call)
    {
      call_user_func_array(array($service, $call[0]), $this->resolveServices($this->resolveValue($call[1])));
    }

    if ($callable = $definition->getConfigurator())
    {
      if (is_array($callable) && is_object($callable[0]) && $callable[0] instanceof sfServiceReference)
      {
        $callable[0] = $this->getService((string) $callable[0]);
      }
      elseif (is_array($callable))
      {
        $callable[0] = $this->resolveValue($callable[0]);
      }

      if (!is_callable($callable))
      {
        throw new InvalidArgumentException(sprintf('The configure callable for class "%s" is not a callable.', get_class($service)));
      }

      call_user_func($callable, $service);
    }

  }
}
最后,我在
/library/MyStuff/controller.php
中创建了一个自定义控制器类,我的所有控制器都从该类继承:

class MyStuff_Controller extends Zend_Controller_Action {
    /**
     * @override
     */
    public function dispatch($action)
    {
        // NOTE: the application settings have to be saved 
        // in the registry with key "config"
        $config = Zend_Registry::get('config');

        if($config['ioc']['controllers']['enableIoc'])
        {
            $sc = new MyStuff_Ioc_Builder();

            $loader = new sfServiceContainerLoaderFileXml($sc);
            $loader->load($config['ioc']['controllers']['wiringXml']);

            $sc->initializeServiceInstance($this);
        }

        parent::dispatch($action);
    }
}

这主要是使用IoC框架来初始化已经创建的控制器实例(
$this
)。我做的简单测试似乎能满足我的要求。。。让我们看看这在现实生活中的表现


不知何故,它仍然是猴子补丁,但是Zend框架似乎没有提供一个钩子,我可以用自定义控制器工厂创建控制器实例,所以这是我想到的最好的…

我目前正在研究同一个问题,经过深入研究,我决定使用Symfony依赖注入组件。你可以从官方网站上获得好的信息

我已经在bootstrap中构建了定制的getContainer()方法,它现在重新启动服务容器,并且它可以简单地用于像

public function init()
{
    $sc = $this->getInvokeArg('bootstrap')->getContainer();
    $this->placesService = $sc->get('PlacesService');
}

在这里你可以找到如何做到这一点。但是我更改了ContainerFactory,因为它使用的是Symfony2组件,而不是第一个版本。

您也可以使用PHP-DI ZF桥接器:


我知道这个问题很老了,但在ZF1中寻找DI时,搜索引擎中会弹出很高的问题,所以我想我会添加一个解决方案,不需要您自己编写。

与Zend Framework 3的服务经理一起

正式文件:

  • 控制器上的依赖项通常由DI构造函数注入器注入
  • 我可以提供一个例子,将负责创建ViewModel实例的工厂注入控制器
  • 例如:

    控制器 `

    
    创建控制器
    

    ` 完整示例位于


    我希望它能帮助一些人

    控制器自然地依赖于MVC中的模型,所以这有点困难。您可以模拟Places类,该类允许索引操作周围的已知状态。不过,我很想看到一个实际的答案。@Rob Olmos:我想象的是一个控制器工厂从ioc容器中创建实际的控制器。在ASP.NET MVC中,这实际上很容易做到。您希望在控制器中测试什么?控制器操作99%的时间返回HTML响应。你会对响应运行字符串比较吗?@Eran Galperin:嗯,是的,当然,控制器大部分时间都返回HTML响应
    class JsonController extends AbstractActionController
    {
        private $_jsonFactory;
        private $_smsRepository;
        public function __construct(JsonFactory $jsonFactory, SmsRepository $smsRepository)
        {
            $this->_jsonFactory = $jsonFactory;
            $this->_smsRepository = $smsRepository;
        }
    ...
    }
    
    class JsonControllerFactory implements FactoryInterface
    {
        /**
         * @param ContainerInterface $serviceManager
         * @param string $requestedName
         * @param array|null $options
         * @return JsonController
         */
        public function __invoke(ContainerInterface $serviceManager, $requestedName, array $options = null)
        {
            //improve using get method and callable
            $jsonModelFactory = new JsonFactory();
            $smsRepositoryClass = $serviceManager->get(SmsRepository::class);
            return new JsonController($jsonModelFactory, $smsRepositoryClass);
        }
    }