如何在Zend Framework中使用依赖注入? [英] How to use dependency injection in Zend Framework?

查看:58
本文介绍了如何在Zend Framework中使用依赖注入?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当前,我正在尝试学习Zend框架,因此我买了《 Zend Framework in Action》一书。



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

  class IndexController扩展了Zend_Controller_Action 
{
公共功能indexAction()
{
$ this-> view-&title; title ='Welcome';
$ placesFinder = new Places();
$ this-> view-> places = $ placesFinder-> fetchLatest();
}
}

地点是从数据库中获取最新位置的模型类。有什么问题困扰我:我应该如何在隔离中测试 IndexController ?由于对 Places 类的引用是硬编码的,因此我无法在 IndexController 中注入任何存根或模拟。 p>

我希望拥有这样的东西:

  class IndexController扩展了Zend_Controller_Action 
{
private $ placesFinder;

//在这里我可以注入任何东西:模拟,存根,真实实例
公共函数setPlacesFinder($ places)
{
$ this-> placesFinder = $地方;
}

公共功能indexAction()
{
$ this-> view-&title; title ='Welcome';
$ this-> view-> places = $ this-> placesFinder-> fetchLatest();
}
}

我发布的第一个代码示例绝对不是单位测试友好,因为 IndexController 不能单独进行测试。第二个要好得多。现在我只需要某种方法就可以将模型实例注入到控制器对象中。



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

解决方案

模型逻辑



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



我的实现



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




  • 允许注入对动作的任何依赖

  • 验证动作参数,例如当期望整数时,您可能无法在 $ _ GET 中传递数组



我的完整该代码还允许生成基于URL的规范URL(用于SEO或用于统计信息的唯一页面哈希),该规范URL是基于或必需或已处理的操作参数。为此,我使用了这个抽象的动作控制器和自定义的Request对象,但是在这里我们不讨论这种情况。



显然,我使用了Reflections自动确定动作参数并依赖对象。



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



DocBlock批注正逐渐成为众所周知的行业标准,并且出于评估目的对其进行解析变得更加流行(例如,Doctrine 2) 。我在很多应用程序中都使用了这种技术,并且效果很好。



写这个类的灵感来自操作,现在带有参数! Jani Hartikainen的博客文章



因此,代码如下:

 <?php 

/ **
*增强型动作控制器
*
*将请求参数映射到操作方法
*
*重要:
*当您声明带有默认参数的可选参数
*可能不会被可选参数
强制执行,例如
* @example
* indexAction($ username ='tom',$ pageid); //错误的
* indexAction($ pageid,$ username = tom); // OK
*
*每个参数必须具有@param DocBlock
* @param DocBlocks的顺序*重要*
*
*允许在操作:
* @example
* * @param int $ pageid
* * @param Default_Form_Test $ form
*公共函数indexAction($ pageid,Default_Form_Test $ form = null)
*
* /
抽象类Your_Controller_Action扩展了Zend_Controller_Action
{
/ **
*
* @var数组
* /
protected $ _basicTypes = array(
'int','integer','bool','boolean',
'string','array','object',
'double','float'
);

/ **
*检测是否存在分派的动作
*
* @param string $ action
* @return bool
* /
受保护的函数_hasAction($ action)
{
if($ this-> getInvokeArg('useCaseSensitiveActions')){
trigger_error(
'使用区分大小写的动作
'已过时;请不要依赖此功能'
);

返回true;
}

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

返回true;
}

返回false;
}

/ **
*
* @param string $ action
* @Zend_Reflection_Parameter对象的返回数组
* /
受保护的函数_actionReflectionParams($ action)
{
$ reflMethod = new Zend_Reflection_Method($ this,$ action);
$ parameters = $ reflMethod-> getParameters();

返回$参数;
}

/ **
*
* @param Zend_Reflection_Parameter $ parameter
* @返回字符串
* @在需要时抛出Your_Controller_Action_Exception @参数缺失
* /
受保护的函数_getParameterType(Zend_Reflection_Parameter $ parameter)
{
//获取参数类型
$ reflClass = $ parameter-> getClass() ;

if(Zend_Reflection_Class的$ reflClass实例){
$ type = $ reflClass-> getName();
}否则,如果($ parameter-> isArray()){
$ type ='array';
} else {
$ type = $ parameter-> getType();
}

if(null === $ type){
抛出new Your_Controller_Action_Exception(
sprintf(
未找到'必需的@param DocBlock %s',$ parameter-> getName()

);
}

return $ type;
}

/ **
*
* @param Zend_Reflection_Parameter $ parameter
* @return Mixed
* @抛出Your_Controller_Action_Exception必要参数时缺少
* /
受保护的函数_getParameterValue(Zend_Reflection_Parameter $ parameter)
{
$ name = $ parameter-> getName();
$ requestValue = $ this-> getRequest()-> getParam($ name);

if(null!== $ requestValue){
$ value = $ requestValue;
}否则,如果($ parameter-> isDefaultValueAvailable()){
$ value = $ parameter-> getDefaultValue();
} else {
if(!$ parameter-> isOptional()){
throw new Your_Controller_Action_Exception(
sprintf(缺少参数必需的值:'%s' ,$ name));
}

$ value = null;
}

返回$ value;
}

/ **
*
* @param混合$ value
* /
受保护的函数_fixValueType($ value,$ type )
{
if(in_array($ type,$ this-> _basicTypes)){
settype($ value,$ type);
}

返回$ value;
}

/ **
*分派请求的操作
*
* @param string $ action方法的操作名称
* @ return void
* /
公共功能dispatch($ action)
{
$ request = $ this-> getRequest();

//通知动作帮助者preDispatch状态
$ this-> _helper-> notifyPreDispatch();

$ this-> preDispatch();
if($ request-> isDispatched()){
// preDispatch()并未更改操作,因此我们可以继续
if($ this-&_; _ hasAction($操作)){

$ 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;
}否则{
$ value = $ this-&_; _ fixValueType($ value,$ type);
$ requestArgs [$ name] = $ value;
}

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

//处理规范的网址

$ allArgs = array_merge($ requestArgs,$ dependencyObjects);
//使用
参数调度动作call_user_func_array(array($ this,$ action),$ allArgs);
} else {
$ this-> __ call($ action,array());
}
$ this-> postDispatch();
}

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

}

要使用此功能,只需:

  Your_FineController扩展了Your_Controller_Action {} 

并像往常一样为动作提供注释(至少您已经应该;)。



例如

  / ** 
* @param int $ id强制参数
* @param字符串$ sorting不需要的参数
* @param Your_Model_Name $ model可选依赖对象
* /
公共函数indexAction($ id,$ sorting = null,Your_Model_Name $ model = null)
{
//模型已被自动实例化如果为空
$ entry = $ model-> getOneById($ id,$ sorting);
}

(需要DocBlock,但是我使用Netbeans IDE,所以DocBlock是自动的根据操作参数生成)


Currently I am trying to learn the Zend Framework and therefore I bought the book "Zend Framework in Action".

In chapter 3, a basic model and controller is introduced along with unit tests for both of them. The basic controller looks like this:

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

Places is the model class that fetches the latest places from the database. What bugs me here: how should I test the IndexController in isolation? As the reference to the Places class is "hardcoded", I cant inject any stubs or mocks in IndexController.

What I would rather like to have is something like this:

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

The first code sample I posted is most definately NOT unit test friendly as IndexController cannot be tested in isolation. The second one is much better. Now I just need some way to inject the model instances into the controller objects.

I know that the Zend Framework per se has no component for dependency injection. But there are some good frameworks out there for PHP, can any be used together with Zend Framework? Or is there some other way to do this in Zend Framework?

解决方案

Logic to models

First of all, it's worth to mention, that controllers should need only functional tests, though all the logic belongs to models.

My implementation

Here is an excerpt from my Action Controller implementation, which solves the following problems:

  • allows inject any dependency to actions
  • validates the action parameters, e.g. you may not pass array in $_GET when integer is expected

My full code allows also to generate canonical URL (for SEO or unique page hash for stats) based or required or handled action params. For this, I use this abstract Action Controller and custom Request object, but this is not the case we discuss here.

Obviously, I use Reflections to automatically determine action parameters and dependency objects.

This is a huge advantage and simplifies the code, but also has an impact in performance (minimal and not important in case of my app and server), but you may implement some caching to speed it up. Calculate the benefits and the drawbacks, then decide.

DocBlock annotations are becoming a pretty well known industry standard, and parsing it for evaluation purposes becomes more popular (e.g. Doctrine 2). I used this technique for many apps and it worked nicely.

Writing this class I was inspired by Actions, now with params! and Jani Hartikainen's blog post.

So, here is the code:

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

}

To use this, just:

Your_FineController extends Your_Controller_Action {}

and provide annotations to actions, as usual (at least you already should ;).

e.g.

/**
 * @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 is required, however I use Netbeans IDE, so the DocBlock is automatically generated based on action arguments)

这篇关于如何在Zend Framework中使用依赖注入?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆