-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix association handling when there is a MappedSuperclass in the middle of an inheritance hierarchy #8415
Fix association handling when there is a MappedSuperclass in the middle of an inheritance hierarchy #8415
Conversation
33f0f3d
to
77acfb8
Compare
4414090
to
5c0d4bf
Compare
Please add something to sum #7825 (comment) up in the docs, that kind of knowledge of what is supported and what isn't could come in handy to both users and maintainers. |
I don't think 2.7 is still maintained, but https://github.com/doctrine/orm/blob/2.8.x/.doctrine-project.json says it is. Maybe @beberlei or @SenseException will know? |
@greg0ire Regarding additional documentation on Is a I sometimes had the impression that things also work without the declaration? Also, what about |
Sure, but it won't be me I'm afraid 😅 |
For ORM it should the current version that is supported. In that case it's 2.8. I'm not sure if there are exceptions for 2.7, like security issues. I think this should target @mpdude From the docs: https://www.doctrine-project.org/projects/doctrine-orm/en/2.8/reference/inheritance-mapping.html
If there are entities with similar mapping, the MappedSuperclass can contain these common fields. There's not much extended info about other possible use cases and I'm not sure if there are some that are considered as a intentional feature. The sentence
seems to open a lot of possibilities that might not be covered by ORM updates. |
Updated, see next comment. |
Whether or not the base class from my example is Just as a side note, when the class is not
This seems to confirm that an
But as I said, it does not matter here. Regarding |
Would be great to see this working! Just for reference, here's what @guilhermeblanco said in #7825:
|
5c0d4bf
to
719fb73
Compare
@andrews05 Great to have you on board here! You must have spent a lot of time working on #7825, so I think you have some valuable expertise to add. From your point of view, are there any edge cases we should cover as well? |
5939fc8
to
038f0e6
Compare
@mpdude Hm, I would say if it's passing all the tests I added, then that should cover all the edge cases I'm aware of. |
Could anybody give me hints what I could do to get this along? 🙏🏻 |
@mpdude since 2.8 is unmaintained, first thing to do, is to change the branch to then you can tag someone who can do review for this part of code upd: you can also see the history https://github.com/doctrine/orm/commits/2.9.x/lib/Doctrine/ORM/Mapping |
6e9e456
to
2b47af6
Compare
@beberlei you last worked on this area of the code in 2010 when you did 7dc8ef1. @guilhermeblanco you were involved in the discussion of #7825, which should be fixed by this PR. Would it be possible for either of you to review this PR? 💚 |
Rebased onto |
@mpdude I removed the usage of the simple annotation loader a while ago. This means the annotations in your tests need to come with corresponding use statements. |
Thank you @greg0ire for the heads-up. |
I'd still like to get this resolved and merged, and I am willing to spend the time to port and this to the current 2.x branch. @beberlei Do you see a chance to review this and what would you need to make this as easy as possible? |
26694a5
to
0408e70
Compare
@derrabus Would it be possible for you to review this? If it is too complex, has missing context or similar, let me know and I'll try to make it as easy as possible for you. |
a2a27b5
to
ed87b18
Compare
…le of an inheritance hierarchy This fixes two closely related bugs. 1. When inheriting a to-one association from a mapped superclass, update the `sourceEntity` class name to the current class only when the association is actually _declared_ in the mapped superclass. 2. Reject association types that are not allowed on mapped superclasses only when they are actually _declared_ in a mapped superclass, not when inherited from parent classes. Currently, when a many-to-one association is inherited from a `MappedSuperclass`, mapping information will be updated so that the association has the current (inheriting) class as the source entity. https://github.com/doctrine/orm/blob/2138cc93834cfae9cd3f86c991fa051a3129b693/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php#L384-L393 This was added in 7dc8ef1 for [DDC-671](doctrine#5181). The reason for this is that a mapped superclass is not an entity itself and has no table. So, in the database, associations can only be from the inheriting entities' tables towards the referred-to target. This is also the reason for the limitation that only to-one associations may be added in mapped superclasses, since for those the database foreign key can be placed on the table(s) of the inheriting entities (and there may be more than one child class). Neither the decision to update the `sourceEntity` nor the validation check should be based on `$parent->isMappedSuperclass`. This only works in the simple case where the class hierarchy is `Mapped Superclass → Entity`. The check is wrong when we have an inheritance hierarchy set up and the class hierarchy is `Base Entity → Mapped Superclass → Child Entity`. Bug 1: The association should keep the root entity as the source. After all, in a JTI, the root table will contain the foreign key, and we need to base joins on that table when traversing `FROM LeafClass l JOIN l.target`. Bug 2: Do not reject the to-many association declared in the base class. It is ok to have the reverse (owning) side point back to the base entity, as it would be if there were no mapped superclasses at all. The mapped superclass does not declare, add or otherwise interfere with the to-many association at all. Base the decision to change the `sourceEntity` on `$mapping['inherited']` being set. This field points to the topmost _parent entity_ class in the ancestry tree where the relationship mapping appears for the first time. When it is not set, the current class is the first _entity_ class in the hierarchy with that association. Since we are inheriting the relation, it must have been added in a mapped superclass above, but was not yet present in the nearest parent entity class. In that case, it may only be a to-one association and the source entity needs to be updated. (See doctrine#10396 for a clarification of the semantics of `inherited`.) Here is a simplified example of the class hierarchy. See the two tests added for more details – one is for checking the correct usage of a to-one association against/with the base class in JTI. The other is to test that a to-many association on the base class is not rejected. I am sure that there are other tests that (still) cover the update of `sourceEntity` is happening. ```php /** * @entity */ class AssociationTarget { /** * @column(type="integer") * @id * @GeneratedValue */ public $id; } /** * @entity * @InheritanceType("JOINED") * @DiscriminatorColumn(name="discriminator", type="string") * @DiscriminatorMap({"1" = "BaseClass", "2" = "LeafClass"}) */ class BaseClass { /** * @column(type="integer") * @id * @GeneratedValue */ public $id; /** * @manytoone(targetEntity="AssociationTarget") */ public $target; } /** * @MappedSuperclass */ class MediumSuperclass extends BaseClass { } /** * @entity */ class LeafClass extends MediumSuperclass { } ``` When querying `FROM LeafClass l`, it should be possible to `JOIN l.target`. This currently leads to an SQL error because the SQL join will be made via `LeafClass.target_id` instead of `BaseClass.target_id`. `LeafClass` is considered the `sourceEntity` for the association – which is wrong–, and so the foreign key field is expected to be in the `LeafClass` table (using JTI here). Fixes doctrine#5998, fixes doctrine#7825. I have removed the abstract entity class, since it is not relevant for the issue and took the discussion off course. Also, the discriminator map now contains all classes. Added the second variant of the bug, namely that a to-many association would wrongly be rejected in the same situation.
ed87b18
to
8d9ebed
Compare
Thanks @mpdude ! |
Thank you all! |
…ed superclass in the hierarchy This picks the test case from doctrine#9517 and rebases it onto 2.14.x. The problem has been covered in doctrine#8415, so this PR closes doctrine#9517 and fixes doctrine#9516. Co-authored-by: Robert D'Ercole <[email protected]>
…ed superclass in the hierarchy This picks the test case from doctrine#9517 and rebases it onto 2.14.x. The problem has been covered in doctrine#8415, so this PR closes doctrine#9517 and fixes doctrine#9516. Co-authored-by: Robert D'Ercole <[email protected]>
This fixes two closely related bugs.
sourceEntity
class name to the current class only when the association is actually declared in the mapped superclass.Background
Currently, when a many-to-one association is inherited from a
MappedSuperclass
, mapping information will be updated so that the association has the current (inheriting) class as the source entity.orm/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php
Lines 384 to 393 in 2138cc9
This was added in 7dc8ef1 for DDC-671.
The reason for this is that a mapped superclass is not an entity itself and has no table.
So, in the database, associations can only be from the inheriting entities' tables towards the referred-to target. This is also the reason for the limitation that only to-one associations may be added in mapped superclasses, since for those the database foreign key can be placed on the table(s) of the inheriting entities (and there may be more than one child class).
Issue with the current code
Neither the decision to update the
sourceEntity
nor the validation check should be based on$parent->isMappedSuperclass
.This only works in the simple case where the class hierarchy is
Mapped Superclass → Entity
.The check is wrong when we have an inheritance hierarchy set up and the class hierarchy is
Base Entity → Mapped Superclass → Child Entity
.Bug 1: The association should keep the root entity as the source. After all, in a JTI, the root table will contain the foreign key, and we need to base joins on that table when traversing
FROM LeafClass l JOIN l.target
.Bug 2: Do not reject the to-many association declared in the base class. It is ok to have the reverse (owning) side point back to the base entity, as it would be if there were no mapped superclasses at all. The mapped superclass does not declare, add or otherwise interfere with the to-many association at all.
Suggested fix
Base the decision to change the
sourceEntity
on$mapping['inherited']
being set. This field points to the topmost parent entity class in the ancestry tree where the relationship mapping appears for the first time.When it is not set, the current class is the first entity class in the hierarchy with that association. Since we are inheriting the relation, it must have been added in a mapped superclass above, but was not yet present in the nearest parent entity class.
In that case, it may only be a to-one association and the source entity needs to be updated.
(See #10396 for a clarification of the semantics of
inherited
.)Example
Here is a simplified example of the class hierarchy.
See the two tests added for more details – one is for checking the correct usage of a to-one association against/with the base class in JTI. The other is to test that a to-many association on the base class is not rejected.
I am sure that there are other tests that (still) cover the update of
sourceEntity
is happening.When querying
FROM LeafClass l
, it should be possible toJOIN l.target
. This currently leads to an SQL error because the SQL join will be made viaLeafClass.target_id
instead ofBaseClass.target_id
.LeafClass
is considered thesourceEntity
for the association – which is wrong–, and so the foreign key field is expected to be in theLeafClass
table (using JTI here).Fixes #5998, fixes #7825.
Updated:
I have removed the abstract entity class, since it is not relevant for the issue and took the discussion off course. Also, the discriminator map now contains all classes.
Updated 2:
Added the second variant of the bug, namely that a to-many association would wrongly be rejected in the same situation.