amjadiqbal/refresher
A Laravel package by Amjad Iqbal that provides a simple refresher utility for resetting and restoring application data, helping developers quickly return development or testing environments to a clean, known state with minimal setup.
What it is: A Laravel test-suite utility that replaces RefreshDatabase / LazilyRefreshDatabase with snapshot-based database refresh. Instead of re-running migrations for every test, it snapshots a freshly migrated database once and restores it per test. Surfaces: RefreshesWithRefresher, LazilyRefreshesWithRefresher, php artisan refresher:snapshot, refresher:clear, --fresh-options, and a php artisan about panel.
Architecture fit: Very low intrusion. It is a require-dev-only package with no production runtime surface, no schema coupling, and no application-code changes beyond swapping one trait in the base TestCase. Critically, the trait now overrides only migrateDatabases() and passes migrateFreshUsing() through, so $seed/$seeder, #[Seed]/#[Seeder], $dropViews, and $dropTypes behave exactly as core RefreshDatabase does, and Laravel's own in-memory PDO cache and $migrated bookkeeping are left intact. From a TPM standpoint that is the key property: the framework contract is preserved rather than reimplemented, so behavioral drift risk is contained.
Integration feasibility: High for PHP 8.3+ / Symfony 7.2+ stacks on MySQL, MariaDB, or SQLite. The release narrows constraints (symfony/process ^7.2|^8.0, PHP ^8.3), which is good for Laravel 13/Symfony 8 apps but effectively locks out Symfony 6-era applications — that must be checked against our composer matrix before committing.
Technical risk: Medium-low, concentrated in two areas rather than spread. First, snapshot correctness — the top historical failure mode (stale snapshots, seed state not captured) is directly addressed: the key now covers loadMigrationsFrom() paths, seeders/factories when seeding, the driver, and the migrate:fresh options, and is machine-independent so a CI-cached snapshot matches across runners. Second, restore destructiveness — MySQL restore now drops every table and view first, matching migrate:fresh; this is correct but means any test relying on residual schema from another branch or a prior test will break. That is a latent bug being surfaced, not a regression, but it will produce red builds during rollout.
Key questions:
$seed/$seeder, so any green result on a seeded suite is untrustworthy and must be re-baselined, not just re-run.refresher:snapshot step? Without that number the business case is anecdotal.about panel exposing a support link hints at external or commercial backing — we need an SLA and a bus-factor read before depending on it.Stack: Add as a dev dependency only. Target apps on Laravel 12/13, PHP 8.3+, Symfony 7.2/8.0. Confirm the correct driver value per environment — driver => mariadb is now distinct from mysql, and MariaDB users should set it explicitly. MySQL credentials via MYSQL_PWD and unix_socket support improve container and CI compatibility out of the box.
Migration path:
RefreshDatabase → RefreshesWithRefresher (or LazilyRefreshDatabase → LazilyRefreshesWithRefresher) in that base TestCase only.Compatibility: The constraint narrowing is the main compatibility gate — Symfony 6 apps cannot take this release, and staying on 1.0.x means carrying the seeding bug, which is a worse trade for any seeded suite. The destructive-restore change is semantics-compatible with migrate:fresh, so teams already writing tests against core behavior should be unaffected. Long dumps no longer hitting Symfony Process's 60-second timeout removes a hard ceiling on schema size.
Sequencing: Upgrade the package first and re-run seeded suites to expose the seeding fix before enabling any caching — otherwise stale snapshots and the seeding change confound each other. Then wire refresher:snapshot into CI ahead of the test job, key the cache on the machine-independent snapshot key plus the lockfile, add refresher:clear to cache-purge and dependency-bump jobs, and only then measure and publicize the speedup.
Maintenance: Minimal steady-state burden. No production footprint, snapshots are ephemeral CI artifacts, and refresher:clear gives a blunt but effective invalidation lever. The countervailing signal is constraint churn: two dependency-floor narrowings in a single minor release indicates active development but also compatibility drift, so pin the version and review upgrades deliberately rather than floating.
Support: Blast radius is bounded — the escape hatch is reverting one trait, so a package failure degrades test runtime, not application behavior. That makes it a low-severity dependency from an incident standpoint. The open item is external support terms; the about support link should be resolved into an actual owner and SLA before this becomes load-bearing for CI.
Scaling: The win compounds with suite size and migration count: restore cost scales with table count, not migration count, so the largest suites benefit most. Machine-independent keying means parallel runners share one valid snapshot instead of each regenerating it, which is the difference between a CI cost saving and a CI cost increase. The WAL checkpoint and stale -wal/-shm cleanup on SQLite remove a class of flaky, hard-to-reproduce failures that would otherwise erode trust in the suite.
Failure modes:
Ramp-up: Low. One trait import per base TestCase plus a CI documentation note on snapshot generation and invalidation. Budget roughly half a day of engineering to pilot and validate, plus triage time for tests exposed by the seeding fix and the destructive restore — that triage, not the integration, is the real cost and should be scoped explicitly.
How can I help you explore Laravel packages today?