├── LICENSE ├── README.md ├── composer.json └── src ├── Console └── Migrate.php ├── Constants └── Drivers.php ├── Database ├── Builder.php ├── Manager.php ├── MySQLBuilder.php ├── PostgresBuilder.php └── SqlServerBuilder.php ├── Exceptions └── InvalidArgumentException.php ├── Facades └── BuilderManager.php └── ServiceProvider.php /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Migrate DB for Laravel 2 | 3 | Migrate DB 4 | 5 | [![Stable Version][badge_stable]][link_packagist] 6 | [![Total Downloads][badge_downloads]][link_packagist] 7 | [![License][badge_license]][link_license] 8 | 9 | > Easy data transfer from one database to another 10 | 11 | ## Installation 12 | 13 | To get the latest version of `Migrate DB`, simply require the project using [Composer](https://getcomposer.org): 14 | 15 | ```bash 16 | composer require dragon-code/migrate-db --dev 17 | ``` 18 | 19 | Or manually update `require-dev` block of `composer.json` and run `composer update`. 20 | 21 | ```json 22 | { 23 | "require-dev": { 24 | "dragon-code/migrate-db": "^3.0" 25 | } 26 | } 27 | ``` 28 | 29 | ## Compatibility 30 | 31 | | Service | Versions | 32 | |:----------|:-----------------------------------| 33 | | PHP | ^8.0 | 34 | | Laravel | ^8.0, ^9.0, ^10.0, ^11.0, ^12.0 | 35 | | Databases | MySQL 5.7+, PostgreSQL 9.5+, MSSQL | 36 | 37 | | Laravel \ PostgreSQL | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 38 | |:---------------------|----|----|----|----|----|----|----|----|----| 39 | | 8 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | 40 | | 9 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | 41 | | 10 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | 42 | | 11 | ✖️ | ✖️ | ✖️ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | 43 | | 12 | ✖️ | ✖️ | ✖️ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | 44 | 45 | 46 | ## Usage 47 | 48 | Create a new database and set up both connections in the `connections` section of 49 | the [config/database.php](https://github.com/laravel/laravel/blob/master/config/database.php) file, then run the `db:migrate` console command passing two 50 | parameters: 51 | 52 | ```bash 53 | php artisan db:migrate --schema-from=foo --schema-to=bar 54 | ``` 55 | 56 | ### For Certain Tables 57 | 58 | ```bash 59 | php artisan db:migrate --schema-from=foo --schema-to=bar --tables=table1 --tables=table2 60 | ``` 61 | 62 | ### Exclude Certain Tables 63 | 64 | ```bash 65 | php artisan db:migrate --schema-from=foo --schema-to=bar --exclude=table1 --exclude=table2 66 | ``` 67 | 68 | where: 69 | 70 | * `foo` - Source [connection](https://github.com/laravel/laravel/blob/master/config/database.php) name 71 | * `bar` - Target [connection](https://github.com/laravel/laravel/blob/master/config/database.php) name 72 | 73 | Follow on screen instructions and then command will perform all migrations on the source and destination databases and transfer all records from the old to the new one. 74 | 75 | Enjoy 😊 76 | 77 | 78 | ## License 79 | 80 | This package is licensed under the [MIT License](LICENSE). 81 | 82 | 83 | [badge_downloads]: https://img.shields.io/packagist/dt/dragon-code/migrate-db.svg?style=flat-square 84 | 85 | [badge_license]: https://img.shields.io/packagist/l/dragon-code/migrate-db.svg?style=flat-square 86 | 87 | [badge_stable]: https://img.shields.io/github/v/release/TheDragonCode/migrate-db?label=stable&style=flat-square 88 | 89 | [link_build]: https://github.com/TheDragonCode/migrate-db/actions 90 | 91 | [link_license]: LICENSE 92 | 93 | [link_packagist]: https://packagist.org/packages/dragon-code/migrate-db 94 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dragon-code/migrate-db", 3 | "description": "Easy data transfer from one database to another", 4 | "license": "MIT", 5 | "type": "library", 6 | "keywords": [ 7 | "database", 8 | "migrating", 9 | "migration", 10 | "move", 11 | "transfer", 12 | "dragon-code", 13 | "dragon", 14 | "andrey-helldar" 15 | ], 16 | "authors": [ 17 | { 18 | "name": "Andrey Helldar", 19 | "email": "helldar@dragon-code.pro", 20 | "homepage": "https://dragon-code.pro" 21 | } 22 | ], 23 | "support": { 24 | "issues": "https://github.com/TheDragonCode/migrate-db/issues", 25 | "source": "https://github.com/TheDragonCode/migrate-db" 26 | }, 27 | "funding": [ 28 | { 29 | "type": "boosty", 30 | "url": "https://boosty.to/dragon-code" 31 | }, 32 | { 33 | "type": "yoomoney", 34 | "url": "https://yoomoney.ru/to/410012608840929" 35 | } 36 | ], 37 | "require": { 38 | "php": "^8.0", 39 | "ext-pdo": "*", 40 | "dragon-code/contracts": "^2.15", 41 | "dragon-code/support": "^6.0", 42 | "illuminate/contracts": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0", 43 | "illuminate/database": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0", 44 | "illuminate/support": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0" 45 | }, 46 | "require-dev": { 47 | "ext-pdo_mysql": "*", 48 | "ext-pdo_pgsql": "*", 49 | "doctrine/dbal": "^3.0 || ^4.0", 50 | "mockery/mockery": "^1.0", 51 | "orchestra/testbench": "^6.0 || ^7.0 || ^8.0 || ^9.0 || ^10.0", 52 | "phpunit/phpunit": "^9.6 || ^10.0 || ^11.0" 53 | }, 54 | "suggest": { 55 | "doctrine/dbal": "[For Laravel 8-10] Required to rename columns and drop SQLite columns (^3.5.1)." 56 | }, 57 | "minimum-stability": "stable", 58 | "prefer-stable": true, 59 | "autoload": { 60 | "psr-4": { 61 | "DragonCode\\MigrateDB\\": "src" 62 | } 63 | }, 64 | "autoload-dev": { 65 | "psr-4": { 66 | "Tests\\": "tests" 67 | } 68 | }, 69 | "config": { 70 | "allow-plugins": { 71 | "dragon-code/codestyler": true, 72 | "ergebnis/composer-normalize": true, 73 | "friendsofphp/php-cs-fixer": true, 74 | "symfony/thanks": true 75 | }, 76 | "preferred-install": "dist", 77 | "sort-packages": true 78 | }, 79 | "extra": { 80 | "laravel": { 81 | "providers": [ 82 | "DragonCode\\MigrateDB\\ServiceProvider" 83 | ] 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Console/Migrate.php: -------------------------------------------------------------------------------- 1 | validateOptions(); 74 | $this->resolveBuilders(); 75 | $this->resolveOptions(); 76 | $this->cleanTargetDatabase(); 77 | $this->runMigrations(); 78 | 79 | $this->disableForeign(); 80 | $this->runTransfer(); 81 | $this->enableForeign(); 82 | 83 | $this->showStatus(); 84 | } 85 | 86 | protected function showStatus(): void 87 | { 88 | $this->displayMessage('Migrated Tables', $this->migrated); 89 | $this->displayMessage('Excluded Tables', $this->excluded); 90 | $this->displayMessage('Tables does not exist in source connection', $this->tables_not_exists); 91 | } 92 | 93 | protected function displayMessage(string $message, array $context = []): void 94 | { 95 | $this->info($message); 96 | 97 | if ($context) { 98 | $this->info(implode(',', $context)); 99 | } 100 | } 101 | 102 | protected function runTransfer(): void 103 | { 104 | $this->displayMessage('Transferring data...' . PHP_EOL); 105 | 106 | $this->withProgressBar($this->tables(), function (string $table) { 107 | if (in_array($table, $this->excludes)) { 108 | $this->excluded[] = $table; 109 | 110 | return; 111 | } 112 | 113 | if ($this->doesntHasTable($this->source(), $table)) { 114 | $this->tables_not_exists[] = $table; 115 | 116 | return; 117 | } 118 | 119 | $this->truncateTable($table); 120 | $this->migrateTable($table, $this->source->getPrimaryKey($table)); 121 | }); 122 | 123 | $this->displayMessage(PHP_EOL); 124 | } 125 | 126 | protected function truncateTable(string $table): void 127 | { 128 | if ($this->truncate) { 129 | $this->builder($this->target(), $table)->truncate(); 130 | } 131 | } 132 | 133 | protected function migrateTable(string $table, string $column): void 134 | { 135 | Log::info('Transferring data from: ' . $table); 136 | 137 | $this->builder($this->source(), $table) 138 | ->orderBy($column) 139 | ->chunk(1000, function (Collection $items) use ($table) { 140 | $this->builder($this->target(), $table)->insert( 141 | Arr::resolve($items) 142 | ); 143 | }); 144 | 145 | $this->migrated[] = $table; 146 | } 147 | 148 | protected function isSkippable(string $table, string $column): bool 149 | { 150 | return ! $this->truncate && $this->isNumericColumn($table, $column); 151 | } 152 | 153 | protected function isNumericColumn(string $table, string $column): bool 154 | { 155 | $type = $this->getPrimaryKeyType($this->source(), $table, $column); 156 | 157 | return ! in_array($type, ['string', 'char', 'ulid', 'uuid'], true); 158 | } 159 | 160 | protected function tables(): array 161 | { 162 | if ($this->tables) { 163 | return $this->tables; 164 | } 165 | 166 | return $this->retrieve_tables_from_target 167 | ? $this->target->getAllTables() 168 | : $this->source->getAllTables(); 169 | } 170 | 171 | protected function cleanTargetDatabase(): void 172 | { 173 | if (! $this->drop_target) { 174 | return; 175 | } 176 | 177 | $this->displayMessage('Clearing the target database...'); 178 | 179 | $this->target->dropAllTables(); 180 | } 181 | 182 | protected function runMigrations(): void 183 | { 184 | $on = $this->getMigrationOption(); 185 | 186 | if ($this->isMigrationNotRequired($on)) { 187 | return; 188 | } 189 | 190 | $this->displayMessage('Run migrations on the databases...'); 191 | 192 | if ($this->shouldRunOnSource($on)) { 193 | $this->migrate($this->source()); 194 | } 195 | 196 | if ($this->drop_target || $this->shouldRunOnSource($on) || $this->shouldRunOnTarget($on)) { 197 | $this->migrate($this->target()); 198 | } 199 | } 200 | 201 | protected function isMigrationNotRequired(string $on): bool 202 | { 203 | return $on === $this->none; 204 | } 205 | 206 | protected function shouldRunOnTarget(string $on): bool 207 | { 208 | return $on === $this->target_connection; 209 | } 210 | 211 | protected function shouldRunOnSource(string $on): bool 212 | { 213 | return $on === $this->source_connection; 214 | } 215 | 216 | protected function migrate(string $connection): void 217 | { 218 | $this->call('migrate', ['--database' => $connection]); 219 | } 220 | 221 | protected function disableForeign(): void 222 | { 223 | $this->target->disableForeign(); 224 | } 225 | 226 | protected function enableForeign(): void 227 | { 228 | $this->target->enableForeign(); 229 | } 230 | 231 | protected function source(): string 232 | { 233 | return $this->validatedOption('schema-from'); 234 | } 235 | 236 | protected function target(): string 237 | { 238 | return $this->validatedOption('schema-to'); 239 | } 240 | 241 | protected function getTablesOption(): array 242 | { 243 | return $this->option('tables'); 244 | } 245 | 246 | protected function getExcludeOption(): array 247 | { 248 | return $this->option('exclude'); 249 | } 250 | 251 | protected function getMigrationOption(): string 252 | { 253 | return $this->choice('Please choose option to run migration on which connection?', $this->choices, 0); 254 | } 255 | 256 | protected function confirmTableListOption(): bool 257 | { 258 | return $this->confirm( 259 | 'Please confirm table list should be retrieved from target connection? (incase if source connection does not support it)', 260 | false 261 | ); 262 | } 263 | 264 | protected function confirmTruncateTableOption(): bool 265 | { 266 | return $this->confirm('Please confirm whether to truncate target table before transfer?', false); 267 | } 268 | 269 | protected function confirmDropOption(): bool 270 | { 271 | return $this->confirm('Please choose whether to drop target tables before migration?', false); 272 | } 273 | 274 | protected function validatedOption(string $key): string 275 | { 276 | if ($schema = $this->option($key)) { 277 | return $schema; 278 | } 279 | 280 | throw new InvalidArgumentException($key); 281 | } 282 | 283 | protected function validateOptions(): void 284 | { 285 | $this->validatedOption('schema-from'); 286 | $this->validatedOption('schema-to'); 287 | } 288 | 289 | protected function resolveBuilder(string $connection): Builder 290 | { 291 | return BuilderManager::of($connection)->get(); 292 | } 293 | 294 | protected function resolveBuilders(): void 295 | { 296 | $this->source = $this->resolveBuilder($this->source()); 297 | $this->target = $this->resolveBuilder($this->target()); 298 | } 299 | 300 | protected function resolveOptions(): void 301 | { 302 | $this->tables = $this->getTablesOption(); 303 | $this->excludes = $this->getExcludeOption(); 304 | 305 | if (empty($this->tables) && $this->confirmTableListOption()) { 306 | $this->retrieve_tables_from_target = true; 307 | } 308 | 309 | if ($this->confirmTruncateTableOption()) { 310 | $this->truncate = true; 311 | } 312 | 313 | if (empty($this->tables) && empty($this->excludes) && $this->truncate && $this->confirmDropOption()) { 314 | $this->drop_target = true; 315 | } 316 | } 317 | 318 | protected function builder(string $connection, string $table): QueryBuilder 319 | { 320 | return $this->connection($connection)->table($table); 321 | } 322 | 323 | protected function doesntHasTable(string $connection, string $table): bool 324 | { 325 | return ! Schema::connection($connection)->hasTable($table); 326 | } 327 | 328 | protected function getPrimaryKeyType(string $connection, string $table, string $column): string 329 | { 330 | if (method_exists($this->connection($connection), 'getDoctrineColumn')) { 331 | return $this->connection($connection)->getDoctrineColumn($table, $column)->getType()->getName(); 332 | } 333 | 334 | return $this->connection($connection)->getSchemaBuilder()->getColumnType($table, $column); 335 | } 336 | 337 | protected function connection(string $name): Connection 338 | { 339 | return DB::connection($name); 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /src/Constants/Drivers.php: -------------------------------------------------------------------------------- 1 | connection = $connection; 26 | } 27 | 28 | /** 29 | * @return SchemaBuilder|\Illuminate\Database\Schema\MySqlBuilder|\Illuminate\Database\Schema\PostgresBuilder 30 | */ 31 | public function schema(): SchemaBuilder 32 | { 33 | return $this->connection->getSchemaBuilder(); 34 | } 35 | 36 | public function getAllTables(): array 37 | { 38 | $schema = method_exists($this->schema(), 'getCurrentSchemaName') 39 | ? $this->schema()->getCurrentSchemaName() 40 | : null; 41 | 42 | $tables = method_exists($this->schema(), 'getAllTables') 43 | ? $this->schema()->getAllTables() 44 | : $this->schema()->getTables($schema); 45 | 46 | $key = $this->tableNameColumn(); 47 | 48 | return $this->pluckTableNames($this->filteredTables($tables, $key), $key); 49 | } 50 | 51 | public function dropAllTables(): void 52 | { 53 | $this->schema()->dropAllTables(); 54 | } 55 | 56 | public function disableForeign(): void 57 | { 58 | $this->schema()->disableForeignKeyConstraints(); 59 | } 60 | 61 | public function enableForeign(): void 62 | { 63 | $this->schema()->enableForeignKeyConstraints(); 64 | } 65 | 66 | public function getPrimaryKey(string $table): string 67 | { 68 | $columns = $this->columns($table); 69 | 70 | return Arr::first($columns); 71 | } 72 | 73 | protected function columns(string $table): array 74 | { 75 | return $this->schema()->getColumnListing($table); 76 | } 77 | 78 | protected function filteredTables(array $tables, string $key): array 79 | { 80 | return array_filter($tables, static function (array|stdClass $table) use ($key) { 81 | $name = is_array($table) ? $table['name'] : $table->{$key}; 82 | 83 | return $name !== 'migrations'; 84 | }); 85 | } 86 | 87 | protected function pluckTableNames(array $tables, string $key): array 88 | { 89 | return array_map(static function (array|stdClass $table) use ($key) { 90 | return is_array($table) ? $table['name'] : $table->{$key}; 91 | }, $tables); 92 | } 93 | 94 | protected function database(): string 95 | { 96 | return $this->connection->getDatabaseName(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Database/Manager.php: -------------------------------------------------------------------------------- 1 | MySQLBuilder::class, 22 | Drivers::POSTGRES => PostgresBuilder::class, 23 | Drivers::SQL_SERVER => SqlServerBuilder::class, 24 | ]; 25 | 26 | public function of(string $connection): self 27 | { 28 | $this->connection = $connection; 29 | 30 | return $this; 31 | } 32 | 33 | public function get(): BuilderContract 34 | { 35 | $builder = $this->getBuilder(); 36 | 37 | return $builder::make($this->connection()); 38 | } 39 | 40 | /** 41 | * @return Builder|string 42 | */ 43 | protected function getBuilder(): string 44 | { 45 | return $this->builders[$this->driver()]; 46 | } 47 | 48 | protected function connection(): Connection 49 | { 50 | return $this->factory()->make($this->config()); 51 | } 52 | 53 | protected function factory(): ConnectionFactory 54 | { 55 | return new ConnectionFactory($this->container()); 56 | } 57 | 58 | protected function container(): Container 59 | { 60 | return Container::getInstance(); 61 | } 62 | 63 | protected function config(): array 64 | { 65 | $key = 'database.connections.' . $this->connection; 66 | 67 | if (Config::has($key)) { 68 | return Config::get($key); 69 | } 70 | 71 | throw new InvalidArgumentException("Unsupported driver [{$this->connection}]."); 72 | } 73 | 74 | protected function driver(): string 75 | { 76 | return Arr::get($this->config(), 'driver'); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Database/MySQLBuilder.php: -------------------------------------------------------------------------------- 1 | database(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Database/PostgresBuilder.php: -------------------------------------------------------------------------------- 1 | bootCommands(); 15 | } 16 | 17 | protected function bootCommands(): void 18 | { 19 | if ($this->app->runningInConsole()) { 20 | $this->commands([ 21 | Migrate::class, 22 | ]); 23 | } 24 | } 25 | } 26 | --------------------------------------------------------------------------------