-
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
Support for polymorph association through interfaces #6072
Comments
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? |
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? |
You can indeed set an interface as Here are some resources about it (they all involve using the Marco Pivetta On Thu, Oct 6, 2016 at 8:12 PM, davekok notifications@github.com wrote:
|
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. |
Then I'm still not understanding what is going on. An inheritance is the Is that what you were asking? Marco Pivetta On Fri, Oct 7, 2016 at 2:01 PM, davekok notifications@github.com wrote:
|
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. |
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. |
Well aware what it is - I gave you a way to implement it, did you try doing On 8 Oct 2016 11:28 a.m., "davekok" notifications@github.com wrote:
|
Ok how do I resolve one interface to many different classes? The article only mentions resolving one interface to one class. |
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? |
@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). |
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.
|
@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? |
@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. |
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
**
* Activity entity.
*
* For example an activity has the following properties:
*
* - actor: Who did the activity?
* - verb: What he did?
* - published: When was it done?
* - object: What it refers to?
*
* <actor> <verb> <published>
* patient requested appointment on 2016-10-21
* message <object>
*
* @ORM\Entity(readOnly=true)
* @HasPolymorphicRelation()
*/
class Activity
{
/**
* @var string
*
* @ORM\Id()
* @ORM\Column(type="uuid")
*/
private $id;
/**
* @var Chronos
*
* @ORM\Column(type="chronos_datetime")
*/
private $published;
/**
* @var string
*
* @ORM\Column()
*/
private $verb;
/**
* @var string
*
* @ORM\Column()
*/
private $actorType;
/**
* @var string
*
* @ORM\Column()
*/
private $actorId;
/**
* @var mixed
*
* @PolymorphicRelation()
*/
private $actor;
/**
* @var string
*
* @ORM\Column()
*/
private $objectType;
/**
* @var string
*
* @ORM\Column()
*/
private $objectId;
/**
* @var mixed
*
* @PolymorphicRelation()
*/
private $object;
/**
* @var string
*
* @ORM\Column()
*/
private $targetType;
/**
* @var string
*
* @ORM\Column()
*/
private $targetId;
/**
* @var mixed
*
* @PolymorphicRelation()
*/
private $target;
} 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. |
To me, this looks like a misuse of types, as the fields become (from the outside) effectively Take this field for example: /**
* @var mixed
*
* @PolymorphicRelation()
*/
private $actor; This should simply be mapped as In alternative, I suggest not loading the associations at runtime at all, and instead keeping two split fields (or an
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. |
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 I managed to use |
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". |
Anyone managed to implement polymorphic associations without extending a class? |
@Ocramius Should the |
I don't fully understand it, but it appears that this is doing what you asked for? |
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.
The text was updated successfully, but these errors were encountered: