How to move assets between containers in Statamic 5

I recently realized that in one of my blueprints, inside a custom “captioned image” Bard set I was unintentionally storing images in a local asset container, instead of my Digital Ocean-backed container. On top of that, these images were in some cases very large (20MB+ on the extreme end), as I was not running any optimizations, and just let the team upload any image size they desired. The largest possible image resolution displayed on the website is well under 2,000×2,000 pixels, and so it was just a waste of resources. After activating static image caching for assets, the server also started to crash on some pages, when glide tag tried to optimize a few of these 20MB+ images at once. As a result, I had two problems to tackle:
Migrate (hundreds of) images to a different asset container
Cut down the size of images to something reasonable
As far as I know Statamic doesn’t let you natively move assets between containers, but I was able to come up with a quick custom Artisan command doing just that. As a bonus, after setting a `max_upload_size` preset, and activating process source images setting on my target container, I was also able to automatically have the size of the images trimmed to 2,000×2,000 pixels. Here’s the command I used:
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Statamic\Facades\Asset;
class ChangeAssetContainer extends Command
{
protected $signature = 'assets:change-container {path} {fromContainer} {toContainer}';
protected $description = 'Move asset to another container.';
public function handle()
{
$path = $this->argument('path');
$fromContainer = $this->argument('fromContainer');
$toContainer = $this->argument('toContainer');
$oldAsset = Asset::query()->where('container', $fromContainer)->where('path', $path)->get()->first();
if (! $oldAsset) {
$this->error('Asset not found in the source container.');
return 1;
}
$newAsset = Asset::query()->where('container', $toContainer)->where('path', $path)->get()->first();
if ($newAsset) {
$this->error('Asset already exists in the destination container.');
return 1;
}
$tempFilePath = $oldAsset->path();
$this->info('Copying asset to temp file...');
Storage::writeStream($tempFilePath, $oldAsset->stream());
$uploadedFile = new UploadedFile(
Storage::path($tempFilePath),
$oldAsset->basename,
$oldAsset->mimeType(),
);
$newAsset = Asset::make()->container($toContainer)->path($path);
$newAsset->data($oldAsset->data());
$newAsset->save();
$this->info('Uploading asset to the new container...');
$newAsset->upload($uploadedFile);
$this->info('Deleting temp file...');
Storage::delete($tempFilePath);
$this->info('Deleting old asset...');
$oldAsset->delete();
}
}
You run the command as:
php artisan assets:change-container your_image.jpg old_container_name new_container_name
You can of course programmatically select images to migrate. In my case, I ran the following one-off script, to move all images from that captioned_image
Bard set in articles
collection.
\Statamic\Facades\Entry::whereCollection('articles')->each(function ($entry) {
collect($entry->content)->each(function ($value) {
if ($value->type == 'captioned_image') {
$path = $value->toArray()['image']->raw();
\Illuminate\Support\Facades\Artisan::call('assets:change-container', [
'path' => $path,
'fromContainer' => 'assets',
'toContainer' => 'spaces',
]);
}
});
});
This little script helped me save quite a bit of time. Let me know in the comments section if you found it useful too.
Subscribe to my newsletter
Read articles from Amadeusz Annissimo directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
