Closed GuySartorelli closed 3 months ago
Note that an alternative solution, which would not be a breaking change, would be to allow opting-in to storing the has_many
relation in a join table - with the inverse relationship being a belongs_to
.
This is the approach Doctrine takes to handle this use case - you have have the same approach we have by default (store in has_one
, has_many
looks up the has_one
), or you can opt to use a join table.
This is also how the Eloquent ORM works - it has the equivalent of a has_many
which stores its relational data in a join table, with a belongs_to
as the (optional) inverse relationship. It only uses the has_one
style when dealing with polymorphic relations.
This is good for a concrete or polymorphic has_many
with a concrete belongs_to
but doesn't solve the use case that this issue was intended to solve, which is for the has_one
(or belongs_to
) side to be polymorphic.
This was resolved in https://github.com/silverstripe/silverstripe-framework/pull/11084
Currently, there's no way to have multiple
has_many
relations on a class which all link back to the samehas_one
relation, such as this example:With this code, adding a child record to either
has_many
list will result in it displaying in both. The current documented way to do this is to have twohas_one
relations and use dot notation to indicate whichhas_many
is for whichhas_one
, but that's not useful if you're creating a model in a module and you want the permissions (can* methods) to related to the parenthas_one
relation.So, for example, if I implemented this method in
MyChildObject
:In that scenario, the permissions are only correct for whichever
has_many
ends up having its dot notation point at that relation.Proposed solution
On the
has_one
relation, save thehas_many
relation it corresponds to as well (similar to how polymorphichas_one
stores the classname)This would mean the following changes (at a minimum) would be required:
DataObjectSchema::cacheDatabaseFields()
needs to add a new"{$fieldName}Relation"
or similarly named database column for allhas_one
relationshas_many
relations can point to it. The latter would probably be too hard to detect.HasManyList
would need to accept a relation name either as a constructor argument or via a new setter method, or both.has_many
relation it represents and would be used in its query (WHERE {$fieldName}Relation = {$this->relationName}
) and to store the value against records that are added to the list.HasManyList
(eager loading,DataObject::getComponents()
, etc) would need to account for this new behaviourDataQuery::joinHasManyRelation()
probably needs to account for the new columnhas_one
that doesn't relate to ahas_many
- there's no need to store the relation in any other scenario I think but we gotta make sure nothing else breaks.NOTES
has_many
- if there is more than onehas_one
that thehas_many
could be targetting, thehas_many
still needs to know whichhas_one
it's supposed to save its relation against in the first place. Dot notation is still required to make that association.