How to Build a Polymorphic One-to-Many Factory in Laravel

Published: 1 year ago - Updated: 10 months ago

5 minutes - 213 Words

article 'How to Build a Polymorphic One-to-Many Factory in Laravel' banner

Summary

In this blog post, I will guide you through the steps to create a Polymorphic One-to-Many factory in Laravel.

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.

desc

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.

desc

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.

desc

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!

Add Comment

Conversations (0)

Sign up for our newsletter

Stay up to date with the latest news and articles.