PHPackages                             malikad778/laravel-migration-guard - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. malikad778/laravel-migration-guard

ActiveLibrary[Database &amp; ORM](/categories/database)

malikad778/laravel-migration-guard
==================================

Catch dangerous database migrations before they reach production. The strong\_migrations equivalent for Laravel. Zero configuration. Framework-native.

v1.0.0(2mo ago)18722↓50%MITPHPPHP ^8.2CI passing

Since Feb 23Pushed 2mo agoCompare

[ Source](https://github.com/malikad778/Laravel-migration-guard)[ Packagist](https://packagist.org/packages/malikad778/laravel-migration-guard)[ RSS](/packages/malikad778-laravel-migration-guard/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (1)Dependencies (9)Versions (2)Used By (0)

🛡️ laravel-migration-guard
==========================

[](#️-laravel-migration-guard)

**Catch dangerous database migrations before they reach production.**

The `strong_migrations` equivalent for Laravel. Static analysis. Zero configuration. Framework-native.

[![Tests](https://camo.githubusercontent.com/96c3a30129b97203b273355df4b4632c504537e7821c283f295f1ebc91fc9cbe/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6d616c696b61643737382f4c61726176656c2d6d6967726174696f6e2d67756172642f74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265266c6f676f3d676974687562)](https://github.com/malikad778/Laravel-migration-guard/actions/workflows/tests.yml)[![PHP Version](https://camo.githubusercontent.com/300a58e256251efdbc33601a47ccda09830b2aa1e31a2188de74ff5503661088/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e322532422d3838393242463f7374796c653d666c61742d737175617265266c6f676f3d706870266c6f676f436f6c6f723d7768697465)](https://php.net)[![Laravel](https://camo.githubusercontent.com/b21bc2003aa619c22eebfa5bcd69dca1cc7effe70502ebde76cd4801f3c84f9e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c61726176656c2d3130253230253743253230313125323025374325323031322d4646324432303f7374796c653d666c61742d737175617265266c6f676f3d6c61726176656c266c6f676f436f6c6f723d7768697465)](https://laravel.com)[![Latest Version on Packagist](https://camo.githubusercontent.com/dc3e3b91558f9f31fa27d14f0a9072564c185efc1385b5356c8d16db4343a40c/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6d616c696b61643737382f6c61726176656c2d6d6967726174696f6e2d67756172643f7374796c653d666c61742d737175617265266c6f676f3d7061636b6167697374266c6f676f436f6c6f723d7768697465)](https://packagist.org/packages/malikad778/laravel-migration-guard)[![Total Downloads](https://camo.githubusercontent.com/52745ee95d5555ad11c757f0d86345ac084af268f6c09491a3328718fb987c07/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6d616c696b61643737382f6c61726176656c2d6d6967726174696f6e2d67756172643f7374796c653d666c61742d737175617265266c6f676f3d7061636b6167697374266c6f676f436f6c6f723d776869746526636f6c6f723d344341463530)](https://packagist.org/packages/malikad778/laravel-migration-guard)[![License](https://camo.githubusercontent.com/31d08e3cd0416d7ae1eec9222e603e287136a16ba4f64c8727f8e3783cc703c5/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f6d616c696b61643737382f4c61726176656c2d6d6967726174696f6e2d67756172643f7374796c653d666c61742d73717561726526636f6c6f723d313641303835)](LICENSE.md)[![Stars](https://camo.githubusercontent.com/4d3a691f543a3f6dff22c064a3b4a2af26a8e992653d3799527f0132c2fef073/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f73746172732f6d616c696b61643737382f4c61726176656c2d6d6967726174696f6e2d67756172643f7374796c653d666c61742d737175617265266c6f676f3d67697468756226636f6c6f723d663163343066)](https://github.com/malikad778/Laravel-migration-guard/stargazers)[![Issues](https://camo.githubusercontent.com/cf6a421c76bbd5bdaa4f82c79fdf2a7a1c49cdf562f322cb326ee106dcee712f/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6973737565732f6d616c696b61643737382f4c61726176656c2d6d6967726174696f6e2d67756172643f7374796c653d666c61742d737175617265266c6f676f3d676974687562)](https://github.com/malikad778/Laravel-migration-guard/issues)[![PRs Welcome](https://camo.githubusercontent.com/b9794c36c8acae9ee430571528e7cff7c489b661499684da5399059ecf4631f1/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5052732d77656c636f6d652d627269676874677265656e3f7374796c653d666c61742d737175617265)](https://github.com/malikad778/Laravel-migration-guard/pulls)[![Pest](https://camo.githubusercontent.com/bde8a6cd9e88424ddf1b82b167d190bd022a5c627a6cf8b7a3c88ddf259c1971/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f746573746564253230776974682d506573742d4132353946463f7374796c653d666c61742d737175617265266c6f676f3d70657374706870266c6f676f436f6c6f723d7768697465)](https://pestphp.com)

---

[![Demo](https://raw.githubusercontent.com/malikad778/Laravel-migration-guard/main/demo.gif)](https://raw.githubusercontent.com/malikad778/Laravel-migration-guard/main/demo.gif)

The Problem
-----------

[](#the-problem)

Every Laravel team doing zero-downtime deployments has eventually had a migration incident. These operations **succeed without errors in development**, then cause production outages anywhere from immediately to hours later:

OperationWhat breaks in production`dropColumn()`Old app instances still query the dropped column — immediate DB errors during the deployment window`NOT NULL` without defaultFull table rewrite on MySQL &lt; 8.0 — locks reads **and** writes for minutes on large tables`renameColumn()`Old instances use old name, new instances use new name — one of them is always wrong`addIndex()` without `INPLACE`MySQL &lt; 8.0 holds a full write lock while building the index — minutes on busy tables`change()` column typeFull table rewrite, potential silent data truncation (e.g. `VARCHAR(50)` → `VARCHAR(40)`)`Schema::rename()`Every Eloquent model and raw query referencing the old table name breaks immediately`truncate()` in a migrationProduction data permanently destroyed — migrations are the wrong place for data deletionRails developers have had [`strong_migrations`](https://github.com/ankane/strong_migrations) (4,000+ GitHub stars) for years. **The Laravel ecosystem has no maintained equivalent.** Every team solves this by hand: code review checklists, tribal knowledge, and hoping nobody forgets to check.

`laravel-migration-guard` eliminates that risk by making `artisan migrate` production-aware — without changing your workflow.

---

Installation
------------

[](#installation)

```
composer require --dev malikad778/laravel-migration-guard
```

The package auto-discovers via Laravel's package discovery. No manual registration required.

Optionally publish the config file:

```
php artisan vendor:publish --tag=migration-guard-config
```

**That's it.** Out of the box, with zero configuration, the guard:

- ✅ Hooks into `artisan migrate` and warns before any dangerous migration runs
- ✅ Is active only when `APP_ENV` is `production` or `staging`
- ✅ Is completely silent in `local` and `testing` environments
- ✅ Outputs warnings inline before execution, allowing you to abort with `Ctrl+C`

---

How It Works
------------

[](#how-it-works)

The package uses **static analysis** — it parses your migration PHP files into an Abstract Syntax Tree (AST) using [`nikic/php-parser`](https://github.com/nikic/PHP-Parser) and walks the tree looking for dangerous method call patterns.

This means:

- **No database connection needed** — analysis works against raw PHP files in any environment, including CI/CD pipelines
- **Sub-millisecond per file** — PHP AST parsing is extremely fast; 200 migration files takes under a second
- **Only the `up()` method is analysed** — `down()` rollbacks are intentionally excluded
- **`Schema::create()` is excluded** — creating a fresh table with no existing rows is always safe; only `Schema::table()` operations are checked

### Analysis Pipeline

[](#analysis-pipeline)

```
Migration file
      ↓
  PHP-Parser AST
      ↓
  Extract up() method body
      ↓
  Walk AST nodes (Schema::table / Schema::create context tracked)
      ↓
  Run registered check visitors
      ↓
  Collect Issue objects (severity, table, column, message, safe alternative)
      ↓
  Console / JSON / GitHub Annotation reporter

```

---

Safety Checks
-------------

[](#safety-checks)

Nine checks are included. All enabled by default, individually configurable.

Check IDSeverityWhat It Detects`drop_column`🔴 BREAKING`dropColumn()` or `dropColumns()` on an existing table`drop_table`🔴 BREAKING`Schema::drop()` or `Schema::dropIfExists()``rename_column`🔴 BREAKING`renameColumn()` on any table`rename_table`🔴 BREAKING`Schema::rename()``modify_primary_key`🔴 BREAKING`dropPrimary()` or `primary()` on an existing table`truncate`🔴 BREAKING`DB::table()->truncate()` inside a migration`add_column_not_null`🟡 HIGHColumn added without `->nullable()` or `->default()``change_column_type`🟡 HIGH`->change()` modifying an existing column type`add_index`🔵 MEDIUMIndex added to a critical or large table---

Check Details &amp; Safe Alternatives
-------------------------------------

[](#check-details--safe-alternatives)

### Drop Column — BREAKING

[](#drop-column--breaking)

```
// ❌ DANGEROUS
Schema::table('invoices', function (Blueprint $table) {
    $table->dropColumn('amount');
});
```

**Why:** During a zero-downtime deployment, old app instances run alongside the new schema. Any query touching the dropped column fails immediately with a database error.

**Safe approach:**

1. **Deploy 1:** Remove all code references to the column (models, queries, `$fillable`, `$casts`)
2. **Deploy 2:** Drop the column after confirming no running instance references it

---

### Add NOT NULL Column Without Default — HIGH

[](#add-not-null-column-without-default--high)

```
// ❌ DANGEROUS — locks the table on MySQL < 8.0
Schema::table('users', function (Blueprint $table) {
    $table->string('status');
});

// ✅ SAFE
Schema::table('users', function (Blueprint $table) {
    $table->string('status')->nullable();
});
```

**Why:** MySQL &lt; 8.0 requires a full table rewrite when adding a `NOT NULL` column without a default. On a large table this blocks all reads and writes for minutes.

**Safe approach:**

1. Add the column as `->nullable()` (instant, no lock)
2. Backfill existing rows: `User::whereNull('status')->update(['status' => 'active'])`
3. Add the `NOT NULL` constraint in a separate migration after backfill completes

---

### Rename Column / Table — BREAKING

[](#rename-column--table--breaking)

```
// ❌ DANGEROUS
Schema::table('users', function (Blueprint $table) {
    $table->renameColumn('name', 'full_name');
});

Schema::rename('users', 'customers');
```

**Why:** Old instances use the old name, new instances use the new name — one is always wrong during the deployment window. Eloquent models, raw queries, and `$fillable` arrays all break.

**Safe approach:** Add new column → copy data → update code → deploy → drop old column in a follow-up migration.

---

### Add Index (on critical/large tables) — MEDIUM

[](#add-index-on-criticallarge-tables--medium)

```
// ⚠️  RISKY on tables with millions of rows
Schema::table('orders', function (Blueprint $table) {
    $table->index('user_id');
});

// ✅ SAFE — use native syntax for online index creation
DB::statement('ALTER TABLE orders ADD INDEX idx_user_id (user_id) ALGORITHM=INPLACE, LOCK=NONE');
```

**Why:** MySQL &lt; 8.0 holds a full write lock while building an index. MySQL 8.0+ and PostgreSQL support online index builds but require specific syntax that Laravel migrations do not use by default.

---

### Change Column Type — HIGH

[](#change-column-type--high)

```
// ❌ DANGEROUS
Schema::table('users', function (Blueprint $table) {
    $table->string('bio', 100)->change(); // was VARCHAR(255)
});
```

**Why:** A full table rewrite is required in most databases. Implicit type coercions can silently corrupt data (e.g. `VARCHAR(255)` → `VARCHAR(100)` truncates existing values). Indexes on the column may be dropped.

**Safe approach:** Add new column of the correct type → migrate data → update code → deploy → drop old column.

---

Example Warning Output
----------------------

[](#example-warning-output)

```
$ php artisan migrate

Running migrations...

  ┌──────────────────────────────────────────────────────────────┐
  │  MIGRATION GUARD  │  BREAKING                               │
  └──────────────────────────────────────────────────────────────┘
  File   : 2024_01_15_000001_drop_amount_column.php
  Line   : 12
  Check  : drop_column
  Table  : invoices
  Column : amount

  Dropping column 'amount' from 'invoices' is dangerous.
  Running app instances may still query this column.

  Safe approach:
  1. Remove code references to 'amount' in this deployment.
  2. Drop the column in a follow-up deployment.

  Continue anyway? [y/N]

```

---

Configuration
-------------

[](#configuration)

```
