dongyue9860 2018-05-23 15:44 采纳率: 100%
浏览 57
已采纳

PHP Data Mapper,具有域之间的递归MTM关系

I'm reading a lot around the Model layer in PHP MVC and developing a Data Mapper (yes, I'm reinventing the wheel, it's learning).

I can claim I understand the relationship between Data Mappers and Domain Objects.

What I'm struggling to implement in a coherent fashion is how to retrieve nested relations in the Data Mapper, without making the Mapper dependent on other Mappers.

Here's an example of some related domains:

  1. User
  2. Role
  3. Permissions

So a Role has many Permissions, and a User has many Roles:

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

When calling UserMapper -> fetch($id), I should get a User domain. For the sake of (absurd) simplicity, the User domain has one property, roles, a collection of Role domains.

So far, so good. In the UserMapper, I can join the roles table to users, match for the primary key, get all the roles associated with each user, and create a Role domain for each one. The Role collection is added to the user.

The problem

This Role collection isn't complete. Again for the sake of absurd simplicity, each Role has one property, permissions, a collection of Permission domains.

All we've done so far is populate the User domain with a Role collection, but we haven't got the permissions associated with each Role in the collection. The Role collection is incomplete.

My question is: how to populate the Permission collection for each role, in each user?

Idea 1

This is the trivially simple idea (in terms of PHP implementation). UserMapper starts to look like this:

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

And in the RoleMapper:

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

In the UserMapper, we'd fetch the matching Users. Then, we'd iterate the User collection, and call RoleMapper -> fetch_by_user($user -> id). The Role Mapper gets the matching roles, iterates the resulting Role collection, and calls PermissionMapper -> fetch_by_role($role -> id).

This is easy to implement. It's blindingly obvious what's going on. But it feels wrong, and would probably be horribly inefficient in use (at least, for anything with more complicated relations than this example).

I don't like that data mappers are dependent on other data mappers - each data mapper is no longer self-contained, and it's not always doing its own mapping. The UserMapper does not know what will exactly be in the collection it returns - we assume we can trust the RoleMapper, but conceptually, we've broken encapsulation.

Idea 2

Where I started. The UserMapper uses a join to get the roles associated with each user.

Initially, this seems fine. It's efficient - we're just executing one SQL statement. Data Mappers don't need to be constrained to one table - I'm comfortable with that.

But... what happens when we need to get relations recursively?

What I'm less comfortable with, is the idea that the UserMapper not only needs to know that Users have Roles (that's fine), but that Roles have Permissions. This detail seems to lie beyond the UserMapper's concern.

What if later on, Roles also have many "Chocolate Foobars?" I don't want to be updating the UserMapper to retrieve all these Chocolate Foobars properly too.

It's possible that we also add a new "Pet" domain, and Pets can have Roles too. So now I have to update my UserMapper and my PetMapper to support these crazy "Chocolate Foobars" that are associated with Roles. This issue is entirely removed by Idea 1... but that has its own problems as outlined above.

Conclusion

I started writing this post in the hope that putting things down simply would present me with a solution (can't tell you how many times I've typed into Stack Overflow and ended up not hitting enter). So far I haven't found one.

Neither of these approaches seems "right." On the one hand, Idea 1 maintains individual data mapper consistency and appears to have higher maintainability. But it will also result in a very high query count (even if they are highly optimised queries based on indexes) and requires data mappers to have other data mappers as dependencies, which feels wrong.

But Idea 2 is little better. A join at the top-level data mapper works fine... unless the Domain you're joining itself requires joins.

I'd be interested to hear approaches to solving this problem. How do you manage nested relations between Domains in a Data Mapper scenario?

The eventual aim is by calling UserMapper -> fetch(), we get User domains populated with Role collections, that are themselves populated with Permission collections. Users are MTM with Roles, Roles are MTM with Permissions.

This is just an example scenario. Others include Blog post -> Image(s) -> Category(ies), or Workstation -> Phone(s) -> Camera(s) -> Component(s).

Taking the latter example, WorkstationMapper-> fetch() should return a WorkstationCollection where we can do: Workstation -> get_phone("TA-1033") -> get_camera("front") -> has_component("1080p video capture component").

  • 写回答

1条回答 默认 最新

  • dongqiongzheng0615 2018-05-23 17:38
    关注

    Why are you trying to build a full ORM? That way will lead to madness.

    So, your user has multiple roles and each role has multiple permissions and you want to load it all. So why don't you do it this way:

    $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 
    

    Basically, if you are good at SQL, this will require only two queries. And this way there isn't really any magic as long as you stick to (at least partially) clear naming scheme for your mappers. You only fetch the parts that you need.

    And, if you actually needed the permissions and the roles are just an intermediary for that goals, just make it simpler:

    $permissions = new PermissionCollection;
    $permissions->forUser(new Entity\User($id))
    
    $permissionMapper = Mapper\PermissionCollection($pdo);
    $permissionMapper->fetch($permissions);
    

    Have a two-deep join in the mapper, if the permission collection has a user class assigned as a condition.

    If you started out with ORMs, then it seems like the natural to make recursive loaders, that populate everything. But that not how you write data mappers. It's how you make universal ORMs.
    Stick to YAGNI principle.

    TL;DR

    Write mapper that handle you existing scenarios instead of some generic "load everything" tasks.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 扩散模型sd.webui使用时报错“Nonetype”
  • ¥15 stm32流水灯+呼吸灯+外部中断按键
  • ¥15 将二维数组,按照假设的规定,如0/1/0 == "4",把对应列位置写成一个字符并打印输出该字符
  • ¥15 NX MCD仿真与博途通讯不了啥情况
  • ¥15 win11家庭中文版安装docker遇到Hyper-V启用失败解决办法整理
  • ¥15 gradio的web端页面格式不对的问题
  • ¥15 求大家看看Nonce如何配置
  • ¥15 Matlab怎么求解含参的二重积分?
  • ¥15 苹果手机突然连不上wifi了?
  • ¥15 cgictest.cgi文件无法访问