Personally I would create a command and a seeder for that
<?php
namespace App\Console\Commands;
use Database\Seeders\OldDbSeeder;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class SeedOldDatabase extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'seed:from-old-db database {--driver=} {--host=} {--username=} {--password=} {--charset=} {--collation=} {--prefix=}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Imports data from an old application with a different schema';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$this->call('down');
$default = DB::getDefaultConnection();
config(['database.connections.old_db' => [
'driver' => $this->option('driver') ?: config("database.connections.$default.driver"),
'host' => $this->option('host') ?: config("database.connections.$default.host"),
'database' => $this->argument('database') ?: config("database.connections.$default.database"),
'username' => $this->option('username') ?: config("database.connections.$default.username"),
'password' => $this->option('password') ?: config("database.connections.$default.password"),
'charset' => $this->option('charset') ?: config("database.connections.$default.charset"),
'collation' => $this->option('collation') ?: config("database.connections.$default.collation"),
'prefix' => $this->option('prefix') ?: config("database.connections.$default.prefix"),
]]);
$connection = DB::connection('old_db');
(new OldDbSeeder())->run($connection);
$this->call('up');
return 0;
}
}
<?php
namespace Database\Seeders;
use App\Models\Speaker;
use App\Structs\SocialLinks;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Database\QueryException;
use Illuminate\Database\Seeder;
class OldDbSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run(ConnectionInterface $connection)
{
// Select from old db
$connection
->table('app_speaker', 's')
->join('app_media as m', 's.image_id', '=', 'm.id')
->orderBy('s.created_at')
->whereNotIn('slug', Speaker::query()->select('slug')->get()->pluck('slug'))
->chunk(200, function ($speakers) {
foreach ($speakers as $oldSpeaker) {
$oldSpeaker = (array)$oldSpeaker;
$speaker = new Speaker();
$speaker->slug = $oldSpeaker['slug'];
$speaker->first_name = $oldSpeaker['firstName'];
$speaker->last_name = $oldSpeaker['lastName'];
$speaker->setCreatedAt($oldSpeaker['created_at']);
$speaker->setUpdatedAt($oldSpeaker['updated_at']);
$speaker->job_title = $oldSpeaker['profession'];
$speaker->social_links = new SocialLinks([
'facebook' => $oldSpeaker['facebookLink'],
'twitter' => $oldSpeaker['twitterLink'],
]);
$speaker->description = html_entity_decode($oldSpeaker['content']);
if (!$oldSpeaker['status']) {
$speaker->{$speaker->getDeletedAtColumn()} = $oldSpeaker['updated_at'];
}
try {
$speaker->save();
} catch (QueryException) {
echo "Speaker " . $speaker->slug . " could not be saved" . PHP_EOL;
// Ignore errors
}
}
});
}
}
I don't really see the point of doing in batches in cli, you should have no limit of memory and time and your application probably needs this data before being deployed without too much downtime to the website, so might as well put the old site offline, with the new one in it's place in maintenance mode, then just run the command once, wait the 1h of import if it's really that big and then go out of maintenance
You can chunk the select statements for sure to improve the perfomance of the seeder as demonstrated (if you select 20 million rows at once, I don't think it will be very happy)