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

发布于 2024-10-12 17:13:13 字数 6768 浏览 4 评论 0原文

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

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

但是,由于 .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 不允许嵌套类,因此我需要在控制器“外部”调用一个类,以帮助以一种使代码清晰易读的方式管理此抽象

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

I am writing an CSV/Excel-->MySQL import manager for an MVC application (Kohana/PHP).

I have a controller named "ImportManager" which has an action named "index" (default) which displays in a grid all the valid .csv and .xls files that are in a specific directory and ready for import. The user can then choose the files he wants to import.

However, since .csv files import into one database table and .xls files import into multiple database tables, I needed to handle this abstraction. Hence I created a helper class called SmartImportFile to which I send each file be it .csv or .xls and then I get then ask this "smart" object to add the worksheets from that file (be they one or many) to my collection. Here is my action method in PHP code:

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;
}

The SmartImportFile class looks like this:

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;
    }

}

In a code review I was told that it might be better not to have a helper class like this but to keep the bulk of the code in the controller action method itself. The argumentation was that you should be able to look at the code in a controller action and see what it does instead of having it call external helper classes outside of itself. I disagree. My argumentation is:

  • you should create a helper class anytime it makes code clearer, as in this case, it abstracts away the fact that some files have one worksheet or many worksheets in them, and allows for easy future extension, if, say, we want to also import from sqlite files or even directories with files in them, this class abstraction would be able to handle this nicely.
  • moving the bulk of the code from this helper class back into the controller would force me to create internal variables in the controller which make sense for this particular action, but may or may not make sense to other action methods within the controller.
  • if I were programming this in C# I would make this helper class a nested class which would literally be an internal data structure that is inside of and only available to the controller class, but since PHP does not allow nested classes, I need to call a class "outside" the controller to help manage this abstraction in a way that makes the code clear and readable

Based on your experience of programming in the MVC pattern, should the above helper class be refactored back into the controller or not?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

云柯 2024-10-19 17:13:13

控制器有两种方法:变薄或变厚。当我开始我的 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。主类使用一种特殊的方法来检测应该如何处理数据(策略模式)。控制器只是将文件列表传递给 Backend_Application_SmartImport 类的实例,并将 process 方法的结果传递给视图。

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

There are two approaches to controllers: make it thin or thick. When I started my adventure with MVC I made a mistake of creating thick controllers - now I prefer make it as thin as possible. Your solution is good in my opinion.

Here is how I would redesigned your code even further:

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
}

The idea is to have two classes responsible for processing xml and cvs inheriting from a base class. The main class uses a special method to detect how the data should be processed (Strategy Pattern). The controller just passed a list of files to the instance of Backend_Application_SmartImport class and passes result of process method to the view.

The advantage of my solution is that code is more decoupled and you can easily and in a clean way add new types of processing like xml, pdf, etc.

痴骨ら 2024-10-19 17:13:13

我同意你的观点,爱德华。

您的 ImportController 执行控制器应执行的操作。它生成文件列表供用户查看和操作,然后将该列表传递给视图以供其显示。我假设您有一个 process 操作或类似操作,用于在用户选择文件时处理请求,然后将该文件传递给相关的帮助程序。

Helper 是抽象的完美示例,其使用和存在完全合理。无论如何,它不与控制器耦合,也不需要耦合。 Helper 可以轻松地用在控制器不存在的其他场景中,例如 CRON 任务,这是一个公共 API,用户可以在没有 ImportController 的情况下以编程方式调用该 API。

这是你的权利。把它粘在他们身上!

I agree with you Edward.

Your ImportController does what a Controller is meant to do. It generates the list of files for the user to view and act on, it then passes that list to the View for it to display. I am presuming that you have a process action or similar which is handles the request when a user has selected a file, this file is then passed on to the Helper in question.

The Helper is a perfect example of abstraction and entirely justified in its usage and existence. It is not coupled with the Controller in anyway and doesn't need to be. The Helper could be easily used in other scenarios where the Controller is not present, for example a CRON task, a public API which users can call programmatically without your ImportController.

Your right on the ball with this one. Stick it to 'em!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文