Php Zend Action Controller-重构策略

Php Zend Action Controller-重构策略,php,model-view-controller,zend-framework,refactoring,Php,Model View Controller,Zend Framework,Refactoring,我已经在Zend Framework(1.10)上构建了一个首次运行的web服务,现在我正在寻找重构我的动作控制器中的一些逻辑的方法,以便我和我的团队的其他成员更容易扩展和维护该服务 我知道哪里有重构的机会,但我不清楚如何进行重构的最佳策略。关于控制器的最佳文档和教程只讨论小规模的应用程序,而没有真正讨论如何抽象更重复的代码,这些代码会逐渐扩展到更大的规模 我们的动作控制器的基本结构是: 从请求体提取XML消息-这包括针对特定于操作的relaxNG模式的验证 准备XML响应 验证请求消息中的数据

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

我知道哪里有重构的机会,但我不清楚如何进行重构的最佳策略。关于控制器的最佳文档和教程只讨论小规模的应用程序,而没有真正讨论如何抽象更重复的代码,这些代码会逐渐扩展到更大的规模

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

  • 从请求体提取XML消息-这包括针对特定于操作的relaxNG模式的验证
  • 准备XML响应
  • 验证请求消息中的数据(无效数据引发异常-将消息添加到立即发送的响应中)
  • 执行数据库操作(选择/插入/更新/删除)
  • 返回操作的成功或失败,以及所需的信息
  • 一个简单的示例是此操作,它根据一组灵活的标准返回供应商列表:

    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();
        }
    }
    
    类Api\u供应商控制器扩展Lib\u控制器\u操作
    {  
    公共函数getDetailsAction()
    {
    试一试{
    $request=新的Lib_XML_请求('1.0');
    $request->load($this->getRequest()->getRawBody(),dirname(_文件)'/../resources/xml/relaxng/vendor/getDetails.xml');
    }catch(Lib\u XML\u请求\u异常$e){
    //日志异常(如果记录器可用)
    如果($log=$this->getLog()){
    $log->warn('API/Vendor/getDetails:验证传入请求消息时出错',$e);
    }
    //提升为一般错误
    抛出新的Zend\u控制器\u操作\u异常($e->getMessage(),400);
    }
    $response=new Lib_XML_response('API/vendor/getDetails');
    试一试{
    $criteria=array();
    $fields=$request->getElementsByTagName('field');
    对于($i=0;$i<$fields->length;$i++){
    $name=trim($fields->item($i)->attributes->getNamedItem('name')->nodeValue);
    如果(!isset($criteria[$name])){
    $criteria[$name]=array();
    }
    $criteria[$name][]=trim($fields->item($i)->childNodes->item(0)->nodeValue);
    }
    $vendors=$this->_映射程序['vendor']->查找($criteria);
    if(计数($供应商)<1){
    抛出新的Api_VendorController_异常(“找不到任何符合您标准的供应商”);
    }
    $response->append('success');
    foreach($供应商作为$供应商){
    $v=$vendor->toArray();
    $response->append('vendor',$v);
    }
    }捕获(Api\u供应商控制器\u例外$e){
    //发送失败消息
    $error=$response->append('error');
    $response->appendChild($error,'message',$e->getMessage());
    //日志异常(如果记录器可用)
    如果($log=$this->getLog()){
    $log->warn('API/Account/GetDetails:'。$e->getMessage(),$e);
    }
    }
    echo$response->save();
    }
    }
    
    那么-知道我的控制器中的共性在哪里,在保持Zend风格和PHPUnit可测试性的同时重构的最佳策略是什么

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

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

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

  • 将共同点聚集到行动助手中


  • 由于每次发出请求时都必须执行此步骤,因此可以将接收、解析和验证接收到的请求存储在Zend_Controller_插件中,该插件将在所有控制器的每个
    PreDispatch
    中运行。(只有在XML请求标准化的情况下才可以这样做)(如果您使用
    XMLRPC
    REST
    或某种标准方式来构建对服务的请求,您可以期待在ZF中构建这些模块)

    数据的验证(特定于操作)可以在控制器方法中完成(然后由需要它的操作调用)(如果您的参数特定于该控制器的一个或多个操作)或者,如果控制器/操作之间有许多共享参数,您也可以使用模式
    工厂
    生成器

    // 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
    
    这将从控制器中删除所有数据处理操作,控制器只需检查输入是否正常,然后相应地进行调度

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

    处理数据(格式化和转义(例如,如果响应中包含它们,则替换为),发送给视图帮助程序以生成XML,然后在视图中打印(
    echo
    )数据(这将是用户的响应)


    也许可以将公共性下推到服务/存储库类中?这样的类是可测试的,可以跨控制器使用,并且可以使控制器代码更加紧凑。另一种方法是将通用性收集到动作助手中。您有任何示例的链接吗?如果它们包括测试代码,那就太好了——Zend和TTD/自动测试对我来说还是很新的
    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
    }