dskripchenko/laravel-schemify
Schemify is a Laravel package that simplifies schema definition and generation with a clean, expressive API, helping you describe and manage database structures in code while keeping migrations organized and maintainable.
Architecture fit. This package sits at the intersection of two unstable surfaces: Laravel's console command API and the Postgres schema/session model. It overrides framework commands (migrate, migrate:fresh, migrate:install, migrate:status, migrate:reset, db:seed, db:wipe, plus a layers:migrate entrypoint) to inject a --layer option, and it implements layer switching by issuing SET search_path on a reused connection rather than reconnecting. That is a sound design for schema-per-tenant Postgres — it avoids connection churn and keeps a single pool — but it means the package's correctness depends on framework internals it does not own.
Integration feasibility. Drop-in and low-effort for a Postgres + Laravel stack already doing schema-per-tenant or environment-per-schema work. No application-code changes are implied; adoption cost is concentrated in verification, not implementation.
Technical risk. Elevated, and this release is the evidence rather than the cause. Both fixes address silent, late-failing defects:
--layer disappeared on Laravel 13.21+ because the option was declared in getOptions(), which Symfony's console only consults for commands without a $signature. The command still resolved to the package's class — so nothing looked broken until a user passed the flag and the run aborted. Declaring in configure() is the durable fix, but the notes say --layer was missing from half the overridden commands, which implies a subset was already migrated and others may still be signature- or option-path-dependent.Schema::hasTable() was not. The observable result was migrate:install skipping table creation and the following migrate failing — i.e. layers:migrate was broken from the second layer onward on those versions. Laravel 13 was immune only because it asks current_schema() instead of reading config.The pattern: correctness is version-conditional and failures surface at execution time, not at boot. A schema-per-tenant tool that silently targets the wrong schema is a data-integrity-adjacent failure mode, not a cosmetic one.
Key questions.
getOptions() rather than configure(), and is that set now empty?composer.json or only in docs?SET search_path behave under persistent connections (Octane) and transaction-mode poolers (PgBouncer)? Is state reset on worker recycle?currentLayer() accessor) so application code and tests can assert it without querying current_schema() themselves?Stack fit. Best fit: Laravel 11–13 on PostgreSQL, multi-tenant via schema-per-tenant, with migrations/seeds needing to run per tenant from a single codebase. Poor fit outside Postgres, and unnecessary if tenancy is row-scoped or database-per-tenant.
Migration path. Low-risk patch upgrade (v3.4.2, bugfix-only, no deprecations or breaking changes). Pin >=3.4.2 — do not accept 3.4.0/3.4.1 on Laravel 11/12, where layers:migrate is effectively broken past the first layer. Teams already on 3.4.0/3.4.1 with more than one layer should treat existing schema state as suspect and re-verify per-layer migration tables rather than assume prior runs succeeded.
Compatibility. Two hard couplings to track: (a) Laravel minor versions that convert framework commands to $signature declarations — 13.21 did six at once, and more will follow; (b) Postgres schema introspection path differences between Laravel 11/12 and 13. Both are now handled, but neither is a stable contract. Treat the framework version as a first-class dependency with an explicit supported range.
Sequencing.
layers:migrate → migrate:install → migrate → db:seed, then assert Schema::hasTable('migrations') resolves correctly on each layer.--layer appears in php artisan help <command> output. This is a 10-minute check that catches the exact class of regression this release fixed.Maintenance. The recurring cost is version-watch, not code. Every Laravel minor that reshapes the console or schema APIs can silently degrade this package, and the failure mode is "wrong schema" rather than "exception at boot." Budget a per-framework-upgrade verification pass, and keep the --layer-in-help check in the release checklist permanently.
Support. Expect a specific support profile: "migration ran against the wrong layer" and "flag not recognized" tickets. Both are diagnosable in one step if the team knows to check search_path and command help output; neither is obvious from a stack trace. Write that runbook before rollout.
Scaling. Connection reuse is the right call for throughput — no reconnect per layer switch, pool stays warm. The risk shifts to connection statefulness: under Octane or any long-lived worker, search_path is per-session state that must be reset or explicitly re-set on every request/worker reuse, or one tenant's layer leaks into the next. Under PgBouncer in transaction mode, SET semantics and session-scoped state need explicit validation. Verify this before scaling worker counts, not after.
Failure modes. Three to instrument: (1) option loss — command runs without --layer, defaults to a base layer, migration lands in the wrong schema; (2) introspection divergence — hasTable()/migrate:install disagree with the session path, producing skipped DDL and downstream failures; (3) session leakage — search_path persists across a recycled worker, so a request reads another tenant's schema. (1) and (2) are now fixed but not structurally impossible; (3) is unaffected by this release. Add an assertion that logs the active schema at the start of any layer-scoped job.
Ramp-up. Two knowledge areas, both non-obvious: Postgres search_path and connection-state semantics, and Laravel's console command override mechanics (configure() vs $signature vs getOptions()). A new engineer can use the package in a day but cannot debug it safely without both. Pair the runbook with the staging two-layer test as the onboarding exercise — it exercises every failure mode above in about fifteen minutes.
How can I help you explore Laravel packages today?