Created
February 25, 2025 10:35
-
-
Save neverything/fe86ce4d489282c9cf1dba05502848d9 to your computer and use it in GitHub Desktop.
A Laravel artisan command to migrate files from one disk to another for example to move to S3-compatible object storage and back. See https://silvanhagen.com/talks/meetup-laravel-cloud/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Console\Commands; | |
use Illuminate\Console\Command; | |
use Illuminate\Support\Facades\Storage; | |
class CopyToS3Command extends Command | |
{ | |
protected $signature = 'storage:mirror | |
{source-disk : Source storage disk} | |
{destination-disk : Destination storage disk} | |
{--directories=* : Directories to mirror (e.g. share,avatars,screenshots)}'; | |
protected $description = 'Mirror directories between storage disks using streams'; | |
public function handle(): int | |
{ | |
$sourceDisk = $this->argument('source-disk'); | |
$destinationDisk = $this->argument('destination-disk'); | |
$directories = $this->option('directories'); | |
if (empty($directories)) { | |
$this->error('Please specify at least one directory to mirror using --directories'); | |
return 1; | |
} | |
$totalFiles = 0; | |
$processed = 0; | |
$failed = 0; | |
// Create progress bar for all directories | |
foreach ($directories as $directory) { | |
$totalFiles += count(Storage::disk($sourceDisk)->allFiles($directory)); | |
} | |
$bar = $this->output->createProgressBar($totalFiles); | |
$bar->setFormat(' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%'.PHP_EOL. | |
' Current file: %filename%'); | |
foreach ($directories as $directory) { | |
$this->info("Processing directory: {$directory}"); | |
$files = Storage::disk($sourceDisk)->allFiles($directory); | |
foreach ($files as $file) { | |
$bar->setMessage(basename($file), 'filename'); | |
try { | |
// Using streams for memory efficiency | |
$stream = Storage::disk($sourceDisk)->readStream($file); | |
if ($stream === false) { | |
throw new \RuntimeException("Could not read: {$file}"); | |
} | |
$success = Storage::disk($destinationDisk)->writeStream($file, $stream); | |
if (is_resource($stream)) { | |
fclose($stream); | |
} | |
if ($success) { | |
$processed++; | |
} else { | |
throw new \RuntimeException("Could not write: {$file}"); | |
} | |
} catch (\Exception $e) { | |
$failed++; | |
$this->error(" Failed: {$file} - ".$e->getMessage()); | |
} | |
$bar->advance(); | |
} | |
} | |
$bar->finish(); | |
$this->newLine(2); | |
$this->table( | |
['Metric', 'Value'], | |
[ | |
['Total Files', $totalFiles], | |
['Processed Successfully', $processed], | |
['Failed', $failed], | |
] | |
); | |
return ($failed === 0) ? 0 : 1; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment