Polymorphic relations with Eloquent and Laravel 5
When you're developing a system that is supposed to work with multiple content types (or any other data structures), polymorphism can be quite helpful. Today we'll talk about how to organise your database schema and use Eloquent to implement polymorphic relations between your models.
Short intro
You know, sometimes when you're reading through a tutorial, it's not always clear from the snippets how to actually use them and you want more detail. It can be very frustrating and I'm sure that many of you have been in that situation, especially when you were trying to learn some completely new stuff. So we decided to go further than just providing you with snippets. We'll provide you with a fully functional Laravel application with migrations, models and unit tests.
Grab this repo here for examples used in this post:
https://github.com/Webscope/laravel-eloquent-polymorphic
It's based on our Laravel base setup which you are more than welcome to use as a base for your own projects:
https://github.com/Webscope/laravel-base
Let's get some work done
Imagine that your website needs to work with 2 types of content: articles and events. They both have a title. Articles have a body and Events have a date. We don't want to have a single table that will be used for both models. Instead we want to separate the content into 3 tables – one for generic data, one for event-specific data, and one for article-specific data.
Eloquent makes it really easy to setup and use.
Let's create our models first. They are already created in the repo, by the way.
php artisan make:model Models/Content php artisan make:model Models/Article php artisan make:model Models/Event
Content will be a parent table containing the generic properties for articles and events.
Database migrations
Please go to the database/migrations folder.
The Content table will have 2 extra columns for referencing Articles and Events. content_data_id will be used for storing the Article/Event ids and content_data_type will store the class name of the Article/Event model. The easiest way to set those up is to use the morphs() method. It will create the required columns for you. You just need to provide the relationship key to that method, which in this case is content_data. That key is like a machine name for that relationship and it can have any name that suits your needs. You can also setup the columns manually.
Articles and Events migrations are very straightforward and don't require any extra setup.
Models
Please go to app/Models and check out all the models.
We'll need to setup a few methods on our models to enable the relationship between them. Please note that we're providing the relationship key as one of the arguments. Content model is using the morphTo() method, and Article and Event models use the morphOne() method, because it's a one-to-one relationship.
Tests
Please refer to tests/unit/Models
It's very easy to unit test Eloquent relations with Mockery. We just need to make sure that the morphTo() and morphOne() methods are actually being called and that they are called with correct arguments. We don't really need to test further than that.
Using polymorphic relations
ContentTest.php contains an integration test which shows how to actually use the polymorphic relation. The main idea is that you create a child model first (an Article), then you attach it to the parent (Content model) and you save the parent. If you'll try to create and save the parent first, Eloquent will give an error, because the content_data_id and content_data_type cannot have null values.