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 Schema Sentinel Laravel Package

clcbws/laravel-schema-sentinel

Schema Sentinel watches your Laravel database schema for drift and unexpected changes. It compares live tables against expected migrations, flags mismatches, and alerts you before small surprises become production incidents.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish.

composer require clcbws/laravel-schema-sentinel --dev
php artisan vendor:publish --tag="schema-sentinel-config"

The --dev placement matters: this is a development/CI tool, not a runtime dependency. If you intend to run the audit as part of a deploy gate, promote it to require so the production image has it available.

Look here first, in this order:

  1. config/schema-sentinel.php — connections to audit, ignored tables, and (most importantly) whether the fixer writes migrations or executes DDL directly. Read this before your first run; the default behavior determines whether a mistake is recoverable.
  2. The package's src/ command classes — list them with php artisan list schema to see the actual audit/fix command names for your installed version. Names have shifted between releases, so don't trust a blog post.
  3. The README's "supported column types" or "limitations" section, if present. This is the section that tells you which false positives you'll be fighting.

First use case: local drift detection before you commit.

# Against your local dev database
php artisan schema:audit

# Or a specific connection
php artisan schema:audit --connection=pgsql

Run it on a database you know is in sync (fresh migrate:fresh --seed). If it reports diffs there, you have a baseline problem, not a drift problem — fix that first, because every subsequent run will be noise. Once a clean database reports clean, point it at staging and see what the real world did to your schema.

The one scenario that sells the tool: a hotfix applied directly to production six months ago by someone who has since left. migrate:status says everything is fine; the live schema says a column exists that no migration ever created. This package is the only thing in your stack that notices.

Implementation Patterns

CI as a drift gate (the highest-value pattern).

Add the audit to your pipeline against a database built purely from migrate — no seeds, no manual DDL. This catches a specific class of bug: a migration that doesn't actually produce the schema the author thinks it does (wrong column type, forgotten index, default that didn't apply).

- run: php artisan migrate:fresh --force
- run: php artisan schema:audit --fail-on-drift

The exit code is the contract here — verify the flag name in your version, but the pattern is a non-zero exit when any diff is found, so CI fails loudly.

Scheduled production audit, read-only.

Run the audit nightly with a read-only database user and pipe the output into your alerting. Detect drift as a signal, never auto-fix in production.

// app/Console/Kernel.php or routes/console.php
Schedule::command('schema:audit --connection=production')
    ->daily()
    ->onFailure(fn () => Notification::route('slack', config('alerts.slack'))->notify(...));

A production DB user that can SELECT from information_schema is sufficient. Do not grant DDL.

Fix-generation workflow, human-reviewed.

The intended loop is: audit → generate a corrective migration → review the diff → commit → deploy through your normal pipeline. Treat the generated migration as a draft from a junior developer, not as an authoritative patch:

php artisan schema:audit --format=json > drift.json   # inspect first
php artisan schema:make-fix-migration                 # or similar; check artisan list

Then read the generated file and confirm:

  • The down() method actually reverses the up().
  • No dropColumn on a column with live data you didn't expect.
  • Index and foreign key names match your project's naming convention, or you'll get a second drift report on the next run.

Baseline/snapshot files in version control.

If the package supports a snapshot or expected-schema file (many tools in this space do, to avoid re-parsing hundreds of migrations on every run), commit it. A snapshot that drifts from database/migrations/ is itself a bug — regenerate it whenever migrations land, ideally in the same PR, so reviewers see both.

Team discipline pattern: audit before migrate.

Make composer run dev or your onboarding script do:

php artisan schema:audit || echo "WARNING: local schema drift - consider migrate:fresh"

Long-lived local databases drift constantly (people migrate:rollback and re-run, test branches, etc.). This turns a confusing "works on my machine" bug into a one-line warning.

Multi-connection / tenancy.

If you run multiple databases, audit each connection explicitly rather than assuming the default. For tenant-per-database setups, loop over connections in a queued job rather than auditing synchronously — the intro­spection cost adds up fast.

Gotchas and Tips

False positives will be your main cost. Laravel's schema builder loses information that lives in the database: exact varchar lengths in some drivers, index ordering, collations, column comments, ON UPDATE clauses, and check constraints. Expect the audit to flag attributes you cannot express in a migration. The right response is a growing ignore list in the config, not a migration that fights the database.

Squashed migrations break naive comparison. If you've run schema:dump (Laravel's built-in squash into database/schema/*.sql), the tool may only see the squashed file — or nothing at all. Verify audit behavior on a squashed repo before you rely on it, and re-baseline after every squash. This is the single most common reason a "clean" repo reports 40 diffs.

Fix ordering is not guaranteed. Generated corrective migrations are typically produced in inspection order. If two fixes touch the same table (drop a column, then rename another), the batch can fail halfway. Split multi-table fixes into separate migration files, or run them one at a time on staging first.

Destructive fixes deserve a separate command. If the fixer can drop columns or tables, it should require an explicit flag. If it doesn't, treat the fix command as dangerous by default: run it against a copy of the database (mysqldump / pg_dump restore) and diff the resulting SQL before touching anything real.

Driver-specific quirks. MySQL, PostgreSQL, and SQLite introspect very differently. SQLite in particular cannot report much (no named constraints in older versions, limited type info), so expect high noise on SQLite test databases and low-value results. Prefer auditing against the same engine you run in production — a MySQL-vs-PostgreSQL comparison is not meaningful.

Version matrix risk. With zero dependents and a single maintainer, this package has not been battle-tested against every Laravel/PHP combination. Pin the version in composer.json ("clcbws/laravel-schema-sentinel": "^1.0" rather than *), and add the audit job to a separate CI stage so a package failure doesn't block deploys. The last release date being in the future relative to typical Laravel release cadences is worth a sanity check against your framework version's composer.json constraints before you adopt.

Extension points to look for. If the package exposes a contract for custom type comparators (a Comparator or TypeMapper interface), use it — mapping enum → varchar and json → text in a custom comparator is usually cleaner than blanket-ignoring tables. Search the source for interfaces before reaching for ignore_tables.

Debugging a suspicious diff. Run with verbose output and, if available, a JSON or SQL format so you can see the expected vs actual definition side by side. Then check the live definition directly:

SHOW CREATE TABLE orders;                    -- MySQL
\d+ orders                                   -- PostgreSQL

If the live definition is what you intended and the migration agrees, it's a tool limitation — file it under ignores.

Don't let it substitute for migrate:status. migrate:status tells you which migration files have run; this tells you what the schema is. They answer different questions and you need both. A repo can be fully migrated and fully drifted at the same time.

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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console