Skip to content

Instantly share code, notes, and snippets.

@neverything
Created February 25, 2025 10:35
Show Gist options
  • Save neverything/fe86ce4d489282c9cf1dba05502848d9 to your computer and use it in GitHub Desktop.
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/
<?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