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