Skip to content
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

2019/06/11/doctrine-inheritance #4

Open
romaricdrigon opened this issue Jun 11, 2019 · 22 comments
Open

2019/06/11/doctrine-inheritance #4

romaricdrigon opened this issue Jun 11, 2019 · 22 comments

Comments

@romaricdrigon
Copy link
Owner

romaricdrigon commented Jun 11, 2019

Please comment below! Comments will automatically be published on the blog, too.

@Aerendir
Copy link

There is also another useful tool in the Doctrine’s toolbox: embeddables.

They are really really powerful and are a perfect fit for things like pictures or videos like in your example: they are basically value objects and embeddables make use and persistence of value objects really a piece of cake!

@romaricdrigon
Copy link
Owner Author

Yes, they are a great and underused Doctrine feature!
Thank you for bringing those up.

Indeed they would make sense for my first example. They would be my first option when you have a value object, and traits being best when you want to add just a few properties without meaning on their own (ie, adding "createdAt" and "updatedAt" fields...).

@Aerendir
Copy link

Exactly: this is exactly the way I use both of them! :)
This is really a great article: it clarifies me some things that I knew but never though about much more: thank you!

@romaricdrigon
Copy link
Owner Author

Thank you, I appreciate :)
I wanted to start a serie about what I learnt from DDD this week, I will likely write about value objects soon, thank you for making me remember that.

@Aerendir
Copy link

Oh, great!

Maybe you will find interesting the post I wrote about value objects: https://io.serendipityhq.com/experience/php-and-doctrine-immutable-objects-value-objects-and-embeddables/

I also wrote a small library around the most common value objects from which you may find inspiration: https://github.com/Aerendir/PHPValueObjects

@marcosdipaolo
Copy link

marcosdipaolo commented Feb 22, 2020

I still don't understand how the database and entities would look like in a polymorphic many to many relationship, say:

class Tag {}
class PostTag extends Tag {}
class VideoTag extends Tag {}
class Post {}
class Video{}

What i would like to have is a many-to-many relationsihp between Post and Tag, and another one between Video and Tag and, if possible, all tags in the tags table with single inheritance.

Laravel's Eloquent works with a pivot with tag_id, taggable_type and taggable_id.
What would be the Doctrine's way?

@Aerendir
Copy link

@marcosdipaolo , exactly the same.

You will have a table post_tag, one video_tag (Many-to-Many, Bidirectional).

About single inheritance, are you speaking about a tree of tags?

It is possible through a self referencing relationship:

@marcosdipaolo
Copy link

marcosdipaolo commented Feb 22, 2020

Yes, sorry, i forgot to write the Tag classes, now i've edited it. (no tree)
I'm working with Laravel-Doctrine and Fluent. So my question also goes for the Mapping side.

@romaricdrigon
Copy link
Owner Author

@marcosdipaolo I'm not sure to correctly get your scenario.

First of all, are VideoTag and PostTag really different? Ie., do they each have different properties?
If not, what are the benefits of having those in the same tag table?
The main point of my article was to avoid Doctrine inheritance as much as possible, so I would rather not push you to a suboptimal solution.

@cjhaas
Copy link

cjhaas commented Mar 21, 2020

Thanks for that great post. Besides doing a great job explaining the different types (better than Doctrine's documentation, IMHO, too), your final word of maybe just using traits really simplified my project and my thought process!

@marcosdipaolo
Copy link

marcosdipaolo commented Jul 14, 2020

@marcosdipaolo I'm not sure to correctly get your scenario.

First of all, are VideoTag and PostTag really different? Ie., do they each have different properties?
If not, what are the benefits of having those in the same tag table?
The main point of my article was to avoid Doctrine inheritance as much as possible, so I would rather not push you to a suboptimal solution.

@romaricdrigon forget about the example, what i need is a manyToMany polymorphic relationship where an entity can have manyToMany relationships with more than one entity trying to avoid having multiple pivot tables. I just don't understand how you achieve that with doctrine.
The pivot table would be something like this:

tag_id taggable_id taggable_type
5 7 App\Video
3 5 App\Post

@parijke
Copy link

parijke commented Jul 15, 2020

@marcosdipaolo that is what I am looking for as well....

@romaricdrigon
Copy link
Owner Author

romaricdrigon commented Jul 21, 2020

@marcosdipaolo you will be hitting one of the limitations of Doctrine inheritance: Doctrine can not handle polymorphic relationships (as far I know. In database). In the database, the taggable_id column has to be a foreign key either of video either of post.

The best solution I can think of would be to have nullable fields, as such:

tag_id video_id post_id
5 7 NULL
3 NULL 5

PHP-side, you can have a getTagged getter in Tag class returning either a Video, either a Post, if you really need it. But DQL will be a little bit more verbose, as you have many fields.

@marcosdipaolo
Copy link

@marcosdipaolo you will be hitting one of the limitations of Doctrine inheritance: Doctrine can not handle polymorphic relationships (as far I know. In database). In the database, the taggable_id column has to be a foreign key either of video either of post.

The best solution I can think of would be to have nullable fields, as such:

tag_id video_id post_id
5 7 NULL
3 NULL 5
PHP-side, you can have a getTagged getter in Tag class returning either a Video, either a Post, if you really need it. But DQL will be a little bit more verbose, as you have many fields.

thanks for your answer @romaricdrigon
I've done before what you're showing me there. I was simply trying another approach. It turned down that it WORKED
I just created a Taggable entity which represents the relationship. It has

tag_id taggable_id taggable_type
5 7 App\Video
3 5 App\Post

I just have to create a 'two columns' index between taggable_id and taggable_type which the db use to search much quickly.
I'll probable will map the entity names to integers to make taggable_type and integer table.

Of course there isn't a queryBuilder method for that, something like this is necesary (I made the type a ValueObject):

public function getByEntityAndType(Taggable $entity, TaggableType $type)
{
        $qb = $this->createQueryBuilder('cc');
        $expr = $qb->expr();
        $qb->where($expr->andX(
            $expr->eq('taggableType.type', ':type'),
            $expr->eq('taggableId', ':id')
        ));
        $qb->setParameter('type', $type->getType());
        $qb->setParameter('id', $entity->getId());
        return $qb->getQuery()->getResult();
}

@parijke
Copy link

parijke commented Jul 22, 2020

@marcosdipaolo if I am not misteaken you cannot use those querybuilder calls inside a Entity. So for example how do you handle inside the Video entity a getTags() call to retrieve an ArrayCollection?

@marcosdipaolo
Copy link

marcosdipaolo commented Jul 22, 2020

@marcosdipaolo if I am not misteaken you cannot use those querybuilder calls inside a Entity. So for example how do you handle inside the Video entity a getTags() call to retrieve an ArrayCollection?

hi @parijke
That's not in the entity, it's in the repository class
You're actually right, you cannot have a neither a traditional relationship nor fancy methods in the entity. You need a service/repository class that query the relationship entity

@parijke
Copy link

parijke commented Jul 23, 2020

Do you have a visible repo where you used this technique? I need something like this for attachments to all kind of entities

@vuvgaG
Copy link

vuvgaG commented May 6, 2021

Can you explain further on

Composition, ie. having 3 separate Article, Picture and Video entities, would have been a better choice

@cjhaas
Copy link

cjhaas commented May 6, 2021

Can you explain further on

Composition, ie. having 3 separate Article, Picture and Video entities, would have been a better choice

In that specific case, using CTI or STI the biggest drawback that I can think of is what if you want to have 2 pictures or videos? For both STI and CTI, you'd have another nullable column in the database, or you'd have to create a ArticleWithTwoPictures entity, and the obvious scale problems associated with it for 3, 4 and n versions.

Instead, using composition, an article can have zero or more pictures and zero or more videos. If you have a need for a version where a certain number of pictures and/or videos are required, you can handle that in your validation logic before persistence, but it really doesn't (always) need to be represented in the entity itself, nor does the database need to be aware of that.

There are definitely times when this isn't the case, and the author notes that they have seen one valid example, but it is pretty rare.

(I'm saying all of this having built a full CTI system with infinite hierarchy, where every entity was based on a shared "root object", and every object also supported a "parent object". Describing the restrictions on what could or could not participate in the parent/child relationship was part of the nightmare and required insanely complicated SQL with hard-coded magic strings, and, CTEs were all but impossible. It was great when I built it, but 6 months later it was a nightmare to make the smallest changes. After a couple of years I finally migrated back to dedicated entities with composition and the code is so much easier to reason about.

@romaricdrigon
Copy link
Owner Author

@vuvgaG-dukgu3-wysnox just to rephrase, the better option would have been one Article entity with a one-to-many relationship to Picture entities ; and a one-to-many relationship to Video entities. cjhaas described quite well details and technical advantages. I would add that the first and foremost reasoning for me, when doing software architecture, is always around vocabulary, around the semantic. I try to see if concepts are semantically different, and if so, inheritance (Doctrine or not) is a no go.

@cjhaas thank you for the story, in italics, it is always nice to read other people experiences. Though of course I could wish you nicer and more successful experiences :)

@siganushka
Copy link

Great article

Using traits can work very well, and I have been using them (https://github.com/siganushka/generic-bundle), but traits are not perfect, such as constructors, and the order of attributes cannot be controlled.

@AlexandreSherozia
Copy link

Hey, thanks a lot for this exciting article.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants