cybex/laravel-protector
Laravel Protector adds a lightweight, configurable protection layer to your Laravel routes, making it easy to shield staging sites, internal tools, or private pages with a simple password gate—no full authentication setup required. Quick to install and customize.
Install the package and publish its config before running anything:
composer require cybex/laravel-protector
php artisan vendor:publish --provider="Cybex\Protector\ProtectorServiceProvider"
(The exact publish tag varies between releases — php artisan vendor:publish and picking the Protector entry is the safe route. Same for command names: run php artisan list protector after install, since this is a small, low-adoption package and the README occasionally lags the code.)
Where to look first, in order:
config/protector.php — this is the package's entire surface area. Expect keys covering the source/target DB connection, the dump/restore binary paths (mysqldump, mysql), the backup destination (a Laravel filesystem disk), and a list of tables to exclude.php artisan list protector — confirms the two or three commands you'll actually use (typically a backup command and an import/restore command taking a filename).The first use case to get working end-to-end: run a backup against a local database, confirm the .sql (or .sql.gz) file lands where you expect on the configured disk, then import that same file into a scratch database. Doing this locally first means you're not debugging binary paths and credentials against production data.
Scheduled production backups with rotation. Register the backup command in app/Console/Kernel.php (or routes/console.php on Laravel 11+) and pair it with your own retention logic — the package generates files, it doesn't necessarily prune them:
$schedule->command('protector:backup')
->dailyAt('02:00')
->onOneServer()
->withoutOverlapping();
Off-box storage. Point the backup disk at S3 or an SFTP disk so the artefact survives the server, and so developers can pull it without SSH. This also decouples the "generate" step (production, privileged) from the "consume" step (laptop/CI, unprivileged).
Refresh local dev from production on demand. The canonical workflow: download the latest dump to a shared location, then run the import command against your local connection after wiping:
php artisan db:wipe --database=local
php artisan protector:import storage/app/protector/latest.sql --database=local
Wrap those two in your own php artisan app:refresh-from-production command so nobody has to remember the ordering, and so you can add a confirmation prompt.
Staging/preview seeding in CI. A deploy job or a GitHub Actions workflow can restore the most recent production dump into the preview environment before running Playwright against realistic data. Gate it behind a manual trigger or a schedule — you don't want a nightly deploy blocked on a multi-GB restore.
Exclude the noisy tables. Sessions, cache, jobs, failed_jobs, telescope entries, and log tables are almost never worth importing and are usually the largest. Handle them in the config's exclusion list rather than post-import TRUNCATEs, because a partial dump is far cheaper to move around.
Anonymise before it leaves production. If the package doesn't scrub for you, chain a scrubbing step into your own command after import (or better, run UPDATEs against a throwaway copy before dumping). Keep the scrub logic in your app, not in the vendor package, so it survives upgrades.
Multi-connection setups. Explicitly configure separate source and destination connections rather than relying on the default one. This matters if production uses a different driver, host, or credentials shape than your laptop.
mysqldump and mysql client versions that differ from the server cause cryptic failures. MySQL 8 clients against a 5.7/MariaDB server need --column-statistics=0; caching_sha2_password auth failures usually mean an old client. Set full binary paths in config — which mysqldump first, because PHP-FPM's PATH is not your shell's.ps. If you can, use a --defaults-extra-file (~/.my.cnf) or the package's own credential-passing mechanism rather than interpolating a password into the dump command. Also check you're not writing the command string into laravel.log on failure — that's how DB passwords end up in log aggregators.--single-transaction (InnoDB) so you don't take write locks; --quick keeps memory flat on large tables. A naive dump of a big database will stall your app for minutes.db:wipe (or drop/recreate) first. Use --force/--no-interaction only in scripted contexts.SET FOREIGN_KEY_CHECKS=0 around the import is usually necessary. On the scrubbing side, a naive "anonymise all emails to [email protected]" hits the unique index on row two.DEFINER clauses break cross-environment imports. Triggers, views, and routines dumped from production reference a MySQL user that doesn't exist locally. Either exclude them from the dump or strip DEFINER in a post-processing step.app()->environment() and abort) in your wrapper command, and consider not registering the import command at all in production.--max-execution-time or chunk if the package supports it. memory_limit is usually fine because mysql < file.sql streams, but only if you're actually shelling out rather than reading the file into PHP.composer.json, read the (short) source rather than the docs when something is unclear, and wrap its commands in your own artisan command rather than forking. You'll get upgrade safety and a place to add Slack notifications, retention, and environment guards.How can I help you explore Laravel packages today?