这个MVC控制器代码是否需要重构?

时间:2011-01-19 08:09:39

标签: php model-view-controller controller helpers

我正在为MVC应用程序(Kohana / PHP)编写 CSV / Excel - > MySQL导入管理器

我有一个名为“ImportManager”的控制器,它有一个名为“index”的操作(默认),它在网格中显示所有有效的.csv.xls文件在特定目录中并准备导入。然后,用户可以选择要导入的文件。

但是,由于.csv个文件导入一个数据库表,而.xls个文件导入多个数据库表,我需要处理此抽象即可。因此,我创建了一个名为SmartImportFile帮助类,我将每个文件发送到.csv.xls,然后我会问这个“智能”对象将该文件中的工作表(是一个或多个)添加到我的集合中。这是我在PHP代码中的动作方法:

public function action_index()
{
    $view = new View('backend/application/importmanager');

    $smart_worksheets = array();
    $raw_files = glob('/data/import/*.*');
    if (count($raw_files) > 0)
    {
        foreach ($raw_files as $raw_file)
        {
            $smart_import_file = new Backend_Application_Smartimportfile($raw_file);
            $smart_worksheets = $smart_import_file->add_smart_worksheets_to($smart_worksheets); 
        }
    }
    $view->set('smart_worksheets', $smart_worksheets);

    $this->request->response = $view;
}

SmartImportFile类如下所示:

class Backend_Application_Smartimportfile
{
    protected $file_name;
    protected $file_extension;
    protected $file_size;
    protected $when_file_copied;
    protected $file_name_without_extension;
    protected $path_info;
    protected $current_smart_worksheet = array();

    protected $smart_worksheets = array();

    public function __construct($file_name)
    {
        $this->file_name = $file_name;
        $this->file_name_without_extension = current(explode('.', basename($this->file_name)));

        $this->path_info = pathinfo($this->file_name);
        $this->when_file_copied = date('Y-m-d H:i:s', filectime($this->file_name));
        $this->file_extension = strtolower($this->path_info['extension']);
        $this->file_extension = strtolower(pathinfo($this->file_name, PATHINFO_EXTENSION));
        if(in_array($this->file_extension, array('csv','xls','xlsx')))
        {
            $this->current_smart_worksheet = array();
            $this->process_file();
        }
    }

    private function process_file()
    {
        $this->file_size = filesize($this->file_name);
        if(in_array($this->file_extension, array('xls','xlsx')))
        {
            if($this->file_size < 4000000)
            {
                $this->process_all_worksheets_of_excel_file();
            }
        }
        else if($this->file_extension == 'csv')
        {
            $this->process_csv_file();
        }

    }

    private function process_all_worksheets_of_excel_file()
    {
        $worksheet_names = Import_Driver_Excel::get_worksheet_names_as_array($this->file_name);
        if (count($worksheet_names) > 0)
        {
            foreach ($worksheet_names as $worksheet_name)
            {
                $this->current_smart_worksheet['name'] = basename($this->file_name).' ('.$worksheet_name.')';
                $this->current_smart_worksheet['kind'] = strtoupper($this->file_extension);
                $this->current_smart_worksheet['file_size'] = $this->file_size;
                $this->current_smart_worksheet['when_file_copied'] = $this->when_file_copied;
                $this->current_smart_worksheet['table_name'] = $this->file_name_without_extension.'__'.$worksheet_name;
                $this->assign_database_table_fields();
                $this->smart_worksheets[] = $this->current_smart_worksheet;
            }
        }
    }

    private function process_csv_file()
    {
        $this->current_smart_worksheet['name'] = basename($this->file_name);
        $this->current_smart_worksheet['kind'] = strtoupper($this->file_extension);
        $this->current_smart_worksheet['file_size'] = $this->file_size;
        $this->current_smart_worksheet['when_file_copied'] = $this->when_file_copied;

        $this->current_smart_worksheet['table_name'] = $this->file_name_without_extension;
        $this->assign_database_table_fields();


        $this->smart_worksheets[] = $this->current_smart_worksheet;
    }

    private function assign_database_table_fields()
    {
        $db = Database::instance('import_excel');
        $sql = "SHOW TABLE STATUS WHERE name = '".$this->current_smart_worksheet['table_name']."'";
        $result = $db->query(Database::SELECT, $sql, FALSE)->as_array();
        if(count($result))
        {
            $when_table_created = $result[0]['Create_time'];
            $when_file_copied_as_date = strtotime($this->when_file_copied);
            $when_table_created_as_date = strtotime($when_table_created);
            if($when_file_copied_as_date > $when_table_created_as_date)
            {
                $this->current_smart_worksheet['status'] = 'backend.application.import.status.needtoreimport';
            }
            else
            {
                $this->current_smart_worksheet['status'] = 'backend.application.import.status.isuptodate';
            }
            $this->current_smart_worksheet['when_table_created'] = $when_table_created;
        }
        else
        {
            $this->current_smart_worksheet['when_table_created'] = 'backend.application.import.status.tabledoesnotexist';
            $this->current_smart_worksheet['status'] = 'backend.application.import.status.needtoimport';
        }
    }

    public function add_smart_worksheets_to(Array $smart_worksheets = array())
    {
        return array_merge($smart_worksheets, $this->get_smart_worksheets());
    }

    public function get_smart_worksheets()
    {
        if ( ! is_array($this->smart_worksheets))
        {
            return array();
        }

        return $this->smart_worksheets;
    }

}

代码审核中,我被告知可能更好没有像这样的帮助类,但要保留控制器操作方法中的大部分代码本身。论证是你应该能够查看控制器动作中的代码并查看它的作用,而不是让它本身调用外部辅助类。 我不同意。我的论证是:

  • 你应该在代码更清晰的任何时候创建一个帮助类,因为在这种情况下,它抽象出一些文件有一个工作表或许多文件的事实其中的工作表,如果我们想要从 sqlite 文件甚至目录导入其中的文件,也可以轻松将来扩展,类抽象可以很好地处理这个问题。
  • 将大部分代码从此帮助程序类移回控制器将迫使我在控制器中创建内部变量,这对于此特定操作有意义,但可能有意义或可能没有意义控制器内的其他操作方法。
  • 如果我在 C#中对此进行编程,我会将此帮助类设为嵌套类,这实际上是内部数据结构在控制器类内部并且仅可用,但由于 PHP 不允许嵌套类,我需要在控制器“外部”调用一个类来帮助管理这种抽象,使代码清晰

根据您在MVC模式中编程的经验,是否应将上述辅助类重构为控制器?

2 个答案:

答案 0 :(得分:2)

控制器有两种方法:使其变薄或变厚。当我开始使用MVC进行冒险时,我犯了一个创建厚控制器的错误 - 现在我更喜欢尽可能地让它变薄。在我看来,你的解决方案很好。

以下是我将如何进一步重新设计代码的方法:

class Backend_Application_SmartImport {

    public function __construct( $raw_files ) {
    }

    public function process() {     
        foreach ($raw_files as $raw_file) {
            // (...)
            $oSmartImportFileInstance = $this->getSmartImportFileInstance( $smart_import_file_extension );
        }
    }   

    protected function getSmartImportFileInstance( $smart_import_file_extension ) {
        switch ( $smart_import_file_extension ) {
            case 'xml':
                return new Backend_Application_SmartImportFileXml();
            // (...)
        }
    }
}

abstract class Backend_Application_SmartImportFile {
    // common methods for importing from xml or cvs
    abstract function process();
}

class Backend_Application_SmartImportFileCVS extends Backend_Application_SmartImportFile {
    // methods specified for cvs importing
}

class Backend_Application_SmartImportFileXls extends Backend_Application_SmartImportFile {
    // methods specified for xls importing
}

这个想法是让两个类负责处理从基类继承的xml和cvs。主类使用特殊方法来检测数据的处理方式(Strategy Pattern)。控制器只是将文件列表传递给Backend_Application_SmartImport类的实例,并将流程方法的结果传递给视图。

我的解决方案的优点是代码更加分离,您可以轻松,干净地添加新类型的处理,如xml,pdf等。

答案 1 :(得分:1)

我同意你的看法。

您的ImportController执行控制器的目的。它生成用户要查看和操作的文件列表,然后将该列表传递给View以显示它。我假设你有一个process动作或类似动作,当用户选择了一个文件时,它会处理请求,然后将这个文件传递给有问题的Helper。

Helper是抽象的完美范例,在使用和存在方面完全合理。无论如何它都不与Controller耦合,也不需要。 Helper可以很容易地用在Controller不存在的其他场景中,例如CRON任务,用户可以在没有ImportController的情况下以编程方式调用的公共API。

这个球的权利。坚持下去吧!