当前位置:  开发笔记 > 编程语言 > 正文

如何在Zend Framework中使用依赖注入?

如何解决《如何在ZendFramework中使用依赖注入?》经验,为你挑选了2个好方法。

目前我正在尝试学习Zend Framework,因此我买了"Zend Framework in Action"这本书.

在第3章中,介绍了基本模型和控制器以及它们的单元测试.基本控制器如下所示:

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

Places是从数据库中获取最新位置的模型类.什么错误我在这里:我应该如何测试IndexController隔离?由于对Places类的引用是"硬编码",我无法注入任何存根或模拟IndexController.

我宁愿拥有的是这样的:

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 Framework本身没有用于依赖注入的组件.但是PHP有一些很好的框架,可以和Zend Framework一起使用吗?或者在Zend Framework中还有其他一些方法吗?



1> takeshin..:

模型的逻辑

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

我的实施

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

允许将任何依赖项注入操作

验证动作参数,例如,$_GET当期望整数时,您可能不会传入数组

我的完整代码还允许生成基于或要求或处理动作参数的规范URL(用于SEO或统计数据的唯一页面哈希).为此,我使用这个抽象的Action Controller和自定义Request对象,但这不是我们在这里讨论的情况.

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

这是一个巨大的优势并简化了代码,但也对性能产生了影响(在我的应用程序和服务器的情况下,这一点很小并且不重要),但是您可以实现一些缓存来加速它.计算好处和缺点,然后决定.

DocBlock注释正在成为一个众所周知的行业标准,并且为了评估目的而对其进行解析变得更加流行(例如,Doctrine 2).我在许多应用程序中使用了这种技术,它运行良好.

写这堂课我受到了动作的启发,现在有了参数!和Jani Hartikainen的博客文章.

所以,这是代码:

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();
    }

}

要使用它,只需:

Your_FineController extends Your_Controller_Action {}

并像往常一样为行动提供注释(至少你已经应该这样做了).

例如

/**
 * @param int $id Mandatory parameter
 * @param string $sorting Not required parameter
 * @param Your_Model_Name $model Optional dependency object 
 */
public function indexAction($id, $sorting = null, Your_Model_Name $model = null) 
{
    // model has been already automatically instantiated if null
    $entry = $model->getOneById($id, $sorting);
}

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



2> Max..:

好的,我就是这样做的:

作为IoC框架,我使用了symfony框架的这个组件(但是我没有下载最新版本,我使用了之前在项目中使用的旧版本...记住这一点!).我在下面添加了它的类/library/ioc/lib/.

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

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

    sfServiceContainerAutoloader::register();
}

接下来,我做了一些设置,application.ini其中设置了布线xml的路径,并允许禁用自动依赖注入,例如在单元测试中:

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 Framework来初始化已经创建的控制器实例($this).我做的简单测试似乎做了我想做的事情......让我们看看它在现实生活中的表现如何.;)

它仍然以某种方式进行猴子修补,但是Zend Framework似乎没有提供一个钩子,我可以用自定义控制器工厂创建控制器实例,所以这是我想出的最好的...

推荐阅读
赛亚兔备_393
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有