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.
Your Seeder Should Describe a Shape, Not Your Laptop
Full Article
Original Source
Read the full article at Dev →KhanList aggregates and links to publicly available news content. We do not host full articles from third-party sources. Always verify important information with original sources.