mirror of
https://github.com/doctrine/orm.git
synced 2026-03-23 22:42:18 +01:00
Support for polymorph association through interfaces #5283
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @ghost on GitHub (Oct 6, 2016).
Currently Doctrine has support for polymorphic associations through inheritance however this use is rather limited. It would be nice if polymorphic associations are also possible through interfaces. That is that you can declare a PHP interface and tag it with an @Entity annotation and implement these interfaces in class entities through the implements keyword. Then allowing the targetEntity of associations to be an interface with @Entity annotation creating a polymorphic association.
Though in interface languages properties are often defined which must be implemented in classes when implemented. PHP has no such abilities and uses getters and setters instead. I think in the same way a interface entity should not be allowed to define any columns or associations. Instead only define the getters and setters if such are necessary. And leave it to the implementing classes to define the columns or associations on the properties. For DBAL this means the interface entities have no known fields or associations but the INSTANCE OF operator should be usable.
This would fix the rather inconvenience that you either need to implement your own polymorphs when inheritance is not suitable or risk using inheritance which would make things to coupled and create difficulties later on in development.
@Ocramius commented on GitHub (Oct 6, 2016):
You can already reference interfaces, as long as you provide a concrete implementation at runtime, in a listener that acts during metadata loading.
Is that what you are looking for?
@ghost commented on GitHub (Oct 6, 2016):
That depends can I set an interface as a targetEntity for a OneToOne or ManyToOne association? And will such an association create two fields in the database one for the type of entity and another for the id in the table that contains the OneToOne or ManyToOne association?
@Ocramius commented on GitHub (Oct 7, 2016):
You can indeed set an interface as
targetEntityin any association type,but the ORM still expects a concrete instance once metadata is loaded.
Here are some resources about it (they all involve using the
"ResolveTargetEntityListener":
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/cookbook/resolve-target-entity-listener.html
Marco Pivetta
http://twitter.com/Ocramius
http://ocramius.github.com/
On Thu, Oct 6, 2016 at 8:12 PM, davekok notifications@github.com wrote:
@ghost commented on GitHub (Oct 7, 2016):
Great but not what I asked. The goal is to have a polymorphic association. Implemented through interfaces. It is already possible through inheritance so it should not be to difficult.
@Ocramius commented on GitHub (Oct 7, 2016):
Then I'm still not understanding what is going on. An inheritance is the
current way forward, and it works. It is possible to emulate an inheritance
by replacing the root of the inheritance with a fake (non-existing) class,
and an interface as mapped type (to be replaced at runtime).
Is that what you were asking?
Marco Pivetta
http://twitter.com/Ocramius
http://ocramius.github.com/
On Fri, Oct 7, 2016 at 2:01 PM, davekok notifications@github.com wrote:
@ghost commented on GitHub (Oct 8, 2016):
I am not asking to replace a mapped type at runtime. I am asking to store the type as part of the association as is customary with a polymorph. Basically the same way it is done with the DiscriminatorColumn for inheritance but using interfaces instead. Perhaps you should look up what a polymorph is.
@ghost commented on GitHub (Oct 8, 2016):
To further clarrify sometimes single inheritance is not enough. In these cases you use interfaces. As far as I know you can't use interfaces as an alternative to multiple inheritance in Doctrine.
@Ocramius commented on GitHub (Oct 8, 2016):
Well aware what it is - I gave you a way to implement it, did you try doing
so?
On 8 Oct 2016 11:28 a.m., "davekok" notifications@github.com wrote:
@ghost commented on GitHub (Oct 9, 2016):
Ok how do I resolve one interface to many different classes? The article only mentions resolving one interface to one class.
@ghost commented on GitHub (Oct 17, 2016):
So recap, the ResolveTargetEntityListener is great for decoupling bundles allowing the programmer to set a concrete class at runtime. However it is not a polymorph in the sense that multiple concrete classes can be used. You must choose one.
The issue is however not about decoupling bundles at all. The idea is to use interfaces as an alternative to multiple inheritance (not possible anyways in PHP) within the same bundle. Thus adding something like a DiscriminatorColumn annotation to an interface declaration. An entity may then associate to an interface instead of a base class. And when storing the id in one column the actual entity to use is stored in another column.
Is Doctrine team interested in implementing this?
@lcobucci commented on GitHub (Oct 18, 2016):
@davekok can you please give us a good use case for this feature? Please also provide an explanation about the database side (especially regarding the foreign keys).
@ghost commented on GitHub (Oct 18, 2016):
Here is an abstract use case. As far as I know no foreign keys are possible with polymorphs as it links to multiple tables and SQL does not support that. I think polymorphs using interfaces instead of CTI should only be allowed on ManyToOne or ManyToMany associations. As they can not own what is referenced which should make the necessity of a foreign keys less important. As long as id's are never changed. In the case of deleting a entity referenced through a polymorph a join should not fail but simply not find it as if it had been null. I would leave it to the programmer to decide to implement something to reset polymorph association to null on deletion.
@lcobucci commented on GitHub (Oct 18, 2016):
@davekok I was looking for a concrete use case but anyway 😜
Although it looks a good idea at first sight, I find it hard to see a good explanation on why implement this. I mean, there are some trade-offs that I wouldn't allow on things I write (like the lack of foreign keys).
My opinion is that this is a no go, it adds a unnecessary complexity to the ORM and to the schema without adding a huge benefit on design.
@Ocramius what do you think?
@ghost commented on GitHub (Oct 18, 2016):
@lcobucci Is there a alternative in case you really do need it without excessive joins? It seems to me this should not be more difficult then joined CTI. Personally I would prefer using either single table CTI or this and drop joined CTI all together.
Still structural problems are best fixed (avoided) through interfaces. Forcing CTI perhaps lessens the burden of doctrine but increases the burden on applications. This seems to me a must have feature. Otherwise I will never be able use more generalized data models. I would also have to use intermediate tables for the sole purpose of using foreign keys and have excessive joins in queries.
@1ed commented on GitHub (Oct 18, 2016):
I implemented something like this in a project using a lifecycle listener and some custom annotations. In my case I needed a way to add events to an activity stream like
The listener looks for HasPolymorphicRelation and PolymorphicRelation annotations and populates type and id fields automatically at prePrestist and hydrates the related entities at postLoad. It works great until I need to update or delete the relations (which was not required in my case, as the activity stream is read only).
As a reference eloquent also has a feature something like this https://laravel.com/docs/5.3/eloquent-relationships#polymorphic-relations.
@Ocramius commented on GitHub (Feb 5, 2018):
To me, this looks like a misuse of types, as the fields become (from the outside) effectively
mixedormixed[].Take this field for example:
This should simply be mapped as
Actor, which should be the root of a Single Table Inheritance (STI) or a Joined Table Inheritance (JTI). TheActorsymbol should be defined and referenced, and subtypes should be covariant to it. TheActorsymbol may be a class or interface, but in case of an interface, some metadata replacement is required at runtime.In alternative, I suggest not loading the associations at runtime at all, and instead keeping two split fields (or an
Embeddable) for each of these associations, representing:The last solution I proposed is simpler, as it doesn't involve complex reference type variations at runtime, and is simpler to reason about also later on without too much magic going on.
@erop commented on GitHub (Apr 13, 2020):
Not sure it is correct to re-open such an old issue but could not find any other info related to the topic.
Here's my real-life use case. I'm building an application which has
PolicyApplicationentity. This is the one holding a customer request for issuing a car insurance policy. There definitely should be aPolicyHolderInterfaceimplemented both byPersonandOrganisationentities. Yeah, I could use one of the inheritance mappings documented. But it only applies to classes. Thus I need to “extend” it just once in both of my concrete target classes. But what if nestedVehicle::$ownerneeded to be eitherPersonorOrganisationas well? I.e. I need polymorphic association again in the samePolicyApplicationbut on the other level in the nested object. And I believe they should implement hm-m-m….OwnerInterfacethat time.I managed to use
prePersistandpostLoadmethods of entity listeners to populate@EmbeddedintoPolicyApplicationLinkObjectholdingtypeandinstanceIdon Creation (prePersist) and Retrieving (postLoad)PolicyApplication. But it seems do not work while Updating 😕@erop commented on GitHub (Apr 13, 2020):
In a minute after hitting Comment button I started googling hibernate polymorphic association and found this page. I think the topic starter (and me) meant something like that to be available in Doctrine, possibility of having interfaces as an association "root".
@parijke commented on GitHub (Jun 28, 2020):
Anyone managed to implement polymorphic associations without extending a class?
@mpdude commented on GitHub (Dec 8, 2020):
@Ocramius Should the
ResolveTargetEntityListenermechanism work in Discriminator Maps as well?@parijke commented on GitHub (Jun 4, 2021):
I don't fully understand it, but it appears that this is doing what you asked for?
https://github.com/mareg/commentable