[PR #10473] Allow to-many associations on mapped superclasses w/ ResolveTargetEntityListener #12391

Closed
opened 2026-01-22 16:13:54 +01:00 by admin · 0 comments
Owner

Original Pull Request: https://github.com/doctrine/orm/pull/10473

State: closed
Merged: Yes


Allow to-many associations to be used on mapped superclasses when the owning (inverse) side does not refer back to the mapped superclass, thanks to ResolveTargetEntityListener.

Current situation

The documentation states:

No database table will be created for a mapped superclass itself

[...] persistent relationships defined by a mapped superclass must be unidirectional (with an owning side only). This means that One-To-Many associations are not possible on a mapped superclass at all.

That's a though limitation.

Obviously apparently Probably the limitation comes from the fact that in a to-many association the "many" side has to hold a foreign key. Since the mapped superclass does not have a database table (it's not an entity), no such backreference can be established.

Currently, to-many associations trigger an exception as soon as they are seen on a mapped superclass:

d6c0031d44/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php (L459-L461)

ResolveTargetEntityListener

The ResolveTargetEntityListener can be used to substitute interface or class names in mapping configuration at runtime, during the metadata load phase.

When this gimmick is used to replace all references to the mapped superclass with an entity class in time, it should be possible to have to-many associations on the inheriting entity classes.

Suggested solution

Instead of rejecting to-many associations on mapped superclasses right away, validate that at the end of the day (after the loadClassMetadata event has been processed) no association may target at a non-entity class. That includes mapped superclasses as well as transient classes.

Motivating example

Consider a library that comes with a User base class. This class is abstract and has to be subclassed/filled when the library is used.

By making this a mapped superclass, library users have the freedom to either have a simple user entity class or a user class hierarchy, but we do not impose any requirements on them. (NB we also don't want to have a root entity in the library, because that would have to declare the entire class hierarchy, including library users' classes.)

The actual user class to be used will be configured through the ResolveTargetEntityListener.

The library also includes a SocialMediaAccount entity. A User can have multiple of these accounts, and we want to be able to navigate the accounts from the user side.

To make the example even more fancy, there is a self-referencing association on the User: A User has been created by another user, and holds a collection of all other Users it created.

The test case contained in this PR contains this example and validates that all association mappings look just as if the final user class had been written as an entity directly, without the superclass.

Potential review talking points

  • Am I missing other reasons why to-many is not feasible?
  • We now reject association mappings with targetEntitys that are not entities; relevant BC break? (IMHO: no.)

Review tip

Review commit by commit, not all files at once. The last commit adds a lot of entity declarations that were previously missed in tests and now raised exceptions; that's a lot of clutter in the PR.

Relationship to #10455

This PR here does not make any changes to the inner workings of the ORM (!).

In fact, the example given can be run with current versions/releases of the ORM with no issues. That is possible because the one config validation rule that would stop it is in fact not effective, since the annotations/attribute mapping drivers do not report the potentially problematic association mapping in the first place.

This would change with #10455. Once that is merged, the to-many association would be dismissed right away and break the example.

So we need this PR here to keep examples like the one given working with #10455, by finding a more precise rule why/when to reject invalid configurations.

Other: This closes #10398, since the check no longer exists.

**Original Pull Request:** https://github.com/doctrine/orm/pull/10473 **State:** closed **Merged:** Yes --- Allow to-many associations to be used on mapped superclasses when the owning (inverse) side does not refer back to the mapped superclass, thanks to `ResolveTargetEntityListener`. #### Current situation The [documentation states](https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html): > No database table will be created for a mapped superclass itself > [...] persistent relationships defined by a mapped superclass must be unidirectional (with an owning side only). This means that One-To-Many associations are not possible on a mapped superclass at all. That's a though limitation. ~Obviously~ ~apparently~ Probably the limitation comes from the fact that in a to-many association the "many" side has to hold a foreign key. Since the mapped superclass does not have a database table (it's not an entity), no such backreference can be established. Currently, to-many associations trigger an exception as soon as they are seen on a mapped superclass: https://github.com/doctrine/orm/blob/d6c0031d44f04e04bbc0cd57a3ed7e05c7ea8b40/lib/Doctrine/ORM/Mapping/ClassMetadataFactory.php#L459-L461 #### `ResolveTargetEntityListener` The `ResolveTargetEntityListener` can be used to substitute interface or class names in mapping configuration at runtime, during the metadata load phase. When this gimmick is used to replace _all_ references to the mapped superclass with an entity class in time, it should be possible to have to-many associations on the inheriting entity classes. #### Suggested solution Instead of rejecting to-many associations on mapped superclasses right away, validate that at the end of the day (after the `loadClassMetadata` event has been processed) no association may target at a non-entity class. That includes mapped superclasses as well as transient classes. #### Motivating example Consider a library that comes with a `User` base class. This class is `abstract` and has to be subclassed/filled when the library is used. By making this a mapped superclass, library users have the freedom to either have a simple user entity class or a user class hierarchy, but we do not impose any requirements on them. (NB we also don't want to have a root entity in the library, because that would have to declare the entire class hierarchy, including library users' classes.) The actual user class to be used will be configured through the `ResolveTargetEntityListener`. The library also includes a `SocialMediaAccount` entity. A `User` can have multiple of these accounts, and we want to be able to navigate the accounts from the user side. To make the example even more fancy, there is a self-referencing association on the `User`: A `User` has been created by another user, and holds a collection of all other `User`s it created. The test case contained in this PR contains this example and validates that all association mappings look just as if the final user class had been written as an entity directly, without the superclass. #### Potential review talking points - Am I missing other reasons why to-many is not feasible? - We now reject association mappings with `targetEntity`s that are not entities; relevant BC break? (IMHO: no.) #### Review tip Review commit by commit, not all files at once. The last commit adds a lot of entity declarations that were previously missed in tests and now raised exceptions; that's a lot of clutter in the PR. #### Relationship to #10455 This PR here does not make any changes to the inner workings of the ORM (!). In fact, the example given can be run with current versions/releases of the ORM with no issues. That is possible because the one config validation rule that would stop it is in fact not effective, since the annotations/attribute mapping drivers do not report the potentially problematic association mapping in the first place. This would change with #10455. Once that is merged, the to-many association would be dismissed right away and break the example. So we need this PR here to keep examples like the one given working with #10455, by finding a more precise rule why/when to reject invalid configurations. Other: This closes #10398, since the check no longer exists.
admin added the pull-request label 2026-01-22 16:13:54 +01:00
admin closed this issue 2026-01-22 16:13:54 +01:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#12391