In my app, I have a few entities that share some common properties. Example entities:
Image
Video
Event
ForumPost
I want these classes to share some common functions, such as:
Have a list of comments, as well as keep counts of comments
Voting
List of subscribers and subscriber counts
View counts
etc..
I really would not want duplicate these functions for each of the entities above. So... I've been reading about Doctrine inheritance mapping and I've come up with an idea that "Class Table Inheritance" is the most elegant way to solve this. The plan was as follows:
Create a parent Post entity
/**
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", type="string")
*/
class Post
{
protected $likeCount;
protected $subscriberCount;
protected $subscribers;
// ...
}
Have many-to-one relations of likes, comments, votes, etc to Post entity:
class PostLike
{
/**
* #ORM\ManyToOne(targetEntity="Post")
* #ORM\JoinColumn(name="post_id", referencedColumnName="id", nullable=false)
*/
protected $post;
}
Have the Image, Video, etc entity extend Post:
class Video extends Post
{
// Post properties become available here
}
All worked like charm until I looked at the actual queries Doctrine is executing. The problem is: whenever I select a PostLike, doctrine will eager-load Post entity as well, which will in turn join all the child tables as well. So for example, a simple PostLike remove operation triggers a join on 4 tables and will actually join even more tables as my hierarchy grows.
If I have 10 entities extending Post, that would make a join on 10 tables. I am not a fan of premature optimization, but this just doesn't sound like a good plan.
In Doctrine documentation, this behavior is actually mentioned at "6.3.2. Performance impact".
Finally, my question: What are my other options to create reusable tables with doctrine?
I was thinking to do a One-To-One mapping (Post entity being a property of the Video, as well as the Image, etc), but I've read in a few blogs that One-to-One should be avoided with doctrine also for performance reasons.
thanks!
Edit and ansewer to Raphaël Malié regarding Mapped Superclasses:
I am aware that I can use Mapped Superclass and Trait features to avoid code duplication. However, these features allow me to reuse the code but don't allow me to reuse sql tables. Here is an example:
Say I want Image, Video and Event entities to have comments. I can create one Mapped Superclass AbstractComment and then extend it in ImageComment, VideoComment, EventComment. Next, I want to add comment votes. Since I can't reuse same sql table, I will need to create ImageCommentVote, VideoCommentVote, EventCommentVote, etc. Then I want users to be able to report abusive comments. There goes again: ImageCommentReport, VideoCommentReport, EventCommentReport. You get the idea. Every feature that I want to add to comment will need a separate table for each entity like image, video, etc. Lots of tables.
The reasons why I prefer to use centralized approach instead of using traits with many tables:
Easier administration. I can easily search/edit/delete comments of the entire app by sending a query to one table.
The same controller can handle actions for multiple entities, no need to create one abstract and many concrete controllers
Showing user notifications about new comments/likes etc is MUCH easier when actions are centralized.
I am also aware of the drawbacks of such system, but I can live with them for now. My question is how to get Doctrine to do what I want without actually joining everything when not necessary.
In my opinion you are doing wrong. Your entities have nothing in common, except some functionalities, so there is no reason to use single table or class table inheritance.
You should use Traits: http://php.net/manual/en/language.oop5.traits.php
It works with Doctrine. You define one or several traits with some properties / methods with annotations, and then you import these traits in your entities.
You can also use a Mapped Superclass : http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html#mapped-superclasses which is quite close to your example, but with a different entity annotation.
After some thought, your decision really depends on the size of your database. If it is big the performance hit of the Class Tabel approach might be unacceptable. But if your tables are small, the performance hit is minor and you can use all the advantages of Class Table Inheritance, which you discussed yourself in your post.
Related
I'm using Symfony4. I have a database structure with three tables, with two one-to-many relations relating them. For instance:
House -> Person -> Dog
Each house can contain many persons, each person can own many dogs. (My database isn't really about houses and dogs, the actual table names have been changed to protect the innocent.) Using Doctrine entities, I of course have functions like $house->getPersons() and $person->getDogs(). But what is the correct way to implement a function like $house->getDogs()? There are several solutions I can come up with, but none of them seem like a "good" solution to me:
I could simply add a one-to-many relation relating House to Dog. However, this goes against one of the fundamental rules of normal database structure: no redundant data. The relation house->person->dog is already expressed in the database, so adding another relation directly from house->dog is not only redundant, but could potentially lead to a situation where a dog's person lives in one house, but the dog lives in another (which is not something I want).
I could just do something like
foreach($house->getPersons() as $person){
$person->getDogs();
// Do something with each dog
...
}
This isn't well optimized, as I'd be running a separate query for each person - potentially a lot of queries - when I could easily be running one query with a couple joins.
I could use the HouseRepository or a query builder within the House entity to run a custom query. This, as I understand it, is frowned upon in Symfony. We generally don't want any DQL or use of repositories in the entity classes, correct?
I could use the HouseRepository from within services/controllers to run a custom query. This would be a simple way, but feels a little inelegant. Nevertheless, I have a feeling this might be the "right" way.
To sum up: I have a feeling I should be able to somehow put this fairly simple double-join in my entity, and not have to go to the repository level to get this data. To be clear, I'm not asking how to write a DQL join, I'm asking where the right place to put it is, or if there's some clever Symfony way to do this using Criteria or something similar.
If you want call $house->getDogs() you could add a method in your House entity.
public function getDogs()
{
/** #var Doctrine\Common\Collections\ArrayCollection */
$dogs = new ArrayCollection();
if ($persons = $this->getPersons()) {
foreach($persons as $person) {
if ($d = $person->getDogs()) {
$dogs->add($d);
}
}
}
return $dogs;
}
Iterating through each person in the way you've done it is perfectly valid. If you are looking to minimize the hits to the database, you can use some join statements to "pre select" everything you need all in one trip to the db. Using a technique like the one shown here, try the code below.
Assuming one to many relationship dog->persons and a second one to many relationship person->dogs, then in your HouseRepository use a method that pulls all relevant data in a single query, including all related Person(s) and Dog(s).
class HouseRepository extends ServiceEntityRepository
{
$qb = $this->createQueryBuilder('house')
->leftJoin('house.persons', 'persons')
->addSelect('persons')
->leftJoin('persons.dogs', 'dogs')
->addSelect('dogs')
->getQuery()->getResult();
}
This is untested of course, and I suspect the returned data structure will be so messy that even if it works your code might be ugly to handle it.
Let's say I have a Setting entity with some fields like IntValue, dateValue, stringValue and some linked entities, like countries (ManyToMany to entity Country), languages (ManyToMany to Language) etc.
Settings are created by users and assigned to specific objects (not important here, but I wanted to clarify).
Now I suddenly need to have UserDefaultSetting, which will be the same, but with additional user field (ManyToOne to User entity).
I tried to extend existing Setting entity class with one more field added. The problem is, as I looked at the schema update SQL, it created new table for the new entity, but without all the tables needed to ORM connections (mostly ManyToMany). Just one table with "scalar" fields.
So previously I've had setting table with int_value, date_value etc. but also setting_country and setting_language tables, linking ManyToMany relations. After creating child entity, Doctrine created only user_default_setting table with int_value, date_value etc. and additionally user_id column, but I can't see any relation/link tables.
I know I should've been do it with abstract base entity class, but at the time I started, I didn't know that and now part of the project is on production (don't look at me like that, I blame the client) and I don't want to change that "base" class now. Can I inherit everything from non-abstract entity class in a way it will work?
UPDATE: everything explained. See Cerad's comment. Thanks!
What is the difference between the 2 options above? When is it preferable to choose each option?
The basic difference between them is:
When using the orphanRemoval=true option Doctrine makes the assumption
that the entities are privately owned and will NOT be reused by other
entities. If you neglect this assumption your entities will get
deleted by Doctrine even if you assigned the orphaned entity to
another one.
Say your User has one-to-many relation to Comment. If you are using cascade="remove", you can remove the reference for Comment from one User, and then attach that Comment to another User. When you persist them, they will be correctly saved. But if you are using orphanRemoval=true, even if you will remove given Comment from one User, and then attach to another User, this comment will be deleted during persist, because the reference has been deleted.
Using Symfony 2.5+ and Doctrine 2.2+, is it a bad idea to create multiple entities which all have a ManyToMany association with one other single shared entity?
Background
Put on your thinking caps :) I am building 2.0 of our existing CMS in Symfony 2.5 where we have many different "Content Types", e.g. Article, Profile, Map, Image, Video, Document, etc. When users create a new piece of content (based on one of these content types) they may choose images to associate with that content (yes the image is a content type as well).
The content types are really a CMS construct for making it easier for people to add content to the site. Each content type has a controller which determines what entity the content type maps to. For the most part, each content type has it's own entity for persisting the data. The image, document and video content types are a little different in that they all map to a shared media entity (so that I can easily manage the cloud storage for all these files from one table, since they are all essentially "media" and the content type distinction is largely just UI).
So my plan is to make a bidirectional ManyToMany association between each content type entity (e.g. Articles) and the Media entity. So that when someone selects an image to associate with an article, the association is mapped in a join table article_media. This will allow us to find all content where an image is in use, so that we don't delete an active image (or at least make sure they explicitly agree).
For the sake of simplification, we'll just map one $media association, instead of separate ones for images, documents and videos, which could also be associated with an article (I think we will handle this with a controller that extracts the full Media list for separate form inputs and then merges them back into the Media array for persistence, so that we don't have to make multiple calls to the database).
This requires that the Article entity have an association like this:
/**
*
* #ORM\ManyToMany(targetEntity="\Gutensite\MediaBundle\Entity\Media", inversedBy="version", cascade={"persist", "remove"})
* #ORM\JoinTable(name="article_media")
*/
protected $media;
And the Media Entity will have an inverse association like this:
/**
* #ORM\ManyToMany(targetEntity="\Gutensite\ArticleBundle\Entity\Article", mappedBy=“media”)
*/
protected $article;
But if I have multiple content types, each entity will need this as well, and so soon the Media entity will have a lot of additional associations:
// Article Association plus additional ones for each entity...
/**
* #ORM\ManyToMany(targetEntity="\Gutensite\ProfileBundle\Entity\Profile", mappedBy=“media”)
*/
protected $profile;
/**
* This is self referential since a video may need to specify a thumbnail image...
* I'm not sure yet how best to do this, but it will be necessary.
* #ORM\ManyToMany(targetEntity="\Gutensite\MediaBundle\Entity\Media", mappedBy=“media”)
*/
protected $media;
// ...etc...
This would end up creating unique join tables for every content type, e.g. article_media, profile_media, media_media. That is probably nice for keeping these tables smaller.
Cons
The downside of this is that everytime we create a new content type, we have to add an additional association on the Media entity. If we start to get 10+ content types, that's a lot of associations. Is that a problem?
Any other problems?
Alternatives
Is it possible to make the join table have a entity field that indicates the content type, so that we only have one join table for all the different content types? For example:
id | entity | entityId | MediaId
If so how would you define in Doctrine this extra entity distinction field to prevent collisions with entityId for different content types?
Other Considerations
Later when we allow third party developers to create their own content types, this solution will no longer work because we don't want there to be hundreds of associations. So in this case, we thought that we would create an intermediate entity like MediaMap. Each entity will have a OneToMany relationship with MediaMap (uni-directional so that we only have to define the inverese side on MediaMap back to every content type). And then MediaMap will have a ManyToMany relationship with Media with a join table in between. That way Media will only maintain one association to the MediaMap.
This seems a bit cumbersome, and introduces another database query into the sequence, which I don't at all like.
Solutions
So is there a better way to do this?
Should we just maintain our own MediaMap table manually, and not rely on Doctrine entity association?
I think your first alternative is the best answer. You will have to do a manual lookup to get the original entity from the media, or to get all the media used by an entity, but you can write that as a utility class pretty quickly. This method will work no matter how many different content types you add in the future.
Your table would look like this
id | entity class (ie Biz/CMS/Article) | entityId | mediaId
There are ways to make this even more abstract that I am using on a project right now, but this would be a good start for your project.
Just out of curiosity, why did you choose to create your own CMS instead of using one of the existing options?
I have a site with many Users and many Designs, each of which was created by one User. Users can also select other Designs as a favorite, and can go to a page that shows their favorites. So, there's a many-to-many relationship between Users and Designs termed "Favorite".
I'm wondering what the best way is to implement this Favorite relationship. I'm only ever looking for Favorites for a particular user, and only on one page. It seems that Doctrine defaults to returning an object with all its related objects, so I'm concerned that by adding this relationship I'll suddenly get a ton of extra objects added to all my Design list API calls.
Advice on how to set this up? I have repositories in place for all entities.
I'll share an implementation I have between Tags and Posts (Posts can have multiple tags, and each tag can be associated with more than one post). I think it's similar enough to get you started. It's super simple to setup:
In the Post entity, I have this:
/**
* #ManyToMany(targetEntity="Tag", inversedBy="posts")
* #JoinTable(name="activity_relationship",
* joinColumns={#JoinColumn(name="object", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="subject", referencedColumnName="id")}
* )
*/
protected $tags;
In the Tag entity, I have this:
/**
* #ManyToMany(targetEntity="Post", mappedBy="tags")
*/
protected $posts;
There no entity necessary for the joining table.
In the case of posts carrying tags, it's enough for me to just access the tags property since there will always be a small amount, but each tag is likely to have tons and tons of posts, so I want some LIMIT and OFFSET controls over this. For this reason, as Cerad mentioned above, you don't need to use the $posts variable directly, instead I query that separately using dql.
SELECT t
FROM Post p
JOIN p.tags t
<add whatever other stuff you want>
Whichever way you prefer to execute that query, you will probably have a query object from Doctrine, to apply the LIMIT and OFFSET, just do
$query->setMaxResults($limit);
$query->setFirstResult($offset);