Adopt Laravel's Image facade for media processing, upgrade Intervention to v4

Laravel 13's Image facade wraps Intervention Image v4 internally, so
switching our upload width probe and resize job/command to it required
bumping intervention/image ^3 -> ^4 (and its intervention/gif ^5
dependency). Removes our own ImageManager container binding and
config/image.php in favour of Laravel's built-in driver resolution.

Also fixes a latent filename mismatch in ProcessMediaJobTest that Pint's
stricter typing on the new Image API turned into a hard TypeError, and
tidies config/flare.php to use imported class names instead of FQCNs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Sorsgn85nw7uQyRMNvzyD
This commit is contained in:
Jonny Barnes 2026-08-01 17:07:38 +01:00
commit 31c49ac3fc
Signed by: jonny
SSH key fingerprint: SHA256:CTuSlns5U7qlD9jqHvtnVmfYV3Zwl2Z7WnJ4/dqOaL8
9 changed files with 54 additions and 89 deletions

View file

@ -6,9 +6,9 @@ namespace App\Console\Commands;
use App\Models\Media;
use Illuminate\Console\Command;
use Illuminate\Image\ImageException;
use Illuminate\Support\Facades\Image;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\Exceptions\DecoderException;
use Intervention\Image\ImageManager;
class ReprocessMediaImages extends Command
{
@ -16,7 +16,7 @@ class ReprocessMediaImages extends Command
protected $description = 'Regenerate medium and small image variants using correct aspect-ratio scaling';
public function handle(ImageManager $manager): void
public function handle(): void
{
$media = Media::where('type', 'image')
->whereNotNull('image_widths')
@ -44,10 +44,10 @@ class ReprocessMediaImages extends Command
$this->info("Processing: {$path}");
$image = Image::fromStorage($path, 'public');
try {
$file = Storage::disk('public')->get($path);
$image = $manager->read($file);
} catch (DecoderException) {
$image->width();
} catch (ImageException) {
$this->warn(' Could not decode image, skipping.');
continue;
@ -57,11 +57,8 @@ class ReprocessMediaImages extends Command
$extension = array_pop($filenameParts);
$basename = trim(implode('.', $filenameParts), '.');
$medium = $image->scale(width: 1000);
Storage::disk('public')->put($basename.'-medium.'.$extension, (string) $medium->encode());
$small = $image->scale(width: 500);
Storage::disk('public')->put($basename.'-small.'.$extension, (string) $small->encode());
Storage::disk('public')->put($basename.'-medium.'.$extension, $image->scale(width: 1000)->toBytes());
Storage::disk('public')->put($basename.'-small.'.$extension, $image->scale(width: 500)->toBytes());
$this->info(' Done.');
}

View file

@ -13,9 +13,10 @@ use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Http\UploadedFile;
use Illuminate\Image\ImageException;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Image;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\ImageManager;
use Ramsey\Uuid\Uuid;
class MicropubMediaController extends Controller
@ -111,12 +112,9 @@ class MicropubMediaController extends Controller
$filename = Storage::disk('local')->putFile('media', $file);
/** @var ImageManager $manager */
$manager = resolve(ImageManager::class);
try {
$image = $manager->read($request->file('file'));
$width = $image->width();
} catch (Exception) {
$width = Image::fromUpload($request->file('file'))->width();
} catch (ImageException) {
// not an image
$width = null;
}

View file

@ -7,11 +7,11 @@ namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Image\ImageException;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Image;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\Exceptions\DecoderException;
use Intervention\Image\ImageManager;
class ProcessMedia implements ShouldQueue
{
@ -30,15 +30,16 @@ class ProcessMedia implements ShouldQueue
/**
* Execute the job.
*/
public function handle(ImageManager $manager): void
public function handle(): void
{
// Load file
$file = Storage::disk('local')->get($this->filename);
// Open file
$image = Image::fromStorage($this->filename, 'local');
try {
$image = $manager->read($file);
} catch (DecoderException) {
$width = $image->width();
} catch (ImageException) {
// not an image; delete file and end job
Storage::disk('local')->delete($this->filename);
@ -49,18 +50,15 @@ class ProcessMedia implements ShouldQueue
Storage::disk('public')->put($this->filename, $file);
// Create smaller versions if necessary
if ($image->width() > 1000) {
if ($width > 1000) {
$filenameParts = explode('.', $this->filename);
$extension = array_pop($filenameParts);
// the following achieves this data flow
// foo.bar.png => ['foo', 'bar', 'png'] => ['foo', 'bar'] => foo.bar
$basename = trim(implode('.', $filenameParts), '.');
$medium = $image->scale(width: 1000);
Storage::disk('public')->put($basename.'-medium.'.$extension, (string) $medium->encode());
$small = $image->scale(width: 500);
Storage::disk('public')->put($basename.'-small.'.$extension, (string) $small->encode());
Storage::disk('public')->put($basename.'-medium.'.$extension, $image->scale(width: 1000)->toBytes());
Storage::disk('public')->put($basename.'-small.'.$extension, $image->scale(width: 500)->toBytes());
}
// Now we can delete the locally saved image

View file

@ -7,7 +7,6 @@ use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\ServiceProvider;
use Intervention\Image\ImageManager;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
@ -30,11 +29,6 @@ class AppServiceProvider extends ServiceProvider
*/
public function boot(): void
{
// configure Intervention/Image
$this->app->bind('Intervention\Image\ImageManager', function () {
return ImageManager::withDriver(config('image.driver'));
});
/**
* Paginate a standard Laravel Collection.
*