hassanejazpvt/phased-migrations
Run Laravel migrations in controlled phases across deployments to avoid downtime and race conditions. Split schema changes into stages, apply them incrementally, and keep every environment in sync with safe, repeatable database rollouts.
Install the package and publish its config before writing any migrations:
composer require hassanejazpvt/phased-migrations
php artisan vendor:publish --tag=phased-migrations-config
The three things to read first, in order:
config/phased-migrations.php) — this is the contract for the package. It defines the phase names, the order they run in, and where each phase’s migration files live. Everything else follows from it.src/PhasedMigrationsServiceProvider.php) — tells you which Artisan commands are registered and whether the package wraps or replaces Laravel’s own Migrator. That determines whether php artisan migrate still behaves normally.README and the tests/ directory — the 1.0 release notes are only a changelog link, so the test suite is the most reliable description of supported behaviour. Treat it as the spec for edge cases like rollback and migrate:fresh.First use case to try: the classic expand/contract column rename. Add an expand-phase migration that adds display_name and backfills it, deploy, then a contract-phase migration that drops name. Run both phases locally against SQLite or a scratch database and confirm the order with php artisan migrate:status before you put it anywhere near a deploy pipeline. If that two-step works, the package is doing its job.
Organise around phases, not timestamps. The core workflow is moving files out of the flat database/migrations directory into per-phase directories (or tagging them with a phase attribute, depending on how the config exposes it). Adopt that layout immediately, even for trivial migrations — retrofitting later means touching every file.
Map phases onto deploy stages. The pattern that pays off daily is binding each phase to a pipeline step:
This is the expand/contract discipline; the package’s value is that it makes the boundary explicit and enforceable rather than a convention people forget.
Make phase execution explicit in CI. Don’t rely on a bare php artisan migrate in your deploy script — invoke each phase as its own command step so a failure in the post-deploy phase doesn’t silently leave you half-applied. Wire this into Envoyer/Forge deploy hooks, GitHub Actions, or your Kubernetes init container the same way you’d wire any other migration step.
Keep migrations idempotent where the phase allows it. Because phases may be re-run manually during an incident, backfill migrations should be safe to execute twice (use insertOrIgnore, updateOrInsert, or guard on column existence). This costs little and saves a lot at 2am.
Use it in tests. Point your test suite at a fresh database and run all phases in order in a base test case or RefreshDatabase equivalent. If the package doesn’t hook into RefreshDatabase automatically, add a small trait that runs the phased migrator once per suite.
Local development: run all phases in declaration order so your local schema matches production after a full deploy. Don’t try to simulate partial deploys locally — that’s what a staging environment with real sequential deploys is for.
migrate:rollback does when migrations from several phases are mixed in the same batch. Ideally you roll back one phase at a time, in reverse. Check this against the source or tests before you rely on it during an incident.migrate:fresh and migrate:refresh may behave differently from the phased commands. Verify both paths early; the first one you reach for in local debugging is usually migrate:fresh.config/phased-migrations.php on a deployed environment, php artisan config:cache is required before the change takes effect. Add it to your deploy script alongside the migration step.composer.json ("^1.0"), read the source before upgrading across minor versions, and don’t assume semver discipline from a package that shipped 1.0 without release notes.migrations table. If the package adds a phase column or a parallel tracking table, confirm how it records batches — this matters the first time you need to reconcile what actually ran in production.How can I help you explore Laravel packages today?