Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Database Refactors Laravel Package

signifly/laravel-database-refactors

A Laravel package for organising database refactors as trackable, repeatable classes separate from migrations. Run schema and data refactors safely, keep a history of applied refactors, and roll changes out consistently across environments.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a small, single-purpose package: it adds a migration-like runner for data refactors — the backfills, column moves, and normalizations that you don't want living inside schema migrations. Before adopting, note it is archived (last release 2021-05-21) and only supports the Laravel versions of that era; check composer.json constraints against your app, or plan to vendor the few files it ships.

Minimal setup:

composer require signifly/laravel-database-refactors
php artisan vendor:publish --tag=database-refactors   # config + migration
php artisan migrate                                   # creates the `refactors` tracking table

Where to look first, in this order:

  1. config/database-refactors.php — table name and the refactor path (database/refactors by default).
  2. The published migration — the tracking table is the whole mechanism; understanding it explains everything else.
  3. The Refactor base class and the refactor / refactor:rollback commands in src/ — the package is tiny, so read it end to end in ten minutes.

First use case — a backfill that shouldn't be a migration:

php artisan make:refactor BackfillUserSlugs
// database/refactors/2021_05_21_000000_backfill_user_slugs.php
use Illuminate\Support\Facades\DB;
use Signifly\DatabaseRefactors\Refactor;

return new class extends Refactor
{
    public function up(): void
    {
        DB::table('users')->whereNull('slug')->orderBy('id')
            ->chunkById(500, function ($users) {
                foreach ($users as $user) {
                    DB::table('users')->where('id', $user->id)
                        ->update(['slug' => Str::slug($user->name).'-'.$user->id]);
                }
            });
    }

    public function down(): void
    {
        // deliberately a no-op: we don't want to drop data
    }
};
php artisan refactor

Implementation Patterns

Split DDL from DML. Keep php artisan migrate for schema and php artisan refactor for data. The canonical sequence for a rename or type change is: migration adds the new column → refactor backfills it → later migration drops the old column. Each step is independently deployable and reversible.

Deploy pipeline. Add the refactor runner after migrations in your release script, and make it explicit rather than implicit:

php artisan migrate --force
php artisan refactor --force   # verify the flag exists in this version's signature

If the command has no --force, guard it in production with --env=production confirmation or call it from your own artisan command so it isn't skipped silently.

Chunk everything. Use chunkById() or lazyById(); never get(). Data refactors are the one place where a 2M-row table is normal. Prefer DB::table() over Eloquent so model events, casts, and global scopes (soft deletes especially) don't corrupt the backfill — or wrap in Model::withoutEvents() if you must use models.

Make down() honest. Either implement a real inverse or make it a documented no-op/exception. Silent half-reversals are worse than a loud failure. For destructive refactors, throw in down() and reverse manually.

Write refactors against a frozen schema snapshot. A refactor written today may run against a schema that has moved on in six months. Qualify columns explicitly, avoid SELECT *, and don't reference model attributes that may be renamed.

Idempotency inside up(). The tracker prevents double-running, but partial failures and manual re-runs happen. whereNull(...), updateOrInsert, and firstOrCreate-style guards make a refactor safe to run twice.

Testing. RefreshDatabase resets your tables but the refactors bookkeeping is just another table, so seed representative rows and invoke the refactor directly:

(new BackfillUserSlugs)->up();   // or resolve the file the way the command does
$this->assertDatabaseHas('users', ['id' => 1, 'slug' => 'jane-doe-1']);

Add one test per refactor asserting the post-state; that's your regression net when the refactor is edited.

Large / risky backfills. Pair the refactor with a feature flag: deploy dual-write, run the refactor, verify counts, then flip reads and drop the old path. For anything over a few minutes, consider running the refactor as a queued job the command dispatches, so the deploy isn't blocked.

Conventions. One refactor per PR, descriptive class name (BackfillUserSlugs, MoveBillingAddressToAddresses), and a comment at the top stating why it exists and which release introduced it.

Gotchas and Tips

  • It's archived. No Laravel 10/11 support, no security patches, no maintainer. If the code fits your needs, consider copying the command + base class + migration into app/ — it's a few hundred lines and removes a dead dependency.
  • Ordering vs. migrations. A refactor that touches a column added in the same deploy will fail if it runs before the migration. Always chain migrate && refactor in that order, and never run refactors on a fresh database before migrations finish.
  • Run-once semantics are permanent per environment. Once a refactor is recorded, editing the file and re-running does nothing. Write a new refactor instead — resisted temptation to "fix" an old one.
  • Rollback re-arms it. Rolling back removes the record, so the next refactor invocation re-runs up(). If your up() isn't idempotent, rollback becomes a footgun.
  • MySQL has no transactional DDL/DML safety net. Wrap refactor bodies in DB::transaction() only where the driver supports it; otherwise design for resumability (idempotent updates, progress markers).
  • Silent no-op runs. If your refactor file doesn't return the class instance in the shape the loader expects, it may be skipped quietly. Verify by checking the refactors table after the first run and using -v for output.
  • Long transactions and locks. A single UPDATE ... JOIN over millions of rows will hold locks. Batch it, and run it off-peak; on replicas, watch for lag while backfilling.
  • Model drift. Historical refactors referencing User::where(...) break when the model changes or is deleted. Use the query builder, or an inline class snapshot of the columns you need.
  • Debugging checklist: does a row exist in refactors? Does the file live in the configured path with the expected naming convention? Does up() throw when run manually via tinker? That covers 90% of "it didn't run."
  • Extension points are shallow — the base Refactor class and the command are the only seams. Subclass the base to add shared helpers (a chunked() wrapper, a logger, a progress bar), and publish/tweak the config to point at a different directory if you want refactors grouped by domain (database/refactors/billing, etc.).
  • Don't let it become a second migration system. If a change is pure DDL, it belongs in a migration. Refactors are for data — the moment the two blur, rollback ordering becomes unpredictable.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo