Zend Action Controller - 重构策略

时间:2012-01-12 03:05:40

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

我已经在Zend Framework(1.10)上构建了一个首次运行的Web服务,现在我正在研究如何重构我的Action Controllers中的一些逻辑,这样我和其他人都会更容易我的团队扩展和维护服务。

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

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

  1. 从请求正文中提取XML消息 - 这包括针对特定于操作的relaxNG架构进行验证
  2. 准备XML响应
  3. 验证请求消息中的数据(无效数据会引发异常 - 将消息添加到立即发送的响应中)
  4. 执行数据库操作(选择/插入/更新/删除)
  5. 使用所需信息返回操作的成功或失败
  6. 一个简单的例子是这个动作,它根据一组灵活的标准返回供应商列表:

    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-like并且还可以使用PHPUnit进行测试?

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

2 个答案:

答案 0 :(得分:1)

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

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

  2. 将共性纳入行动助手。

答案 1 :(得分:1)

由于每次发出请求时都必须执行此步骤,因此您可以在Zend_Controller_Plugin中存储接收,解析和验证收到的请求,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

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

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

处理数据(格式和转义(替换&lt; with&amp; lt;如果要包含在响应中),发送给视图助手以构建XML然后打印(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
}