【问题标题】:What is the best practice for adding persistence to an MVC model?向 MVC 模型添加持久性的最佳实践是什么?
【发布时间】:2010-12-09 22:22:24
【问题描述】:

我正在用 PHP 实现一个超轻量级的 MVC 框架。从数据库、文件等加载数据似乎是一个普遍的观点,应该独立于模型,我同意。我不确定是将这个“数据层”链接到 MVC 的最佳方式。


Datastore 与 Model 交互

//controller
public function update()
{

 $model = $this->loadModel('foo');
 $data = $this->loadDataStore('foo', $model);

 $data->loadBar(9); //loads data and populates Model
 $model->setBar('bar');
 $data->save(); //reads data from Model and saves

}

控制器在模型和数据存储之间进行中介

看起来有点冗长,需要模型知道数据存储存在。

//controller
public function update()
{

 $model = $this->loadModel('foo');
 $data = $this->loadDataStore('foo');

 $model->setDataStore($data);

 $model->getDataStore->loadBar(9); //loads data and populates Model
 $model->setBar('bar');
 $model->getDataStore->save(); //reads data from Model and saves

}

数据存储扩展模型

如果我们想保存一个扩展数据库数据存储到平面文件数据存储的模型会发生什么?

//controller
public function update()
{

 $model = $this->loadHybrid('foo'); //get_class == Datastore_Database

 $model->loadBar(9); //loads data and populates
 $model->setBar('bar');
 $model->save(); //saves

}

模型扩展数据存储

这允许模型可移植性,但这样扩展似乎是错误的。此外,数据存储无法使用模型的任何方法。

//controller extends model
public function update()
{

 $model = $this->loadHybrid('foo');  //get_class == Model

 $model->loadBar(9); //loads data and populates
 $model->setBar('bar');
 $model->save(); //saves

}

编辑:模型与 DAO 通信

//model
public function __construct($dao)
{
    $this->dao = $dao;
}

//model
public function setBar($bar)
{
    //a bunch of business logic goes here
    $this->dao->setBar($bar);
}

//controller
public function update()
{
    $model = $this->loadModel('foo');
    $model->setBar('baz');
    $model->save();
}

非常感谢您对“最佳”选项或替代选项的任何意见。

【问题讨论】:

    标签: php model-view-controller frameworks


    【解决方案1】:

    我认为它不属于控制器 - 这确实是视图的一部分,它可以来来去去。业务逻辑和持久性不应依赖于视图或控制器。

    一个单独的服务层让您有机会将该逻辑作为分布式组件公开给非基于浏览器的客户端(例如,移动视图、批处理等)

    我不会让模型在面向对象的意义上扩展数据存储,因为继承是一种 IS-A 关系。正如您所指出的,该模型不是关系数据库。这只是持久化信息的众多选择之一。

    我认为这是更多的组合。持久层可以是一个单独的 DAO,它可以持久保存模型对象,而不需要它们知道它们的寿命更长的事实。或者你有 mixin 行为,其中模型宣传它具有 CRUD 操作的事实,但它只是将请求传递给给定的实现。

    通常的 Spring 习惯用法会具有一个独立于控制器的服务层。它了解用例和工作单元。它使用模型和持久性对象来实现用例的目标。

    MVC 意味着三个参与者。我会说比典型应用程序中的层更多 - 需要添加持久性和服务。

    更新:

    如果模型对象没有从持久化包中导入任何东西,那么它对此一无所知。这是一个简单的示例,使用一个简单的模型类 Person 及其 DAO 接口,每个接口都在自己的包中:

    package persistence.model;
    
    public class Person
    {
        private String name;
    
        public Person(String name)
        {
            this.name = name;
        }
    
        public String getName()
        {
            return name;
        }
    
        public void setName(String name)
        {
            this.name = name;
        }
    }
    

    请注意,DAO 接口导入模型包,而不是相反:

    package persistence.persistence;
    
    import persistence.model.Person;
    
    import java.util.List;
    
    public interface PersonDao
    {
        Person find(Long id);
        List<Person> find();
    
        void saveOrUpdate(Person p);
    }
    

    【讨论】:

    • 模型广告它具有 CRUD 操作是不言自明的,但我有点不确定如何在完全不了解它的模型的情况下实现 DAO。
    • 同意你的意见。但是,当您说“这确实是视图的一部分,可以来去去去”时,视图的一部分到底是什么?我以为我们只是在谈论模型和 orm/dao/data-persistence。 (也许评论为时已晚......晚了2年=P)
    • 就像我在第一句话中所说的:“来来去去”指的是控制器,在我看来这是视图的一部分。 (严格的 MVC 解释器会不同意。)DAO 逻辑不属于那里,除非在处理不可变的只读数据的特殊情况下。
    【解决方案2】:

    RedBeanPHP 使用一个名为 Fuse 的系统来解决这个问题。 (维基上的详细信息:http://redbeanphp.com/community/wiki/index.php/Fuse

    //Controller
    $foo = R::load("foo", 9);
    $foo->bar = "bar";
    R::store( $foo );
    

    在这里,您的模型实际上只不过是一个 bean。但是,系统会根据需要将 bean 与完整的模型融合在一起。

    class Model_Foo extends RedBean_SimpleModel {
      public function update() {
        if ($this->bar!="bar") do something... 
      }
    }
    

    框架将 Model_Foo 动态绑定到“foo”类型的 bean,并在保存之前调用 update() 方法。这是您可以存储业务规则的地方。除了update(),你还可以使用open()和delete()。

    【讨论】:

      【解决方案3】:

      我的投票是支持模型扩展数据存储的路线。这似乎是最自然的,因为实际上您的模型底层数据存储的扩展。但是,您可能希望使用服务设计模式将模型对象的创建与结果对象的使用解耦。

      class Model {
         protected $_tablename == __CLASSNAME__; // or some derivative of the classname
         protected $_datastore;
         protected $_typeOfDataStore;
      
         function __construct( $type == 'mysql' ) { 
            $this->_typeOfDataStore = $type;
            $this->bindToDataStore();
         }
      
         function bindToDataStore( $ds ) { 
            $this->_datastore = DataStoreFactory::buildDataStore( $this->_typeOfDataStore );
         }
      
      }
      
      class DataStoreFactory {
      
         function buildDataStore( $type ) {
           switch ( $type ) {
             case 'flatfile' : return new FlatFileDataStore();
             case 'mysql': return new MySQLDataStore();
           }
         }
      }
      
      class DataStoreBase {
         function connect() { }
         function disconnect() { }
         function getData() { }
         // ...
      } 
      
      class FlatFileDataStore extends DataStoreBase { }
      class MySQLDataStore extends DataStoreBase { 
         function runQuery() { }
      }
      

      那是建筑部分。当您需要创建/实现新模型时,您可以简单地扩展模型:

      class Users extends Model {
         protected $_tablename = 'users';
         protected $_typeOfDataStore = 'flatfile';
      
         function getAllUsers() {
            return $this->runQuery( "SELECT * FROM " . $this->_tablename );
         }
      
      }
      

      在你的控制器中:

      $usersModel = new Users();
      $usersModel->getAllUsers();
      

      您指出的关于数据存储无法使用模型的任何方法的反对意见是对系统的良好约束。模型是数据存储的消费者,因此数据存储不应直接调用模型对象的函数。模型应保持控制并在需要时使用数据存储服务。

      【讨论】:

        【解决方案4】:

        我认为您正在尝试构建一个与名称和概念过于紧密联系的框架。我发现模型-视图-控制器成语中的名称和概念没有严格定义。我还发现,在该范式中工作的代码有时需要能够改变规则。

        @duffymo 关于模型组件有一个非常重要的观点:模型不是数据存储,它只是有一个数据存储。如果不清楚,另一种思考方式是查看每个实例应该是什么。 “数据存储”对象的一个​​实例代表了一种资源,它介导对任意数量数据的访问。通常它是一个数据库连接。 “模型”对象的实例通常是具有多条数据的离散标识。通常它表示表中的数据库行,但它可以是文本文件中的一行,也可以是文件存储中的一个文件。

        对 Controller 和 View 应用相同的问题,您可以看到 Model 部分是完全不同的动物。虽然一次通常只有一个 Controller 对象和一个 View 对象存在,但很可能同时存在多个不同类型的不同 Model 对象。

        不幸的是,我也知道很多 MVC“框架”将“模型”定义为 API 层,您可以在该层后面执行 SQL 语句。在这些框架中,“模型”是一个静态类或单个实例,它只是提供几乎无用的命名空间分区。许多程序员认为这应该是有道理的,并与之斗争。 这就是为什么我建议您不要使用 MVC 习语的名称和概念。

        我首选的编写结构化 PHP 的方式只是向 MVC 范式致敬。它的顶部有调度程序和控制器逻辑,底部有 HTML 标记。这很有效,因为控制器和视图通常紧密耦合,并且将它们放在同一个文件中非常类似于 PHP。是的,在视图式代码中不执行控制器式操作确实需要纪律,但我宁愿自己做,也不愿让框架强迫我这样做。这些页面并不独立,但是,它们包含许多通用逻辑,并且可以导入轻量级调度程序系统之类的东西,甚至可以调用所有通用控制器逻辑。

        但通用逻辑提供的主要内容是数据抽象层——基本上是我的答案顶部描述的模型层。我编写的数据抽象层基本上是三样东西:一个数据库处理程序、一个“对象”对象和一个“集合”对象。

        数据库处理程序是数据库中所有对象的数据存储。它的主要目的是使不进行查询和获得响应的数据库活动都集中在一个地方。大多数数据库调用都在对象核心中。数据库处理程序对对象核心一无所知:它只接受 SQL 并返回结果集。

        另外两个对象被设计为子类,如果实例化它们自己将不起作用。使用继承和特殊的扩展声明(由静态类方法完成),他们知道如何转 数据库数据到一个对象。继承的“对象”对象的实例代表声明类的一行。它在实例化时被赋予一个 ID,并在需要检索其一行数据时查询数据库。它还跟踪更改,并且可以在被告知时执行单行更新。它提供的 API 完全没有 SQL:对象有字段 you ->get() 和 ->set(),完成后可以 ->save()。

        “集合”对象知道如何获取特定对象的经过过滤的行列表,并且知道如何将其转换为适当的单独对象,每个对象的功能就像它们被单独实例化一样。 (集合对象也支持对象本身,但我很少使用此功能,因为它目前有一些实现限制。我发现大多数程序员都难以掌握这个概念。)

        大多数对象只需要核心继承代码和特定表的声明。

        最终效果是 控制器代码 可以做如下明显的事情:

        $ticket = Ticket::Create($ticket_id);
        $ticket->set('queue', $new_queue);
        $ticket->set('queue_changed', date('Y-m-d H:i:s'));
        $ticket->save();
        

        ... 而不是像这样不透明的东西:

        TicketModel::ChangeTicketQueue($ticket_id, $new_queue);
        

        这种方法还允许您在 Ticket 对象中编写可能会在 queue 字段更改时更新其他字段或其他对象的内容,并且在您更改该字段时始终会发生这种情况,而不必记住在每个可以更改票证队列的函数。这也意味着您可以向 Ticket 添加其他方法:$ticket-&gt;get_queue() 为您执行 return TicketQueue::Create($this-&gt;get('queue')); 可能是有意义的。面向对象的编程是最好的! :-)

        所以,为了最终回答您的问题,我会在您的问题中推荐 none 的方法。你的模型对象应该为控制器代码提供一个统一的 API,它们如何存储数据取决于它们。如果它在数据库中,那么他们需要组织自己的调用来执行此操作。但是模型不是数据存储:但是,它可能一个。

        【讨论】:

        • 你是在建议“模型与 DAO 通信”之类的东西吗? (我在原始问题中添加了一个新示例来反映这一点。)
        • 是的,这将是一个很好的描述。但是,请注意“业务逻辑”的含义:我见过的大多数符合“业务逻辑”的代码都属于控制器,而不是模型。
        【解决方案5】:

        我最终用数据存储类装饰了我的模型:

        //datastore class
        public function __call($name, $arguments)
        {
        
            if (!method_exists($this->model, $name))
                throw new Exception('does not exist!');
        
            return call_user_func_array(array($this->model, $name), $arguments);
        
        }
        
        //controller - decorating
        $model = new Model;
        $datastore = new Datastore($model);
        $datastore->actions();
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-02-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-02-27
          • 2013-06-16
          • 2018-09-17
          相关资源
          最近更新 更多