├── LICENSE ├── README.md ├── composer.json ├── config └── settings.php └── src ├── Concerns └── About.php ├── Listeners ├── DumpedListener.php ├── FilesListener.php └── Listener.php ├── Schema ├── MySqlSchemaState.php ├── PostgresSchemaState.php └── SQLiteSchemaState.php ├── Service ├── Dumper.php ├── Files.php └── Tables.php └── ServiceProvider.php /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024-2025 Andrey Helldar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Database Data Dumper for Laravel 2 | 3 | ![the dragon code database data dumper](https://preview.dragon-code.pro/the-dragon-code/database-data-dumper.svg?brand=laravel) 4 | 5 | [![Stable Version][badge_stable]][link_packagist] 6 | [![Total Downloads][badge_downloads]][link_packagist] 7 | [![Github Workflow Status][badge_build]][link_build] 8 | [![License][badge_license]][link_license] 9 | 10 | ## Introduction 11 | 12 | The [squashing migrations](https://laravel.com/docs/migrations#squashing-migrations) in Laravel does not export data 13 | from tables? 14 | 15 | There is a solution! 16 | 17 | ### How it works? 18 | 19 | After installing and configuring the package, you simply run the console command `php artisan schema:dump` (with or 20 | without flags - it's up to you), and the final SQL dump file will contain the data structure including the contents of 21 | the tables you specified at the configuration stage. 22 | 23 | This will allow you to painlessly execute the `php artisan schema:dump --prune` command, which will remove unnecessary 24 | migration files. 25 | 26 | ## Requirements 27 | 28 | - Laravel 10, 11, 12 29 | - PHP 8.2 or higher 30 | - Databases: 31 | - Sqlite 3 32 | - MySQL 5.7, 8, 9 33 | - PostgreSQL 12, 13, 14, 15, 16, 17 34 | 35 | ## Installation 36 | 37 | To get the latest version of `Database Data Dumper`, simply require the project 38 | using [Composer](https://getcomposer.org): 39 | 40 | ```Bash 41 | composer require dragon-code/laravel-data-dumper 42 | ``` 43 | 44 | Or manually update `require` block of `composer.json` and run `composer update`. 45 | 46 | ```json 47 | { 48 | "require": { 49 | "dragon-code/laravel-data-dumper": "^1.0" 50 | } 51 | } 52 | ``` 53 | 54 | ## Configuration 55 | 56 | Since Laravel mechanism for publishing configuration files does not allow them to be merged on the fly, 57 | a new array element must be added to the [`config/database.php`](config/settings.php) file: 58 | 59 | ```php 60 | return [ 61 | /* 62 | |-------------------------------------------------------------------------- 63 | | Schema Settings 64 | |-------------------------------------------------------------------------- 65 | | 66 | | This block will contain the names of the tables for which it is 67 | | necessary to export data along with the table schema. 68 | | 69 | */ 70 | 71 | 'schema' => [ 72 | 'tables' => [ 73 | // 'foo', 74 | // 'bar', 75 | // App\Models\Article::class, 76 | 77 | // 'qwerty1' => ['column_name_1', 'database/foo'], 78 | // 'qwerty2' => ['column_name_2', __DIR__ . '/../bar'], 79 | ], 80 | ], 81 | ]; 82 | ``` 83 | 84 | After that, add to the array the names of the tables for which you want to export data. 85 | 86 | That's it. Now you can run the [`php artisan schema:dump`](https://laravel.com/docs/migrations#squashing-migrations) 87 | console command and enjoy the result. 88 | 89 | > [!NOTE] 90 | > 91 | > If you need to delete files from a folder to which a table is related (for example, the `migrations` table), 92 | > you can specify an array of two values in the parameter, where the first element should be the name of the column 93 | > containing the file name, and the second element should be absolute or relative folder path. 94 | 95 | > [!WARNING] 96 | > 97 | > Laravel 11.35.2 and below does not know how to report the presence of the `--prune` parameter when calling the 98 | > `artisan schema:dump` console command, so specifying paths to folders will always delete files from them. 99 | > 100 | > Starting with Laravel version 11.36, files will be deleted only when calling the console command with the 101 | > `--prune` parameter. 102 | 103 | 104 | ## License 105 | 106 | This package is licensed under the [MIT License](LICENSE). 107 | 108 | 109 | [badge_build]: https://img.shields.io/github/actions/workflow/status/TheDragonCode/laravel-data-dumper/tests.yml?style=flat-square 110 | 111 | [badge_downloads]: https://img.shields.io/packagist/dt/dragon-code/laravel-data-dumper.svg?style=flat-square 112 | 113 | [badge_license]: https://img.shields.io/packagist/l/dragon-code/laravel-data-dumper.svg?style=flat-square 114 | 115 | [badge_stable]: https://img.shields.io/github/v/release/TheDragonCode/laravel-data-dumper?label=stable&style=flat-square 116 | 117 | [link_build]: https://github.com/TheDragonCode/laravel-data-dumper/actions 118 | 119 | [link_license]: LICENSE 120 | 121 | [link_packagist]: https://packagist.org/packages/dragon-code/laravel-data-dumper 122 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dragon-code/laravel-data-dumper", 3 | "description": "Adding data from certain tables when executing the `php artisan schema:dump` console command", 4 | "license": "MIT", 5 | "type": "library", 6 | "keywords": [ 7 | "laravel", 8 | "migration", 9 | "migrations", 10 | "database", 11 | "schema", 12 | "dumper", 13 | "dump", 14 | "squash", 15 | "dragon-code", 16 | "andrey-helldar" 17 | ], 18 | "authors": [ 19 | { 20 | "name": "Andrey Helldar", 21 | "email": "helldar@dragon-code.pro", 22 | "homepage": "https://dragon-code.pro" 23 | } 24 | ], 25 | "support": { 26 | "issues": "https://github.com/TheDragonCode/laravel-data-dumper/issues", 27 | "source": "https://github.com/TheDragonCode/laravel-data-dumper" 28 | }, 29 | "funding": [ 30 | { 31 | "type": "boosty", 32 | "url": "https://boosty.to/dragon-code" 33 | }, 34 | { 35 | "type": "yoomoney", 36 | "url": "https://yoomoney.ru/to/410012608840929" 37 | } 38 | ], 39 | "require": { 40 | "php": "^8.2", 41 | "composer-runtime-api": "^2.2", 42 | "dragon-code/support": "^6.13", 43 | "illuminate/config": "^10.0 || ^11.0 || ^12.0", 44 | "illuminate/database": "^10.0 || ^11.0 || ^12.0", 45 | "illuminate/support": "^10.0 || ^11.0 || ^12.0" 46 | }, 47 | "require-dev": { 48 | "ext-pdo": "*", 49 | "mockery/mockery": "^1.3.1", 50 | "orchestra/testbench": "^8.0 || ^9.0 || ^10.0", 51 | "pestphp/pest": "^2.34 || ^3.0" 52 | }, 53 | "minimum-stability": "stable", 54 | "prefer-stable": true, 55 | "autoload": { 56 | "psr-4": { 57 | "DragonCode\\LaravelDataDumper\\": "src" 58 | } 59 | }, 60 | "autoload-dev": { 61 | "psr-4": { 62 | "Tests\\": "tests" 63 | } 64 | }, 65 | "config": { 66 | "allow-plugins": { 67 | "dragon-code/codestyler": true, 68 | "ergebnis/composer-normalize": true, 69 | "friendsofphp/php-cs-fixer": true, 70 | "pestphp/pest-plugin": true, 71 | "symfony/thanks": true 72 | }, 73 | "preferred-install": "dist", 74 | "sort-packages": true 75 | }, 76 | "extra": { 77 | "laravel": { 78 | "providers": [ 79 | "DragonCode\\LaravelDataDumper\\ServiceProvider" 80 | ] 81 | } 82 | }, 83 | "scripts": { 84 | "test": [ 85 | "vendor/bin/pest" 86 | ] 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /config/settings.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'tables' => [ 18 | // 'foo', 19 | // 'bar', 20 | // App\Models\Settings::class, 21 | 22 | // 'qwerty1' => ['column_name_1', 'database/foo'], 23 | // 'qwerty2' => ['column_name_2', __DIR__ . '/../bar'], 24 | ], 25 | ], 26 | ]; 27 | -------------------------------------------------------------------------------- /src/Concerns/About.php: -------------------------------------------------------------------------------- 1 | getPackageName(), fn () => [ 22 | 'Version' => $this->getPackageVersion(), 23 | ]); 24 | } 25 | } 26 | 27 | protected function getPackageName(): string 28 | { 29 | return Str::of($this->loadPackageName()) 30 | ->after('/') 31 | ->snake() 32 | ->replace('_', ' ') 33 | ->title() 34 | ->toString(); 35 | } 36 | 37 | protected function getPackageVersion(): string 38 | { 39 | return InstalledVersions::getPrettyVersion($this->loadPackageName()); 40 | } 41 | 42 | protected function loadPackageName(): string 43 | { 44 | return $this->packageName ??= Arr::ofFile($this->composer)->get('name'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Listeners/DumpedListener.php: -------------------------------------------------------------------------------- 1 | dumper->dump( 21 | $event->connection, 22 | $event->path, 23 | $this->tables->dumpable() 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Listeners/FilesListener.php: -------------------------------------------------------------------------------- 1 | files->clean( 22 | $event->connection, 23 | $this->tables->dumpable() 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Listeners/Listener.php: -------------------------------------------------------------------------------- 1 | appendMigrationData($path); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Schema/PostgresSchemaState.php: -------------------------------------------------------------------------------- 1 | baseDumpCommand() . ' -t ' . $this->migrationTable . ' --data-only >> ' . $path, 16 | ]); 17 | 18 | $commands->map(function (string $command, int|string $path) { 19 | $this->makeProcess($command)->mustRun($this->output, $this->variables($path)); 20 | }); 21 | } 22 | 23 | protected function variables(int|string $path): array 24 | { 25 | return array_merge($this->baseVariables($this->connection->getConfig()), [ 26 | 'LARAVEL_LOAD_PATH' => $path, 27 | ]); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Schema/SQLiteSchemaState.php: -------------------------------------------------------------------------------- 1 | appendMigrationData($path); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Service/Dumper.php: -------------------------------------------------------------------------------- 1 | isNotMigration($table)) { 26 | $this->export($connection, $path, $table); 27 | } 28 | } 29 | } 30 | 31 | protected function export(Connection $connection, string $path, string $table): void 32 | { 33 | $this->schemaState($connection) 34 | ?->withMigrationTable($table) 35 | ?->dump($connection, $path); 36 | } 37 | 38 | protected function schemaState(Connection $connection): ?SchemaState 39 | { 40 | return match (get_class($connection)) { 41 | SQLiteConnection::class => new SQLiteSchemaState($connection, null, null), 42 | MySqlConnection::class => new MySqlSchemaState($connection, null, null), 43 | PostgresConnection::class => new PostgresSchemaState($connection, null, null), 44 | default => null 45 | }; 46 | } 47 | 48 | protected function isNotMigration(string $table): bool 49 | { 50 | return $table !== $this->migrationTable(); 51 | } 52 | 53 | protected function migrationTable(): string 54 | { 55 | return is_array($migration = config('database.migrations')) ? $migration['table'] : $migration; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Service/Files.php: -------------------------------------------------------------------------------- 1 | $params) { 19 | if (is_array($params) && count($params) === 2) { 20 | $this->deleteFiles($connection, $table, $params[0], $params[1]); 21 | } 22 | } 23 | } 24 | 25 | protected function deleteFiles(Connection $connection, string $table, string $column, string $path): void 26 | { 27 | $connection->table($table)->when( 28 | $this->hasIdColumn($connection, $table), 29 | fn (Builder $query) => $query->lazyById(), 30 | fn (Builder $query) => $query->lazyById(column: $column), 31 | )->each( 32 | fn ($item) => $this->delete($path, $item->{$column}) 33 | ); 34 | } 35 | 36 | protected function delete(string $path, string $filename): void 37 | { 38 | if (! $dir = $this->directory($path)) { 39 | return; 40 | } 41 | 42 | if (! file_exists($dir . '/' . $filename)) { 43 | $filename = $this->findFile($dir, $filename); 44 | } 45 | 46 | File::ensureDelete($dir . '/' . $filename); 47 | } 48 | 49 | protected function directory(string $path): false|string 50 | { 51 | if (realpath($path) && is_dir($path)) { 52 | return rtrim($path, '\\/'); 53 | } 54 | 55 | $path = realpath(base_path($path)); 56 | 57 | if ($path && is_dir($path)) { 58 | return rtrim($path, '\\/'); 59 | } 60 | 61 | return false; 62 | } 63 | 64 | protected function findFile(string $path, string $filename): string 65 | { 66 | return $this->find($path, function (string $name) use ($filename) { 67 | return Str::contains($this->resolvePath($name), $this->resolvePath($filename)); 68 | }) ?? $filename; 69 | } 70 | 71 | protected function find(string $path, Closure $when): ?string 72 | { 73 | return File::names($path, $when, true)[0] ?? null; 74 | } 75 | 76 | protected function resolvePath(string $path): string 77 | { 78 | return str_replace('\\', '/', $path); 79 | } 80 | 81 | protected function hasIdColumn(Connection $connection, string $table): bool 82 | { 83 | return Schema::connection($connection->getName())->hasColumn($table, 'id'); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Service/Tables.php: -------------------------------------------------------------------------------- 1 | tables()) 15 | ->intersectByKeys(collect($this->available())->flip()) 16 | ->all(); 17 | } 18 | 19 | protected function tables(): array 20 | { 21 | return collect(config('database.schema.tables')) 22 | ->mapWithKeys(function (array|string $value, int|string $key) { 23 | if (is_numeric($key)) { 24 | $value = is_string($value) && ! is_numeric($value) ? $value : null; 25 | 26 | return [$this->fromModels($value) => null]; 27 | } 28 | 29 | if (is_string($key) && is_array($value)) { 30 | return [$this->fromModels($key) => $value]; 31 | } 32 | 33 | return [null => null]; 34 | })->reject( 35 | fn (mixed $value, ?string $key) => is_null($key) 36 | )->all(); 37 | } 38 | 39 | protected function available(): array 40 | { 41 | return array_column($this->tableSchema(), 'name'); 42 | } 43 | 44 | protected function tableSchema(): array 45 | { 46 | return Schema::getTables(); 47 | } 48 | 49 | protected function fromModels(Model|string|null $table): ?string 50 | { 51 | if (empty($table)) { 52 | return null; 53 | } 54 | 55 | if (! class_exists($table)) { 56 | return $table; 57 | } 58 | 59 | return (new $table())->getTable(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/ServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 22 | $this->registerAbout(); 23 | $this->bootListener(); 24 | } 25 | } 26 | 27 | public function register(): void 28 | { 29 | $this->registerConfig(); 30 | } 31 | 32 | protected function bootListener(): void 33 | { 34 | Event::listen(SchemaDumped::class, DumpedListener::class); 35 | 36 | class_exists(MigrationsPruned::class) 37 | ? Event::listen(MigrationsPruned::class, FilesListener::class) 38 | : Event::listen(SchemaDumped::class, FilesListener::class); 39 | } 40 | 41 | protected function registerConfig(): void 42 | { 43 | $this->mergeConfigFrom(__DIR__ . '/../config/settings.php', 'database'); 44 | } 45 | } 46 | --------------------------------------------------------------------------------