【问题标题】:Zend Action Controller - refactoring strategyZend Action Controller - 重构策略
【发布时间】:2012-02-08 10:03:54
【问题描述】:

我已经在 Zend Framework (1.10) 上构建了一个首次运行的 Web 服务,现在我正在寻找方法来重构我的动作控制器中的一些逻辑,以便对我和其他人来说更容易我的团队来扩展和维护服务。

我可以看到哪里有重构的机会,但我不清楚如何采用最佳策略。最好的控制器文档和教程只讨论小规模的应用程序,并没有真正讨论如何抽象出更重复的代码,这些代码会蔓延到更大的范围内。

我们的动作控制器的基本结构是:

  1. 从请求正文中提取 XML 消息 - 这包括针对特定于操作的 RelaxNG 架构进行验证
  2. 准备 XML 响应
  3. 验证请求消息中的数据(无效数据引发异常 - 将消息添加到立即发送的响应中)
  4. 执行数据库操作(选择/插入/更新/删除)
  5. 返回操作的成功或失败以及所需的信息

一个简单的例子是这个动作,它根据一组灵活的标准返回供应商列表:

class Api_VendorController extends Lib_Controller_Action
{  
    public function getDetailsAction()
    {
        try {
            $request = new Lib_XML_Request('1.0');
            $request->load($this->getRequest()->getRawBody(), dirname(__FILE__) . '/../resources/xml/relaxng/vendor/getDetails.xml');
        } catch (Lib_XML_Request_Exception $e) {
            // Log exception, if logger available
            if ($log = $this->getLog()) {
                $log->warn('API/Vendor/getDetails: Error validating incoming request message', $e);
            }

            // Elevate as general error
            throw new Zend_Controller_Action_Exception($e->getMessage(), 400);
        }

        $response = new Lib_XML_Response('API/vendor/getDetails');

        try {
            $criteria = array();
            $fields = $request->getElementsByTagName('field');
            for ($i = 0; $i < $fields->length; $i++) {
                $name = trim($fields->item($i)->attributes->getNamedItem('name')->nodeValue);
                if (!isset($criteria[$name])) {
                    $criteria[$name] = array();
                }
                $criteria[$name][] = trim($fields->item($i)->childNodes->item(0)->nodeValue);
            }

            $vendors = $this->_mappers['vendor']->find($criteria);
            if (count($vendors) < 1) {
                throw new Api_VendorController_Exception('Could not find any vendors matching your criteria');
            }

            $response->append('success');
            foreach ($vendors as $vendor) {
                $v = $vendor->toArray();
                $response->append('vendor', $v);
            }

        } catch (Api_VendorController_Exception $e) {
            // Send failure message
            $error = $response->append('error');
            $response->appendChild($error, 'message', $e->getMessage());

            // Log exception, if logger available
            if ($log = $this->getLog()) {
                $log->warn('API/Account/GetDetails: ' . $e->getMessage(), $e);
            }
        }

        echo $response->save();
    }
}

那么 - 知道我的控制器中的共性在哪里,什么是重构的最佳策略,同时保持它类似于 Zend 并且还可以使用 PHPUnit 进行测试?

我确实考虑过将更多控制器逻辑抽象到父类 (Lib_Controller_Action) 中,但这使得单元测试更加复杂,在我看来这是错误的。

【问题讨论】:

  • 也许将通用性下推到服务/存储库类中?这样的类是可测试的,可以跨控制器使用,并且可以使控制器代码更紧凑。
  • 另一种方法是将共性收集到行动助手中。

标签: php model-view-controller zend-framework refactoring


【解决方案1】:

由于每次发出请求时都必须执行此步骤,因此您可以将接收、解析和验证接收到的请求存储在 Zend_Controller_Plugin 中,该插件将在所有控制器的每个PreDispatch 中运行。 (仅当您的 XML 请求标准化时才可行)(如果您使用 XMLRPCREST 或一些标准方式来构建对您的服务的请求,您可以期待那些内置在 ZF 中的模块)

数据验证(特定于动作)可以在控制器方法中完成(然后由需要它的动作调用)(如果您的参数特定于该控制器的一个或多个动作)或在控制器/动作之间有很多共享参数的情况下,您可以使用模式 FactoryBuilder 来实现

// call to the factory
$filteredRequest = My_Param_Factory::factory($controller, $action, $paramsArray) // call the right builder based on the action/controller combination

// the actual Factory

class My_Param_Factory 
{
    public static function factory($controller, $action, $params)
    {
        $builderClass = "My_Param_Builder_" . ucfirst($controller) . '_' . ucfirst($action);

        $builder = new $builderClass($params);

        return $builder->build();
    }
}

然后,您的构建器将根据特定构建器需求调用特定参数验证类(这将提高可重用性)

在您的控制器中,如果每个必需的参数都有效,则将处理传递给正确模型的正确方法

$userModel->getUserInfo($id) // for example

这将从控制器中删除所有数据处理操作,控制器只需检查输入是否正常,然后进行相应的调度。

将结果(错误或成功)存储在将发送到视图的变量中

处理数据(格式化和转义(例如,如果要包含在响应中,则将 echo) 数据(这将是您的用户的响应)。

public function getDetailsAction()
{
    if ($areRequestParamsValid === true) {
        // process data
    } else {
        // Build specific error message (or call action helper or controller method if error is general)
    }

    $this->view->data = $result
}

【讨论】:

    【解决方案2】:

    两个想法(只是从上面的 cmets 中创建一个答案):

    1. 将通用性下推到服务/存储库类中?这样的类是可测试的,可以跨控制器使用,并且可以使控制器代码更紧凑。

    2. 将通用性收集到行动助手中。

    【讨论】:

    • 您有任何示例的链接吗?如果它们包含测试代码,那就太好了 - Zend 和 TTD/自动化测试对我来说还是很新的。
    • 嗯,这是我疯狂地挥动手臂以分散注意力的部分,指向谷歌搜索 PHPUnit/TDD。一些具体的参考资料: (1) 我发现 ZF 本身的单元测试很有指导意义。 (2) 我喜欢source code of Dasprids blog 使用服务来精简控制器代码的示例。 (3) Doctrine2 存储库是将数据访问下推到存储库类的好例子。 (4) 来自 MWOP 的 This article 是一个很好的操作助手介绍。
    • 如果您将评论中的内容放入您的答案中,我会接受它
    • @HorusKol:您真的认为值得在答案正文中复制吗?我认为它可能在 cmets 中也可以访问。 WDYT?无论如何,希望它有所帮助。谢谢和欢呼。 ;-)
    猜你喜欢
    • 2012-05-13
    • 2016-12-22
    • 2023-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-09
    • 1970-01-01
    相关资源
    最近更新 更多