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 Database State Laravel Package

pxlrbt/laravel-database-state

Manage and apply named states to your Laravel database, making it easy to switch between predefined datasets for testing, demos, and local development. Define states once and reuse them across environments.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and orient in four commands.

composer require pxlrbt/laravel-database-state
php artisan vendor:publish --tag="database-state-config"
php artisan list | grep -i state
cat config/database-state.php

Treat artisan list as the authoritative API surface. With 4 stars and 0 dependents, neither the README nor any blog post is trustworthy about exact command names, flags, or the base class signature — the command list and the source in vendor/pxlrbt/laravel-database-state/src are.

Read in this order:

  1. config/database-state.php — where state classes live, which connection is targeted, where snapshots are stored. You will edit this in every non-default setup.
  2. The base state class in src/ — confirm the method name you override (state(), run(), up()?) and whether it exposes helpers. v2.0.0 was a major bump for a changelog that reads like housekeeping, so assume something moved.
  3. vendor/pxlrbt/laravel-database-state/tests — added in v2.0.0 and now the best behaviour spec available. It pins the base class signature and shows exactly how the maintainer invokes commands in tests. When the README is ambiguous, the tests settle it faster than reading source.
  4. The README usage section, last, for the intended narrative rather than the truth.

Decide which capability you're adopting before writing code. The package does two things with very different blast radii: seeding critical state into real databases, and snapshotting/restoring for fast test runs. The README blurs them. If your motive is test velocity, your first PR wires restore into TestCase/phpunit.xml. If your motive is production provisioning, your first PR is the roles/permissions baseline. Don't let one drag the other in.

First use case that pays for itself: the roles/permissions baseline. Your app cannot boot for an admin without admin, editor, viewer and their permissions — but db:seed also creates fake users and demo orders, so nobody runs it in production. Extract only the critical slice into one state class, make it re-runnable, and point both DatabaseSeeder and your deploy at it.

// app/DatabaseState/RolesAndPermissionsState.php
// Verify base class name + overridden method against the v2 test suite first.
class RolesAndPermissionsState extends DatabaseState
{
    public function state(): void
    {
        $admin = Role::updateOrCreate(
            ['name' => 'admin'],
            ['label' => 'Administrator'],
        );

        foreach (['users.view', 'users.edit', 'billing.manage'] as $permission) {
            $admin->permissions()->syncWithoutDetaching([
                Permission::firstOrCreate(['name' => $permission])->id,
            ]);
        }
    }
}

Run it against a restored production snapshot or a staging clone before it ever touches a real deploy.

Implementation Patterns

One state class per domain. RolesAndPermissionsState, PlanTiersState, SystemSettingsState. Each independently runnable, reviewable, and safe to re-run. The monolithic SeedEverything class is precisely what you're trying to escape — a diff to it is unreviewable and a failure in it is unrecoverable.

Every write is updateOrCreate against a deliberate natural key. Express intent as "make this row look like this," never "insert a row." Pick the key consciously (name, slug, code) — a drifting key produces duplicates the package cannot detect, and you'll find them in a permission check six weeks later.

Wire it into the deploy between migrate and cache warm-up.

php artisan migrate --force
php artisan database-state:apply      # confirm exact name from `artisan list`
php artisan optimize

States may reference columns added by migrations, so migrate first. Permission caches, config caches, and Redis permission maps must be flushed after the state lands — if you cache permissions yourself, flush that explicitly; optimize won't know about it, and long-lived queue workers hold their own copy, so restart them.

syncWithoutDetaching for pivots by default; sync only when the definition is genuinely complete truth. sync deletes pivot rows absent from your list — correct for "this role has exactly these permissions," catastrophic if an admin granted a permission by hand in production last Tuesday.

Call the same classes from DatabaseSeeder.

class DatabaseSeeder extends Seeder
{
    public function run(): void
    {
        $this->call([
            RolesAndPermissionsState::class,
            PlanTiersState::class,
        ]);

        // demo data layers on top, dev/local only
        User::factory()->count(20)->create();
    }
}

One definition of "what must exist," and a bug in a state class surfaces on a developer's laptop the same day rather than during a deploy.

Test idempotency, not just the outcome.

public function test_state_is_idempotent(): void
{
    $this->artisan('database-state:apply');
    $before = Role::count();

    $this->artisan('database-state:apply');

    $this->assertSame($before, Role::count());
    $this->assertSame(1, Role::where('name', 'admin')->count());
}

Running apply twice in one test catches almost every real defect in a definition — missing natural keys, create() sneaking in, pivot duplication.

Add a scheduled drift check. Run apply (dry-run/verbose if the command supports it) against a read replica nightly and alert on pending changes. Silent divergence — someone renamed a role by hand during an incident — is the failure mode that matters, and it's invisible until a customer hits a broken permission.

Use snapshot/restore for tests only after the state classes are trusted. Snapshot after a green run on a clean schema, and restore into the same driver and PHP/Laravel version you run in CI. A SQLite snapshot is worthless to a MySQL CI job.

Encode the ordering once. Wrap migrate → apply → cache flush in your own deploy command or a Makefile target so the sequence lives in the repo, not in someone's memory at 2am.

Gotchas and Tips

Treat v2.0.0 as breaking until you've proven otherwise. The changelog says "add test suite and modernize dev dependencies" — and the maintainer still chose a major. The realistic change is a bumped PHP/Laravel floor. Before composer update: diff the package's composer.json against your platform constraints, re-run artisan list | grep -i state, and re-publish the config to a temp path and diff it against your published copy. A stale config silently ignores new keys.

Deleting is the dangerous direction. updateOrCreate is safe; pruning is one typo from a production incident. If the package supports removing stale rows, leave it off until you have a staging clone to rehearse on. Prefer is_active = false over deleting anything a foreign key points at.

Timestamps will churn. updateOrCreate touches updated_at on every run even when nothing changed, which breaks drift detection and pollutes audit trails. If that matters, fetch-compare-save instead, or exclude the model from timestamp bumping.

Ordering and foreign keys are your job. Roles before role_permission pivots; plans before subscriptions. If the package supports explicit ordering, use it. If not, keep each class internally ordered — never rely on filesystem scan order, which will surprise you the day someone adds a file.

Tenancy is not handled for you. Decide explicitly whether state is global (roles, permissions, plan definitions) or per-tenant (settings overrides). A state class runs once as invoked; it will not iterate tenants. A per-tenant class needs the tenant context established before the command fires. Test with two tenants.

Environment guards. The package exists to write to production, so assume it does not block production by default. Guard anything that must never run there, and build a --dry-run-on-staging habit before promoting.

Debugging a failed run: restore a production snapshot into a throwaway database, add verbose output, and wrap a suspect state class in DB::transaction() locally so a failure leaves nothing half-written. In production, remember a multi-statement class is not atomic — partial application is a valid intermediate state if you design each statement to be independently idempotent. You can also lift a failing scenario from your app into a scratch test that boots the package's own v2 fixtures; that's usually faster than reasoning about semantics from source.

Extension points worth reaching for: the published config (state paths, connection, storage location), the base state class (override setup/teardown, add helpers for repeated upserts), and the artisan commands themselves (wrap, don't reimplement).

Pin the exact version. Four stars, zero dependents, and a major bump whose changelog doesn't justify it argue for "pxlrbt/laravel-database-state": "2.0.0" rather than ^2.0. Read the compare diff before bumping, and upgrade on a branch with the idempotency test green. The new test suite is a real maturity signal — behaviour is now pinned by assertions — but it does not make the public API stable, and it is no substitute for pinning.

Know the boundary. This is for critical, deterministic, low-volume reference data. It is not a backfill tool, a bulk import mechanism, or an analytics pipeline. If someone scopes a 2M-row data migration into a state class, stop them.

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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package