【问题标题】:PHP MVC: Data Mapper pattern: class designPHP MVC:数据映射器模式:类设计
【发布时间】:2017-06-30 22:46:22
【问题描述】:

我有一个带有域对象和数据映射器的 Web MVC 应用程序。数据映射器的类方法包含所有数据库查询逻辑。我试图避免镜像任何数据库结构,因此在构造 sql 语句时实现最大的灵活性。所以,原则上,我尽量不使用任何 ORM 或 ActiveRecord 结构/模式。

我举个例子: 通常,我可以有一个抽象类AbstractDataMapper 被所有特定的数据映射器类继承——比如UserDataMapper 类。然后我可以在AbstractDataMapper 中定义一个findById() 方法,以通过给定的id 值获取特定表的记录——比如users——例如用户身份。但这意味着我总是从单个表中获取记录,而无法使用任何左连接也从与给定 id - 用户 ID 对应的其他一些表中获取一些其他详细信息。

所以,我的问题是: 在这些条件下——我自己有义务这样做,我应该实现一个抽象数据映射器类,还是每个数据映射器类都应该包含它自己完全“专有”的数据访问层实现?

我希望我能清楚地表达我的想法。如果我不清楚或者您有任何问题,请告诉我。

非常感谢您的时间和耐心。

【问题讨论】:

  • 作为我自己提供 500 赏金的人,这里有一个免费的建议:不要。我不得不要求模组稍后删除该帖子,因为我收到的答案是垃圾。它会吓跑那些不那么自信的人。而且,如果问题不是 ASAP 问题,您可以通过提供为期两周的 100 点赏金来获得更好的结果。见鬼,500 点预算,你可以在“赏金”部分置顶一个多月。
  • @tereško 感谢您的评论和善意的建议。我不知道。因此,我重新编辑了赏金部分。最后,我的目标确实是吸引好的答案。

标签: php oop design-patterns model-view-controller data-mapping


【解决方案1】:

如果我明白你的意思...

让你所有的具体映射器都从一个公共类继承 SQL 有几个你错过的问题:

  • 域对象中的参数名称取决于列的名称
  • 映射器中有一个“获取方法”,它没有对应的表
  • 您仍然拥有超类所期望的配置(表名)
  • DB 架构必须将 id 作为所有 PRIMARY KEY 列的名称

现在,我将尝试解开其中的每一个。

参数和列名

要创建共享的findById() 方法,唯一实用的方法是围绕以下内容构建它:

"SELECT * FROM {$this->tableName} WHERE id = :id"

主要问题实际上是通配符* 符号。

使用数据映射器填充实体有两种主要方法:使用 setter 或使用反射。在这两种情况下,参数/设置器的“名称”都隐含在您选择的列中。

在普通查询中,您可以执行SELECT name AS fullName FROM ... 之类的操作,这使您可以使用查询重新命名 字段。但是对于“统一方法”,没有好的选择。

每个mapper都可以通过id获取数据?

所以,问题是,除非您有一个每个表的映射器结构(在这种情况下,活动记录开始看起来像实用的选项),否则您最终会遇到几个(非常常见的)“边缘情况”场景映射器:

  • 仅用于保存数据
  • 处理集合而非单一实体
  • 聚合来自多个表的数据
  • 适用于具有复合键的表
  • 它实际上不是一个表,而是一个 SQL 视图
  • ...或以上的组合

您最初的想法在小规模项目中可以正常工作(一个或两个映射器是“边缘案例”)。但是对于大型项目,findById() 的使用将是例外而不是常态。

独立育儿?

要真正在超类中获得这个findById() 方法,您需要一种将表名传达给它的方法。这意味着,您的类定义中有 protected $tableName 之类的东西。

您可以通过在抽象映射器类中添加abstract function getTableName() 来缓解它,该类在实现时会返回一个全局常量值。

但是,当您的映射器需要处理多个表时会发生什么。

对我来说,这似乎是一种代码味道,因为信息实际上跨越了两个边界(因为没有更好的词)。当此代码中断时,将在超类中为 SQL 显示错误,这不是错误的来源(特别是,如果您使用常量)。

命名主键

这是一个比较有争议的观点:)

据我所知,调用所有主列id 的做法来自各种 ORM。所产生的惩罚仅适用于可读性(和代码维护)。考虑以下两个查询:

SELECT ar.id, ac.id 
  FROM Articles AS ar LEFT JOIN 
       Accounts AS ac ON ac.id = ar.account_id 
 WHERE ar.status = 'published'

SELECT ar.article_id, ac.account_id 
  FROM Articles AS ar LEFT JOIN 
       Accounts AS ac USING(account_id)
 WHERE ar.status = 'published'

随着数据库架构的增长和查询变得越来越复杂,实际跟踪“id”在什么情况下代表什么变得越来越难。

我的建议是为列尝试相同的名称,当它是主键时,当它是外键时(如果可能,因为在某些情况下,例如“闭表,它是不可行的)。基本上,所有列相同类型的商店 ID 应该具有相同的名称。

作为一个小奖励,您可以获得USING() 语法糖。

TL;DR

坏主意。你基本上是在破坏LSP

【讨论】:

  • 我刚看了你的回答,发现我需要更多的时间——现在有点累。我只想说:非常感谢你,tereško。以后我一定会深入阅读。
  • 嗨。我读了你的答案,实际上很多次。哇 :-) 如果可以的话,我想就你的答案问你两件事。您围绕findById() 示例构建了所有论点。您的意思是只向我展示一个案例,但也适用于其他映射器方法/情况。我理解对了吗?如果是这样的话,那么是的,你很好地理解了我的观点。我担心我只会收到有关使用 findById() 方法的答案,所以没有考虑整个数据映射器结构/设计。
  • 我的第二个问题是关于第 (3) 部分:“代码气味...信息实际上跨越了两个边界”,您的意思是我可以从多个表中获取数据一次,或者你的意思是关于类结构?
  • 我的最后一个问题,为了确定:在 TL;DR 你的意思是 实现抽象数据映射器类的坏主意,因为你基本上是通过使用它来破坏 LSP我>?很抱歉用这些问题打扰您,但我无法将我的理解与您自己的观点联系起来。再次感谢您的出色回答。
  • findById() 只是移动到超类的任何 SQL 代码的一个方便示例,甚至像 delete()exists() 这样的方法(不受“通配符问题”的影响) .至于“两个边界”部分..好吧..很难确定它甚至似乎是一个问题。归结为-调试。如果数据库架构发生变化而有人不知道所有部分,此更改会影响 PHP 代码(在您实际拥有团队的大型项目中的常见问题)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-10-01
  • 2010-09-17
  • 1970-01-01
  • 2017-04-22
  • 2017-10-25
  • 2011-10-11
  • 2013-04-10
相关资源
最近更新 更多