gebeer Posted 17 hours ago Posted 17 hours ago It's a rainy Sunday where I'm at. The monsoon is hitting hard. Perfect time to write a love letter. Addressed to a module, my favorite PW module of all times. And its creator @bernhard, of course. A Love Letter to RockMigrations by a long-time user and contributor Dear RockMigrations, I’ve been using you for years. Built sites with you, shipped modules that lean on you, even chipped in a few pull requests along the way. And honestly, I took you for granted. You just worked. Fields appeared. Templates materialized. Modules shipped their own schema like little self-contained suitcases. I never wrote down why you’re so good. I just kept using you. Recently I found myself comparing you to other approaches, side by side, line by line. And I realized: I should write this down. Not because I learned something new, but because seeing the alternatives made your elegance impossible to ignore. Let me count the ways. The Declaration of Truth Most migration systems hand me a blank file and say “write code.” Fair enough. But you, RM, let me declare what a field or template is: // site/RockMigrations/fields/subtitle.php return [ 'type' => 'text', 'label' => 'Subtitle', 'columnWidth' => 50, ]; That’s it. No $fields->save(), no $field->type = wire('modules')->get(...). Just the truth. The current, desired state. You figure out the rest. This means the file is my source of truth, not a historical log of what someone did at 9:14 AM on a Tuesday. If I want to know what the subtitle field looks like right now, I open one file. Not a trail of timestamped breadcrumbs. Circular References? You Solved Them. Elegantly. Every migration system hits the same wall: Template A needs to know about Template B, and Template B needs to know about Template A. Who goes first? Most systems tell me to manage this manually. Order my timestamps. Cross my fingers. You do something way smarter: two passes. Pass 1: Create everything. All fields, all templates, all roles. They exist as empty vessels. Pass 2: Configure everything. Wire up parent-child relationships, attach fields, set permissions. Everyone knows everyone now. Circular dependencies become non-circular across passes. No timestamp juggling. No depends_on arrays. It just works. And if I need to inject imperative code at the right moment, you give me four lifecycle hooks: beforeAssets, afterAssets, beforeData, afterData. Escape hatches at exactly the right places. Not too early, not too late. I’ve used these to install modules, create reference pages, and detach stale fields, all in the right order, every time. You’re Non-Destructive by Default If I remove a field from a template’s config array, you don’t rip it out. You leave it alone. Because you assume I might still need it, or that removing it accidentally would be catastrophic. If I actually want to detach a field, I tell you explicitly: a removeFieldFromTemplate() call, right there in the same config file or in the relevant hook. Destruction is a conscious choice, not a side effect. This alone has saved me from myself more times than I’d like to admit. Modules Ship Their Own Schema A module can drop a RockMigrations/fields/ folder into its own directory, and you just… discover it. No registration. No global config. No “please add this module to your migration scan path.” site/modules/Foo/ ├── Foo.module.php └── RockMigrations/ ├── fields/bar.php → foo_bar └── templates/baz.php → foo_baz You auto-prefix field names with the module name. You auto-tag them. When I look at a field in the admin, I can see which module owns it. And when the module uninstalls, its ___uninstall() method can call a migration script that cleans everything up, fields, templates, the lot. Optionally guarded behind a checkbox in the module config so the site admin decides whether to keep the data. No manual cleanup. No orphaned fields haunting the database for years. A module is a self-contained package: code and schema. And you respect that boundary. No sprawl into a shared site/migrations/. No “did file 47 or module X create this field?” The answer is in the file path on disk. This design choice makes every module I’ve built feel cleaner and more portable. Free Constants, Free IDE Support By just creating a marker file (RockMigrationsConstants.php), you auto-generate class constants for every field and template: // Auto-generated. Do not edit. class RockMigrationsConstants { const field_subtitle = 'subtitle'; const template_award = 'award'; } Now my IDE autocompletes RockMigrationsConstants::field_... and I never type "subtitle" as a raw string again. No typos. No silent breakage when I rename a field. You generate this from the config files, the same single source of truth. Silly bugs prevented. You’ve Grown Without Bloating You’re not a proof of concept. You’re not a shiny new thing I’m nervously trying in production. You’ve grown organically across versions, picking up MagicPages, deploy helpers, auto-release workflows, and about forty other things. You’re the Swiss Army knife that somehow doesn’t feel bloated. I’ve watched you mature over the years. Every feature felt like it earned its place. Nothing feels bolted on. Things that did at one time got removed. Other solutions are ... not bad Look, I need to say this clearly: the other migration approaches out there are not bad. They’re genuinely good work by talented people, and I’m grateful we have choices. A ProcessWire ecosystem with multiple migration approaches is a healthy ecosystem. Every approach has its place and its audience. But every time I sit down and compare feature to feature, RockMigrations comes out uniquely complete. Declarative config? Check. Two-pass circular dependency resolution? Check. Module self-containment with auto-discovery? Check. Non-destructive by default? Check. Auto-generated constants? Check. Battle-tested over years? Check. Not all in one package anywhere else. So if you’re on the fence, give it a spin. Create a site/RockMigrations/fields/ folder, drop in a config array, refresh modules, and watch it work. You might just stick around for years too. Yours declaratively, a long-time user 9 3
Recommended Posts