У Laravel 13.20 з’явився вбудований інструмент для обробки зображень — новий компонент Illuminate\Image, представлений у Pull Request #59276. Раніше для зміни розміру аватара чи конвертації файлів у WebP доводилося встановлювати сторонні пакети.
Тепер фреймворк пропонує зручний (fluent) та незмінний (immutable) API, який охоплює базові потреби: зміну розміру, обрізку, конвертацію форматів, контроль якості, ефекти та збереження результату на будь-який диск файлової системи.
У цьому туторіалі ми розберемо можливості API на практиці: обробимо аватар, згенеруємо адаптивні варіанти зображень та застосуємо трансформації за певних умов.
# Налаштування
Драйвери GD та Imagick працюють на базі Intervention Image v4. Це рекомендована, але не обов'язкова залежність, тому її потрібно встановити вручну:
composer require intervention/image:^4.0
За замовчуванням Laravel використовує драйвер GD. Якщо на вашому сервері встановлено розширення Imagick, ви можете зробити його основним у конфігурації images.default або змінювати драйвер безпосередньо під час виклику:
$image->usingImagick()->toBytes();
// Або за назвою:
$image->using('imagick')->toBytes();
Якщо викликати драйвер без встановленого Intervention Image, Laravel видасть ImageException з чіткою інструкцією, що саме потрібно доставити.
# Створення екземпляра Image
Зображення можна отримати з будь-якого джерела: завантаження, диск, локальний шлях, URL або сирі байти. Новий метод Request::image() — найзручніший спосіб для роботи з аплоадами:
$image = $request->image('avatar'); // ?Illuminate\Image\Image
Метод повертає null, якщо поле відсутнє або не є файлом, тому спочатку валідуйте вхідні дані як зазвичай.
Фасад Image та сервіс Storage дозволяють працювати з іншими джерелами:
use Illuminate\Support\Facades\Image;
use Illuminate\Support\Facades\Storage;
$image = Image::fromPath('/path/to/photo.jpg');
$image = Image::fromUrl('https://example.com/photo.jpg');
$image = Image::fromStorage('uploads/photo.jpg', 's3');
$image = Image::fromBytes($contents);
$image = Image::fromBase64($encoded);
// Еквівалент fromStorage():
$image = Storage::disk('s3')->image('uploads/photo.jpg');
# Як працює ланцюжок викликів
Кожна трансформація повертає новий екземпляр Image. Обробка не розпочнеться, доки ви не запитаєте фінальний результат (через store(), toBytes(), width() тощо). Це дозволяє створювати кілька варіантів зображення на основі одного базового об'єкта, не боячись, що зміни одного варіанту вплинуть на інший:
$photo = Image::fromStorage('uploads/photo.jpg')->orient();
$thumbnail = $photo->cover(300, 300)->quality(60)->toWebp();
$display = $photo->scale(width: 1600)->quality(80)->toWebp();
$thumbnail->storeAs('photos', 'photo-thumb.webp', disk: 's3');
$display->storeAs('photos', 'photo-display.webp', disk: 's3');
Виклик orient() автоматично повертає зображення згідно з даними EXIF — це корисно для фотографій, зроблених на смартфони.
# Зміна розміру: cover, contain, scale, resize та crop
API пропонує п'ять методів для зміни габаритів:
cover($width, $height): масштабує та обрізає зображення, щоб воно повністю заповнило вказані розміри. Ідеально для аватарів та прев’ю.contain($width, $height, $background): вписує зображення в межі, додаючи тло (за потреби) для збереження пропорцій.scale($width, $height): пропорційно змінює розмір, але ніколи не збільшує оригінал (upscale). Можна вказати лише одну сторону.resize($width, $height): примусово встановлює розміри, що може призвести до спотворення пропорцій.crop($width, $height, $x, $y): вирізає фрагмент зображення за заданими координатами.
$image->cover(512, 512); // Квадрат, обрізаний під розмір
$image->contain(800, 600, '#fff'); // Вписано у рамку на білому тлі
$image->scale(width: 1200); // Пропорційно, тільки зменшення
$image->crop(400, 300, x: 100, y: 50);
# Ефекти та коригування
Для базової корекції доступні такі методи:
$image
->rotate(90) // Поворот за годинниковою стрілкою
->blur(10) // Розмиття (0–100)
->sharpen(15) // Різкість (0–100)
->grayscale() // Чорно-білий фільтр
->flip() // Вертикальне відображення
->flop(); // Горизонтальне відображення
# Формати, якість та optimize()
Конвертація виконується методами toWebp(), toJpg(), toPng(), toGif(), toAvif() та toBmp(). Параметр quality (1–100) працює для WebP, JPEG та AVIF:
$image->toWebp()->quality(80);
Метод optimize() — це швидкий спосіб конвертувати зображення у WebP з якістю 70 за замовчуванням:
$image->optimize(); // WebP, якість 70
$image->optimize('avif', 60); // AVIF, якість 60
Драйвери підтримують JPEG, PNG, GIF, BMP та WebP на вході.
# Збереження та отримання результату
Методи збереження ідентичні до UploadedFile у Laravel:
$path = $image->store('avatars'); // Випадкове ім'я (хеш)
$path = $image->storeAs('avatars', 'user-1.webp'); // Чітка назва
$path = $image->storePublicly('avatars', disk: 's3'); // Публічний доступ
При використанні випадкового імені розширення автоматично зміниться на відповідне формату (наприклад, .webp після виклику toWebp()).
Також можна отримати дані безпосередньо через toBytes(), toBase64() або toDataUri().
Методи інспекції повертають дані вже обробленого зображення:
$image->width(); // Ширина
$image->height(); // Висота
$image->dimensions(); // [ширина, висота]
$image->mimeType(); // Наприклад, "image/webp"
$image->extension(); // Наприклад, "webp"
Data URI зручно використовувати для вбудовування маленьких прев’ю прямо в HTML або email:
$avatar = Storage::image("avatars/{$user->avatar}")
->cover(64, 64)
->toDataUri();
# Приклад: завантаження аватара
Ось повний цикл обробки в контролері: вирівнювання за EXIF, обрізка до 512x512, оптимізація та збереження на S3 з публічним доступом:
public function update(Request $request)
{
$request->validate([
'avatar' => ['required', 'image', 'max:5120'],
]);
$path = $request->image('avatar')
->orient()
->cover(512, 512)
->optimize()
->storePublicly('avatars', disk: 's3');
$request->user()->update(['avatar_path' => $path]);
return back();
}
Генерація адаптивних варіантів у циклі:
foreach ([480, 960, 1440] as $width) {
Image::fromStorage($path, 's3')
->scale(width: $width)
->toWebp()
->storeAs('photos/variants', "{$name}-{$width}w.webp", disk: 's3');
}
Оскільки scale() не збільшує зображення, оригінал шириною 900 пікселів не буде розтягнутий до 1440.
# Умовні трансформації
Клас Image використовує трейт Conditionable, тому методи when() та unless() працюють як і в інших частинах Laravel:
$image = $request->image('photo')
->when($request->boolean('grayscale'), fn ($image) => $image->grayscale())
->scale(width: 1200)
->optimize();
# Розширення та кастомізація
Laravel дозволяє реєструвати власні драйвери через Image::extend(). Крім того, за допомогою Image::transformUsing() можна перевизначити логіку конкретної трансформації для певного драйвера:
use Illuminate\Image\Transformations\Blur;
use Illuminate\Support\Facades\Image;
Image::transformUsing('gd', Blur::class, function ($image, Blur $blur) {
return $image->blur(min(100, $blur->amount * 2));
});
Ви також можете створити власну трансформацію, реалізувавши контракт Illuminate\Contracts\Image\Transformation, і передати її в метод $image->transform().
# Важливі нюанси
- Зображення не можна серіалізувати. Ви не можете передати екземпляр
Imageбезпосередньо в чергу (Queue) — це викличе помилку. Спочатку збережіть файл, а в джобу передавайте лише шлях. - Лінива обробка та кешування. Трансформації виконуються один раз при першому виклику виводу. Наступні виклики
width()чиtoBytes()використовують уже готовий результат. - Обробка помилок. При роботі з непідтримуваними форматами або пошкодженими файлами Laravel викидає
ImageException, що дозволяє зручно обробляти винятки в одному блоці try/catch.
Функціонал доступний у Laravel 13.20, а деталі реалізації можна переглянути в Pull Request #59276.