Idempotent Database Seeding in Laravel Preview Environments

Idempotent Database Seeding in Laravel Preview Environments

Idempotent Database Seeding in Laravel Preview Environments

When working with GitHub pull requests (PRs) and deploying preview environments using Glimpse, a common question we receive is: “How can I run seeders in my preview environment?” If you’re deploying fresh environments regularly, you’ll likely want to populate your database with sample data for testing. The good news? We have a solution that automates this process, ensuring your preview environments are as complete as your production setup.

In this post, we’ll introduce a trait you can use in your Laravel projects to run seeders only once per database—implementing idempotent database seeding to keep things efficient, clean, and reliable across your preview environments.

The Challenge: Running Seeders in Preview Environments

Preview environments provide a powerful way to test and review changes before they hit production. Glimpse allows you to spin up isolated preview environments directly from your GitHub PRs, making sure each feature, fix, or update can be tested in a real-world setting before merging.

But what about database seeders? Running seeders every time you deploy a new preview environment might seem repetitive or redundant. This can lead to duplicated data or performance hits if not managed properly. That’s where the idempotent seeding provided by the CanSeedOncePerDatabase trait comes in.

Solving Seeder Redundancy with Idempotent Database Seeding

The CanSeedOncePerDatabase trait ensures idempotency by making sure seeders only run once per database. By creating a dedicated table to track which seeders have already been executed, this trait prevents redundant seeding and keeps your preview environments consistent.

Here’s the code for the trait:

<?php

namespace Database\Seeders;

use Illuminate\Console\View\Components\TwoColumnDetail;
use Illuminate\Support\Facades\DB;

trait CanSeedOncePerDatabase
{
    protected string $seedersTable = 'seeders';

    protected bool $seedersTableExists = false;

    public function callOncePerDatabase($class, $silent = false, array $parameters = []): void
    {
        if ($this->seederHasAlreadyBeenCalled($class)) {
            if ($silent === false && isset($this->command)) {
                with(new TwoColumnDetail($this->command->getOutput()))->render(
                    $class,
                    '<fg=gray>Seeder had already run on this database</> <fg=yellow;options=bold>SKIPPING</>'
                );

                $this->command->getOutput()->writeln('');
            }

            return;
        }

        $this->call($class, $silent, $parameters);

        $this->markSeederAsCalled($class);
    }

    protected function seederHasAlreadyBeenCalled($class): bool
    {
        $this->createSeedersTableIfNotExists();

        return DB::table($this->seedersTable)
            ->where('seeder', $class)
            ->exists();
    }

    protected function markSeederAsCalled($class): void
    {
        $this->createSeedersTableIfNotExists();

        DB::table($this->seedersTable)
            ->insert(['seeder' => $class]);
    }

    protected function createSeedersTableIfNotExists(): void
    {
        if ($this->seedersTableExists) {
            return;
        }

        $schema = DB::connection()->getSchemaBuilder();

        if (! $schema->hasTable($this->seedersTable)) {
            $schema->create($this->seedersTable, function ($table) {
                $table->string('seeder')->unique();
            });
        }

        $this->seedersTableExists = true;
    }
}

How to Use the CanSeedOncePerDatabase Trait

Using this trait in your Laravel project is simple. Follow these steps to ensure your seeders only run once per database in your preview environments:

Step 1: Add the Trait to Your Seeder Class

In any of your seeder classes, simply add the CanSeedOncePerDatabase trait. For example, in your DatabaseSeeder.php file:

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    use CanSeedOncePerDatabase;

    public function run()
    {
        // Call your seeders using the `callOncePerDatabase` method instead of `call`.
        $this->callOncePerDatabase(UserSeeder::class);
        $this->callOncePerDatabase(ProductSeeder::class);
    }
}

Step 2: Use callOncePerDatabase to Run Seeders

Instead of using the default call() method to run your seeders, use the callOncePerDatabase() method provided by the trait. This ensures that the seeder will only be executed once per database.

$this->callOncePerDatabase(UserSeeder::class);

You can pass additional parameters to callOncePerDatabase() just like you would with the normal call() method.

Step 3: Add Artisan Command to Your Deployment Script

To trigger the seeding process during your preview environment setup, add the following Artisan command to your deployment script. This ensures that your seeders are executed when Glimpse deploys your preview environment.

php artisan db:seed --force

The --force flag is required to run the seeders in non-interactive environments, such as during automated deployments in Glimpse.

To include this in your deployment script, simply add it as part of the setup process for your preview environment. If you’re using Laravel Forge in conjunction with Glimpse, you can add the command to your Forge deployment script under the “After Deploy” section.

php artisan migrate --force
php artisan db:seed --force

Case Study: Using Idempotent Seeding at RiskAdvisor

At RiskAdvisor, we recently implemented the CanSeedOncePerDatabase trait in conjunction with Glimpse to automate our preview environments. Each time we deploy a new PR, Glimpse sets up a fresh environment with a clean database.

Before using this trait, we encountered challenges with running seeders repeatedly, which caused some issues:

  • Duplicated Data: Seeders were running every time, creating unnecessary duplication.
  • Time-Consuming: Manually tracking which seeders had been run in each environment became cumbersome.

By incorporating idempotent database seeding with the CanSeedOncePerDatabase trait, we ensured that seeders only ran once per environment, maintaining a clean database setup for each preview while significantly reducing setup time and preventing data duplication.

A special shout out to Arunas Skirius, the developer who implemented this solution at RiskAdvisor. Arunas’ expertise in Laravel and infrastructure automation was key to setting up this trait efficiently.

This implementation has streamlined our development process, allowing us to focus on testing and shipping features, confident that our preview environments are fresh and accurate.

Ready to Automate Your Preview Environments?

If you’re building with Laravel and want to take the hassle out of managing preview environments, Glimpse can help. With Glimpse, your GitHub pull requests are automatically deployed to isolated environments using Laravel Forge—fully equipped with the right data, thanks to tools like the CanSeedOncePerDatabase trait and its idempotent database seeding.

Sign up for Glimpse today and see how easy it is to automate your preview environments.

Whether you’re working on a small project or managing a large team like RiskAdvisor, Glimpse ensures that your testing and development workflow remains smooth, automated, and hassle-free. Start your free trial now and deploy your first preview environment in minutes!


If you have any questions or need help setting up seeders in your preview environments, feel free to reach out or check out our documentation.

Start previewing your GitHub pull requests today. Sidebar Title

This is the description, change it in Branding settings.