NotionCommotion Posted October 15, 2022 Share Posted October 15, 2022 I need certain entity classes (i.e. ResourceUnderAccessControOne and ResourceUnderAccessControlTwo) to have more granular access control, and will need to store applicable for each resource on a per-record basis. I am using Doctrine, and one way to do so is have ResourceUnderAccessControlOne and ResourceUnderAccessControlTwo extend AccessControl, however, there are certain shortcomings with Doctrine and inheritance. As an alternative approach, I am thinking of using a one-to-one between each individual resource class and Acl, however, for no particular reason, I have concerns with this approach. Should I? The following provides a bit more context. I do not believe AccessControl will ever need to directly get the resource tied to it, but the resource will need to directly access its associated AccessControl object. For this schema, ResourceUnderAccessControlOne and ResourceUnderAccessControlTwo can share the same AccessControl which goes against business rules, however, either I can have PHP enforce this rule or add a "type" column to all tables and add the appropriate foreign keys. While "maybe" I shouldn't be implementing access control this way, I would still hope for advise for the stated question. Thanks! AccessControl - id (pk) - applicableDataForAccessControl ResourceUnderAccessControlOne - id (pk, FK to AccessControl.id) - applicableDataforResourceOne ResourceUnderAccessControlTwo - id (pk, FK to AccessControl.id) - applicableDataforResourceTwo Quote Link to comment https://forums.phpfreaks.com/topic/315426-alternatives-to-inheritance-question-273/ Share on other sites More sharing options...
requinix Posted October 16, 2022 Share Posted October 16, 2022 I mean, I guess composition makes sense here? I wouldn't think a resource "is a" access control, but it certainly could "use a" access control. But depending what "access control" is, I'm wondering if maybe what you need is an intermediate "access policy"? Quote Link to comment https://forums.phpfreaks.com/topic/315426-alternatives-to-inheritance-question-273/#findComment-1601688 Share on other sites More sharing options...
maxxd Posted October 16, 2022 Share Posted October 16, 2022 Yeah, there are still I think kind of a ton questions here. Inheritance could potentially serve the purpose in this case (see Laravel controllers, models, etc. for example) but maybe you'd be better off with a prototype or adapter pattern? Or, assuming I'm not reading the question wrong, maybe an interface; that way you'd have known methods with dynamic implementations. Quote Link to comment https://forums.phpfreaks.com/topic/315426-alternatives-to-inheritance-question-273/#findComment-1601690 Share on other sites More sharing options...
NotionCommotion Posted October 16, 2022 Author Share Posted October 16, 2022 Thank you requinix and maxxd, Sorry about the lack of details and misused definitions. "Resource" is just an entity class and "access control" is the required permission needed to access a particular record. In addition, there is a ResourceMember object which is associated with a single User and a single resource object which also has required permission data, and this is the part which I am struggling with. One approach I might take is something like: AbstractResourceUnderAccessControl - id (PK) - discriminator - applicableData ResourceUnderAccessControlOne extends AbstractResourceUnderAccessControl - id (PK, FK to resource_under_access_control.id) - applicableData ResourceUnderAccessControlTwo extends AbstractResourceUnderAccessControl - id (PK, FK to resource_under_access_control.id) - applicableData ResourceMember - resource_under_access_control_id (PK, FK to resource_under_access_control.id) - user_id (PK, FK to user.id) - applicableData But doing it this way paints me into a corner, and would like to consider having each ResourceUnderAccessControl and the associated AccessControl different objects and using composition. But it just seems wrong having all these independent ResourceUnderAccessControl entities having a one-to-one relationship to a single AccessControl entity. In addition to making it more complicated to have the database enforce an AccessControl object to only be associated with a single resource, there is also the concern of later putting another resource under access control and having conflicting IDs, however, for this situation, I am using ULIDs and am not worried about it. While I very much appreciate your specific recommendations and advise how to best solve my immediate access control need, I also very much want to better figure out this generic concept as I have been struggling with it literally for years (composition-over-inheritance-examples, alternatives-to-entity-inheritance, favor-traits-with-interfaces-over-inheritance). Is there anything fundamentally wrong with doing it as I showed in my initial post? Quote Link to comment https://forums.phpfreaks.com/topic/315426-alternatives-to-inheritance-question-273/#findComment-1601692 Share on other sites More sharing options...
requinix Posted October 16, 2022 Share Posted October 16, 2022 I think I might have guessed right regarding an access policy. How much "customization" does each resource need regarding its access? I would assume not much, and that they all typically pick from a small handful of possibilities. If so then you have access policies, a resource uses an access policy, every user has something to consume policies in a similar design, then you manage access through those secondary objects. user <-> permission policy <-> access policy <-> resource The principle here is that a resource does not try to decide how and where it can be used - it has a policy which manages that. And a user doesn't decide how and what it can use - it has permissions that decide. Consider a roller coaster ride. They'll have a sign saying "you must be this tall to ride" and a person who enforces that; the roller coaster is the resource and the sign is the access policy. When someone wants to ride, they present themselves; they are a user and their permissions are their physical appearance (ie. height). The person who enforces the height requirement would then be the code used to implement the system - someone who understands the access policy, the permissions, and how to evaluate the two together. 2 Quote Link to comment https://forums.phpfreaks.com/topic/315426-alternatives-to-inheritance-question-273/#findComment-1601697 Share on other sites More sharing options...
NotionCommotion Posted October 16, 2022 Author Share Posted October 16, 2022 I like the roller coaster analogy! Yep, the roller coaster access policy is as follows: Giant Dipper Ride with an adult: 36" tall or 6 years old Ride solo: 48" tall or 13 years old American Eagle Ride with an adult: 32" tall or 9 years old Ride solo: 38" tall or 15 years old The Fury Ride with an adult: 38" tall or 5 years old Ride solo: 42" tall or 16 years old And, not just roller coasters, but similar requirements are used for ferris wheels, haunted houses, bumper cars, and carousels which are modelled slightly differently than roller coasters. If the user doesn't meet these requirements, they may take a specialized class specifically for a given ride, and based on the grade they received, the requirements are reduced. I model it as such, but later, there is some new requirement and I find the inheritance prevents the flexibility to make the required changes. So, how should I model it? AbstractRide - id (PK) - minimum_height_with_adult - minimum_age_with_adult - minimum_height_solo - minimum_age_solo - discriminator (roller_coaster, ferris_wheel, etc) - applicableCommonData RollerCoaster extends AbstractRide - id (PK, FK to AbstractRide.id) - applicableData FerrisWheel extends AbstractRide - id (PK, FK to AbstractRide.id) - applicableData HauntedHouse extends AbstractRide... UserTakesRideClass - ride_id (PK, FK to AbstractRide.id) - user_id (PK, FK to User.id) - grade User - id - height - age Quote Link to comment https://forums.phpfreaks.com/topic/315426-alternatives-to-inheritance-question-273/#findComment-1601699 Share on other sites More sharing options...
requinix Posted October 17, 2022 Share Posted October 17, 2022 Can't be abstract anymore. What kind of resources and what sorts of ways of controlling access do you need for them? Quote Link to comment https://forums.phpfreaks.com/topic/315426-alternatives-to-inheritance-question-273/#findComment-1601700 Share on other sites More sharing options...
maxxd Posted October 17, 2022 Share Posted October 17, 2022 Yeah, at this point I don't think there's enough concrete information to give advice. Given what you've described, I'm not sure I can come up with a change that would bork the whole system less than it would using a different pattern. Also, as I understand it using an abstract class is not quite the same as inheritance - it's more akin to using an interface with some pre-baked methods. While this can be problematic for the same reasons as concrete inheritance, it can also imply an interface for an API. From my reading, folks in general seem less adamant against extending an abstract class than a concrete one. Either way, as much as you're able without talking out of turn job-wise, what exactly is the situation? Quote Link to comment https://forums.phpfreaks.com/topic/315426-alternatives-to-inheritance-question-273/#findComment-1601702 Share on other sites More sharing options...
NotionCommotion Posted October 17, 2022 Author Share Posted October 17, 2022 I am not trying to be vague and the roller coaster example is very close. And while I appreciate your help regarding my approach to access policy, my desire is to determine how best to define the database schema and relate the entity objects. I have provided specific details below and hope that it doesn't distract from what I thought was a simple question regarding modelling using composition instead of inheritance. I have these entities (Project, Asset, Vendor) which have properties and some (Project, Asset, but not Vendor) also act as a container for documents. There are three types of users: AdminUser, InternalUser, and ExternalUser, and internal and external users will typically have different access policies. Admin users have the capability to create the parent entities and specify on a per-record basis the access policy for the parent entity and for the documents which they contain. The access policy for the parent entity determines ability to read and update with possible values ALL/NONE. The access policy for the documents is common to all documents contained in a given parent entity, determines ability to create, read, update, and delete with possible values ALL/OWNER/NONE where OWNER represents the logged on user being the user who created the document (note that create doesn't support OWNER). Both internal and external users may also join as a member to a given parent entity, and their permission policy maybe be modified for the given entity. An example Project is as follows, Asset will look the same, and Vendor will look the same except not have the document permission policies. { "id": "01GFJTS5W4DQGVRD6FT1P5SQNW", "name": "MyProject", "type": "project", "otherProperties": "bla", "internalUserAccessPolicy": { "read_parent": "ALL", "update_parent": "NONE", "create_documents": "ALL", "read_documents": "ALL", "update_documents": "OWNER", "delete_documents": "NONE" }, "externallUserAccessPolicy": { "read_parent": "NONE", "update_parent": "NONE", "create_documents": "NONE", "read_documents": "OWNER", "update_documents": "OWNER", "delete_documents": "NONE" }, "members": [{ "userId": "01GFJTSKMT2VVZF90X89D33S38", "UserPermissionPolicy": { "read_parent": "ALL", "update_parent": "NONE", "create_documents": "ALL", "read_documents": "ALL", "update_documents": "OWNER", "delete_documents": "OWNER" } }, { "userId": "01GFJV47D64FNBRM4YW1HGK49J", "UserPermissionPolicy": { "read_parent": "ALL", "update_parent": "NONE", "create_documents": "ALL", "read_documents": "OWNER", "update_documents": "OWNER", "delete_documents": "NONE" } } ], "documents": [{ "documentId": "01GFJVKAC2CT077865KX97TQVH", "name": "siteplan.pdf", "path": "https://example.com/documents/01GFJV9WV055RZDDYFA4W6MPJC", "owner": "01GFJTSKMT2VVZF90X89D33S38" }, { "documentId": "01GFJV9WV055RZDDYFA4W6MPJC", "name": "drawings.pdf", "path": "https://example.com/documents/01GFJVKAC2CT077865KX97TQVH", "owner": "01GFJTSKMT2VVZF90X89D33S38" } ] } The schema is currently as follows: AbstractResource - id (PK) - name - type - internalUserAccessPolicy (value object) - externallUserAccessPolicy (value object) - otherProperties Project extends AbstractResource - id (PK, one-to-one FK to AbstractResource.id) - otherProperties Asset extends AbstractResource - id (PK, one-to-one FK to AbstractResource.id) - otherProperties User - id (PK) - name Member - resourceId (PK, many-to-many FK to AbstractResource.id) - userId (PK, many-to-many FK to User.id) - userAccessPermission (value object) I am thinking of changing the schema to the following where Project and Asset will now have an AccessPolicy property: ResourceAccessPolicy - id (PK) - discriminator (ResourceAccessPolicy or DocumentAccessPolicy) - internalUserResourceAccessPolicy (value object) - externallUserResourcePolicy (value object) DocumentAccessPolicy extends ResourceAccessPolicy - id (PK, one-to-one FK to ResourceAccessPolicy.id) - internalUserDocumentAccessPolicy (value object) - externallUserDocumentPolicy (value object) Project - id (PK, one-to-one FK to DocumentAccessPolicy.id) - discriminatorCopy (FK to ResourceAccessPolicy.className in order to prevent multiple resources from being associated with the same ResourceAccessPolicy) - otherProperties Asset - id (PK, one-to-one FK to DocumentAccessPolicy.id) - discriminatorCopy (FK to ResourceAccessPolicy.className in order to prevent multiple resources from being associated with the same ResourceAccessPolicy) - otherProperties Vendor - id (PK, one-to-one FK to ResourceAccessPolicy.id) - discriminatorCopy (FK to ResourceAccessPolicy.className in order to prevent multiple resources from being associated with the same ResourceAccessPolicy) - otherProperties User - id (PK) - name Member - accessPolicyId (PK, FK to ResourceAccessPolicy.id) - userId (PK, FK to User.id) - userAccessPermission (value object) Maybe it is obvious that I should do it this way, but I've never done it this way and for unknown reasons, have concerns. While I will gladly accept any access policy specific critique, please also let me know whether there is anything fundamentally wrong with this schema approach and whether I should make any changes. Quote Link to comment https://forums.phpfreaks.com/topic/315426-alternatives-to-inheritance-question-273/#findComment-1601708 Share on other sites More sharing options...
NotionCommotion Posted October 17, 2022 Author Share Posted October 17, 2022 I gave a little thought on why I have concerns about my proposed schema changes (Option 1). The problem is I have more concerns about the alternate approaches I could think of (Option 2 and Option 3). Option 1. Object that has/owns a property references that property instead of the other way around. Orphan PermissionPolicy records can exist if no Projects, Assets, etc reference it. Can't delete a Projects, Assets, etc, and cascade delete the PermissionPolicy record. Adds complexity to ensure that a given PermissionPolicy is referenced by only a single Project, Asset, etc. Can't switch from OneToOne to ManyToOne by simply removing the unique constraint. Project - Id (PK) - PermissionPolicyId (FK to PermissionPolicy.Id, UNIQUE) - Data Asset - Id (PK) - PermissionPolicyId (FK to PermissionPolicy.Id, UNIQUE) - Data PermissionPolicy - Id (PK) - RequiredPermissionData Member - PermissionPolicyId (PK, FK to PermissionPolicy.Id) - UserId (PK, FK to User.Id) - MemberPermissionData User - Id (PK) - GeneralUserPermissionData - OtherData Option 2. Unique tables for each entity. I don't care for the duplication. User will have individual collections for each type. Project - Id (PK) - Data Asset - Id (PK) - Data ProjectPermissionPolicy - Id (PK) - ProjectId (FK to Project.Id, CASCADE DELETE, UNIQUE) - RequiredPermissionData AssetPermissionPolicy - Id (PK) - AssetId (FK to Asset.Id, CASCADE DELETE, UNIQUE) - RequiredPermissionData ProjectMember - ProjectPermissionPolicyId (PK, FK to ProjectPermissionPolicy.Id, CASCADE DELETE) - UserId (PK, FK to User.Id) - MemberPermissionData AssetMember - AssetPermissionPolicyId (PK, FK to AssetPermissionPolicy.Id, CASCADE DELETE) - UserId (PK, FK to User.Id) - MemberPermissionData User - Id (PK) - GeneralUserPermissionData - OtherData Option 3. Extend every entity from AbstractBaseEntity. I have a few entities which do use inheritance (i.e. CommonToAllSpecification and CustomUserDefinedSpecification) and I will need to have CommonToAllSpecification joined even though there is no access policy for it. Later adding access control to an entity will require changing the primary keys. AbstractBaseEntity - Id (PK) Project extends AbstractBaseEntity - Id (PK) - Data Asset extends AbstractBaseEntity - Id (PK) - Data PermissionPolicy - Id (PK) - AbstractBaseEntityId (FK to AbstractBaseEntity.Id, CASCADE DELETE, UNIQUE) - RequiredPermissionData Member - PermissionPolicyId (PK, FK to PermissionPolicy.Id, CASCADE DELETE) - UserId (PK, FK to User.Id) - MemberPermissionData User - Id (PK) - GeneralUserPermissionData - OtherData Quote Link to comment https://forums.phpfreaks.com/topic/315426-alternatives-to-inheritance-question-273/#findComment-1601730 Share on other sites More sharing options...
NotionCommotion Posted October 20, 2022 Author Share Posted October 20, 2022 Really hoping to get my dilemma resolved as I have tried to multiple times and it just stays in limbo. I provided more access control detail per request even though my real question relates to how best to allow a single object to have reciprocating relationships to multiple other objects. I recognize that no one has any obligation to help, yet many of you have done so and most of what I know is thanks to you. If there just isn't a good answer, please also let me know that so I may stop searching. I am starting to think that ORMs or at least Doctrine too closely couple the DB schema to the object model, and if I wish to use one, I need to compromise on the database schema or other aspects. Thanks again Quote Link to comment https://forums.phpfreaks.com/topic/315426-alternatives-to-inheritance-question-273/#findComment-1601805 Share on other sites More sharing options...
NotionCommotion Posted October 24, 2022 Author Share Posted October 24, 2022 Please? Quote Link to comment https://forums.phpfreaks.com/topic/315426-alternatives-to-inheritance-question-273/#findComment-1601884 Share on other sites More sharing options...
NotionCommotion Posted November 1, 2022 Author Share Posted November 1, 2022 Pretty please? Quote Link to comment https://forums.phpfreaks.com/topic/315426-alternatives-to-inheritance-question-273/#findComment-1602119 Share on other sites More sharing options...
maxxd Posted November 1, 2022 Share Posted November 1, 2022 The first schema is probably where I'd start looking at it, although it may be worth it to add a linking table between the members and permissions policies tables. But honestly that's going to have to be dictated by the current business logic for the project. Quote Link to comment https://forums.phpfreaks.com/topic/315426-alternatives-to-inheritance-question-273/#findComment-1602120 Share on other sites More sharing options...
NotionCommotion Posted November 5, 2022 Author Share Posted November 5, 2022 Thanks Maxxd, Glad you didn't totally bash the first schema as it is currently what I am going with! It is just different than anything I've done in the past and I think (thought?) different than what others do, and usually that means I am doing it wrong. Appreciate your response! Quote Link to comment https://forums.phpfreaks.com/topic/315426-alternatives-to-inheritance-question-273/#findComment-1602221 Share on other sites More sharing options...
maxxd Posted November 6, 2022 Share Posted November 6, 2022 (edited) OK, so I'm looking through this again and while I still think I'd start at your option 1, I have a couple questions about your reasons for questioning that approach. On 10/17/2022 at 6:45 PM, NotionCommotion said: Adds complexity to ensure that a given PermissionPolicy is referenced by only a single Project, Asset, etc. Why do you need a PermissionPolicy to be referenced by only 1 project or asset? Seems to me that permissions would span projects and assets at least, unless there's a very specific set of business logic that dictates this. I have to admit I can't imagine what that would be, but I'm not a business person, so... On 10/17/2022 at 6:45 PM, NotionCommotion said: Can't switch from OneToOne to ManyToOne by simply removing the unique constraint. Is this a concern? If there's a possibility of needing a "many-to-one" relationship, IMO (obviously) there's no need to think about a one-to-one and it's probably better to consider the possibility of a many-to-many relationship just in terms of future-proofing. It doesn't add that much cognitive load to have a linking table even if it may not be strictly necessary at the time, but it gives teams the possibility of expanding scope without too much ... well, cussing to be honest. Edited November 6, 2022 by maxxd Quote Link to comment https://forums.phpfreaks.com/topic/315426-alternatives-to-inheritance-question-273/#findComment-1602238 Share on other sites More sharing options...
NotionCommotion Posted November 6, 2022 Author Share Posted November 6, 2022 15 hours ago, maxxd said: Why do you need a PermissionPolicy to be referenced by only 1 project or asset? Seems to me that permissions would span projects and assets at least, unless there's a very specific set of business logic that dictates this. I have to admit I can't imagine what that would be, but I'm not a business person, so... Projects and assets act as "document container" and the PermissionPolicy which can be referenced by only 1 project or asset applies to the documents, and right or wrong it isn't desirable to have changes to one container affect another. 16 hours ago, maxxd said: Is this a concern? If there's a possibility of needing a "many-to-one" relationship, IMO (obviously) there's no need to think about a one-to-one and it's probably better to consider the possibility of a many-to-many relationship just in terms of future-proofing. It doesn't add that much cognitive load to have a linking table even if it may not be strictly necessary at the time, but it gives teams the possibility of expanding scope without too much ... well, cussing to be honest. No concerns about any added work if I ever need to change, but some concerns that it is going against everything I've ever read regarding proper database design. I did a quick google search to make sure I was not just imaging it, and all feverishly state that the FK of the parent table must be stored in child table, and that the parent table is the one which can exists on its own without the presence of the child table. But what do they know, so I pulled out my Simple SQL book to see what Rudy says, and he says that the foreign key goes in the table on the many side of the relationship, and for one-to-ones, it should be the same if it did have a many side (potentially, I misinterpreted the part about one-and-ones, and even if I didn't, he was not as adamant on that part) . Forgetting about my specific use case and a different way of asking this question. Have you ever had the need to move the foreign key from the child table to the parent table? Did you have any issues? Quote Link to comment https://forums.phpfreaks.com/topic/315426-alternatives-to-inheritance-question-273/#findComment-1602279 Share on other sites More sharing options...
maxxd Posted November 7, 2022 Share Posted November 7, 2022 Not gonna lie, I'm kinda missing the restrictions on the permission policy so I'll just trust that it's necessary to structure that way. As to the primary key/foreign key part - just for me - unless I know it's going to be a one-to-one relationship forever I'll assume it's going to turn into a many-to-many at some point. It's probably just my innate desire/tendency to over-engineer so, so many things, but I have gotten to a point where it's no harder to reason about than a simple one-to-one link. It's just me and YMMV, but I personally have never found it harder to code on a one-to-one with an intermediary table than it is to refactor a one-to-one relationship into any other relationship type. Quote Link to comment https://forums.phpfreaks.com/topic/315426-alternatives-to-inheritance-question-273/#findComment-1602284 Share on other sites More sharing options...
NotionCommotion Posted November 8, 2022 Author Share Posted November 8, 2022 On 11/6/2022 at 7:31 PM, maxxd said: As to the primary key/foreign key part - just for me - unless I know it's going to be a one-to-one relationship forever I'll assume it's going to turn into a many-to-many at some point. It's probably just my innate desire/tendency to over-engineer so, so many things, but I have gotten to a point where it's no harder to reason about than a simple one-to-one link. It's just me and YMMV, but I personally have never found it harder to code on a one-to-one with an intermediary table than it is to refactor a one-to-one relationship into any other relationship type. Thanks Maxxd, I didn't think of it until you brought it up, but vaguely recall reading some blog awhile ago where they promoted always using an intermediary table. Don't know if I agree with "always", but I do think it helps for some situations. Will give it some thought and definitely consider. Thanks! Quote Link to comment https://forums.phpfreaks.com/topic/315426-alternatives-to-inheritance-question-273/#findComment-1602328 Share on other sites More sharing options...
NotionCommotion Posted November 12, 2022 Author Share Posted November 12, 2022 Actually, I think there is an Option 4, and while it goes down the inheritance path, it doesn't bring all the baggage. Instead of extending some big complicated class, only extend small simple classes which are less likely to require future changes. I also recognize that this solution does as you recommend and uses an intermediary table. Still isn't an alternative to inheritance, but at least makes inheritance less painful. I went back and forth whether Project or ProjectPermissionPolicy should hold the other's PK as neither should exist on its own without the presence of the other. I was going to come up with some awesome abstract Person/Heart analogies, but fortunately decided that when the first rule of thumb doesn't apply, the next rule of thumb should be that the parent table is the one which most logically would be delete. Project - Id (PK) - Data Asset - Id (PK) - Data AbstractPermissionPolicy - Id (PK) - discriminator - Data ProjectPermissionPolicy extends AbstractPermissionPolicy - Id (PK, FK to AbstractPermissionPolicy.id) - resourceId (FK to project.id, delete cascade) AssetPermissionPolicy extends AbstractPermissionPolicy - Id (PK, FK to AbstractPermissionPolicy.id) - resourceId (FK to asset.id, delete cascade) Member - PermissionPolicyId (PK, FK to AbstractPermissionPolicy.Id) - UserId (PK, FK to User.Id) - MemberPermissionData User - Id (PK) - GeneralUserPermissionData - OtherData Quote Link to comment https://forums.phpfreaks.com/topic/315426-alternatives-to-inheritance-question-273/#findComment-1602508 Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.