Your Seeder Should Describe a Shape, Not Your Laptop

작성자

카테고리:

← 피드로
DEV Community · Nasrul Hazim · 2026-07-20 개발(SW)

TL;DR

  • Rewrote a bootstrap seeder so the deployment stack — DB engine, queue runner, PHP version — is config, not hardcoded.
  • Renamed it away from a phase-based name (MvpSeeder). If a class name describes when you wrote it, it expires.
  • Trimmed a CI matrix from three PHP versions to one. Matrix breadth should match what you actually deploy.

The seeder that only worked for me

Bootstrap seeders rot predictably. Version one seeds exactly the environment the author had open at the time: one database engine, one queue setup, one PHP version. It works, so nobody touches it. Six months later someone needs Postgres and discovers the choice was never a choice.

I hit that on a control-plane app — the thing that describes and provisions deployments. Its seeder encoded a single stack shape, so I pulled the variable parts into config.

'control_plane' => [
    'default_blueprint' => 'stack-laravel',   // or -ha, -single-node
    'database'    => env('CONTROL_PLANE_DB', 'mysql'),      // mysql | postgresql
    'queue'       => env('CONTROL_PLANE_QUEUE', 'horizon'), // horizon | default
    'php_version' => env('CONTROL_PLANE_PHP', '8.4'),

    'apps' => [
        // each entry may override blueprint / queue per app
    ],
],

Enter fullscreen mode Exit fullscreen mode

Knob Options Default from Blueprint single-node, standard, HA config Database MySQL, PostgreSQL env Queue runner Horizon, plain queue:work env PHP runtime 8.3 – 8.5 env

Two rules kept it tidy. Every knob has a sane default, so a fresh clone still seeds without a populated .env. And every knob is overridable per app — the moment you have more than one workload, one will be the exception.

   config/seeder.php
        |
        v
   PrepareSeeder  ---->  ControlPlaneSeeder
                              |
                +-------------+-------------+
                v             v             v
           blueprint       database      queue runner
        (single/std/ha)  (mysql/pgsql)  (horizon/default)
                \             |             /
                 +-----> Workload + Deployment

Enter fullscreen mode Exit fullscreen mode

Horizon or not is a real fork

Both options run under supervisord, but they’re different operational bargains:

Horizon Plain queue:work Dashboard Yes No Auto-balancing across queues Yes Manual worker counts Extra dependency Redis required Any queue driver Config surface config/horizon.php Supervisor program blocks

Horizon is the better default for real queue load. But a single-node app running three jobs a day doesn’t need Redis in the stack just for a dashboard nobody opens. Per-app key means neither choice forks the codebase.

Rename anything named after a phase

The seeder used to be MvpSeeder. It’s now ControlPlaneSeeder.

MvpSeeder answers “when was this written”. ControlPlaneSeeder answers “what does this do”. The first stops being interesting the moment the MVP ships; the second stays true for the life of the class. Same for NewUserService, V2Controller, TempJob — timestamps pretending to be names.

While I was in there: the CI matrix

Related cleanup elsewhere. A test workflow was running PHP 8.3, 8.4 and 8.5. Trimmed to 8.5.

Matrix breadth should match your deployment surface. A published package genuinely needs to prove it works across versions — its users pick the runtime, not you. An application you deploy yourself runs exactly one PHP version, and testing two others buys nothing but Actions minutes.

Project type Matrix Published package Every supported PHP/framework combo Deployed application The version you actually deploy

Also removed a duplicate workflow running the same suite twice per push. Free 50% cut, which is the best kind.

Test the knobs

Seeders are worth a test the moment they encode decisions rather than fixtures:

it('seeds the queue runner from config', function () {
    config(['seeder.control_plane.queue' => 'default']);

    $this->seed(ControlPlaneSeeder::class);

    expect(Deployment::first()->queue_runner)->toBe('default');
});

Enter fullscreen mode Exit fullscreen mode

Without it, “the config knob works” is an assumption that holds until the one time you actually flip it — usually on a fresh server at an inconvenient hour.

Takeaway

A seeder is documentation that executes. If it says “this system runs MySQL with Horizon on PHP 8.4”, it should be describing a default, not a constraint. Push the specifics to config, keep the shape in the class, and give every knob a default that works out of the box.

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다