topoff / laravel-database-downloader
Dumps + Downloads a mysql database from a production server and imports it into the dev environment
Package info
github.com/topoff/laravel-database-downloader
pkg:composer/topoff/laravel-database-downloader
Fund package maintenance!
Requires
- php: ^8.4
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/boost: ^2.6
- laravel/pao: ^1.1
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^11.0
- pestphp/pest: ^5.0
- pestphp/pest-plugin-arch: ^5.0
- pestphp/pest-plugin-laravel: ^5.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- rector/rector: ^2.3
- spatie/laravel-ray: ^1.35
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
This package allows you to easily dump and download a MySQL database from a production or backup server and import it into your local development environment. It also supports downloading files from storage and provides an event-driven way to handle post-import tasks.
Installation
You can install the package via composer:
composer require topoff/laravel-database-downloader
You can publish the config file with:
php artisan vendor:publish --tag="database-downloader-config"
Usage
The package provides a command to download and import the database:
php artisan db:download
By default it imports the latest backup (--source=backup).
Options
--source=:backup(default),staging-dump,staging-dump-structure,live-dumporlive-dump-structure. A remote dump asks for confirmation (it briefly blocks the database).--dropExisting: Drop the database before import if it exists.--files: DispatchDatabaseImportedwithfilesImported = true; the package itself does not copy files — a listener in your app importspublic/storage.--table=: Import only this table out of the dump.--dbName=: Use a different database name instead of the default.--import-from-local-file-path=: Import from a specific local file instead of downloading.--use-existing: Import the dump files an earlier run left in the local download directory, regardless of their age and without asking.
Dump files left in the local download directory by an earlier run are deleted and downloaded
fresh without asking when they are older than 24 hours (config existing_files_max_age_hours).
Younger files are offered for reuse — always all of them together (a backup holds several dumps),
and a left-behind backup archive is unpacked again rather than trusting partly extracted files.
A .zip or .sql.gz passed via --import-from-local-file-path is unpacked into the local
download directory; the file itself stays untouched.
Creating the database
To create the database itself (a plain CREATE DATABASE) with the charset and
collation defined in config/database.php, use:
php artisan db:create
Laravel's migrate only applies the configured charset/collation when creating
tables — it does not create the database. This command fills that gap by
issuing CREATE DATABASE IF NOT EXISTS ... DEFAULT CHARACTER SET ... COLLATE ...
using the credentials of the configured connection.
Options
--dbName=: Use a different database name instead of the configured one.--charset=: Override the charset (defaults to the connection charset).--collation=: Override the collation (defaults to the connection collation).--dropExisting: Drop the database first if it already exists.
Like db:download, this command refuses to run in the production environment.
Anonymization
Right after the import — and before the DatabaseImported event — the command overwrites
contact data in the imported database, so no real personal data stays on development
machines. This is on by default in every environment except staging
(DB_DOWNLOADER_ANONYMIZE=true|false overrides the default; there is no CLI switch).
- Detected by column name (text columns only): email (
email,*_email,email_backup, …), phone (phone,*_phone,mobile,tel, …) and street address (address,address_2,street,*_address, …; notip_address). - Placeholders:
<hash>@example.test,+4100…(not dialable),Musterstrasse <n>. They are derived from a salted hash, so the same email gets the same placeholder in all tables of one run (unique indexes and joins on email keep working). The salt is random per run and not stored.NULLand empty values stay as they are. - Dump files are always deleted while anonymization is on — also after a failed run,
without asking. A file passed via
--import-from-local-file-pathis yours: it is kept and the command warns about it (inside the download directory you are asked first; in non-interactive mode that directory is cleared as before). - Failures: if the import or the anonymization fails, the command anonymizes what was
imported (best effort), exits with an error, does not dispatch
DatabaseImportedand tells you if the database may still contain real data. - A dump containing
CREATE DATABASE/USEfor another database than the import target is rejected before the import (also with anonymization off). - Aborted runs (Ctrl+C,
kill) get the same clean-up as failed ones (needsext-pcntl). Akillof only the PHP process takes effect once the running shell step (download, import) has ended; Ctrl+C stops that step right away.
Configure the rest in config/database-downloader.php:
// columns the name detection cannot know, or wrong matches to exclude 'anonymize_columns' => [ 'email_log' => ['to' => 'contact'], // 'email' | 'phone' | 'address' | 'contact' 'users' => ['email_invalid_reason' => false], // false = leave untouched ], // email addresses of these domains are kept (per value; a value listing several addresses never is) 'anonymize_keep_email_domains' => ['your-company.com'], // rows matching the SQL condition are kept entirely 'anonymize_keep_rows' => ['users' => 'is_admin = 1'],
Limits: only email, phone and street address are overwritten — names, postcodes, free
text and anything in JSON columns stay real. Columns with unusual names are not detected;
add them to anonymize_columns. Importing a single table (--table) uses a new salt, so its
placeholder emails no longer match those of an earlier full import.
Events
After a successful import, the package dispatches a Topoff\DatabaseDownloader\Events\DatabaseImported event. You can listen to this event to perform post-import tasks like data sanitization or environment-specific adjustments.
// Optionally Register the Listener in your EventServiceProvider or AppServiceProvider, Laravel does this automatically Event::listen( \Topoff\DatabaseDownloader\Events\DatabaseImported::class, \App\Listeners\YourPostImportListener::class, );
The event contains the following properties:
filesImported: A boolean indicating if the--filesoption was used.
Laravel Boost Integration
This package is configured to work with Laravel Boost for enhanced AI-assisted development.
Key features:
- ✅ Artisan script for MCP server support
- ✅ WorkbenchServiceProvider with automatic path configuration
- ✅ CLAUDE.md with package-specific guidelines
- ✅ Security best practices and coding standards
For detailed setup and usage instructions, see BOOST.md.
Security
This package has been thoroughly reviewed for security vulnerabilities:
- Input validation and sanitization
- Shell command injection prevention
- Path traversal protection
- Secure credential handling
For detailed security information, see SECURITY.md.
Development
Code Quality Tools
This package uses several tools to maintain code quality:
Laravel Pint (Code Formatting)
Format code according to Laravel standards:
composer format
Rector (Automated Refactoring)
Preview potential code improvements:
composer rector-dry
Apply automated refactorings:
composer rector
PHPStan (Static Analysis)
Run static analysis:
composer analyse
Run All Quality Checks
composer lint
This runs both Pint and PHPStan.
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.