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

3 php oop model-view-controller design-patterns data-mapping

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

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

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

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

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

ter*_*ško 5

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

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

  • 域对象中的参数名称取决于列的名称
  • 映射器中有一个“获取方法”,没有相应的表
  • 您仍然拥有超类所期望的配置(表名称)
  • 数据库模式必须具有所有列id的名称PRIMARY KEY

现在,我将尝试逐一解开包装。

参数和列名称

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

"SELECT * FROM {$this->tableName} WHERE id = :id"
Run Code Online (Sandbox Code Playgroud)

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

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

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

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'
Run Code Online (Sandbox Code Playgroud)

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

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

作为一个小好处,你可以获得USING()语法糖。

长话短说

馊主意。你基本上破坏了LSP