mouf/schema-analyzer
A PHP package from the Mouf framework that analyzes database schemas, introspecting tables and columns to help map or generate PHP classes from an existing database structure. Useful for reverse-engineering models and keeping code in sync with your schema.
There is no Laravel service provider here — this is a plain PHP utility library that sits on top of Doctrine DBAL's SchemaManager. Treat it as a CLI/one-off introspection tool rather than a runtime dependency.
Install and wire up a DBAL connection
composer require mouf/schema-analyzer doctrine/dbal
Laravel 10 and earlier still exposes a Doctrine connection:
$dbal = DB::connection()->getDoctrineConnection();
Laravel 11+ removed getDoctrineConnection() along with the hard doctrine/dbal requirement, so build the connection yourself from the Laravel config:
use Doctrine\DBAL\DriverManager;
$config = config('database.connections.mysql');
$dbal = DriverManager::getConnection([
'driver' => 'pdo_mysql', // map from $config['driver']
'host' => $config['host'],
'port' => $config['port'],
'dbname' => $config['database'],
'user' => $config['username'],
'password' => $config['password'],
'charset' => $config['charset'] ?? 'utf8mb4',
]);
Where to look first. The README is thin for a package this small (8 stars, 0 dependents), so read src/ directly. Skim for the top-level analyzer class, then confirm the method signatures with php -r or a throwaway tinker session before building anything on them. Because the class names aren't something you should assume from memory, do a grep -r "function " src/ and note what's public.
First use case. Point it at a legacy database you're about to migrate into Laravel migrations, and dump every table, column, index, and foreign key to the console. That gives you a checklist to write Schema::create() calls against, without guessing at column types or FK names.
$analyzer = new SchemaAnalyzer($dbal); // confirm exact class name in src/
foreach ($analyzer->getTables() as $table) {
$this->line($table->getName());
foreach ($table->getColumns() as $column) {
$this->line(" {$column->getName()} {$column->getType()->getName()}");
}
}
Snapshot-and-diff Artisan command. The highest-leverage pattern. Serialize the analyzed schema to JSON and commit it. A later run diffs the live schema against the committed snapshot to catch drift between what migrations say and what the database actually has.
class SnapshotSchema extends Command
{
protected $signature = 'schema:snapshot {--connection=mysql}';
public function handle()
{
$snapshot = collect($this->analyzer()->getTables())
->mapWithKeys(fn ($t) => [
$t->getName() => [
'columns' => collect($t->getColumns())->map->getName()->all(),
'indexes' => collect($t->getIndexes())->map->getName()->all(),
'fks' => collect($t->getForeignKeys())->map->getName()->all(),
],
])
->sortKeys()
->all();
file_put_contents(
database_path('schema-snapshot.json'),
json_encode($snapshot, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
);
}
}
CI drift check. Run schema:snapshot in CI against a database built purely from php artisan migrate:fresh, then git diff --exit-code database/schema-snapshot.json. This fails the build when someone hand-edits a database or forgets to commit a migration. It is a cheap, high-signal guard that Laravel's own tooling does not give you out of the box.
Bind it as a singleton. Don't instantiate per call — the analyzer does real round-trips against information_schema and will be slow if rebuilt repeatedly.
$this->app->singleton(SchemaAnalyzer::class, function ($app) {
return new SchemaAnalyzer($app->make('db.dbal'));
});
Legacy introspection → migration scaffolding. Walk tables in dependency order (FK-free tables first), emit Schema::create() stubs, and let a human fix types. Do not auto-run generated migrations — the type mapping is lossy (see Gotchas).
Pre-deploy schema assertion. A tiny artisan deploy:check-schema that asserts required tables/columns exist before a rolling deploy kicks off. Cheaper than a failed request at 3am.
Documentation and onboarding. Feed the output into a markdown table or generate Mermaid erDiagram blocks from the foreign keys. Since this package is DBAL-native, the FK graph is already there — you're just formatting it.
Version pinning is not optional. With 0 dependents and 8 stars, there is no ecosystem pressure testing upgrades. Pin to a known-good tag in composer.json and read the changelog before bumping. Be prepared to fork — it's a small enough codebase that patching locally is realistic.
DBAL introspection types ≠ Laravel column types. MySQL tinyint(1) may surface as a small integer, enum as a string, Postgres jsonb and arrays as their own DBAL types. Never feed analyzer output straight into Schema::create() without a translation map for every type your app uses.
Case sensitivity bites cross-platform. Postgres folds unquoted identifiers to lowercase; MySQL's behavior depends on lower_case_table_names. Snapshot diffs between a Mac dev box and a Linux CI runner will show phantom changes if you compare raw strings. Normalize with strtolower() before diffing.
Foreign keys and indexes need privileges. On restricted MySQL/Postgres users, information_schema returns partial or empty results rather than an error. If FK lists come back empty on one environment but populated on another, check grants before assuming a schema problem.
Point it at a replica. Introspection queries against information_schema on a large schema can be surprisingly expensive, and on MySQL can take metadata locks. Never run the snapshot command against a production primary during peak traffic.
Laravel 11+ connection plumbing. The missing getDoctrineConnection() is the number one trip-up. Keep one small DbalConnectionFactory helper and reuse it everywhere — don't scatter DriverManager::getConnection() calls with slightly different config arrays.
Extension point that always works. If the analyzer lacks a method you need, drop down to $dbal->createSchemaManager() and call listTableDetails(), introspectTable(), or listTables() directly. The package is a convenience layer, not a walled garden — mixing both is fine and often clearer than hacking the package.
Don't use it in the hot request path. This is deploy/CLI/CI tooling. Caching its output in Redis to serve live requests is a smell; Laravel's own Schema::getColumns() / Schema::getTables() (Laravel 10.30+) are lighter and framework-native for runtime checks.
How can I help you explore Laravel packages today?