PHP code example of mb4it / laravel-dbtodb-migration

1. Go to this page and download the library: Download mb4it/laravel-dbtodb-migration library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

mb4it / laravel-dbtodb-migration example snippets


    return [
        'migrations' => [
            'default' => [
                'source' => 'legacy_mysql',
                'target' => 'pgsql_app',

                'tables' => [
                    // source table => target table => source column => target column
                    'legacy_users' => [
                        'users' => [
                            'id' => 'id',
                            'email' => 'email',
                        ],
                    ],
                ],
            ],
        ],
    ];
    



return [
    // Keep strict mode enabled for real migrations: every mapped target column
    // must exist and every CHANNEL', 'db_to_db'),

    // After a successful non-dry run, realign auto-increment/serial/identity
    // counters for the listed target tables.
    'sync_serial_sequences' => true,
    'sync_serial_sequence_tables' => [
        'users',
        ['table' => 'orders', 'column' => 'id'],
    ],

    'runtime' => [
        'defaults' => [
            // Default chunk for source reads unless a migration/table overrides it.
            'chunk' => 1000,
            // Maximum rows per SQL insert/upsert statement.
            'max_rows_per_upsert' => 500,
            // batch = transaction per chunk; atomic = one transaction per pipeline.
            'transaction_mode' => 'batch',
        ],
        'memory' => [
            // 0 disables periodic memory logs; useful to enable during profiling.
            'memory_log_every_chunks' => 10,
            'force_gc_every_chunks' => 20,
        ],
        'profile_slow_chunk_seconds' => 5.0,
        'cli_memory_limit' => '1G',
    ],

    'auto_transforms' => [
        'enabled' => true,
        'bool' => true,
        'integer' => true,
        'float' => true,
        'json' => true,
        'date' => true,
        'datetime' => true,
        'string' => false,
        'empty_string_to_null' => true,
        'json_invalid' => 'fail', // fail the row when invalid JSON is mapped to json/jsonb.
        'bool_columns' => [
            // Force these columns to boolean even if a driver reports a generic type.
            'users' => ['is_active', 'is_admin'],
        ],
    ],

    'migrations' => [
        'default' => [
            'source' => 'legacy_mysql',
            'target' => 'pgsql_app',

            // Migration-level overrides; table-level source.runtime can override again.
            'runtime' => [
                'defaults' => [
                    'chunk' => 2000,
                    'transaction_mode' => 'batch',
                ],
            ],

            // Steps run in this order when --step is omitted. Use this when facts
            // depend on dimensions that should be migrated first.
            'steps' => [
                'dimensions' => [
                    'tables' => [
                        'legacy_countries' => [
                            'targets' => [
                                'countries' => [
                                    'columns' => [
                                        'iso' => 'iso_code',
                                        'title' => 'name',
                                    ],
                                    'transforms' => [
                                        'iso_code' => ['trim', 'upper'],
                                        'name' => ['trim', 'null_if_empty'],
                                    ],
                                    'upsert_keys' => ['iso_code'],
                                    'operation' => 'upsert',
                                ],
                            ],
                        ],
                    ],
                ],

                'users_and_orders' => [
                    'tables' => [
                        'legacy_users' => [
                            // Source filters are pushed into SQL and reduce rows for all targets.
                            'source' => [
                                'filters' => [
                                    ['column' => 'deleted_at', 'operator' => 'null'],
                                    ['column' => 'email', 'operator' => 'not_null'],
                                    ['column' => 'created_at', 'operator' => 'year', 'value' => 2026],
                                    ['or' => [
                                        ['column' => 'status', 'operator' => 'in', 'values' => ['active', 'pending']],
                                        ['column' => 'is_admin', 'operator' => '=', 'value' => 1],
                                    ]],
                                ],
                                'runtime' => [
                                    // Keyset pagination is preferable for large tables.
                                    'chunk' => 5000,
                                    'keyset_column' => 'id',
                                ],
                            ],

                            'targets' => [
                                // Main target: upsert all filtered users.
                                'users' => [
                                    'columns' => [
                                        'id' => 'id',
                                        'email' => 'email',
                                        'first_name' => 'name',
                                        'status' => 'status',
                                        'country_iso' => 'country_id',
                                        'is_admin' => 'is_admin',
                                        'created_at' => 'created_at',
                                    ],
                                    'transforms' => [
                                        'email' => ['trim', 'lower', 'null_if_empty'],
                                        'name' => [
                                            ['rule' => 'from_columns', 'columns' => ['first_name', 'last_name'], 'separator' => ' '],
                                            ['rule' => 'default', 'value' => 'Anonymous'],
                                        ],
                                        'status' => ['rule' => 'map', 'map' => ['active' => 'enabled', 'pending' => 'new'], 'default' => 'disabled'],
                                        'country_id' => [
                                            'rule' => 'lookup',
                                            'target' => [
                                                'connection' => 'pgsql_app',
                                                'table' => 'countries',
                                                'key' => 'iso_code',
                                                'value' => 'id',
                                            ],
                                        ],
                                    ],
                                    'upsert_keys' => ['id'],
                                    'operation' => 'upsert',
                                    'deduplicate' => ['keys' => ['id'], 'strategy' => 'last'],
                                    'on_row_error' => 'fail',
                                ],

                                // Routed target: only admin source rows are inserted here.
                                'admins' => [
                                    'columns' => [
                                        'id' => 'user_id',
                                        'email' => 'email',
                                    ],
                                    'filters' => [
                                        ['column' => 'is_admin', 'operator' => '=', 'value' => 1],
                                    ],
                                    'operation' => 'insert',
                                    // For insert-like operations, skip bad rows but keep the pipeline running.
                                    'on_row_error' => 'skip_row',
                                ],
                            ],
                        ],

                        'legacy_orders' => [
                            'source' => [
                                'filters' => [
                                    ['column' => 'total', 'operator' => '>=', 'value' => 0],
                                    ['column' => 'user_id', 'operator' => 'exists_in', 'value' => [
                                        'table' => 'legacy_users',
                                        'column' => 'id',
                                        'where' => [
                                            ['column' => 'deleted_at', 'operator' => 'null'],
                                        ],
                                    ]],
                                ],
                                'runtime' => [
                                    'chunk' => 1000,
                                    'keyset_column' => 'id',
                                ],
                            ],
                            'targets' => [
                                'orders' => [
                                    'columns' => [
                                        'id' => 'id',
                                        'user_id' => 'user_id',
                                        'total' => 'total',
                                        'payload_json' => 'payload',
                                        'created_at' => 'created_at',
                                    ],
                                    'transforms' => [
                                        'total' => ['rule' => 'cast', 'type' => 'float'],
                                        'payload' => ['rule' => 'cast', 'type' => 'json'],
                                    ],
                                    'upsert_keys' => ['id'],
                                    'operation' => 'upsert',
                                ],
                            ],
                        ],
                    ],
                ],

                'refresh_reports' => [
                    'tables' => [
                        'legacy_order_report' => [
                            'targets' => [
                                'order_reports' => [
                                    'columns' => [
                                        'order_id' => 'order_id',
                                        'summary' => 'summary',
                                    ],
                                    // Truncate the target once, then insert the fresh snapshot.
                                    'operation' => 'truncate_insert',
                                    'on_row_error' => 'skip_row',
                                ],
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ],
];

'migrations' => [
    'default' => [
        'source' => 'legacy_mysql',
        'target' => 'pgsql_app',
        'tables' => [
            'legacy_users' => [
                'users' => ['id' => 'id', 'email' => 'email'],
            ],
        ],
    ],

    'catalog' => [
        'source' => 'legacy_mysql',
        'target' => 'pgsql_app',
        'tables' => [
            'top_banners' => [
                'catalog_banners' => [
                    'link' => 'link',
                ],
                'catalog_banners_2' => [
                    'name' => 'name',
                ],
            ],
        ],
    ],
],

'migrations' => [
    'catalog' => [
        'source' => 'legacy_mysql',
        'target' => 'pgsql_app',

        'tables' => [
            'top_banners' => [
                // Optional source-table settings.
                'source' => [
                    'filters' => [
                        ['column' => 'active', 'operator' => '=', 'value' => 1],
                    ],
                    'chunk' => 1000,
                    'keyset_column' => 'id',
                ],

                'targets' => [
                    'catalog_banners' => [
                        // You can leave columns empty (`'columns' => []`) if all
                        // columns are generated by transforms or maps are fully implicit.
                        'columns' => [
                            'id' => 'id',
                            'link' => 'link',
                        ],
                        'transforms' => [
                            'link' => ['trim', 'null_if_empty'],
                            // You can add static rules for columns that don't exist in the source:
                            'type' => ['rule' => 'static', 'value' => 1],
                        ],
                        // Optional target filters are evaluated per target after source filters.
                        // Use them to route only some already-read source rows into this target.
                        'filters' => [
                            ['column' => 'link', 'operator' => 'not_null'],
                        ],
                        'upsert_keys' => ['id'],
                        'operation' => 'upsert',
                        // Optional: prevent target unique-key collisions after mapping/transforms.
                        'deduplicate' => ['keys' => ['id'], 'strategy' => 'last'],
                        // Optional: for insert/truncate_insert, skip only rows that fail to write.
                        'on_row_error' => 'fail', // fail|skip_row
                    ],
                ],
            ],
        ],
    ],
],

'migrations' => [
    'catalog' => [
        'source' => 'legacy_mysql',
        'target' => 'pgsql_app',
        'steps' => [
            'dimensions' => [
                'tables' => [
                    'legacy_brands' => ['brands' => ['id' => 'id', 'name' => 'name']],
                ],
            ],
            'facts' => [
                'tables' => [
                    'legacy_products' => ['products' => ['id' => 'id', 'brand_id' => 'brand_id']],
                ],
            ],
        ],
    ],
],

'auto_transforms' => [
    'enabled' => true,              // Master switch.
    'bool' => true,                 // Cast to bool when target column type looks boolean.
    'integer' => true,              // Cast int/smallint/bigint/serial-like target columns.
    'float' => true,                // Cast float/double/real/numeric/decimal target columns.
    'json' => true,                 // Encode arrays/objects for json/jsonb target columns.
    'date' => true,                 // Format date target columns as Y-m-d.
    'datetime' => true,             // Format datetime/timestamp target columns as Y-m-d H:i:s.
    'string' => false,              // Optional scalar-to-string coercion for text/varchar/uuid.
    'empty_string_to_null' => true, // For nullable target columns.
    'json_invalid' => 'keep',       // keep|null|fail for invalid JSON strings.
    'bool_columns' => [
        // Force per-table columns to bool even when the driver does not report a boolean type.
        'users' => ['is_admin', 'is_active'],
    ],
],

'profile_logging' => env('DB_TO_DB_LOG_CHANNEL', 'db_to_db'),

'sync_serial_sequences' => true,
'sync_serial_sequence_tables' => [
    // Optional explicit list. When omitted, the command derives target tables
    // from the executed pipelines.
    'users',
    ['table' => 'orders', 'column' => 'order_id'],
],

'source' => [
    'connection' => 'db_source',
    'table' => 'orders',
    'filters' => [
        ['column' => 'total', 'operator' => '>=', 'value' => 100],
        ['column' => 'total', 'operator' => 'where_column', 'value' => 'paid_total', 'comparison' => '<='],
        ['column' => 'created_at', 'operator' => 'date', 'value' => '2026-05-08'],
        ['column' => 'created_at', 'operator' => 'year', 'value' => 2026],
        ['or' => [
            ['column' => 'status', 'operator' => 'in', 'values' => ['paid', 'shipped']],
            ['and' => [
                ['column' => 'status', 'operator' => '=', 'value' => 'pending'],
                ['column' => 'customer_email', 'operator' => 'like', 'value' => '%@example.com'],
            ]],
        ]],
        ['column' => 'customer_id', 'operator' => 'exists_in', 'value' => [
            'table' => 'customers',
            'column' => 'id',
            'where' => [
                ['column' => 'active', 'operator' => '=', 'value' => 1],
                ['column' => 'deleted_at', 'operator' => 'null'],
            ],
        ]],
    ],
],

'source' => [
    'filters' => fn (Illuminate\Database\Query\Builder $query) => $query
        ->where('active', 1)
        ->whereExists(function ($sub) {
            $sub->selectRaw('1')
                ->from('customers')
                ->whereColumn('customers.id', 'orders.customer_id');
        }),
],

'targets' => [
    'paid_order_exports' => [
        'columns' => ['id' => 'id', 'status' => 'status', 'total' => 'total'],
        'filters' => [
            ['or' => [
                ['column' => 'status', 'operator' => '=', 'value' => 'paid'],
                ['column' => 'customer_email', 'operator' => 'like', 'value' => '%@example.com'],
            ]],
            ['column' => 'created_at', 'operator' => 'month', 'value' => 5],
            ['column' => 'total', 'operator' => 'where_column', 'value' => 'paid_total', 'comparison' => '<='],
        ],
    ],
],

'targets' => [
    'users' => [
        'columns' => [
            'legacy_id' => 'id',
            'email_address' => 'email',
            'first_name' => 'name',
            'status_code' => 'status',
            'created' => 'created_at',
            'country_code' => 'country_id',
        ],

        'transforms' => [
            'email' => ['trim', 'lower', 'null_if_empty'],
            'name' => [
                ['rule' => 'from_columns', 'columns' => ['first_name', 'last_name'], 'separator' => ' '],
                ['rule' => 'default', 'value' => 'Anonymous'],
            ],
            'status' => ['rule' => 'map', 'map' => ['A' => 'active', 'B' => 'blocked'], 'default' => 'new'],
            'created_at' => ['rule' => 'date_format', 'from' => 'Y-m-d H:i:s', 'format' => 'Y-m-d'],
            'country_id' => [
                'rule' => 'lookup',
                'target' => [
                    'connection' => 'pgsql_app',
                    'table' => 'countries',
                    'key' => 'iso_code',
                    'value' => 'id',
                ],
            ],

            'id' => ['rule' => 'cast', 'type' => 'int'],
        ],
    ],
],

'transforms' => [
    // Five-argument closure (preferred).
    'audit_label' => fn ($value, array $row, ?string $src, ?string $dst, ?string $table)
        => sprintf('%s:%s=%s', $table, $dst, $value),

    // Two-argument form (still supported).
    'full_name' => fn ($value, array $row) => trim(($row['first_name'] ?? '').' '.($row['last_name'] ?? '')),

    // Template via `from_columns`.
    'address' => [
        'rule' => 'from_columns',
        'columns' => ['street', 'city', 'country'],
        'template' => '{street}, {city} ({country})',
    ],
],

'tables' => [
    'legacy_users' => [
        'targets' => [
            'users' => [
                'columns' => ['id' => 'id', 'email' => 'email', 'is_admin' => 'is_admin'],
                'upsert_keys' => ['id'],
                'operation' => 'upsert',
            ],
            'admins' => [
                'columns' => ['id' => 'id', 'email' => 'email'],
                'filters' => [
                    ['column' => 'is_admin', 'operator' => '=', 'value' => 1],
                ],
                'operation' => 'insert',
            ],
        ],
    ],
],

'tables' => [
    'legacy_users' => [
        'targets' => [
            'audit_log' => [
                'columns' => [
                    'id' => 'ref_id',
                    'name' => 'source_table',  // placeholder — overwritten by static below.
                    'email' => 'event_type',   // placeholder — overwritten by static below.
                ],
                'transforms' => [
                    'source_table' => ['rule' => 'static', 'value' => 'users'],
                    'event_type'   => ['rule' => 'static', 'value' => 'user_imported'],
                ],
                'operation' => 'insert',
            ],
        ],
    ],
    'legacy_orders' => [
        'targets' => [
            'audit_log' => [
                'columns' => [
                    'id' => 'ref_id',
                    'currency' => 'source_table',
                    'status'   => 'event_type',
                ],
                'transforms' => [
                    'source_table' => ['rule' => 'static', 'value' => 'orders'],
                    'event_type'   => ['rule' => 'static', 'value' => 'order_imported'],
                ],
                'operation' => 'insert',
            ],
        ],
    ],
],
bash
php artisan db:to-db --migration=catalog
php artisan db:to-db --migration=catalog --source=legacy_mysql --target=pgsql_app
php artisan db:to-db --migration=catalog --dry-run