| Install | |
|---|---|
composer require grazulex/laravel-strongmigrations |
|
| Latest Version: | v1.0.0 |
| PHP: | ^8.2 |
Detect dangerous migrations before they hit production. Inspired by the Rails strong_migrations gem.
Laravel Strong Migrations analyzes your migration files using AST parsing (nikic/php-parser) and warns you about operations that could cause downtime, data loss, or lock contention on large tables.
block (prevents migration) or warn (displays warning)safetyAssuredcomposer require grazulex/laravel-strongmigrations
Publish the configuration:
php artisan strong-migrations:install
Strong Migrations listens to Laravel's MigrationStarted event. When you run php artisan migrate, each migration is parsed and checked against the rule engine before it executes.
If a dangerous operation is detected:
| Rule ID | Detects | Severity |
|---|---|---|
remove_column |
Dropping a column | High |
rename_column |
Renaming a column | High |
rename_table |
Renaming a table | High |
create_table_force |
dropIfExists before create |
High |
backfill_in_migration |
Data + schema changes in same migration | Medium |
add_auto_increment |
Adding auto-increment columns | Medium |
raw_sql |
Raw SQL statements | Warning |
wide_index |
Index with > 3 columns | Low |
| Rule ID | Detects | Severity |
|---|---|---|
add_column_with_default |
Adding NOT NULL column with default (< MySQL 8.0.12 / MariaDB 10.3.2) | High |
change_column_type |
Changing column type (table rewrite) | High |
set_not_null_mysql |
Setting NOT NULL constraint | Medium |
| Rule ID | Detects | Severity |
|---|---|---|
add_index_non_concurrent |
Adding index without CONCURRENTLY | High |
add_foreign_key |
Adding foreign key (locks both tables) | High |
add_check_constraint |
Adding check constraint | Medium |
add_unique_constraint |
Adding unique constraint | Medium |
json_column |
Using json instead of jsonb |
Low |
set_not_null_pgsql |
Setting NOT NULL (full table scan) | Medium |
Once installed, Strong Migrations automatically checks all migrations during php artisan migrate:
$ php artisan migrate
Dangerous operation detected in migration: 2025_01_15_000001_drop_email.php
[remove_column] Removing column `email` that is used by the application
will cause errors until application servers are restarted.
Safe alternative:
Step 1 - Ignore the column in your Eloquent model:
protected static array $ignoredColumns = ['email'];
Step 2 - Deploy the code.
Step 3 - Create a migration that drops the column, wrapped in:
StrongMigrations::safetyAssured(function () { ... });
# Text output (default)
php artisan migrate:check
# JSON output (for CI/CD)
php artisan migrate:check --format=json
# GitHub Actions annotations
php artisan migrate:check --format=github
# Custom path
php artisan migrate:check --path=database/migrations
php artisan migrate:analyze 2025_01_15_000001_drop_email.php
When you've verified a migration is safe, wrap it in safetyAssured:
use Grazulex\StrongMigrations\StrongMigrations;
return new class extends Migration
{
public function up(): void
{
StrongMigrations::safetyAssured(function () {
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('legacy_field');
});
});
}
};
Add custom rules in a service provider:
use Grazulex\StrongMigrations\StrongMigrations;
StrongMigrations::addCheck(function (Operation $operation, DatabaseContext $context) {
if ($operation->type === OperationType::AddColumn && $operation->column === 'metadata') {
StrongMigrations::stop('Use `data` instead of `metadata` for consistency.');
}
});
Override the default message for the next violation:
StrongMigrations::setMessage('This migration has been reviewed and approved by the DBA team.');
// config/strong-migrations.php
return [
// Enable/disable the package
'enabled' => env('STRONG_MIGRATIONS_ENABLED', true),
// 'block' or 'warn'
'mode' => env('STRONG_MIGRATIONS_MODE', 'block'),
// Override auto-detected database driver
'database_driver' => env('STRONG_MIGRATIONS_DRIVER'),
// Override auto-detected database version
'database_version' => env('STRONG_MIGRATIONS_VERSION'),
// Skip migrations before this timestamp (format: YYYY_MM_DD_HHMMSS)
'start_after' => null,
// Disable specific rules by ID
'disabled_checks' => [
// 'remove_column',
// 'raw_sql',
],
];
- name: Check migrations
run: php artisan migrate:check --format=github
This outputs annotations that appear directly on pull request diffs.
- name: Check migrations
run: php artisan migrate:check --format=json
Returns exit code 1 if any violations are found.
| Database | Version | Notes |
|---|---|---|
| MySQL | 5.7+ | Version-aware rules (8.0.12+ allows instant ADD COLUMN) |
| MariaDB | 10.2+ | Version-aware rules (10.3.2+ allows instant ADD COLUMN) |
| PostgreSQL | 12+ | CONCURRENTLY, foreign key, and constraint rules |
| SQLite | Any | Reduced rule set (safe for development) |
composer test
composer phpstan # PHPStan level 8
composer pint # Laravel Pint
Please see CONTRIBUTING.md for details.
If you discover a security vulnerability, please see SECURITY.md.
The MIT License (MIT). Please see LICENSE.md for more information.