在 MVC 应用程序中从模型正确调用数据库?

Posted

技术标签:

【中文标题】在 MVC 应用程序中从模型正确调用数据库?【英文标题】:Properly calling the database from Model in an MVC application? 【发布时间】:2011-08-19 05:50:13 【问题描述】:

我正在构建一个小型 MVC 框架,用于学习/实验和小型项目。我需要了解模型内部的基础知识,因为一个完整的 MVC 框架和 ORM 对于几个数据库调用来说是多余的。

Class Model


使用空类我必须在哪里调用 new PDO 对象以进行数据库调用?

在模型中调用查询会是什么样子?

此外,我在哪里可以找到 MVC 的初学者网络/书籍资源(包含大量示例代码)?我听过很多术语,例如业务逻辑和数据库逻辑。我记得在某处读过,您应该将业务逻辑和数据库逻辑分开。我可以稍微理解这个概念,我只是想知道它是什么样子或者它们在代码本身中的含义。我很困惑应该如何将业务逻辑和数据库逻辑分开,但仍然在模型中。

我主要是在寻找代码/逻辑示例作为答案,除了后一段。

【问题讨论】:

【参考方案1】:

警告:此帖子中的信息极度过时。它代表了我对 MVC 模式的理解,就像 2 年前一样。当我开始使用它时,它会更新。大概这个月(2013.09)。 该死的! (2017.11)。

Model 本身不应包含 任何 SQL。曾经。它只包含域业务逻辑。

我推荐的方法是将严格来说不是“业务逻辑”的职责分成另外两组构造:Domain Objects 和Data Mappers。

例如,如果您正在创建博客,则模型不会是 Post。相反,该模型很可能是 Blog ,并且该模型将处理多个 Domain Objects:Post、Comment、User 和其他对象的多个实例。

在您的模型中,域对象不应该知道如何将自己存储在数据库中。甚至要知道存在 任何 形式的存储。这是Data Mappers 的责任。您在模型中应该做的就是致电$mapper->store( $comment );。并且数据映射器应该知道如何存储一种特定类型的域对象,并知道将信息放在哪个表中(通常单个域对象的存储实际上会影响多个表)。


一些代码

(仅来自文件的相关片段):

我假设您知道如何编写一个好的构造函数.. 如果您有疑问,请阅读this article 示例中没有命名空间,但应该是 任何以_ 开头的例子都是protected

来自/application/bootstrap.php

/* --- snip --- */

$connection = new PDO( 'sqlite::memory:' );
$model_factory = new ModelFactory( $connection );

$controller = new SomeController( $request , $model_factory );

/* --- snip --- */

$controller->$action();

/* --- snip --- */
控制器不需要知道数据库连接。 如果要更改整个应用程序的数据库连接,则需要更改单行 要改变模型的制作方式,您需要创建不同的类,该类实现与 ModelFactory 相同的接口

来自/framework/classes/ModelFactory.php

/* --- snip --- */

class ModelFactory implements ModelBuilderInterface

   /* --- snip --- */

   protected function _prepare()
   
      if ( $this->_object_factory === null  )
      
         $this->_object_factory = new DomainObjectFactory;
      
      if ( $this->_mapper_factory === null )
      
         $this->_mapper_factory = new DataMapperFactory( $this->_connection );
      
   

   public function build( $name )
   
      $this->_prepare();
      return new $name( $this->_object_mapper , $this->_data_mapper );
   

   /* --- snip --- */


只有数据映射器会使用数据库,只有映射器工厂需要连接 Model 的所有依赖都注入到构造函数中 应用程序中的每个 DataMapper 实例都使用相同的数据库连接,不需要Global State (video)。

文件/application/controllers/SomeController.php

/* --- snip --- */

   public function get_foobar()
   
      $factory = $this->_model_factory;
      $view = $this->_view;

      $foo = $factory->build( 'FooModel' );
      $bar = $factory->build( 'BarModel' );

      $bar->set_language( $this->_request->get('lang') );

      $view->bind( 'ergo' , $foo );

      /* --- snip --- */

   

/* --- snip --- */
控制器不知道模型创建细节 控制器只负责连线和改变元素的状态

文件/application/models/FooModel.php

/* --- snip --- */

   public function find_something( $param  , $filter )
   
      $something = $this->_object_factory('FooBar');
      $mapper = $this->_mapper_factory('FooMapper');

      $something->set_type( $param );
      $mapper->use_filter( $filter )->fetch( $something );

      return $something;
   

/* --- snip --- */
域对象负责验证给定的参数 视图接收并决定如何呈现它 mapper 获取对象并将存储中的所有必需信息放入其中(不必是 DB .. 它可以从某个文件或外部 REST API 中获取)

我希望这将帮助您理解数据库逻辑和业务逻辑(实际上也是表示逻辑)之间的分离


几点说明

Model 不应该扩展 Database 或 ORM,因为 Model 不是它们的子集。通过扩展一个类,您声明它具有超类的所有特征,但有一些小例外。

class Duck extends Bird
class ForestDuck extends Duck
// this is ok

class Table extends Database
class Person extends Table
// this is kinda stupid and a bit insulting

除了明显的逻辑问题之外,如果您的模型与底层数据库紧密耦合,它会使代码极难测试(谈论Unit Testing (video))。


我个人认为,ORM 在大型项目中是无用的,甚至是有害的。问题源于 ORM 试图弥合两种完全不同的解决问题的方式:OOP 和 SQL。

如果您使用 ORM 开始项目,那么经过短暂的学习曲线,您就可以非常快速地编写简单的查询。但是当您开始遇到 ORM 的限制和问题时,您已经完全投入到 ORM 的使用上(甚至可能聘请了新的人,他们非常擅长您选择,但在普通SQL)。您最终会遇到每个新的与数据库相关的问题都需要越来越多的时间来解决的情况。如果您一直在使用基于 ActiveRecord 模式的 ORM,那么问题会直接影响您的模型。

Uncle Bob 称其为“技术债务”。


几本书

与主题松散相关

Patterns of Enterprise Application Architecture Agile Software Development, Principles, Patterns, and Practices SQL Antipatterns: Avoiding the Pitfalls of Database Programming PHP Object-Oriented Solutions PHP in Action

【讨论】:

您希望将域对象文件存储在哪里(遵循您在本文中定义的松散架构)? 我有使用模仿目录的命名空间的习惯,所以我会在 /application/domain/ 中有 Domain\UserObjectPersistence\FooMapper 之类的东西/application/persistence 目录。【参考方案2】:

根据我的经验,不同的框架对 MVC 的解释有些松散,而且通常会有一些偏差。但是他们通常同意MVC是这样划分的:

模型 - 业务逻辑 + 数据存储/检索/结构化 查看 - 数据展示 控制器 - 分析请求后调用模型上的方法

我经常使用Symfony,可以举几个小例子。请注意,这些已大大简化。 :p

模型:

class MyUnitTable extends Doctrine_Table

    // .. various other pieces of code added by Symfony ... //
    public function getForAccount( Account $_account ) 
    $q = $this
         ->createQuery('e')
         ->leftJoin('e.User u')
         ->leftJoin('u.Profile p')
         ->leftJoin('p.Account a')
         ->where('a.accountid = ?', $_account->getAccountid());
    return $q->execute();
    


class myUnitActions extends sfActions

    public function executeIndex(sfWebRequest $request)
    
        $this->units = Doctrine_Core::getTable('MyUnit')->getForAccount($foo);
    

视图(这里只显示一个 sn-p):

<div id="my-unit-list-container">
    <ul>
    <?php foreach( $units as $unit ): ?>
        <li><?php echo $unit->getName(); ?></li>
    <?php endforeach; ?>
    </ul>
</div>

控制器:

控制器是处理请求的地方。它将分析请求并找出模型中应该调用的操作(例如上面的 myUnitActions::executeIndex())。

最后的笔记:

我相信您可以在上面的代码中看到发生了什么(ORM 增加了很多便利)。您让控制器向模型发送请求,模型在您的问题域中运行,加上实际从数据库中提取数据,最后是显示数据的视图。

这对作为开发人员的您有很多好处,因为它可以更轻松、更可靠地进行测试等。

Symfony 背后的人提供的21 Days With Jobeet 指南是您的最佳读物。这是一部不错的作品。

您还应该查看Symfony 和Zend 的代码。他们都是优秀的。还可以查看一些 ORM,例如 Doctrine 和 Propel。

另请参阅MVC 上的***文章。

【讨论】:

这回答了我的大部分问题,除了第一个问题。我也很欣赏 Symphony/Zend 的链接,但它太多了,我一直在寻找一个简单的例子。至于我已经说过的 ORM,对于这个特定的设置来说它是矫枉过正的。 -1 你的 MVC 元素职责列表是错误的。您可能应该更新有关 MVC 三元组每个部分的含义和角色的知识。 @teresko 感谢您指出这一点。我肯定是昨天把我的头往后拧了。【参考方案3】:

您可以查看Zend framework model 手册中提到的数据映射器模型方法

然后你有一个包含属性的模型,并有一个映射器,它执行实际的数据库交互来填充模型。

【讨论】:

MVC 是否需要映射器? @Tek 不,这不是强制性的,但这样的分离使代码更易于维护。【参考方案4】:

显然您不想每次创建新模型时都创建新连接,因此您希望单独初始化 PDO 对象。 (我确实遇到过一个内部 MVC 框架,其中 Model 扩展了他们的数据库类!你可以想象它并不快......)

也许你想要这样的东西?

abstract class Model

    protected $db = NULL;

    public function __construct (PDO $db)
    
        $this -> db = $db;
    

使用示例

class Article extends Model

    public $props = array ();

    private function getPage ($id)
    
        $q = $this -> db -> prepare ('select * from pages where pageId = :pageId');
        $q -> execute (array ('pageId' => $id));
        $ret = $res -> fetchAll ();
        return ($this -> props = $ret);
    


$db = new PDO ('dsn goes here');

$page = new Article ($db);
$page -> getPage (1234);

至于寻找例子?尝试在谷歌上搜索“php mvc”并查看出现的文章

【讨论】:

谷歌搜索 MVC 是我试图远离的那种事情。我不知道来源的有效性,也不知道它们是否正确,或者只是对这一切的过度简化。 您认为每个模型类的 PDO 对象很慢?我曾经遇到过一个遗留应用程序,其中一个单独的 Query 类扩展了数据库连接类,所以它试图为每个查询打开一个新的数据库连接!我应该把它发给 TheDailyWTF。 是的,说到dailyWTF,我的第一份编程工作是构建一个CMS,以取代原始开发人员必须付钱给火车猴子用香蕉制作的CMS。它将连接到数据库,进行查询,然后再次断开脚本中的每个查询!它也是循环执行的!欢乐时光。

以上是关于在 MVC 应用程序中从模型正确调用数据库?的主要内容,如果未能解决你的问题,请参考以下文章

在 ASP.NET MVC 中从部分视图发布动态表单数据

在 MVC 中从区域渲染局部视图

如何在 MVC 应用程序中从 JavaScript 解析相对路径

在 C# MVC 应用程序中,是不是可以在未更改的控件中从网页提交变量?

在 ASP.Net MVC 应用程序中从 Facebook SDK for .Net 获取 Facebook 用户访问令牌

在 ASP.NET MVC 5 中从数据库加载 Razor 视图