模型类是应该表示实体还是返回实体

时间:2012-08-06 17:18:14

标签: php model-view-controller codeigniter

我正在设计一个CodeIgniter的网络应用程序(但我认为这个问题通常适用于网络应用程序中的MVC模式)。

当我为某个数据库实体设计模型类时(例如,BlogEntry用于说明),我基本上有两个选择:

经典的OOP"方法是让类代表实体,即类的一个实例是一个BlogEntry。在CodeIgniter中,这将导致像

这样的代码
class Blogentry extends CI_Model {
    function load($id) {
      // Access database
      $this->id = $dbresult.id;
      $this->title = $dbresult.title;
      $this->author = $dbresult.author;
    }
}

要访问某些博客条目,我会执行$this->load->model('blogentry');,调用$this->blogentry->load($id),然后使用模型类实例。

我经常在野外看到的另一种方法是让模型在某些数据结构中返回实体。在代码中:

class Blogentry extends CI_Model {
  function get_entry($id) {
    // Access database, yielding $dbresult
    return $dbresult;
}

有了这个,我会写

$this->load->model('blogentry');
$the_entry = $this->blogentry->get_entry($id);
在控制器中

并使用$the_entry。在第二种情况下,该类更像是factorybuilder类。

这两种方法中的任何一种都被认为是正确的方式"在MVC中做M?我想,我已经更频繁地看到了第二种方法,但我并没有看到那么多MVC-Webapps。有关为什么第一种或第二种方法可能更合适的任何想法?

3 个答案:

答案 0 :(得分:12)

首先,CodeIgniter 是一个MVC框架。最适合的范例可能是 MVP 或类似 ORM-Template-Adapter 的东西。因此,让我根据命名谬误分解我的答案:

在经典/传统的MVC中(不是什么框架声称是MVC),模型不是类或单个文件,而是包含所有域业务逻辑的一个层,通常只有Domain ObjectsData Mappers之类的东西,以及处理调用实体和域逻辑之间接口的东西,以便将这些代码保留在控制器之外(例如,在MVC中) 。 Tereško names them "services",这对于准官方名称来说已经足够了。

因此,在传统的MVC中,对模型层的请求通过“服务”进行,然后“服务”与域对象和数据映射器进行交互。

显然,所有这些与CodeIgniter和其他框架称之为“MVC”的内容没什么关系。在这种设计模式中(我简称为“CIMVC”),Model是一个由Controller调用和操作的类,它直接与数据库交互。将数据存储在模型中的成员变量中并不会产生很多语义上的意义,但如果您对类似的东西感兴趣,请查看使用CIMVC编写的任何ORM插件/库。 (虽然ORM解决方案在符合传统MVC方面不会更好......)

对于常见做法,我见过的大多数CI应用程序都将“原始”数据库数据返回给控制器进行处理;这使域业务逻辑保持在模型中。我在CIMVC中的个人偏好是最小化/消除模型中的域业务逻辑。

tl; dr - CodeIgniter中的“传统/适当”MVC基本上是不可能的,所以试图强制你的CI代码符合传统的MVC范式是愚蠢的(并且很可能会让你疯狂)

编辑:为了澄清模型层中有关业务逻辑的一些早期混淆,是的,您应该在模型层中拥有所有业务逻辑。但是,如果正在进行额外的数据处理(排序,操纵等),现在我们进入危险区域以防止DRY和SRP;您希望模型可以重复使用,因此要小心添加如此多的特殊逻辑,使其在应用程序的其他部分变得无法使用。

答案 1 :(得分:4)

这是一个与MVC范例相关的通用答案,因为我对CI有0次经验,但对Symfony,Zend,Propel和Doctrine有丰富的经验。

我发现,在使用doctrine时,在特定结构中返回对象数据时,最好添加一个指定所述结构的方法。例如,Doctrine_Record对象有一个toArray()方法,允许我将对象转换为数组,因此我既可以使用对象访问器,也可以调用toArray()并将对象作为数组处理。

// Accessing object data through available methods
foreach ($object->categories() as $category) {
    var_dump((string) $category);
}

// Accessing object data as an array
$objectAsArray = $object->toArray(true);

foreach ($objectAsArray as $element) {
    var_dump($element['Category']);
}

在一天结束时,您访问数据的方式取决于您的偏好,完全取决于您希望如何操作数据以满足您的需求。在某些情况下,以数组格式处理数据会更快,因为对象水合往往更容易受到性能影响。

但是,您访问数据时,我建议您将biz逻辑保留在模型层,这样您就不会重复代码(DRY:不要重复自己)。

答案 2 :(得分:3)

首先要了解的是CodeIgniter与MVC或MVC启发的设计模式相差甚远。 CodeIgniter实现的模式没有官方名称,但它与Ruby on Rails密切相关。

最严重的问题源于模型层的实施方式。建议文档使用active record模式,它本身会引入一些设计问题:

另一个主要问题是缺乏意见。视图应该是包含表示逻辑的实例。这包括选择用于表示信息的模板,这些模板是从模型层获取的。 视图不是模板,它们是使用模板的结构。

在CodeIgniter之上创建正确的模型层的最佳方法是忽略CI_Model,因为,恕我直言,扩展此类没有任何好处。而是创建像Services这样的东西。这些结构可以作为与模型层交互的接口(不是OO意义上的)。每个服务都可以处理多个domain objectsmappers

基本上,服务从控制器接收命令/消息,这些命令/消息改变模型层的状态并提供从模型层提取信息的方法。通常,提取将在 view 实例中进行,但在CodeIgniter中是不可能的。信息的提取也会发生在“控制器”中,将数据传递给模板。

代码看起来像这样:

class Library 
{

    protected $current_document = null;

    public function acquire_document( $id )
    {
        $document = load_class('Document', 'domain_object');
        $document->set_id( $id );

        $mapper = load_class('Document', 'persistence');
        $mapper->fetch( $document );

        $this->current_document = $document;
    }

    public function get_document_information()
    {
        $document = $this->current_document;        

        if ( $document === null )
        {
            return 'empty';
            // you either did not acquire the document
            // or document was not found
        }
        return $document->get_details();
    }
}

MVC设计模式实际上是两层的组合:模型层和表示层(应该主要来自控制器和视图)。

控制器永远不要直接在域对象(模型层的一部分)上获取,因为这意味着你有一个泄漏的抽象。

因此,总而言之,您所谓的“模型类”应该返回信息。