FYI In Laravel the morphable_type actually refers to the model name. So it will be equal to something like App\User. So it actually doesn't reference the table name at all.
Polymorphism: Why should I violate Database Design?
While I understand the power of Polymorphism, I also understand the basic design principles for Database Design. Polymorphism violates something I consider to be crucial to Database Design: Foreign Key Relationships.
When using Polymorphism, the two columns <morphable>_type and <morphable>_id are used. The <morphable>_type column references the name of a Table in the Database, and the <morphable>_id references an ID within that Table. Nothing new here.
However, I see it as bad practice to mix Meta-Data and Data, i.e. <morphable>_type. If a table is dropped or renamed, the Database can't naturally enforce anything. I understand that Migrations can resolve this, but it doesn't fix the fact that the Database itself can't enforce this relationship.
Furthermore, Foreign Key relationships can only reference one Parent Table. Polymorphism violates this with the <morphable>_id column. Sure, there's no Foreign Key assigned to this column, but now you essentially have a number that has no meaning to the Database. If that key is updated, or the entry is removed, how will the Database know to remove that row? I understand that this too can be resolved, by using the Boot Method in each respective Model, but it doesn't fix the fact that the Database itself can't enforce a Foreign Key that isn't defined.
Honestly, this leaves me stuck. I know that there are other alternatives to Polymorphism that are RDBMS friendly, but all require general maintenance, or make the MVC flow feel unnatural. I'm a Database guy, so violating basic principals is hard for me to do. However, I'm also a Design guy, so I don't want to build a system that's hard to code or unnatural. Polymorphism is where these two clash, as it's an elegant solution, but it violates Database Design.
I know the pros for sticking with proper Database Design, but is Polymorphism really worth breaking principles?
Furthermore, is there a nice, general way to automatically handle all of the nasty maintenance issues that come with Polymorphism? Since Laravel is Database Agnostic, I'm willing to bend the rules a bit, as I can say that the Database Layer of my Application (which would extend into Models and Migrations) is able to successfully enforce Polymorphic relationships.
@jlrdw I'm really just wanting good Database design, having rules that can't be enforced that should be seems like a bad idea.
I've turned down Polymorphism in the past in favor of better Database Design. I ended up with a better Schema, but it did make some of the relationships more complicated.
@Niban I didn't know that. That makes things easier in some cases, as I can just change the Table reference in the Model. Of course, the issue remains: If I change the name of the Model, or it's Namespace (I use App\Models\User), then we're back to the original issue.
Good database design is great if utilized properly. Yes polymorphic doesn't utilize the MySQL capabilities but I argue that when you execute a join you still have to explicitly set the key right? So unless your using MySQL views etc your still not harnessing the power of MySQL. Basically in 90% of use case your just utilizing the index part of the forgiven key along with delete cascade etc not the full power which can effiecently be replaced with index and proper code.
Matter of fact not using foreign keys but useing index is proven to not reduce MySQL speed any what so ever. The majority of issues is code and retrieving more data then is nessesary. I.e. * from table when you really need one column.
So polymorphic is a trick to reduce confusion, the amount of queries, and improve performance. Good luck having 10 photos tables or ten Boolean values and keeping it straight and efficient
Read the alt (hover over the image) text:
https://m.xkcd.com/1205/
@tylernathanreed you should also have a look into Relation::morphMap(['users' => 'App\User']); (put it on the AppServiceProvider boot method)
it will put the map key as the <morphable>_type value, giving you more flexibility to modify namespaces in the future.
@tylernathanreed Well you have a decision to make: go for the “perfect” database schema that will make queries more complicated and harder to implement in Eloquent models. Or just use polymorphism how it’s implemented in Eloquent and get on with building something of value to your client/employer.
Yes, the way polymorphism is implemented in Eloquent may not be the “best” way, but are the downsides really worth causing decision paralysis? Is your database going to explode if you use polymorphism? Are you really going to be changing model/table names? Are you really going to be changing the primary keys of records referenced in a polymorphic relationship (I hope not)? Are these issues so difficult that they can’t be solved with an UPDATE statement if you did change a model/table name or primary key?
This thread was started a year ago. Hopefully @tylernathanreed has found a solution.
@ZetecVan Damn, just seen that. Wonder why @pedro.gaspar decided to reply to a year-old thread in that case :-/
@martinbean This topic has always sat in the back of my mind. No worries.
At my workplace, we're using Laravel, and we're making use of polymorphism quite a bit. We've really only had one big issue with them: There are no foreign keys. Sometimes we forget about certain tables when deleting records (We have a 289 tables right now, so it can be difficult to keep stuff straight). This leaves records that shouldn't exist in our database, and it can break certain code pieces on the polymorphic table that assumed its polymorphic relation existed.
The only other potential issue we've had is our vendor integration. Sometimes we have to give third-party businesses read access to our application. Showing them the <morphable>_type data gives some clue into how our code-base is structured. This issue isn't big enough to deter me from polymorphism, but it is big enough to point out.
After a year of trying out both design patterns, I can definitely say that polymorphism is very useful, but comes with some problems that I mentioned in the OP. I know that in the workforce, the mentality is basically "Screw the problems, we'll find ways around it", which is fine, but that gut feeling of "I told you so" comes back to bite me every time we have issues related to polymorphism.
@tylernathanreed It seems you just have a negative view on Laravel’s implementation of polymorphic relations and are just waiting for a “told you so” moment to validate your prejudice instead of just getting on with things.
Solve problems when they’re problems. If you worry about “what ifs” you’d never write a line of code again ;)
@tylernathanreed Why are you storing morphable_type and morphable_id in the same table? If really want to create 'Soft Relations' I would remove morphable_type from the table and use the morphable_id as a reference to the type. Then make morphable_id a foreign key. Thats what they do; preserve important relations (As you point out). Then you join the table when you need to use that type. Or join on another morphable_id when you use another (Polymoprphic). Joins are good. SQL has theses constructs for a reason. If you insist on managing these things through code you will have the potential for problems. As you correctly pointed out you get orphaned data. And potential errors. Thinking about how a design performs in the future is good. Has your team discussed this as well as you? And forget about Laravel's or any other frameworks conception of a term its just an implementation not a law.
@martinbean My views towards Laravel's implementation of polymorphic relations is that it actually does it very well. The real issue I have is with polymorphism in itself.
I eventually just used to argument of "Eloquent is my database abstraction layer, so as long as I use that to enforce my relationships, I'll be fine", as my original issue with Polymorphism was that I couldn't enforce/create Foreign Keys. However, I also eventually learned that every now and then you do have to go down to the database layer, and run raw queries on it (This is outside of the code, and typically run for one-off cleanups or reporting). I could argue that this is in fact bad practice when using Polymorphism, because you create the possibility of leaving orphaned records. However, it can be difficult to get away from it when the pointy-haired boss is telling you to do it.
@jimmck morphable_type and morphable_id are stored in the same table as per Laravel's implementation of polymorphism. Without the morphable_type column, you don't know what table you're joining to. If you're always going to join to the same table, then you don't need polymorphism. In this case, a simple Foreign Key would suffice.
That is effectively the alternative to Polymorphism: Create a bunch of Foreign Keys, and loose the "morphable" concept all together. However, once you start making heavy use of this practice, you'll discover that you have tons of pivot tables and tons of joins. Joins are a necessary means, but they should be minimized for the sake of performance. In the world of Acadamia, performance doesn't matter all that much, so this approach is typically taken.
This is what put this thought in my mind in the first place. Polymorphism is essentially loosing the ability to use Foreign Keys for the added benefit of simplifying your relationships and minimizing the number of tables and joins you have. The major drawback to Polymorphism is the loss of Foreign Keys. If you're not careful, you can leave a bunch of orphaned records.
@tylernathanreed Polymorphism is not an implementation its a design concept. Laravels implementation is one of many. Moving morphable_type to a separate table allows you to define many morphable types. You join on morphable_id. The type table records defines the type. Polymorphism does not directly lead to loss of foreign keys the implementation does. Foreign keys do indeed provide overhead to the database; which means database quality must be measured when to create them.
@jimmck If the morphable_type column is stored on a separate table, then how would the application know what table morphable_id references? I've never heard of this being done. Are you perhaps thinking of Class Table Inheritance or Single Table Inheritance?
If there's an alternative implementation that Polymorphism that solves some or all of my issues with it, then I'd love to learn more.
I solved the {morphable}_type in a different table in the following method not sure if is the best way but the quick solution is this relationships in the super model
-type = belongs To(activities_types, 'activity_type') //the activity_type
-extra = belongs To(activity->type()->system_class, 'id', 'id') //complementary fields of the generated activity
A small resume of the context i reach to this solution:
I was creating a polymorphic system for different activities, that allows the back office admin creates and deletes the activity type. (Still figuring out what will be the best way to implement a update method since will use auto-generated tables, models and requests for the validation/access the activity the point of the different types is to have different values, but without restrictions or a mess in the db)
Anyway i end up structuring my tables like this.
Activities_types
-id
-system_id //the system name to identify and create the generated classes and files
-system_class //The generated activity class used as the {morphable}_type
-... //Timestamps & soft Deletes, Readable names, descriptions, etc
Activities
-id //Auto implementable id, any generated activity will get access by this id
-generated_id // the id of the generated row
-activity_type // foreign to activity_types_id
-... //shared fields between all the activities(client, employee, name, etc)
So in the super class activities i created the relationships i said in the top of this post and others to the employees, third employees with restricted/unlimited access etc
Hi! I am wondering about the same as OP. It's mostly philosophical, I have to admit, as in reality a working solution is most the time enough. But in theory I'd like to have the best of both worlds: foreign key constraints and Eloquent as well. So it really looks like the price to pay is to not use Laravel's polymorphic relationships.
I am wondering if anyone can share some (potential) downsides to instead of applying polymorphism one would simply just use one-to-one relationships instead between the parent and child models? Sure, I might need to have a belongsTo() call for each relationship instead of a single morphTo() call, but that's not a serious concern for me at all, especially that the other side of the polymorphic relationships would still need one morphOne() per model, so really it only saves half of the work. And I'm curious to see any application, where defining model relationships is such a huge part of the app, that it worth sacrificing cleaner db structure to save few lines of PHP.
But I also assume I am missing the point, so please let me know. What have I left out of consideration?
@tylernathanreed you rised a big issue that i've been facing for ages. I've been working on an application for some time now, I've come across these problems. In my point of view, I would not like to tie up code issues with the database, because one of the things that naturally arises in addition to reference integrity is the issue of code paradigm shift, imagine that tomorrow the team decides to rewrite the code in django or node. These issues that were forced by the framework are difficult to implement or integrate into the new ecosystem.
Now my solution may not even be better but it solves and allows isolation.
We created several foreign keys in the related table, I'll give an example.
post_table
-id
-name
photo_table
-id
-path
page_table
-id
-path
comments_table
-id
-text
-post_id (nullable, on delete/update cascade)
-photo_id (nullable on delete/update cascade)
-page_id (nullable on delete/update cascade)
We write code to solve problems and not to create them. Polymorphism is a very elegant solution to a problem, but with nuance flaws. I suggest we take on this flaws as the next problem/challenge rather than complaining. Suggested solutions include
- Improving sql databases to support native polymorphism (it's easy and simple, and I'm still wondering why it hasn't been done)
- This one is a little tricky; We can add some strict implementation to Laravel's polymorphic implementation to display a warning before modifying parent row(s). I'm quite proficient with sql databases, but I've never interacted with my database directly, I always use tinker. So, this proposal is only relevant to people who interact with the database through tinker.
I recently worked on a fintech application, and I did not see a way I could have survived without polymorphic db relationships. If Laravel's implementation of polymorphism is this elegant, I see no reason why it shouldn't be further improved and standardized.
Also @tylernathanreed I thought it's worth pointing out that Laravel has also made it extremely easy to hide some database fields for being visible when serialized.
As this thread has been referenced elsewhere, I just want to add my two cents although the original question is now 8 years old: it depends.
As a journeyman software developer you have learned all the important patterns and know how to apply them and you have a good understanding for why they exist and how they work. Moving on from that place requires developing an intuition when it is okay to break the rules.
MySQL (and most other SQL databases) does not provide a built-in way to handle polymorphism. But polymorphism is often imposed on a data model by the problem domain. And at the end of the day the important thing is whether your code solves problems in that domain, not whether it is pure or well-written or clean. Those things contribute to producing resilient and maintainable software but they merely act as multipliers, they don't provide value on their own.
Yes, there are drawbacks when using polymorphic relationships. Yes, they do interfere with database level affordances you may be taking for granted. But that means that when you decide to use them, these are things you need to take into consideration and plan for. For example, you can create maintenance scripts that understand the application layer instead of manually executing SQL queries.
It's also worth remembering that in most cases your database is not your API. The database is your persistence layer. It should ideally be an implementation detail in order to make it easier to switch persistence layers in the future should the need arise. You can do that with an API. You can't easily do that with direct database access exposing the specific implementation details of the database itself.
Polymorphism is worth considering if:
- Your application benefits significantly from the flexibility it provides.
- You have the resources to implement and maintain the additional logic required to enforce integrity at the application level.
- You are using a framework like Laravel that offers robust tools to manage these relationships effectively.
However, if maintaining strict database integrity and simplicity is a higher priority, you might want to consider alternative design patterns such as:
- Concrete Table Inheritance: Separate tables for each polymorphic type with a shared parent table.
- Single Table Inheritance: One table with a type column and nullable fields for each subtype.
Polymorphism can indeed violate traditional database design principles, but it offers practical benefits in terms of flexibility and design simplicity. Whether it's worth breaking these principles depends on your application's needs and your willingness to manage the additional complexity through application logic.
By carefully implementing additional checks and maintenance logic, you can achieve a balance between maintaining database integrity and leveraging the advantages of polymorphic relationships.
@IN2SITES Great comment, I'd also point out that there is a "parental" package by Tighten if one decides to go the "Single Table Inheritance" route.
Please or to participate in this conversation.