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 Schemify Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

This package layers multiple Postgres schemas onto a single Laravel connection, so tenant/branch/"layer" data lives in separate schemas while sharing one database. Before anything else, upgrade to 3.4.2 — the two fixes below are prerequisites for a working multi-layer setup on current Laravel versions, not cosmetic patches.

Minimal path:

composer require <vendor>/<package>   # 3.4.2+
php artisan vendor:publish --tag=<package>-config

Where to look first:

  1. The published config — it defines the connection the layers hang off, the schema naming convention, and the list of layers layers:migrate iterates. Read this before touching commands; most "it migrated the wrong schema" reports trace back to it.
  2. The command overrides — the package subclasses Laravel's migrate*, db:seed and db:wipe commands to add --layer. Skim those classes to see exactly which core commands accept a layer and how they resolve the default when --layer is omitted.
  3. The layer-switch API (facade/helper/service) — this is what runs SET search_path on the connection. Everything runtime-facing flows through it.

First use case — provision one layer end to end:

php artisan migrate:install --layer=acme   # creates the per-layer migrations table
php artisan migrate --layer=acme
php artisan db:seed --layer=acme

Then confirm you actually landed where you think:

DB::selectOne('select current_schema()'); // => acme

Once one layer works, php artisan layers:migrate runs migrations across every configured layer — that's the command that should be in your deploy script.

Implementation Patterns

Per-command layer selection. For one-off work, keep it explicit: --layer= on migrate, migrate:fresh, migrate:status, migrate:reset, db:seed, db:wipe. Explicit is better than relying on a default, because the default is connection-config driven and easy to misread in a long shell session.

Bulk operations via layers:migrate. This is the deployment path. Run it after the base migrate (which owns the shared/public schema) and before traffic flips. Because it loops layers in order, the first layer is your canary — if layer one fails, you know the change itself is broken rather than the loop.

Runtime switching. Switch once per request/job at a boundary, not mid-controller:

// middleware or job handle()
$layers->use($request->user()->layer);

try {
    // all queries, Schema:: calls, and model access here
} finally {
    $layers->reset(); // or switch back to the default
}

Pair this with a queued job that carries the layer name in its payload and switches inside handle() — serialising the layer is far more reliable than inferring it from an authenticated user at execution time.

Testing. Use a dedicated test layer rather than the default schema, and reset it in setUp(). If you use RefreshDatabase, make sure the switch happens before the trait boots, or the trait will install a migrations table in the wrong schema. A cheap guard is asserting current_schema() in your base test case.

Seeding and fixtures. Seeders are layer-aware by virtue of --layer, so per-layer reference data (roles, statuses, settings) should live in seeders that are idempotent — you will re-run them on every new layer.

Deploy sequence. migrate → layers:migrate → db:seed --layer=… for new layers only → clear caches. Keep the layer loop out of migrate itself so failures are attributable.

Gotchas and Tips

Command options must be declared in configure(). On Laravel 13.21+, the framework only calls getOptions() for commands without a $signature, and 13.21 moved migrate:fresh, migrate:install, migrate:status, migrate:reset, db:seed and db:wipe to signatures. Before 3.4.2 this silently deleted --layer from those overrides: the command still resolved to the package class, but passing --layer aborted the run. If you subclass any of these commands yourself, add options in configure() — never getOptions(). If you're pinned below 3.4.2 and see The "--layer" option does not exist, you're hitting this, not a config problem.

Stale schema introspection on Laravel 11 and 12. Since 3.4.0 a layer switch reuses the connection and only issues SET search_path. Queries follow the session path and are fine, but the Postgres schema builder on 11/12 reads the schema from the connection config, which still holds the previous layer. So Schema::hasTable('migrations') answers about the wrong layer: migrate:install sees the previous layer's table, skips creating one, and the following migrate dies on a missing table — layers:migrate broke from the second layer onwards. Laravel 13 asks current_schema() and was never affected. On 3.4.2 the config is kept in sync; on older 3.4.x, force a reconnect (purge the connection) after switching before any Schema:: call.

search_path is connection state, not application state. Long-lived workers (Queues, Octane) reuse connections across jobs. Always switch at the start of a job and never assume the previous job left the connection clean; a missing switch silently reads another layer's data.

Every layer needs its own migrations table. migrate:install --layer=x is not optional for layers created outside the normal flow. If a layer was created while the 3.4.0/3.4.1 bug was live, its table may be missing — delete nothing, just re-run migrate:install and reconcile.

Postgres only. The whole mechanism is SET search_path plus the pg schema builder. There is no fallback for MySQL/SQLite, and tests on SQLite will not exercise the layer logic at all.

Debugging checklist. select current_schema() for the live truth; the connection config's schema for what Schema:: believes; migrate:status --layer=x to see which migrations a layer thinks it has. When those three disagree, you have a stale-connection problem, not a migration problem.

Extension points. The command overrides are the intended seam — subclass them, add options in configure(), and delegate to the parent. Keep layer resolution behind the package's switch API rather than calling DB::statement('SET search_path…') yourself, so future releases that change how the connection is managed (as 3.4.0 did) don't strand your code.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings