草庐IT

php - 如何在 Zend Framework 中使用依赖注入(inject)?

coder 2023-06-15 原文

目前我正在努力学习Zend Framework,因此我买了《Zend Framework in Action》这本书。

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

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 中注入(inject)任何 stub 或模拟。

我更想拥有的是这样的东西:

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 不能单独测试。第二个要好得多。现在我只需要一些方法将模型实例注入(inject) Controller 对象。

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

最佳答案

模型逻辑

首先,值得一提的是,尽管所有逻辑都属于模型,但 Controller 应该只需要进行功能测试。

我的实现

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

  • 允许对 Action 注入(inject)任何依赖
  • 验证操作参数,例如当需要整数时,您不能在 $_GET 中传递数组

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

很明显,我使用 Reflections 来自动确定 Action 参数和依赖对象。

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

DocBlock 注释正在成为一个众所周知的行业标准,并且出于评估目的对其进行解析变得越来越流行(例如 Doctrine 2)。我将这种技术用于许多应用程序并且效果很好。

写这门课的灵感来自 Actions, now with params!Jani Hartikainen's blog post .

所以,这是代码:

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

}

要使用它,只需:

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 是根据操作参数自动生成的)

关于php - 如何在 Zend Framework 中使用依赖注入(inject)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3645300/

有关php - 如何在 Zend Framework 中使用依赖注入(inject)?的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 如何在 Ruby 中顺序创建 PI - 2

    出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits

  7. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  8. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  9. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  10. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

随机推荐