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.
我有一个带有域对象和数据映射器的 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.