PHP MVC:数据映射器模式:class 设计

PHP MVC: Data Mapper pattern: class design

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

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

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

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

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

如果我理解你的意思...

让您的所有具体映射器从一个共同的 class 继承 SQL 有几个您忽略的问题:

  • 域对象中的参数名称取决于列的名称
  • 映射器中有一个“获取方法”,没有相应的 table
  • 您仍有配置(table 名称),这是 superclass
  • 所期望的
  • 数据库架构必须将 id 作为所有 PRIMARY KEY 列的名称

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

参数和列名称

要创建一个共享的 findById() 方法,唯一实用的方法是围绕这样的东西构建它:

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

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

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

在普通查询中,您可以执行类似 SELECT name AS fullName FROM ... 的操作,这样您就可以使用查询 重命名 字段。但是“一刀切”,没有好的选择。

每个映射器可以通过id?

获取数据

所以,问题是,除非你有一个 mapper-per-table 结构(在这种情况下,活动记录开始看起来像实用选项),你最终会得到很少(非常常见)“映射器的“边缘案例”场景:

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

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

独立育儿?

要在 superclass 中实际获得此 findById() 方法,您需要一种方法将 table 名称传递给它。这意味着,您在 class 定义中有类似 protected $tableName 的内容。

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

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

对我来说,这似乎是一种 代码味道 ,因为信息实际上跨越了两个边界(因为缺少更好的词)。当此代码中断时,将在 superclass 中显示 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”在什么情况下代表什么变得越来越难。

我的建议是尝试对列使用相同的名称,当它是主键时和它是外键时(如果可能,因为在某些情况下,例如“闭包 tables,它不是可行)。基本上,所有存储相同类型 ID 的列都应具有相同的名称。

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

TL;DR

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