Laravel 13.24 розширює можливості фреймворка з обробки зображень, додаючи визначення домінантного кольору та підтримку форматів HEIC і AVIF. Крім того, реліз включає новий метод modelKeys() для Eloquent query builder, правило валідації array_keys та критичне виправлення продуктивності валідації, яке раніше могло затримувати запит понад хвилину при роботі з великими масивами. Команда Laravel випустила версію 13.24.0 4 серпня 2026 року.
Визначення домінантного кольору в Image API
Перша офіційна Image API тепер може визначати середній колір зображення. Метод dominantColor() зменшує зображення до одного пікселя та повертає його колір у форматі hex-рядка:
use Illuminate\Support\Facades\Image;
$color = Image::fromPath(storage_path('app/photo.jpg'))->dominantColor();
// "#8a6f4c"
Це значення можна використовувати як фоновий колір-заповнювач під час завантаження фотографії або для тонування картки за її власним зображенням. Результат мемоїзується для кожного екземпляра зображення, і якщо у pipeline є незавершені трансформації, вони виконуються перед визначенням кольору, тому ви отримуєте колір зображення, яке збираєтеся зберегти, а не оригіналу.
Те саме визначення доступне як значення фону. Передача dominant до методів contain() або rotate() заповнює порожні простір letterbox або кути повороту власним домінантним кольором зображення замість заздалегідь обраного:
Image::fromUpload($request->file('photo'))
->contain(1200, 800, background: 'dominant')
->store('photos');
Підтримка AVIF та HEIC на вході, вивід у HEIC
Раніше Image API підтримувала вивід AVIF через toAvif(), але відхиляла AVIF-файли на вході. Формати HEIC та HEIF відхилялися в обох напрямках, навіть попри те, що Intervention Image постачається з кодером для них. Фотографії безпосередньо з iPhone мають формат HEIC, тому завантаження доводилося конвертувати ще до потрапляння у фреймворк.
Тепер AVIF, HEIC та HEIF приймаються як вхідні формати, доступні toHeic() та optimize('heic') для виводу, і всі три формати розпізнаються правилом валідації image:
Image::fromPath(storage_path('app/photo.heic'))
->usingImagick()
->cover(1200, 800)
->toAvif()
->quality(80)
->store('photos');
Обидві зміни базуються на можливостях image pipeline. optimize('heif') та MIME-тип image/heif нормалізуються до HEIC, тому файли зберігаються з канонічним розширенням .heic. Метод Image::extension() також вивчив псевдоніми image/x-avif, image/x-heic та image/heif. Обробка HEIC потребує збірки Imagick з вкомпільованим HEIC-кодеком.
Метод modelKeys() у Eloquent Query Builder
Eloquent-колекції давно мали метод modelKeys(), але отримання того самого масиву безпосередньо з запиту означало самостійне зазначення первинного ключа через pluck('id'). Тепер query builder також має цей метод:
$ids = Post::query()->where('published', true)->modelKeys();
// [1, 2, 3]
Він витягує кваліфіковане ім'я ключа з моделі, тому кастомний $primaryKey та з'єднані запити розв'язуються коректно без жорсткого кодування імені стовпця.
Правило валідації array_keys
Rule::array(['sort', 'direction']) вже відхиляє масиви з неочікуваними ключами, але повідомляє про помилку загальним повідомленням "The options field must be an array", яке не каже читачеві, яка саме з двох перевірок провалилася. Нове правило валідації array_keys відповідає лише на питання про ключі і повідомляє про це:
$request->validate([
'options' => Rule::arrayKeys(['sort', 'direction']),
]);
// або як рядок
$request->validate([
'options' => 'array_keys:sort,direction',
]);
Для вхідних даних ['sort' => 'name', 'colour' => 'red'] повідомлення про помилку: "The options field must only contain the following keys: sort, direction." Ключі є дозволеними, а не обов'язковими, тому це обмежує те, що може з'явитися, а не вимагає присутності всього. required_array_keys як і раніше покриває другу половину.
Доступні два плейсхолдери для кастомних повідомлень: :values для прийнятих ключів та :unexpected для ключів, які фактично спричинили помилку (зазвичай це корисніше в API-відповідях):
$request->validate(
['options' => Rule::arrayKeys(['sort', 'direction'])],
['options.array_keys' => 'The :attribute field may not contain :unexpected.'],
);
// The options field may not contain colour.
Оскільки правило звітується як ArrayKeys у $validator->failed(), воно компонується з array, коли ви хочете, щоб обидві перевірки звітувалися незалежно. Rule::array() та його повідомлення залишаються без змін.
Виправлення уповільнення валідації з wildcards на великих масивах
Розгортання правил foo.*.bar було квадратичним відносно кількості розгорнутих атрибутів. ValidationRuleParser::explodeWildcardRules() накопичував результати, переприсвоюючи значення, що повертається з mergeRules(), який приймав накопичений набір за значенням, тому copy-on-write клонував весь набір правил один раз на кожен розгорнутий атрибут. Весь час витрачався в Validator::make(), ще до запуску будь-якого правила.
Числа з pull request при використанні 17 правил під одним wildcard:
- 1,000 елементів: 0.98s → 0.11s
- 4,000 елементів: 18.59s → 0.47s
- 8,000 елементів: 85.13s → 0.98s
При 8,000 елементах payload становить лише близько 1.1 МБ, тому post_max_size та ліміти веб-сервера ніколи не спрацьовували. Правило max:500 на масиві теж не допомагало, оскільки розгортання відбувалося перед його виконанням.
Тіло злиття перенесено в приватний метод, який приймає акумулятор за посиланням. mergeRules() та mergeRulesForAttribute() зберігають свої сигнатури, але explodeWildcardRules() більше не маршрутизується через останній, тому override там більше не впливатиме на розгортання wildcards.
Виправлення Arr::forget(), що видаляв неправильний елемент
Arr::forget() скидав своє внутрішнє посилання назад до масиву верхнього рівня лише після обробки точного збігу ключа верхнього рівня. Після обробки ключа з крапкою посилання залишалося вказувати на вкладений масив, тому наступний ключ у списку розв'язувався відносно цього вкладеного масиву:
$array = ['users' => ['name' => 'Joe', 'id' => 1], 'id' => 99];
Arr::forget($array, ['users.name', 'id']);
// до: ['users' => [], 'id' => 99]
// після: ['users' => ['id' => 1]]
Видалявся неправильний елемент, а запитуваний зберігався, без жодної помилки. Переміщення скидання на початок циклу виправляє це для всього, що делегує до хелпера, включно з Arr::except(), Collection::except(), data_forget() та Uri::withoutQuery().
Інші виправлення та покращення
CompiledRouteCollection перебудовував кожен об'єкт Route при кожному виклику get(), getByAction() та getRoutesByMethod(), що робило 404 дорогими на закешованих маршрутах. Індекси пошуку з raw-атрибутів маршрутів прискорюють 404 проти 2,000 закешованих маршрутів з 71ms до 2.5ms
- Аксесор для існуючого атрибута, який також був у
$appends, отримував null замість збереженого значення, тому $model->price та $model->toArray()['price'] розходилися
Str::replace() та Str::remove() делегують до str_ireplace() для нечутливого до регістру пошуку, яка обробляє лише ASCII. Багатобайтові пошукові терміни тепер використовують патерн /iu; ASCII-терміни йдуть оригінальним шляхом
- Резолв атрибута відношення всередині closure
Relation::noConstraints() пропускав where-умову на первинному ключі. Новий Relation::withConstraints() обгортає резолв атрибутів
PostgresGrammar::wrapJsonPathAttributes() інтерполював JSON path атрибути в quoted SQL літерали без екранування одинарних лапок, на відміну від інших драйверів
- Два псевдоніми, що вказують один на одного, змушували
Container::getAlias() рекурсувати до вичерпання пам'яті; тепер кидається LogicException
NumberPrompt був єдиним типом промпту без fallback, тому number() кидав помилку на Windows, в неінтерактивних контекстах та в тестах замість деградації до Symfony question
ShouldBeUniqueUntilProcessing jobs тепер відстежують власника cache lock та звільняють через restoreLock(), тому retry не може звільнити lock, отриманий новішим dispatch
- Резолв атрибута
Guarded пропускався на Pivot, який перевизначає $guarded з [] і тому провалював перевірку дефолту ['*']
- Затримки jobs поважаються при bulk push до
QueueFake та FailoverQueue, а batch testing fakes використовують immutable timestamps
LengthAwarePaginator захищається від ділення на нуль, коли perPage дорівнює 0, а CursorPaginator переіндексує items консистентно в обох напрямках
- Більшість Artisan-команд перейшли від стилю
$name плюс getArguments()/getOptions() до одного рядка $signature
- Логіка валідації URL була скоригована, і вирішено кілька PHP 8.5 deprecation попереджень щодо null array offset
Примітки до оновлення
Брейкінг-змін для типових застосунків не очікується. Дві зміни варто перевірити, якщо ви розширюєте framework internals: explodeWildcardRules() більше не викликає mergeRulesForAttribute(), тому override цього методу не вплине на розгортання wildcards, і Container::getAlias() тепер кидає LogicException на циклічних псевдонімах, які раніше вичерпували пам'ять. Підтримка HEIC залежить від вашої збірки Imagick з включеним HEIC-кодеком.
Два pull request, що злилися під час цього циклу, були відкочені перед релізом. Перегляньте changelog для деталей по кожному PR при оновленні.