Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Події
Інше
Про нас
Новини 15 серпня 2026

Laravel 13.25: нові можливості роботи з зображеннями через HTTP-маршрути

Вбудований API для роботи із зображеннями у Laravel добре справлявся із записом даних з версії 13.20: прийняти завантажений файл, трансформувати його та зберегти на диск. Проте шлях читання був менш зручним. Щоб віддати змінене зображення через HTTP, доводилося викликати toBytes(), створювати відповідь і вручну встановлювати тип контенту - три рядки шаблонного коду у кожному контролері.

Повернення зображення з маршруту

Laravel 13.25 робить клас Image сумісним із контрактом Responsable, тому екземпляр зображення можна повертати безпосередньо з маршруту чи контролера. У тому ж релізі з'явилися два пов'язані доповнення: Image::fromStream() та публічний метод toFormat(). Разом ці три можливості покривають більшість потреб для ендпоінтів із зображеннями.

use Illuminate\Support\Facades\Image;

Route::get('/avatars/{user}', function (User $user) {
    return Image::fromStorage($user->avatar_path)
        ->cover(200, 200)
        ->toWebp()
        ->quality(80);
});

Це весь необхідний код. Фреймворк викликає toResponse(), який запускає конвеєр обробки, повертає оброблені байти зі статусом 200 і встановлює Content-Type на основі результату, а не вихідного файлу. Наведений маршрут поверне image/webp, навіть якщо збережений файл - JPEG, оскільки заголовок читається з того, що створив конвеєр.

Усе, що повертає Image, працює однаково: метод контролера, invokable-контролер або значення із замикання route model binding. Екземпляр залишається «лінивим» доти, доки щось не запросить байти, тому трансформація не виконується, коли фреймворк лише визначає тип відповіді.

Додавання заголовків кешування

Типова відповідь не містить заголовків кешування, що є правильним для фреймворка, але неправильним для ендпоінта, який змінює розмір зображення при кожному запиті. Викличте toResponse() самостійно, щоб додати власні заголовки:

Route::get('/avatars/{user}', function (Request $request, User $user) {
    return Image::fromStorage($user->avatar_path)
        ->cover(200, 200)
        ->toWebp()
        ->quality(80)
        ->toResponse($request)
        ->setMaxAge(31536000)
        ->setPublic();
});

Метод toResponse() повертає Illuminate\Http\Response, тому доступний повний API відповідей: header(), setEtag(), setLastModified() та інші. Поєднайте тривалий max-age з URL, що змінюється при оновленні зображення (хеш у шляху або query-параметр на основі updated_at моделі), і браузери перестануть запитувати після першого разу.

Масштабування через кешування на диску

Для проєктів із реальним трафіком зміна розміру при кожному запиті - це робота, яку ви виконуєте знову і знову. Підхід, що масштабується: записати похідний файл при першому запиті та віддавати його з диска надалі:

Route::get('/thumbs/{photo}', function (Request $request, Photo $photo) {
    $path = "thumbs/{$photo->id}-{$photo->updated_at->timestamp}.webp";
    
    if (! Storage::disk('public')->exists($path)) {
        Image::fromStorage($photo->path)
            ->cover(400, 400)
            ->toWebp()
            ->quality(80)
            ->storeAs('thumbs', basename($path), 'public');
    }
    
    return Storage::disk('public')->response($path);
});

Включення мітки часу у назву файлу означає, що оновлене фото створює новий шлях, тому старі мініатюри перестають використовуватися без потреби інвалідувати кеш.

Динамічні формати через toFormat()

Раніше ендпоінт, що приймає формат із запиту, потребував match-блоку для перетворення рядка на відповідний виклик методу. Тепер toFormat() публічний і приймає формат безпосередньо:

Route::get('/photos/{photo}.{format}', function (Photo $photo, string $format) {
    return Image::fromStorage($photo->path)
        ->scale(width: 1200)
        ->toFormat($format)
        ->quality(80);
})->where('format', 'webp|avif|jpg');

Допустимі значення: webp, jpg, jpeg, png, gif, avif, heic, heif та bmp, де heif нормалізується до heic. Будь-яке інше значення викине ImageException із форматом у повідомленні, що дасть 500, а не 404, тому обмежте параметр у маршруті, як показано вище, або валідуйте значення перед передачею. Цей самий метод лежить в основі optimize() - версії для випадків, коли потрібно встановити якість одним викликом.

Це робить AVIF-ендпоінт із резервним форматом коротким. Віддайте той формат, який запросив клієнт, і дозвольте елементу <picture> вирішити, який URL завантажить браузер.

Створення із потоку

Метод Image::fromStream() створює екземпляр із потокового ресурсу, що покриває джерела, які не охоплюють інші фабричні методи:

$image = Image::fromStream(Storage::disk('s3')->readStream($path));

Читання є лінивим. fromStream() обгортає ресурс у замикання та не торкається його до запуску конвеєра, тому створення екземпляра, який ви не використаєте, нічого не коштує. Потік без даних викине ImageException із повідомленням "Invalid stream image data." у момент використання, а не при створенні.

Поряд із fromPath(), fromStorage(), fromUpload(), fromUrl(), fromBytes() та fromBase64(), потоковий варіант призначений для будь-чого, дескриптор чого ви вже маєте: тіло php://input на ендпоінті прямого завантаження, файл із zip-архіву або потік від іншої бібліотеки.

Повноцінний приклад ендпоінта

Поєднання всіх трьох можливостей - ендпоінт для зображень, що приймає ширину та формат, читає з S3 і кешує на рік:

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Image;
use Illuminate\Support\Facades\Storage;

Route::get('/media/{media}', function (Request $request, Media $media) {
    $validated = $request->validate([
        'w' => ['integer', 'between:32,2000'],
        'format' => ['in:webp,avif,jpg'],
    ]);
    
    return Image::fromStream(Storage::disk('s3')->readStream($media->path))
        ->scale(width: $validated['w'] ?? 800)
        ->toFormat($validated['format'] ?? 'webp')
        ->quality(80)
        ->toResponse($request)
        ->setMaxAge(31536000)
        ->setPublic();
})->middleware('signed');

Дві деталі варто зберігати. Ширина обмежена, оскільки невалідований розмір на публічному ендпоінті - це запрошення запитувати зміну розміру до 20 000 пікселів. А маршрут підписаний, що запобігає генерації довільних варіантів за ваш рахунок у сховищі. Підписані маршрути Laravel дають це через виклик signedRoute() у в'юхі.

Корисні матеріали

Усі три зміни були внесені Caleb White у pull request'ах #61111, #61109 та #61110. Повні примітки до релізу Laravel 13.25 доступні в офіційному анонсі, де також описано паузу всіх черг та новий UI для artisan dev.

6

Читати в документації

Коментарі

Увійдіть, щоб залишити коментар

Будьте першим, хто залишить коментар!

Читайте також

whereBinary()
Новини 28 серпня 2026

whereBinary(): регістрозалежні запити MySQL у Laravel

Laravel 13.27 додає новий метод whereBinary() для точного побайтового порівняння рядків у MySQL. Він вирішує проблему, коли стандартне collation utf8mb4_unicode_ci ігнорує регістр, акценти та пробіли при порівнянні токенів, slug-ів та інших критичних даних.

ToolSearch
Новини 27 серпня 2026

Laravel AI: завантаження інструментів на вимогу з ToolSearch

Laravel AI v0.11.0 додає механізм відкладеного завантаження інструментів агента через ToolSearch. Замість надсилання всіх тридцяти інструментів одразу, провайдер отримує лише пошуковий запис і завантажує повні визначення тільки тоді, коли модель вирішує їх використати.

2

Вакансії за темою

Hurma System Tech Нова
Сьогодні

Full Stack Developer (PHP, Laravel, Vue.js)

Full Stack Developer на PHP та Laravel з фронтенд-частиною на Vue.js. Розроблення та рефакторинг функціоналу HR-системи, робота з БД, REST API, тестування. Вимоги: PHP 8+, 2+ років Laravel, Vue.js, OOP, SQL, Git, базовий TypeScript. Віддалена робота з гнучким графіком.

Junior Frontend / Fullstack Developer (React/Vue + WordPress)

Junior Frontend/Fullstack розробник для розробки адаптивної верстки, React/Vue-логіки та WordPress-сайтів. Вимоги: HTML5, CSS3, JavaScript ES6+, практичний досвід React або Vue, базові знання WordPress, Git, REST API. Мінімум 5 місяців комерційного досвіду або сильна практична база (Pet-проєкти, фриланс).

INFOBUS Нова
2 дні тому

PHP Developer (Support)

PHP-розробник для підтримки та розвитку PHP-систем і партнерських API. Основні обов'язки: підтримка стабільної роботи систем, виправлення багів, оптимізація баз даних MySQL, розроблення й покращення REST API, діагностика проблем інтеграції. Вимоги: впевнене знання PHP 7.4+, MySQL, SQL, REST API, HTTP, Git, вміння аналізувати логи та тестувати API через Postman/cURL.

Пакети за темою

Aimeos Laravel

aimeos/aimeos-laravel

Cloud-native, API-first Laravel пакет для електронної комерції з інтегрованою штучною інтелектуальністю для надшвидких онлайн-магазинів, маркетплейсів та складних B2B проектів.

8,669 2026.04.1 13 6

Laravel Query Builder

spatie/laravel-query-builder

Легко будуйте Eloquent-запити на основі запитів від API.

4,461 7.3.3 13 10