Get started
Creating a Polymorphic relationship between models can be quite challenging. This relationship allows a single model to belong to multiple other models, which can be useful in many scenarios.
Let’s take a look at one of these scenarios.
we have a posts
and courses
table each of the posts and courses can have a status so instead of creating two tables like posts_status
and courses_status
we can create a polymorphic one-to-many table called statuses
and now we can attach a status for any row or table that we want.
The benefit of this approach is that, if in the future you have a lessons table and you want to assign a status to your lessons you can do it without changing your database schema.
In this article I have assumed that you know how to create migrations and models for the above tables so I will jump straight to creating the factory for statuses
table.
Create factory
As always to create a factory we run the bellow artisan command
php artisan make:factory Status
The above command will create a StatusFactory.php
file in database→factories
directory of Laravel app.
By default the file will look something like this.
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Model>
*/
class StatusFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
//
];
}
}
so lets start by defining the name of the status. in this case I want the status name to be one of the bellow options.
- PUBLISHED
- DRAFT
- PENDING
- REJECTED
To achieve this we will use faker’s randomElement()
method and pass our options to it as an array.
This will return one of the options at random each time a record is created using this factory.
....
public function definition(): array
{
return [
'name' => $this->faker->randomElement([
'PUBLISHED',
'DRAFT',
'PENDING',
'REJECTED',
]),
];
}
...
Next up is the statusable_id
and statusable_type
.
One way to do it would be to create status for only one model(like course) and whenever you are testing another model(like Post) just change the factory. but this will grow tedious and if you add more tables to polymorphic relationship, it will become hard and time consuming to change the factory every time.
return [
'name' => $this->faker->randomElement([
'PUBLISHED',
'DRAFT',
'PENDING',
'REJECTED',
]),
'statusable_id' => Course::factory()->create(),
'statusable_type' => 'App\Models\Course',
];
So let’s try a better way, start by creating another method after definition() we will call it statusable
.
public function statusable()
{
return $this->faker->randomElement([Course::class, Post::class]);
}
The statusable
function uses faker’s randomElement
and I have passed an array of the models that I want to create status for.
If in the future I decided to attach status to Lesson model all I have to do is add the Lesson::class
to the array.
public function statusable()
{
return $this->faker->randomElement([Course::class, Post::class,Lesson::class]);
}
Now we have a statusable
method , let’s get back to our definition
method.
public function definition(): array
{
$statusable = $this->statusable();
return [
'name' => $this->faker->randomElement([
'PUBLISHED',
'DRAFT',
'PENDING',
'REJECTED',
]),
'statusable_id' => $statusable::factory()->create(),
'statusable_type' => $statusable,
];
}
In the definition
method we call statusable
method to get a class from our list and in the statusable_id
key of the array, we call factory
method on that model to create a new model row.
In the statusable_type
key we simply passed model class string to get the type of created statusable.
We are done with the StatusFactory.php
and it looks something like this.
<?php
namespace Database\Factories;
use App\Models\Course;
use App\Models\Post;
use Illuminate\Database\Eloquent\Factories\Factory;
class StatusFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
$statusable = $this->statusable();
return [
'name' => $this->faker->randomElement([
'PUBLISHED',
'DRAFT',
'PENDING',
'REJECTED',
]),
'statusable_id' => $statusable::factory()->create(),
'statusable_type' => $statusable,
];
}
public function statusable()
{
return $this->faker->randomElement([Course::class, Post::class]);
}
}
Let’s test our factory, as you see in the bellow screenshot that statuses table is empty.
So let’s create some row for it using our StatusFactory
.
Open a tinker session by running shell php artisan tinker in the terminal
. Once the tinker session is ready, run the bellow command.
$statuses = Status::factory()->count(6)->create();
It will create 6 rows in the statuses table for us so let’s check our table and see what have we got.
As you see the bellow screen shot we have status records for both Post
and Course
models.
Conclusion
I hope that this blog post has provided you with the knowledge and tools needed to create your own Polymorphic factory in Laravel. Remember to always test your code thoroughly and happy coding!