PHP 域之间具有递归 MTM 关系的数据映射器

PHP Data Mapper with recursive MTM relations between Domains

我在 PHP MVC 中的模型层阅读了很多内容并开发了一个数据映射器(是的,我正在重新发明轮子,它正在学习)。

我可以声称我了解数据映射器和域对象之间的关系。

我努力以连贯的方式实现的是如何在数据映射器中检索嵌套关系,而不使映射器依赖于其他映射器。

这是一些相关域的示例:

  1. 用户
  2. 角色
  3. 权限

所以一个角色有很多权限,一个用户有很多角色:

User -> Role(s) -> Permission(s)

调用UserMapper -> fetch($id)时,我应该得到一个用户域。为了(荒谬的)简单起见,用户域有一个属性,roles,一组角色域。

到目前为止,还不错。在 UserMapper 中,我可以加入 roles table 到 users,匹配主键,获取与每个用户关联的所有角色,并为每个角色创建一个角色域。角色集合已添加到用户。

问题

此角色集合不完整。再次为了简单起见,每个角色都有一个 属性、permissions,一组权限域。

到目前为止我们所做的只是用角色集合填充用户域,但我们还没有获得与集合中每个角色关联的权限。角色集合不完整。

我的问题是:如何为每个用户填充每个角色的权限集合?

想法 1

这是非常简单的想法(就 PHP 实现而言)。 UserMapper 开始看起来像这样:

class UserMapper implements Mapper {
    public function __construct(RoleMapper $mapper)
}

并且在 RoleMapper 中:

class RoleMapper implements Mapper {
    public function __construct(PermissionMapper $mapper)
}

在 UserMapper 中,我们将获取匹配的用户。然后,我们将迭代 User 集合,并调用 RoleMapper -> fetch_by_user($user -> id)。 Role Mapper 获取匹配的角色,迭代生成的 Role 集合,并调用 PermissionMapper -> fetch_by_role($role -> id).

这很容易实现。发生的事情一目了然。但它感觉不对,而且在使用中可能会非常低效(至少,对于关系比这个例子更复杂的任何东西)。

我不喜欢数据映射器依赖于其他数据映射器 - 每个数据映射器不再独立,并且它并不总是进行自己的映射。 UserMapper 不知道集合中的确切内容 returns - 我们假设我们可以信任 RoleMapper,但从概念上讲,我们已经破坏了封装。

想法 2

我开始的地方。 UserMapper 使用联接来获取与每个用户关联的角色。

最初,这似乎很好。它非常高效——我们只执行一个 SQL 语句。数据映射器不需要受限于一个 table - 我对此很满意table。

但是...当我们需要递归地获取关系时会发生什么?

less comfortable 的想法是,UserMapper 不仅需要知道用户有角色(这很好),而且角色有权限。这个细节似乎超出了 UserMapper 的关注范围。

如果以后角色也有很多怎么办"Chocolate Foobars?"我不想更新 UserMapper 来正确检索所有这些 Chocolate Foobars。

我们可能还会添加一个新的 "Pet" 域,宠物也可以拥有角色。所以现在我必须更新我的 UserMapper 和我的 PetMapper 以支持这些与角色关联的疯狂 "Chocolate Foobars"。想法 1 完全消除了这个问题...但是它有自己的问题,如上所述。

结论

我开始写这个 post 希望简单地放下事情会给我一个解决方案(不能告诉你我在 Stack Overflow 中输入了多少次但最终没有按回车).到目前为止我还没有找到。

这些方法似乎都不是"right." 一方面,想法 1 保持了单个数据映射器的一致性,并且似乎具有更高的可维护性。但它也会导致非常高的查询计数(即使它们是基于索引的高度优化的查询)并且需要数据映射器将其他数据映射器作为依赖项,这感觉不对。

不过思路2好一点。顶级数据映射器的连接工作正常...除非您加入的域本身需要连接。

我很想听听解决此问题的方法。在 Data Mapper 场景中如何管理域之间的嵌套关系?

最终目标是通过调用 UserMapper -> fetch(),我们得到填充有角色集合的用户域,这些域本身也填充有权限集合。用户是具有角色的 MTM,角色是具有权限的 MTM。

这只是一个示例场景。其他包括 Blog post -> Image(s) -> Category(ies)Workstation -> Phone(s) -> Camera(s) -> Component(s).

以后一个例子为例,WorkstationMapper-> fetch()应该return一个WorkstationCollection我们可以做的地方:Workstation -> get_phone("TA-1033") -> get_camera("front") -> has_component("1080p video capture component").

您为什么要构建完整的 ORM?那种方式会导致疯狂。

所以,您的用户有多个角色,每个角色都有多个权限,您想要全部加载。那你为什么不这样做呢:

$roles = new Entity\RoleCollection;
$roles->forUser(new Entity\User($id));
// note: you really do not need to fetch user :)

$roleMapper = new Mapper\RoleCollection($pdo);
$roleMmapper->fetch($roles);
// populates the roles; if user is set, populates by user 

$permissionMapper = Mapper\RoleCollectionPermissions($pdo);
$permissionMapper->fetch($roles);
// populates the permission collections inside each of role collection entry 

基本上,如果您擅长 SQL,这将只需要两次查询。只要您坚持(至少部分地)为您的映射器明确命名方案,这种方式就没有任何魔力。您只获取需要的部分。

而且,如果您确实需要权限,而角色只是实现该目标的中介,那就让它更简单:

$permissions = new PermissionCollection;
$permissions->forUser(new Entity\User($id))

$permissionMapper = Mapper\PermissionCollection($pdo);
$permissionMapper->fetch($permissions);

如果权限集合有一个用户 class 作为条件分配,则在映射器中有两个深度连接。

如果您从 ORM 开始,那么制作递归加载器似乎很自然,它可以填充所有内容。但这不是您编写数据映射器的方式。这就是您制作通用 ORM 的方式。
坚持YAGNI原则。

TL;DR

编写映射器来处理您现有的场景,而不是一些通用的 "load everything" 任务。