Laravel 13.20.0 adds first-party image processing to the framework, with an immutable, driver-based API for transforming images that come from uploads, storage, URLs, or raw bytes. The release also brings a WithoutMiddleware controller attribute, a dedicated session prefix for Redis, and a batch of Eloquent and queue additions.
- First-party image processing via the new
Imagefacade - A new
#[WithoutMiddleware]controller attribute - Configure a separate Redis prefix for sessions
- Quietly increment and decrement on Eloquent models
- Enums accepted as
WithoutOverlappingqueue keys - And more
What's New
First-Party Image Processing
Laravel 13.20 introduces an Illuminate\Image component that handles resizing, cropping, format conversion, and storage without reaching for a third-party wrapper.
Images are immutable: every transformation returns a new instance, and the queued transformations are applied when you ask for the result.
The most common path starts with an upload. Request::image() returns an Image instance for a given file key, or null if the key does not hold an uploaded file:
$request->image('avatar')
->cover(200, 200)
->toWebp()
->store('avatars');
You can also build an image from a path, a URL, raw bytes, base64, or a storage disk:
Image::fromPath('/path/to/photo.jpg');
Image::fromUrl('https://example.com/photo.jpg');
Image::fromBytes($bytes);
Image::fromStorage('photos/avatar.jpg', 's3');
Storage::disk('s3')->image('photos/avatar.jpg');
Transformations, output options, and inspection methods are all available on the instance:
// Transformations...
$image->cover(200, 200);
$image->contain(800, 600);
$image->crop(200, 200);
$image->resize(1024, 768);
$image->scale(1200, 800);
$image->rotate(90);
$image->blur(10);
$image->sharpen(10);
$image->grayscale();
$image->flip();
$image->flop();
$image->orient(); // Applies EXIF rotation...
// Output format and quality...
$image->toWebp();
$image->toJpg()->quality(80);
$image->optimize(); // WebP at quality 70...
$image->optimize('jpg', 85);
// Reading and inspecting...
$image->toBytes();
$image->toBase64();
$image->toDataUri();
$image->width();
$image->height();
$image->dimensions();
$image->mimeType();
$image->extension();
Because instances are immutable, you can branch from one source image to produce several variants:
$image = $request->image('photo');
$image->cover(200, 200)->toWebp()->store('thumbnails');
$image->grayscale()->toWebp()->store('grayscale');
Two drivers ship with the component, both backed by Intervention Image v4: GD and Imagick. You can pick a driver per image, and the Image facade lets you override how a driver handles a given transformation:
use Illuminate\Image\Transformations\Sharpen;
// Pick a driver per image...
$image->using('imagick');
$image->usingGd();
$image->usingImagick();
// Override how a driver handles a transformation...
Image::transformUsing('gd', Sharpen::class, function ($image, Sharpen $sharpen) {
// Custom sharpen handling for the GD driver...
return $image;
});
Intervention Image is a suggested dependency rather than a required one, so you install it yourself:
composer require intervention/image
See #59276.
A #[WithoutMiddleware] Controller Attribute
The routing attributes gain a counterpart to #[Middleware]. Where the latter attaches middleware to a controller class or method, #[WithoutMiddleware] excludes it, giving you the "excluding middleware" behavior of route groups at the attribute level:
use App\Http\Middleware\EnsureTokenIsValid;
use Illuminate\Routing\Controllers\Attributes\Middleware;
use Illuminate\Routing\Controllers\Attributes\WithoutMiddleware;
#[Middleware(EnsureTokenIsValid::class)]
class UserController
{
public function index()
{
// Middleware applies here...
}
#[WithoutMiddleware(EnsureTokenIsValid::class)]
public function profile()
{
// ...but not here.
}
}
Matching uses ReflectionAttribute::IS_INSTANCEOF, so subclasses of a middleware are excluded too. See #60709.
Redis Session Prefix
Applications that point both SESSION_DRIVER and CACHE_STORE at Redis have until now had sessions inherit the cache prefix, which makes session keys hard to pick out when you are cleaning up or debugging a shared keystore. A new prefix option in config/session.php lets you set a separate one:
'prefix' => env('SESSION_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-session-'),
The cache store is cloned before the session prefix is applied, so setting it does not affect cache keys. The option is opt-in and mirrors the existing session.connection setting. See #60700.
Quiet Bulk Increments on Eloquent
Eloquent already had incrementQuietly() and decrementQuietly() for single columns. This release fills in the multi-column gap with incrementEachQuietly() and decrementEachQuietly(), which update several columns at once while suppressing model events:
$user->incrementEachQuietly(['posts_count' => 1, 'points' => 10]);
$user->decrementEachQuietly(['credits' => 3, 'tokens' => 2]);
See #60720 and the follow-up fix for dynamic calls in #60737.
Enums as Queue Overlap Keys
The WithoutOverlapping job middleware now accepts a PHP enum as its key, so jobs keyed by an enumerated value no longer need a manual conversion to a string:
class UpdateCategory implements ShouldQueue
{
use Queueable;
public function __construct(protected Category $category) {}
public function middleware(): array
{
return [new WithoutOverlapping($this->category)];
}
}
The change is backward compatible with existing string keys. See #60722.
Other Fixes and Improvements
beforePushing()andafterPushing()callbacks onQueueFake(#60689), andMailFake::assertQueuedTimes()is now public (#60710)assertEmpty()added to theStoragefacade (#60658), and memory usage reported on theWorkerStoppingevent (#60613)make:migrationnow generates collision-free, ordered timestamp prefixes (#60771)#[SensitiveParameter]applied to parameters carrying secrets, keeping them out of stack traces (#60753)- A
capitalizeparameter forStringable::initials()(#60741) Str::containsAll()no longer returnstruefor an empty needles array (#60746), andNumber::forHumans()andabbreviate()no longer return-0for tiny negative values (#60736, #60768)- Fixes for
BelongsToMany::touch()when the related key is notid(#60708),TrustProxieswithat:*and multiple proxies (#60726), andprovidesTemporaryUploadUrls()on the S3 driver (#60755)
Upgrade Notes
No breaking changes are expected for typical applications. The image component's drivers depend on intervention/image (^4.0), which is a suggested package, so install it before using the Image facade. Review the changelog for PR-by-PR details when upgrading.
References
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.