Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама
Новини 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.

10

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

Коментарі

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

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

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

Filament v5.10.0
Новини 12 жовтня 2026

Filament v5.10.0: сітка в rich editor, скелетони таблиць і десятки виправлень

Вийшов Filament v5.10.0: елементи керування сіткою в rich editor, допоміжний контент для віджетів-графіків, скелетони завантаження таблиць, submitOnCompletion() для OneTimeCodeInput та багато виправлень авторизації, доступності й стану.

22
Laravel AI SDK на практиці
Новини 09 жовтня 2026

Laravel AI SDK на практиці: агент, що стоїть за There There

Фрік Ван дер Гертен розповідає, як у Spatie побудували AI-шар хелпдеска There There на Laravel AI SDK: клас агента, інструменти з типізованою JSON-схемою та делегування роботи звичайним domain-діям.

18

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

Playtender
11 днів тому

Middle PHP Developer (SQL / Oracle)

Розробка, доопрацювання та підтримка корпоративної системи «Універсал» на PHP і JavaScript: нові модулі, форми, звіти, бізнес-логіка, REST API та інтеграції. Робота з Oracle Database і PL/SQL, написання й оптимізація SQL-запитів, участь у міграції з «Універсал 7» на «Універсал 9». Вимоги: від 2-3 років комерційного досвіду на PHP, впевнене знання SQL, практичний досвід з JavaScript та базовий з Oracle, розуміння REST API, JSON/XML і Git.

King Group
23 дні тому

Junior+/Middle PHP developer (payment)

Розробник PHP для команди платежів у мікросервісній архітектурі. Основні завдання: розробка бізнес-функціоналу, інтеграції платіжних систем, оптимізація highload-навантаження, архітектурні рішення. Стек: PHP 7.4/8.4, Symfony, Yii, PostgreSQL, Redis, RabbitMQ, Kafka, Docker, Kubernetes. Вимоги: OOP, REST API, Git, SOLID, досвід з Symfony чи Laravel.

Hurma System Tech
24 дні тому

Full Stack Developer

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

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

Aimeos Laravel

aimeos/aimeos-laravel

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

8,702 2026.10.1 13 11

Laravel Query Builder

spatie/laravel-query-builder

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

4,470 7.3.6 13 11