Skip to content

Instantly share code, notes, and snippets.

@godismyjudge95
Created February 21, 2025 18:37
Show Gist options
  • Select an option

  • Save godismyjudge95/f11d2a32de1c81f965ece015f92194f2 to your computer and use it in GitHub Desktop.

Select an option

Save godismyjudge95/f11d2a32de1c81f965ece015f92194f2 to your computer and use it in GitHub Desktop.
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Config;
use function Laravel\Prompts\select;
class MigrateStorage extends Command
{
protected $signature = 'storage:migrate
{source? : Source disk name}
{destination? : Destination disk name}
{--force : Force overwrite of existing files}';
protected $description = 'Migrate files from one storage disk to another using streaming';
protected $sourceDisk;
protected $destinationDisk;
protected $totalFiles = 0;
protected $migratedFiles = 0;
protected $skippedFiles = 0;
public function handle()
{
// Get available disks from config
$availableDisks = array_keys(Config::get('filesystems.disks', []));
if (empty($availableDisks)) {
$this->error('No storage disks configured in config/filesystems.php');
return 1;
}
// Get source disk
$sourceDiskName = $this->argument('source');
if (!empty($sourceDiskName) && !in_array($sourceDiskName, $availableDisks)) {
$this->error("Invalid source disk: {$sourceDiskName}");
$sourceDiskName = '';
}
if (empty($sourceDiskName)) {
$sourceDiskName = select(
'Select source disk',
$availableDisks,
);
}
$destinationDiskName = $this->argument('destination');
if (!empty($destinationDiskName) && !in_array($destinationDiskName, $availableDisks) || $destinationDiskName === $sourceDiskName) {
$this->error("Invalid destination disk: {$destinationDiskName}");
$destinationDiskName = '';
}
if (empty($destinationDiskName)) {
// Filter out the source disk from available destinations
$availableDestinations = collect($availableDisks)
->filter(fn($disk) => $disk !== $sourceDiskName)
->all();
if (empty($availableDestinations)) {
$this->error('No available destination disks (must be different from source)');
return 1;
}
$destinationDiskName = select(
'Select destination disk',
$availableDestinations,
);
}
$this->sourceDisk = Storage::disk($sourceDiskName);
$this->destinationDisk = Storage::disk($destinationDiskName);
$force = $this->option('force');
if (!$force && !$this->confirm("This will migrate all files from \"{$sourceDiskName}\" to \"{$destinationDiskName}\". Continue?")) {
$this->info('Migration cancelled.');
return 0;
}
$this->info('Starting migration...');
$this->info('Scanning source directory...');
// Get all files recursively from source
$files = $this->sourceDisk->allFiles();
$this->totalFiles = count($files);
if ($this->totalFiles === 0) {
$this->info("No files found in source disk \"{$sourceDiskName}\"");
return 0;
}
$this->info("Found {$this->totalFiles} files to migrate.");
$bar = $this->output->createProgressBar($this->totalFiles);
foreach ($files as $filePath) {
try {
// Check if file already exists in destination
if ($this->destinationDisk->exists($filePath) && !$force) {
$this->skippedFiles++;
$bar->advance();
continue;
}
// Ensure the directory exists
$directory = dirname($filePath);
if ($directory !== '.') {
$this->destinationDisk->makeDirectory($directory);
}
// Stream the file from source to destination
$sourceStream = $this->sourceDisk->readStream($filePath);
if ($sourceStream === false) {
throw new \Exception("Could not open source file: {$filePath}");
}
$this->destinationDisk->writeStream($filePath, $sourceStream);
// Clean up the stream
if (is_resource($sourceStream)) {
fclose($sourceStream);
}
$this->migratedFiles++;
$bar->advance();
} catch (\Exception $e) {
$this->error("Error migrating file: {$filePath}");
$this->error($e->getMessage());
// Clean up stream on error if it's still open
if (isset($sourceStream) && is_resource($sourceStream)) {
fclose($sourceStream);
}
}
}
$bar->finish();
$this->newLine();
$this->info('Migration completed!');
$this->info("Total files: {$this->totalFiles}");
$this->info("Migrated: {$this->migratedFiles}");
$this->info("Skipped: {$this->skippedFiles}");
return 0;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment